diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index a3b7acb532b6..b810fba85568 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -4,6 +4,7 @@ + ## 0.9.0 1. [MODEL] Add Wheel Chocks and GSE Safety Cones - @bouveng (Johan Bouveng) @@ -21,22 +22,61 @@ 1. [HYD] Gear system now supports spawning in flight gear up - @Crocket63 (crocket) 1. [MCDU] Added imperial runway length - @derl30n (Leon) 1. [MCDU] Refactor and improve input handling and conversions in MCDU W&B - @sidnov (Sid) -1. [MCDU] Improved arrivals/departure page scrolling, more true-to-life behaviour and cosmetic apperance - @2hwk (2Cas#1022) +1. [MCDU] Improved arrivals/departure page scrolling, more true-to-life behaviour and cosmetic apperance - @2hwk ( 2Cas#1022) 1. [PFD] Show yellow GS reference line in correct conditions - @saschl (saschl#9432) 1. [ECAM] Fix erroneous SLATS NOT IN T.O CONFIG warning during flaps 3 takeoff - @beheh (Benedict Etzel) 1. [MODEL] Improved rivet mesh with more variation and detail - @Grinde (Grinde#4017) 1. [ELEC] Make battery voltmeters update one digit at a time - @beheh (Benedict Etzel) 1. [EWD] E/WD visual improvements - @lukecologne (luke) -1. [SFCC] Add SFCC bus outputs - @lukecologne (luke) -1. [EWD] Use FPPU angles for flaps/slats display - @lukecologne (luke) +1. [SFCC] Add SFCC bus outputs - @lukecologne (luke) +1. [EWD] Use FPPU angles for flaps/slats display - @lukecologne (luke) 1. [FMGC] Basic RNP at or below 0.3 support - @tracernz (Mike) 1. [SD] Improve F/CTL and WHEEL SD pages visuals - @lukecologne (luke) 1. [MCDU] Added formatter to improve text alignment and ease integration - @derl30n (Leon) 1. [EFB] Fix default unit to match UI and other consumers - @tracernz (Mike) +1. [HYD] Added possibility to lock or limit hydraulic actuators velocity - @Crocket63 (crocket) +1. [HYD] Added a new THS mechanism simulation - @Crocket63 (crocket) 1. [HYD] More prox sensors and gear actuator failures - @Crocket63 (crocket) 1. [MCDU] Added 4:3 aspect ratio compatibility to remote mcdu client - @tyler58546 (tyler58546) 1. [HYD] Fixed too slow leak measurement valves operation - @Crocket63 (crocket) 1. [ISIS] Added temporary ISIS font with arrows - @aweissoertel (Alexibexi#7550) +1. [PFD] Improve PFD barberpole rendering and behaviour - @lukecologne (luke) +1. [HYD] Fixed actuator position control demand consistency - @Crocket63 (crocket) +1. [FMGC] Only emit decel point when an approach is selected - @tracernz (Mike) +1. [HYD] Fixed SFCC computer failing to send FPPU commands - @Crocket63 (crocket) +1. [MCDU] Fix padding of arc radii on F-PLN - @tracernz (Mike) +1. [FMGC] Allow stringing of STARs with non-runway approaches - @tracernz (Mike) +1. [SD] Improve COND page visuals, fix some visual bugs on the SD - @lukecologne (luke) +1. [HYD] Aerodynamic forces applied on gear system - @Crocket63 (crocket) +1. [HYD] Fixed Lgciu state machine when reverting gravity extension - @Crocket63 (crocket) +1. [FMGC] Fix unnatural turn direction for path capture transition - @tracernz (Mike) +1. [FMGC] Fix unnatural turn direction for course capture transition - @tracernz (Mike) +1. [FMGC] Update spoiler CD for MSFS SDK clarification - @donstim (donbikes#4084) +1. [HYD] Actuators damping chambers can be asymetrical - @Crocket63 (crocket) +1. [FMGC] Implement procedure turns (PI leg) - @tracernz (Mike) +1. [ECAM] Move EWD to correct AC bus - @tracernz (Mike) +1. [FMGC] Fix inbound leg time for holds - @tracernz (Mike) +1. [MCDU] Improved visuals of Init-A and Init-B page - @derl30n (Leon) +1. [MODEL] Added new animated gear gravity extension handle- @tyler58546 (tyler58546), @MoreRightRudder (Mike), @Crocket63 (crocket), @Lantarius +1. [HYD] Randomised per actuator flow restrictions at plane init - @Crocket63 (crocket) +1. [MCDU] Hide stored elements on A/C Status when there are none - @tracernz (Mike) +1. [FMGC] Fix ident for CD legs - @tracernz (Mike) +1. [HYD] Refactor plane accelerations and added filtering to handle ground collisions - @Crocket63 (crocket) +1. [FMGC] Improve procedure loading performance - @tracernz (Mike) +1. [EFB] Added Payload Loading to EFB - @2hwk (2Cas#1022) +1. [MODEL] Remove blue highlights in lock mode - @tracernz (Mike) +1. [RMP] Fixed the behavior of the RMPs and ACPs (VHFs only) - Julian Sebline (Julian Sebline#8476 on Discord) +1. [PFD] Added option to sync FD/LS buttons between CPT and FO - @Eagle941 (Joe) +1. [EFB] Add automatic loading of a lighting preset - @SpiritZephyr (Ben) +1. [COND] Connect Air Conditioning System to Electrical System - @mjuhe (Miquel Juhe) +1. [ADDON] Simbridge Integration - @lucky38i (Alex) +1. [SD] Visual and functional improvements/fixes to the HYD SD page - @lukecologne (luke) +1. [HYD] Added optional auxiliary hydraulic section in core hydraulic circuits - @Crocket63 +1. [EFB] Added SimBridge Health Check icon to Status Bar - @frankkopp (Frank Kopp) +1. [HYD] Fixed opened gear door wobbling - @Crocket63 +1. [HYD] Fix gear extending whith dual lgciu power loss - @Crocket63 (crocket) +1. [EFB] flyPad pushback option to ignore controller inputs - @frankkopp (Frank Kopp) +1. [FCTL] Add realistically simulated F/CTL computers: ELAC/SEC/FCDC/FAC - @lukecologne (luke) ## 0.8.0 @@ -281,12 +321,14 @@ 1. [RMP] Fixed colour of SEL indicator - @tracernz (Mike) 1. [MISC] Added custom Autobrake event for SimConnect - @aguther (Andreas Guther) 1. [EWD] Fixed failures not re-triggering when resolved - @tricky_dicky (Richard Pilbery) -1. [SOUND] Prevent autopilot disconnect from sounding on C+D spawn - @tricky_dicky (Richard Pilbery) and @saschl (saschl#9432) +1. [SOUND] Prevent autopilot disconnect from sounding on C+D spawn - @tricky_dicky (Richard Pilbery) and @saschl ( + saschl#9432) 1. [FMGC] Removed flight phase transition from TAKEOFF to PREFLIGHT - @beheh (Benedict Etzel) 1. [FMGC] Added flight phase transition to DONE when on ground > 30 s and engines off - @aguther (Andreas Guther) 1. [ATSU] Add FANS-C simulation for CPDLC - @Sven [de en] - (Sven Czarnian) 1. [ATSU] Add VDL3-simulation of datalink transport protocol - @Sven [de en] - (Sven Czarnian) 1. [FMGC] Improve robustness of ILS auto-tuning - @tracernz (Mike) +1. [OVHD] Fixed fire push button not being able to be stowed - Julian Sebline (Julian Sebline#8476 on Discord) ## 0.7.0 @@ -1069,5 +1111,5 @@ 1. [MISC] Standby Instrument stays ON if emergency power should be available, bug fixes - @2hwk (2Cas#1022 on discord) 1. [CDU] Full +/- button functionality - @lhoenig (Lukas Hoenig) 1. [DCDU] Fixed MSG- and MSG+ button labels - @tyler58546 (tyler58546) -1. [OVHD] Fixed fire push button not being able to be stowed - Julian Sebline (Julian Sebline#8476 on Discord) -1. [ISIS] Fixed issue where ISIS was allowing a bug to be set while in the OFF state - Patrick Macken (@Pat M on Discord) +1. [ISIS] Fixed issue where ISIS was allowing a bug to be set while in the OFF state - Patrick Macken (@Pat M on + Discord) diff --git a/.gitignore b/.gitignore index 1709b8c72c64..c3c822a26192 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ /flybywire-aircraft-a320-neo/html_ui/JS/fmgc/ /flybywire-aircraft-a320-neo/html_ui/JS/sentry-client/ /flybywire-aircraft-a320-neo/html_ui/JS/tcas/ +/flybywire-aircraft-a320-neo/html_ui/JS/simbridge-client/ /flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/A32NX/* !/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/A32NX/PFD /flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/A32NX/PFD/*.js @@ -66,7 +67,6 @@ node_modules /src/fadec/obj/ .env src/instruments/buildSrc/custom/* -/flybywire-aircraft-a320-neo/MCDU SERVER/ /src/mcdu-server/client/build !/flybywire-aircraft-a320-neo/CORS SERVER/* /flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/TEXTURE/A320NEO_COCKPIT_DECALSTEXT_ALBD.TIF.dds diff --git a/.vscode/settings.json b/.vscode/settings.json index f336be688999..069784567165 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -39,13 +39,110 @@ "files.insertFinalNewline": true, "files.trimFinalNewlines": true, "[cpp]": { - "editor.defaultFormatter": "xaver.clang-format", + "editor.defaultFormatter": "ms-vscode.cpptools", "editor.formatOnSave": true, "editor.formatOnPaste": false }, "cmake.configureOnOpen": false, "files.associations": { - "memory": "cpp" + "*.cfg": "cfg", + "memory": "cpp", + "__bit_reference": "cpp", + "__bits": "cpp", + "__config": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__functional_base": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__nullptr": "cpp", + "__split_buffer": "cpp", + "__string": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__tuple": "cpp", + "algorithm": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "exception": "cpp", + "forward_list": "cpp", + "fstream": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "map": "cpp", + "mutex": "cpp", + "new": "cpp", + "optional": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "set": "cpp", + "sstream": "cpp", + "stack": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "thread": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "utility": "cpp", + "variant": "cpp", + "vector": "cpp", + "filesystem": "cpp", + "format": "cpp", + "stop_token": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xstddef": "cpp", + "xstring": "cpp", + "xtr1common": "cpp", + "xtree": "cpp", + "xutility": "cpp", + "queue": "cpp", + "__functional_03": "cpp", + "memory_resource": "cpp" }, "typescript.preferences.quoteStyle": "single", "typescript.preferences.importModuleSpecifier": "non-relative" diff --git a/Cargo.lock b/Cargo.lock index dfa483647f96..b1d6c152205e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ dependencies = [ "rand", "rustplotlib", "systems", - "uom 0.32.0", + "uom", ] [[package]] @@ -26,7 +26,7 @@ dependencies = [ "rand", "rstest", "systems", - "uom 0.32.0", + "uom", ] [[package]] @@ -37,7 +37,30 @@ dependencies = [ "msfs", "systems", "systems_wasm", - "uom 0.32.0", + "uom", +] + +[[package]] +name = "a380_systems" +version = "0.1.0" +dependencies = [ + "nalgebra", + "ntest", + "rand", + "rstest", + "systems", + "uom", +] + +[[package]] +name = "a380_systems_wasm" +version = "0.1.0" +dependencies = [ + "a380_systems", + "msfs", + "systems", + "systems_wasm", + "uom", ] [[package]] @@ -101,9 +124,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" dependencies = [ "addr2line", "cc", @@ -178,9 +201,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" -version = "1.3.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cc00842eed744b858222c4c9faf7243aafc6d33f92f96935263ef4d8a41ce21" +checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" dependencies = [ "glob", "libc", @@ -204,9 +227,9 @@ dependencies = [ [[package]] name = "either" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" [[package]] name = "enum_dispatch" @@ -365,9 +388,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", "libc", @@ -415,9 +438,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.119" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "libloading" @@ -437,9 +460,9 @@ checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] @@ -455,9 +478,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "minimal-lexical" @@ -467,18 +490,17 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" dependencies = [ "adler", - "autocfg", ] [[package]] name = "msfs" version = "0.1.0" -source = "git+https://github.com/flybywiresim/msfs-rs?branch=main#4f6290f95c1dcbada65f541dc8c61e1e895e09e9" +source = "git+https://github.com/flybywiresim/msfs-rs?branch=main#f5ebb3d75f5a64fcd4e542129584ca0ee1ef0ebe" dependencies = [ "bindgen", "cc", @@ -490,7 +512,7 @@ dependencies = [ [[package]] name = "msfs_derive" version = "0.1.0" -source = "git+https://github.com/flybywiresim/msfs-rs?branch=main#4f6290f95c1dcbada65f541dc8c61e1e895e09e9" +source = "git+https://github.com/flybywiresim/msfs-rs?branch=main#f5ebb3d75f5a64fcd4e542129584ca0ee1ef0ebe" dependencies = [ "msfs_sdk", "quote", @@ -500,7 +522,7 @@ dependencies = [ [[package]] name = "msfs_sdk" version = "0.1.0" -source = "git+https://github.com/flybywiresim/msfs-rs?branch=main#4f6290f95c1dcbada65f541dc8c61e1e895e09e9" +source = "git+https://github.com/flybywiresim/msfs-rs?branch=main#f5ebb3d75f5a64fcd4e542129584ca0ee1ef0ebe" [[package]] name = "nalgebra" @@ -521,13 +543,12 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", - "version_check", ] [[package]] @@ -593,9 +614,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -614,9 +635,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", "libm", @@ -624,24 +645,24 @@ dependencies = [ [[package]] name = "object" -version = "0.27.1" +version = "0.28.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "paste" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" [[package]] name = "peeking_take_while" @@ -660,9 +681,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -698,18 +719,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.15" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] @@ -762,9 +783,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "regex" -version = "1.5.5" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" dependencies = [ "aho-corasick", "memchr", @@ -773,9 +794,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" [[package]] name = "rstest" @@ -837,18 +858,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote", @@ -875,9 +896,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "strsim" @@ -893,13 +914,13 @@ checksum = "3685c82a045a6af0c488f0550b0f52b4c77d2a52b0ca8aba719f9d268fa96965" [[package]] name = "syn" -version = "1.0.86" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -927,7 +948,7 @@ dependencies = [ "rand", "rand_distr", "rstest", - "uom 0.32.0", + "uom", ] [[package]] @@ -938,7 +959,7 @@ dependencies = [ "fxhash", "msfs", "systems", - "uom 0.30.0", + "uom", ] [[package]] @@ -961,18 +982,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -981,9 +1002,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] @@ -1000,6 +1021,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + [[package]] name = "unicode-width" version = "0.1.9" @@ -1008,25 +1035,15 @@ checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "uom" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e76503e636584f1e10b9b3b9498538279561adcef5412927ba00c2b32c4ce5ed" -dependencies = [ - "num-traits", - "typenum", -] +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "uom" -version = "0.32.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffcb443c899d2adc34f4ae4c64d6e9867b6d3ead2fad03979a8732a4ab6d396f" +checksum = "53e68fe0bfdacf0a6cef0efec5dcc295b836cde69b01ad93feb18488fa82050d" dependencies = [ "num-traits", "typenum", @@ -1046,15 +1063,15 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "which" -version = "4.2.4" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ "either", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index 7df1366043b1..afc8f971c948 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,8 @@ members = [ "src/systems/a320_systems", "src/systems/a320_systems_wasm", + "src/systems/a380_systems", + "src/systems/a380_systems_wasm", "src/systems/systems", "src/systems/systems_wasm", "src/systems/a320_hydraulic_simulation_graphs", diff --git a/docs/Configuration/ModelConfiguration.ini b/docs/Configuration/ModelConfiguration.ini index 2d061194df80..a338c961f969 100644 --- a/docs/Configuration/ModelConfiguration.ini +++ b/docs/Configuration/ModelConfiguration.ini @@ -61,6 +61,21 @@ ; enable fly-by-wire model in wasm ;fly_by_wire_enabled = true +; !! WARNING ONLY FOR DEVELOPMENT !! +; disable ELAC model in wasm. If -1, all ELACs are enabled in WASM, +; 0 means ELAC 1 is disabled in WASM, and 1 means ELAC 2 is disabled in WASM. +;elac_disabled = -1 + +; !! WARNING ONLY FOR DEVELOPMENT !! +; disable SEC model in wasm. If -1, all SECs are enabled in WASM, +; 0 means SEC 1 is disabled in WASM, 1 means SEC 2 is disabled in WASM, etc. +;sec_disabled = -1 + +; !! WARNING ONLY FOR DEVELOPMENT !! +; disable FAC model in wasm. If -1, all FACs are enabled in WASM, +; 0 means FAC 1 is disabled in WASM, 1 means FAC 2 is disabled in WASM, etc. +;fac_disabled = -1 + ; !! WARNING ONLY FOR DEVELOPMENT !! ; enable tailstrike protection ;tailstrike_protection_enabled = true diff --git a/docs/a320-simvars.md b/docs/a320-simvars.md index 2874be9cc232..f58575802d77 100644 --- a/docs/a320-simvars.md +++ b/docs/a320-simvars.md @@ -6,7 +6,6 @@ - [Contents](#contents) - [Uncategorized](#uncategorized) - [EIS Display System](#eis-display-system) - - [Fly-By-Wire System](#fly-by-wire-system) - [ADIRS](#adirs) - [Radio Receivers](#radio-receivers) - [Flight Management System](#flight-management-system) @@ -17,6 +16,7 @@ - [Air Conditioning / Pressurisation / Ventilation](#air-conditioning--pressurisation--ventilation) - [Pneumatic](#pneumatic) - [Flaps / Slats (ATA 27)](#flaps--slats-ata-27) + - [Flight Controls (ATA 27)](#flight-controls-ata-27) - [Landing Gear (ATA 32)](#landing-gear-ata-32) - [ATC (ATA 34)](#atc-ata-34) - [Radio Altimeter (ATA 34)](#radio-altimeter-ata-34) @@ -310,14 +310,6 @@ - vapp calculated for config full whether A32NX_VSPEEDS_LANDING_CONF3 or not - is mach corrected -- A32NX_SPEEDS_ALPHA_PROTECTION - - Number (knots) - - speed where alpha protection is reached with 1g - -- A32NX_SPEEDS_ALPHA_MAX - - Number (knots) - - speed where alpha max is reached with 1g - - A32NX_TRK_FPA_MODE_ACTIVE - Bool - True if TRK/FPA mode is active @@ -783,6 +775,14 @@ - BLUE - YELLOW +- A32NX_HYD_{loop_name}_PUMP_1_SECTION_PRESSURE_SWITCH + - Boolean + - Current pressure switch state in {loop_name} pump section + - {loop_name} + - GREEN + - BLUE + - YELLOW + - A32NX_HYD_{loop_name}_RESERVOIR_LEVEL - Gallon - Current gaugeable fluid level in the {loop_name} hydraulic circuit reservoir @@ -875,6 +875,10 @@ - Useful for home cockpits that use the sim's built-in pop-out feature and do not wish to have these effects present on their displays. +- A32NX_FO_SYNC_EFIS_ENABLED + - Bool + - 1 to sync the status of FD and LS buttons between CPT and FO sides + - A32NX_HYD_{loop_name}_EPUMP_LOW_PRESS - Bool - Electric pump of {loop_name} hydraulic circuit is active but pressure is too low @@ -1061,26 +1065,6 @@ - Percent - Indicates the angle of the right slats out of 27 degrees -- A32NX_LEFT_FLAPS_TARGET_ANGLE - - Degrees - - Indicates the target angle of the left flaps - according to the configuration. - -- A32NX_RIGHT_FLAPS_TARGET_ANGLE - - Degrees - - Indicates the target angle of the right flaps - according to the configuration. - -- A32NX_LEFT_SLATS_TARGET_ANGLE - - Degrees - - Indicates the target angle of the left slats - according to the configuration. - -- A32NX_RIGHT_SLATS_TARGET_ANGLE - - Degrees - - Indicates the target angle of the right slats - according to the configuration. - - A32NX_LEFT_FLAPS_ANGLE - Degrees - The actual angle of the left flaps @@ -1119,7 +1103,8 @@ - A32NX_SPOILERS_ARMED - Bool - - Indicates if the ground spoilers are armed + - Indicates if the ground spoiler handle is physically in the armed position. + DO NOT USE IN SYSTEMS, USE FCDC INSTEAD Value | Meaning --- | --- 0 | disarmed @@ -1127,20 +1112,13 @@ - A32NX_SPOILERS_HANDLE_POSITION - Number - - Indicates the physical handler position without arm/disarm + - Indicates the physical handler position without arm/disarm. + DO NOT USE IN SYSTEMS, USE FCDC INSTEAD Value | Position --- | --- 0 | Retracted 1 | Full extension -- A32NX_SPOILERS_GROUND_SPOILERS_ACTIVE - - Bool - - Indicates if the ground spoilers are active (fully deployed) - Value | Position - --- | --- - 0 | Inactive - 1 | Active - - A32NX_PERFORMANCE_WARNING_ACTIVE - Bool - Indicates if performance warning is active @@ -1398,93 +1376,6 @@ - RDY - FM2 -## Fly-By-Wire System - -- A32NX_LOGGING_FLIGHT_CONTROLS_ENABLED - - Bool - - Indicates if logging of flight control events is enabled - -- A32NX_SIDESTICK_POSITION_X - - Number - - Provides the direct sidestick position (lateral) - Value | Meaning - --- | --- - -1 | full left - 0 | neutral - 1 | full right - -- A32NX_SIDESTICK_POSITION_Y - - Number - - Provides the direct sidestick position (longitudinal) - Value | Meaning - --- | --- - -1 | full forward - 0 | neutral - 1 | full backward - -- A32NX_RUDDER_PEDAL_POSITION - - Number - - Provides the rudder pedal position - Value | Meaning - --- | --- - -100 | full left - 0 | neutral - 100 | full right - -- A32NX_RUDDER_PEDAL_ANIMATION_POSITION - - Number - - Provides the rudder pedal position including rudder trim for animation - Value | Meaning - --- | --- - -100 | full left - 0 | neutral - 100 | full right - -- A32NX_ALPHA_MAX_PERCENTAGE - - Number (0.0 -> 1.0) - - Percentage of current (filtered) alpha to alpha max - - alpha max can be overshoot so values beyond 1.0 should be expected - -- A32NX_BETA_TARGET - - Degrees - - Target beta (sideslip) in case of asymmetric thrust - -- A32NX_AILERON_LEFT_DEFLECTION_DEMAND - - Number - - Provides the left aileron position demand to hydraulics - Value | Meaning - --- | --- - -1.0 | full up - 0.0 | neutral - 1.0 | full down - -- A32NX_AILERON_RIGHT_DEFLECTION_DEMAND - - Number - - Provides the right aileron position demand to hydraulics - Value | Meaning - --- | --- - -1.0 | full down - 0.0 | neutral - 1.0 | full up - -- A32NX_HYD_AILERON_LEFT_DEFLECTION - - Number - - Provides the final left aileron physical position - Value | Meaning - --- | --- - -1.0 | full up - 0.0 | neutral - 1.0 | full down - -- A32NX_HYD_AILERON_RIGHT_DEFLECTION - - Number - - Provides the final right aileron physical position - Value | Meaning - --- | --- - -1.0 | full down - 0.0 | neutral - 1.0 | full up - ## ADIRS In the variables below, {number} should be replaced with one item in the set: { 1, 2, 3 }, unless declared otherwise. @@ -1551,6 +1442,10 @@ In the variables below, {number} should be replaced with one item in the set: { - Seconds - The remaining alignment duration. Zero seconds when the system is aligned or the system is not aligning. +- A32NX_ADIRS_ADR_{number}_CORRECTED_AVERAGE_STATIC_PRESSURE + - Arinc429Word + - The corrected average static pressure. + - A32NX_ADIRS_ADR_{number}_ALTITUDE - Arinc429Word - The altitude. @@ -1733,6 +1628,14 @@ In the variables below, {number} should be replaced with one item in the set: { - L - R +- L:A32NX_FM{number}_LANDING_ELEVATION + - ARINC429 (feet MSL) + - The landing elevation at the active destination + - **Temporary:** there are also simvars with _SSM suffix to carry the SSM until JS is able to write ARINC simvars + - {number} + - 1 - captain's side FMGC + - 2 - f/o's side FMGC + ## Autopilot System - A32NX_FMA_LATERAL_MODE @@ -2401,9 +2304,9 @@ In the variables below, {number} should be replaced with one item in the set: { - Bool - True if the respective {1 or 2} pack flow valve is open -- A32NX_COND_PACK_FLOW +- A32NX_COND_PACK_FLOW_{index} - Percent - - Percentage flow coming out of the packs into the cabin (LO: 80%, NORM: 100%, HI: 120%) + - Percentage flow coming out of each pack {1 or 2} into the cabin (LO: 80%, NORM: 100%, HI: 120%) - A32NX_OVHD_COND_{id}_SELECTOR_KNOB - Percentage @@ -2448,6 +2351,7 @@ In the variables below, {number} should be replaced with one item in the set: { - Percent open of the cabin pressure safety valves - A32NX_PRESS_AUTO_LANDING_ELEVATION + - **Deprecated** - Feet - Automatic landing elevation as calculated by the MCDU when a destination runway is entered @@ -2693,6 +2597,373 @@ In the variables below, {number} should be replaced with one item in the set: { - Note that multiple SFCC are not yet implemented, thus no {number} in the name. - The Flap FPPU angle ranges from 0° to 360° +## Flight Controls (ATA 27) + +- A32NX_FLIGHT_CONTROLS_TRACKING_MODE + - Bool + - Indicates if tracking mode is active: flight controls are coming from external source (ie: YourControls) + +- A32NX_LOGGING_FLIGHT_CONTROLS_ENABLED + - Bool + - Indicates if logging of flight control events is enabled + +- A32NX_FCDC_{number}_DISCRETE_WORD_1 + - Arinc429 + - | Bit | Description | + |:---:|:----------------------------------------:| + | 11 | Pitch Normal Law Active | + | 12 | Pitch Alternate Law 1 Active | + | 13 | Pitch Alternate Law 2 Active | + | 14 | | + | 15 | Pitch Direct Law Active | + | 16 | Roll Normal Law Active | + | 17 | Roll Direct Law Active | + | 18 | | + | 19 | ELAC 1 Pitch Fault | + | 20 | ELAC 1 Roll Fault | + | 21 | ELAC 2 Pitch Fault | + | 22 | ELAC 2 Roll Fault | + | 23 | ELAC 1 Fault | + | 24 | ELAC 2 Fault | + | 25 | SEC 1 Fault | + | 26 | SEC 2 Fault | + | 27 | | + | 28 | FCDC Opposite Fault | + | 29 | SEC 3 Fault | + +- A32NX_FCDC_{number}_DISCRETE_WORD_2 + - Arinc429 + - | Bit | Description | + |:---:|:----------------------------------------:| + | 11 | Left Aileron Blue Fault | + | 12 | Left Aileron Green Fault | + | 13 | Right Aileron Blue Fault | + | 14 | Right Aileron Green Fault | + | 15 | Left Elevator Blue Fault | + | 16 | Left Elevator Green Fault | + | 17 | Right Elevator Blue Fault | + | 18 | Right Elevator Yellow Fault | + | 19 | F/O Priority Locked | + | 20 | Capt Priority Locked | + | 21 | | + | 22 | | + | 23 | | + | 24 | | + | 25 | | + | 26 | | + | 27 | | + | 28 | F/O Sidestick Disabled (Priority) | + | 29 | Capt Sidestick Disabled (Priority) | + +- A32NX_FCDC_{number}_DISCRETE_WORD_3 + - Arinc429 + - | Bit | Description | + |:---:|:----------------------------------------:| + | 11 | Left Aileron Blue Avail | + | 12 | Left Aileron Green Avail | + | 13 | Right Aileron Blue Avail | + | 14 | Right Aileron Green Avail | + | 15 | Left Elevator Blue Avail | + | 16 | Left Elevator Green Avail | + | 17 | Right Elevator Blue Avail | + | 18 | Right Elevator Yellow Avail | + | 19 | ELAC 1 Pushbutton Off | + | 20 | ELAC 2 Pushbutton Off | + | 21 | Spoiler 1 Avail | + | 22 | Spoiler 2 Avail | + | 23 | Spoiler 3 Avail | + | 24 | Spoiler 4 Avail | + | 25 | Spoiler 5 Avail | + | 26 | | + | 27 | SEC 1 Pushbutton Off | + | 28 | SEC 2 Pushbutton Off | + | 29 | SEC 3 Pushbutton Off | + +- A32NX_FCDC_{number}_DISCRETE_WORD_4 + - Arinc429 + - | Bit | Description | + |:---:|:----------------------------------------:| + | 11 | Left Spoiler 1 Out | + | 12 | Right Spoiler 1 Out | + | 13 | Left Spoiler 2 Out | + | 14 | Right Spoiler 2 Out | + | 15 | Left Spoiler 3 Out | + | 16 | Right Spoiler 3 Out | + | 17 | Left Spoiler 4 Out | + | 18 | Right Spoiler 4 Out | + | 19 | Left Spoiler 5 Out | + | 20 | Right Spoiler 5 Out | + | 21 | Spoiler 1 Pos Valid | + | 22 | Spoiler 2 Pos Valid | + | 23 | Spoiler 3 Pos Valid | + | 24 | Spoiler 4 Pos Valid | + | 25 | Spoiler 5 Pos Valid | + | 26 | Ground Spoiler Out | + | 27 | Ground Spoiler Armed | + | 28 | Speed Brake Command | + | 29 | Aileron Droop Active | + +- A32NX_FCDC_{number}_DISCRETE_WORD_5 + - Arinc429 + - | Bit | Description | + |:---:|:----------------------------------------:| + | 11 | SEC 1 Spd Brk Lever Fault | + | 12 | SEC 2 Spd Brk Lever Fault | + | 13 | SEC 3 Spd Brk Lever Fault | + | 14 | SEC 1 Gnd Splr Fault | + | 15 | SEC 2 Gnd Splr Fault | + | 16 | SEC 3 Gnd Splr Fault | + | 17 | | + | 18 | | + | 19 | | + | 20 | | + | 21 | Spoiler 1 Fault | + | 22 | Spoiler 2 Fault | + | 23 | Spoiler 3 Fault | + | 24 | Spoiler 4 Fault | + | 25 | Spoiler 5 Fault | + | 26 | Spd Brk Lever Disagree | + | 27 | Spd Brk Do Not Use | + | 28 | | + | 29 | | + +- A32NX_FCDC_{number}_CAPT_ROLL_COMMAND + - Arinc429 + +- A32NX_FCDC_{number}_FO_ROLL_COMMAND + - Arinc429 + +- A32NX_FCDC_{number}_CAPT_PITCH_COMMAND + - Arinc429 + +- A32NX_FCDC_{number}_FO_PITCH_COMMAND + - Arinc429 + +- A32NX_FCDC_{number}_RUDDER_PEDAL_POS + - Arinc429 + +- A32NX_FCDC_{number}_AILERON_LEFT_POS + - Arinc429 + +- A32NX_FCDC_{number}_ELEVATOR_LEFT_POS + - Arinc429 + +- A32NX_FCDC_{number}_AILERON_RIGHT_POS + - Arinc429 + +- A32NX_FCDC_{number}_ELEVATOR_RIGHT_POS + - Arinc429 + +- A32NX_FCDC_{number}_ELEVATOR_TRIM_POS + - Arinc429 + +- A32NX_FCDC_{number}_SPOILER_LEFT_{spoiler}_POS + - Arinc429 + - {spoiler} + - Number of the spoiler, 1 to 5 + +- A32NX_FCDC_{number}_SPOILER_RIGHT_{spoiler}_POS + - Arinc429 + - {spoiler} + - Number of the spoiler, 1 to 5 + +- A32NX_FCDC_{number}_PRIORITY_LIGHT_{side}_{color}_ON + - Boolean + - Indicates if the spcified priority light should be illuminated + - {side} + - CAPT + - FO + - {color} + - GREEN + - RED + +- A32NX_ELAC_{number}_PUSHBUTTON_PRESSED + - Boolean + +- A32NX_ELAC_{number}_DIGITAL_OP_VALIDATED + - If the ELAC {number} is healthy. + - Boolean + +- A32NX_SEC_{number}_PUSHBUTTON_PRESSED + - Boolean + +- A32NX_SEC_{number}_FAULT_LIGHT_ON + - If the SEC {number} fault light should be illuminated. + - Boolean + +- A32NX_SEC_{number}_GROUND_SPOILER_OUT + - If the SEC {number} indicates that it's ground spoilers are deployed. + - Boolean + +- A32NX_FAC_{number}_PUSHBUTTON_PRESSED + - Boolean + +- A32NX_FAC_{number}_HEALTHY + - If the FAC {number} is healthy. + - Boolean + +- A32NX_{side}_{surface}_{system}_SERVO_SOLENOID_ENERGIZED + - Boolean + - If the servo mode solenoid of the specified servo should be energized. + - {side} + - LEFT + - RIGHT + - {surface} + - ELEV + - AIL + - {system} + - GREEN + - BLUE + - YELLOW + +- A32NX_{side}_SPOILER_{number}_COMMANDED_POSITION + - Number + - The commanded position of the specified servo, in degrees. + - {side} + - LEFT + - RIGHT + - {number} + - 1 to 5 + +- A32NX_{side}_{surface}_{system}_COMMANDED_POSITION + - Number + - The commanded position of the specified servo, in degrees. + - {side} + - LEFT + - RIGHT + - {surface} + - ELEV + - AIL + - {system} + - GREEN + - BLUE + - YELLOW + +- A32NX_YAW_DAMPER_{system}_SERVO_SOLENOID_ENERGIZED + - Boolean + - If the servo mode solenoid of the specified servo should be energized. + - {system} + - GREEN + - YELLOW + +- A32NX_YAW_DAMPER_{system}_COMMANDED_POSITION + - Number + - The commanded position of the specified servo, in degrees. + - {system} + - GREEN + - YELLOW + +- A32NX_RUDDER_TRIM_{number}_ACTIVE_MODE_COMMANDED + - Boolean + - Trim electric motor {number} is commanded active + - {number} + - 1 + - 2 + +- A32NX_RUDDER_TRIM_{number}_COMMANDED_POSITION + - Degree + - Trim electric motor {number} position demand in trim surface deflection angle + - {number} + - 1 + - 2 + +- A32NX_RUDDER_TRAVEL_LIM_{number}_ACTIVE_MODE_COMMANDED + - Boolean + - RTL electric motor {number} is commanded active + - {number} + - 1 + - 2 + +- A32NX_RUDDER_TRAVEL_LIM_{number}_COMMANDED_POSITION + - Degree + - RTL electric motor {number} position demand in trim surface deflection angle + - {number} + - 1 + - 2 + +- A32NX_THS_{number}_ACTIVE_MODE_COMMANDED + - Boolean + - Trim electric motor {number} is commanded active + - {number} + - 1 + - 2 + - 3 + +- A32NX_THS_{number}_COMMANDED_POSITION + - Degree + - Trim electric motor {number} position demand in trim surface deflection angle + +- A32NX_SIDESTICK_POSITION_X + - Number + - Provides the direct sidestick position (lateral) + Value | Meaning + --- | --- + -1 | full left + 0 | neutral + 1 | full right + +- A32NX_SIDESTICK_POSITION_Y + - Number + - Provides the direct sidestick position (longitudinal) + Value | Meaning + --- | --- + -1 | full forward + 0 | neutral + 1 | full backward + +- A32NX_RUDDER_PEDAL_POSITION + - Number + - Provides the rudder pedal position + Value | Meaning + --- | --- + -100 | full left + 0 | neutral + 100 | full right + +- A32NX_RUDDER_PEDAL_ANIMATION_POSITION + - Number + - Provides the rudder pedal position including rudder trim for animation + Value | Meaning + --- | --- + -100 | full left + 0 | neutral + 100 | full right + +- A32NX_HYD_AILERON_LEFT_DEFLECTION + - Number + - Provides the final left aileron physical position + Value | Meaning + --- | --- + -1.0 | full up + 0.0 | neutral + 1.0 | full down + +- A32NX_HYD_AILERON_RIGHT_DEFLECTION + - Number + - Provides the final right aileron physical position + Value | Meaning + --- | --- + -1.0 | full down + 0.0 | neutral + 1.0 | full up + +- A32NX_HYD_THS_TRIM_MANUAL_OVERRIDE + - Boolean + - Feedback signal from the trim actuator system. True if pilot is moving or holding trim wheel + +- A32NX_HYD_TRIM_WHEEL_PERCENT + - Percent + - Trim wheel position in percent + +- A32NX_RUDDER_DEFLECTION_DEMAND + - Number + - Provides the rudder position demand to hydraulics + Value | Meaning + --- | --- + -1.0 | full left + 0.0 | neutral + 1.0 | full right + ## Landing Gear (ATA 32) - A32NX_LGCIU_{number}_DISCRETE_WORD_1 @@ -2732,9 +3003,9 @@ In the variables below, {number} should be replaced with one item in the set: { - | Bit | Description | |:---:|:-----------------------------------------------------------------------------------:| | 11 | LH & RH gear shock absorber compressed (Don't treat GND PWR connected as on ground) | - | 12 | LH gear shock absorber compressed (Don't treat GND PWR connected as on ground) | - | 13 | RH gear shock absorber compressed (Don't treat GND PWR connected as on ground) | - | 14 | Nose gear shock absorber compressed (Don't treat GND PWR connected as on ground) | + | 12 | Nose gear shock absorber compressed (Don't treat GND PWR connected as on ground) | + | 13 | LH gear shock absorber compressed (Don't treat GND PWR connected as on ground) | + | 14 | RH gear shock absorber compressed (Don't treat GND PWR connected as on ground) | | 15 | LH & RH gear downlocked | @@ -2793,6 +3064,7 @@ In the variables below, {number} should be replaced with one item in the set: { - Percent over 100 - {gear} - CENTER + - CENTER_SMALL - LEFT - RIGHT @@ -2804,13 +3076,9 @@ In the variables below, {number} should be replaced with one item in the set: { - LEFT - RIGHT -- A32NX_GEAR_EMERGENCY_EXTENSION_CLICKED - - Indicates the emergency extension handle is clicked in cockpit. - - Bool - -- A32NX_GEAR_EMERGENCY_EXTENSION_IS_TURNED - - Indicates the emergency extension handle is currently turning. - - Bool +- A32NX_GRAVITYGEAR_ROTATE_PCT + - Indicates the position of the gear emergency extension crank handle from 0 to 300 (3 turns) + - Percent ## ATC (ATA 34) diff --git a/flybywire-aircraft-a320-neo-lock-highlight/ModelBehaviorDefs/A32NX/LockHighlight.xml b/flybywire-aircraft-a320-neo-lock-highlight/ModelBehaviorDefs/A32NX/LockHighlight.xml new file mode 100644 index 000000000000..c19a13e187e6 --- /dev/null +++ b/flybywire-aircraft-a320-neo-lock-highlight/ModelBehaviorDefs/A32NX/LockHighlight.xml @@ -0,0 +1,5 @@ + + + + False + diff --git a/flybywire-aircraft-a320-neo/ModelBehaviorDefs/A32NX/Airbus.xml b/flybywire-aircraft-a320-neo/ModelBehaviorDefs/A32NX/Airbus.xml index ec6ee55934e5..6383185025a6 100644 --- a/flybywire-aircraft-a320-neo/ModelBehaviorDefs/A32NX/Airbus.xml +++ b/flybywire-aircraft-a320-neo/ModelBehaviorDefs/A32NX/Airbus.xml @@ -617,70 +617,6 @@ - - - - - - - - - - + + + + diff --git a/src/behavior/src/A32NX_Interior_Printer.xml b/src/behavior/src/A32NX_Interior_Printer.xml index d0f4e7869132..2094238fd682 100644 --- a/src/behavior/src/A32NX_Interior_Printer.xml +++ b/src/behavior/src/A32NX_Interior_Printer.xml @@ -21,6 +21,28 @@ } els{ 0.3 (>O:_ButtonAnimVar) } + + (L:A32NX_PRINTER_PRINTING, bool) 0 > (L:A32NX_PRINTER_TOGGLE, bool) 0 == and if{ + (L:A32NX_PRINTER_ACTIVE) 1 + (>L:A32NX_PRINTER_ACTIVE) + } + (L:A32NX_PRINTER_ACTIVE, number) 300 > if{ + 0 (>L:A32NX_PRINTER_ACTIVE) + 1 (>L:A32NX_PRINTER_TOGGLE) + } + (L:A32NX_PRINTER_PRINTING, bool) 0 == (L:A32NX_PRINTER_TOGGLE, bool) 0 > and if{ + 0 (>L:A32NX_PRINTER_TOGGLE) + 0 (>L:A32NX_PRINTER_ACTIVE) + } + (L:A32NX_PRINTER_PRINTING, bool) 0 == (L:A32NX_PRINTER_ACTIVE, number) 0 > and if{ + 0 (>L:A32NX_PRINTER_ACTIVE) + } + (L:A32NX_PRINTER_PRINTING, bool) 0 > (L:A32NX_PRINTER_TEAR, number) 0 == and + (L:A32NX_PRINTER_PRINTING, bool) 0 > (L:A32NX_PRINTER_TEAR, number) 2 == and or if{ + 1 (>L:A32NX_PRINTER_TEAR) + } + (L:A32NX_PRINTER_PRINTING, bool) 0 == (L:A32NX_PRINTER_TEAR, number) 1 == and if{ + 2 (>L:A32NX_PRINTER_TEAR) + } diff --git a/src/behavior/src/A32NX_Interior_RMP.xml b/src/behavior/src/A32NX_Interior_RMP.xml index 8d007b38943e..58fc50a96bc8 100644 --- a/src/behavior/src/A32NX_Interior_RMP.xml +++ b/src/behavior/src/A32NX_Interior_RMP.xml @@ -64,7 +64,11 @@ + + + 0 (>K:COM3_RADIO_SET_HZ) + BUTTON ASOBO_GT_Push_Button @@ -132,20 +136,50 @@ AM + + + + 7 + + let indicatorsPowered = #INDICATORS_POWERED, bool#; + let id = #ID, number#; + alias toggleSwitch = (L:A32NX_RMP_#SIDE#_TOGGLE_SWITCH, bool); + alias selectedModeLeft = (L:A32NX_RMP_L_SELECTED_MODE, number); + alias selectedModeRight = (L:A32NX_RMP_R_SELECTED_MODE, number); + alias annunciatorLight = (L:A32NX_OVHD_INTLT_ANN, number); + let emissiveDim = if annunciatorLight == 2 { 0.05 } else { 1 }; + // @todo abnormal defined differently with NAV modes available. + let rmpLeftAbnormal = selectedModeLeft != 1 or selectedModeRight == 1; + let rmpRightAbnormal = selectedModeRight != 2 or selectedModeLeft == 2; + let sideLeftCheck = id == 1; + let sideRightCheck = id == 2; + (if (toggleSwitch and ((rmpLeftAbnormal and sideLeftCheck) or (rmpRightAbnormal and sideRightCheck)) or (annunciatorLight == 0)) and indicatorsPowered { + 1 * emissiveDim + } else { + 0 + }) + + + + + 7 let indicatorsPowered = #INDICATORS_POWERED, bool#; + let id = #ID, number#; alias toggleSwitch = (L:A32NX_RMP_#SIDE#_TOGGLE_SWITCH, bool); alias selectedModeLeft = (L:A32NX_RMP_L_SELECTED_MODE, number); alias selectedModeRight = (L:A32NX_RMP_R_SELECTED_MODE, number); alias annunciatorLight = (L:A32NX_OVHD_INTLT_ANN, number); let emissiveDim = if annunciatorLight == 2 { 0.05 } else { 1 }; // @todo abnormal defined differently with NAV modes available. - let rmpLeftAbnormal = selectedModeLeft != 1; - let rmpRightAbnormal = selectedModeRight != 2; - (if (toggleSwitch and (rmpLeftAbnormal or rmpRightAbnormal) or (annunciatorLight == 0)) and indicatorsPowered { + let rmpLeftAbnormal = selectedModeLeft != 1 or selectedModeRight == 1; + let rmpRightAbnormal = selectedModeRight != 2 or selectedModeLeft == 2; + let sideLeftCheck = id == 1; + let sideRightCheck = id == 2; + (if (toggleSwitch and ((rmpLeftAbnormal and sideLeftCheck) or (rmpRightAbnormal and sideRightCheck)) or (annunciatorLight == 0)) and indicatorsPowered { 1 * emissiveDim } else { 0 @@ -156,6 +190,7 @@ + 7 SEL @@ -288,7 +323,7 @@ - (A:LIGHT POTENTIOMETER:#POTENTIOMETER#, Percent over 100) #BACKLIGHTS_POWERED# * + (A:LIGHT POTENTIOMETER:#POTENTIOMETER#, Percent over 100) #BACKLIGHTS_POWERED# (L:A32NX_RMP_#SIDE#_TOGGLE_SWITCH) * * diff --git a/src/failures/src/a320.ts b/src/failures/src/a320.ts index f83e0dcc47ba..96b50241570a 100644 --- a/src/failures/src/a320.ts +++ b/src/failures/src/a320.ts @@ -1,9 +1,18 @@ // One can rightfully argue that this constant shouldn't be located in @flybywiresim/failures. // Once we create an A320 specific package, such as @flybywiresim/a320, we can move it there. export const A320Failure = Object.freeze({ + Fac1Failure: 22000, + Fac2Failure: 22001, TransformerRectifier1: 24000, TransformerRectifier2: 24001, TransformerRectifierEssential: 24002, + Elac1Failure: 27000, + Elac2Failure: 27001, + Sec1Failure: 27002, + Sec2Failure: 27003, + Sec3Failure: 27004, + Fcdc1Failure: 27005, + Fcdc2Failure: 27006, GreenReservoirLeak: 29000, BlueReservoirLeak: 29001, YellowReservoirLeak: 29002, diff --git a/src/fbw/build.sh b/src/fbw/build.sh index 78769a311d4e..05af5824f341 100755 --- a/src/fbw/build.sh +++ b/src/fbw/build.sh @@ -76,6 +76,22 @@ clang++ \ -I "${DIR}/src/inih" \ -I "${DIR}/src/interface" \ "${DIR}/src/interface/SimConnectInterface.cpp" \ + -I "${DIR}/src/busStructures" \ + -I "${DIR}/src/elac" \ + "${DIR}/src/elac/Elac.cpp" \ + -I "${DIR}/src/sec" \ + "${DIR}/src/sec/Sec.cpp" \ + -I "${DIR}/src/fcdc" \ + "${DIR}/src/fcdc/Fcdc.cpp" \ + -I "${DIR}/src/fac" \ + "${DIR}/src/fac/Fac.cpp" \ + -I "${DIR}/src/failures" \ + "${DIR}/src/failures/FailuresConsumer.cpp" \ + -I "${DIR}/src/utils" \ + "${DIR}/src/utils/ConfirmNode.cpp" \ + "${DIR}/src/utils/SRFlipFLop.cpp" \ + "${DIR}/src/utils/PulseNode.cpp" \ + "${DIR}/src/utils/HysteresisNode.cpp" \ -I "${DIR}/src/model" \ "${DIR}/src/model/AutopilotLaws_data.cpp" \ "${DIR}/src/model/AutopilotLaws.cpp" \ @@ -84,8 +100,17 @@ clang++ \ "${DIR}/src/model/Autothrust_data.cpp" \ "${DIR}/src/model/Autothrust.cpp" \ "${DIR}/src/model/Double2MultiWord.cpp" \ - "${DIR}/src/model/FlyByWire_data.cpp" \ - "${DIR}/src/model/FlyByWire.cpp" \ + "${DIR}/src/model/ElacComputer_data.cpp" \ + "${DIR}/src/model/ElacComputer.cpp" \ + "${DIR}/src/model/SecComputer_data.cpp" \ + "${DIR}/src/model/SecComputer.cpp" \ + "${DIR}/src/model/PitchNormalLaw.cpp" \ + "${DIR}/src/model/PitchAlternateLaw.cpp" \ + "${DIR}/src/model/PitchDirectLaw.cpp" \ + "${DIR}/src/model/LateralNormalLaw.cpp" \ + "${DIR}/src/model/LateralDirectLaw.cpp" \ + "${DIR}/src/model/FacComputer_data.cpp" \ + "${DIR}/src/model/FacComputer.cpp" \ "${DIR}/src/model/look1_binlxpw.cpp" \ "${DIR}/src/model/look2_binlcpw.cpp" \ "${DIR}/src/model/look2_binlxpw.cpp" \ @@ -96,13 +121,13 @@ clang++ \ "${DIR}/src/model/uMultiWord2Double.cpp" \ -I "${DIR}/src/zlib" \ "${DIR}/src/zlib/zfstream.cc" \ - "${DIR}/src/AnimationAileronHandler.cpp" \ "${DIR}/src/ElevatorTrimHandler.cpp" \ "${DIR}/src/FlyByWireInterface.cpp" \ "${DIR}/src/FlightDataRecorder.cpp" \ + "${DIR}/src/Arinc429.cpp" \ + "${DIR}/src/Arinc429Utils.cpp" \ "${DIR}/src/LocalVariable.cpp" \ "${DIR}/src/InterpolatingLookupTable.cpp" \ - "${DIR}/src/RudderTrimHandler.cpp" \ "${DIR}/src/SpoilersHandler.cpp" \ "${DIR}/src/ThrottleAxisMapping.cpp" \ "${DIR}/src/CalculatedRadioReceiver.cpp" \ diff --git a/src/fbw/src/AdditionalData.h b/src/fbw/src/AdditionalData.h index 075a34cfc1d6..fe8fd331d3c9 100644 --- a/src/fbw/src/AdditionalData.h +++ b/src/fbw/src/AdditionalData.h @@ -33,4 +33,8 @@ struct AdditionalData { unsigned long long realisticTillerEnabled; double tillerHandlePosition; double noseWheelPosition; + double syncFoEfisEnabled; + double ls1Active; + double ls2Active; + double IsisLsActive; }; diff --git a/src/fbw/src/AnimationAileronHandler.cpp b/src/fbw/src/AnimationAileronHandler.cpp deleted file mode 100644 index ee9653c7e1c8..000000000000 --- a/src/fbw/src/AnimationAileronHandler.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "AnimationAileronHandler.h" -#include - -AnimationAileronHandler::AnimationAileronHandler() { - rateLimiterLeft.setRate(AILERON_RATE); - rateLimiterRight.setRate(AILERON_RATE); - rateLimiterDroop.setRate(DROOP_RATE); - rateLimiterAntiDroop.setRate(ANTI_DROOP_RATE); -} - -void AnimationAileronHandler::update(bool autopilotActive, - bool groundSpoilersActive, - double simulationTime, - double pitchAttitudeDegrees, - double flapsHandleIndex, - double flapsPosition, - double position, - double dt) { - // inhibit condition for anti-droop - if (!areGroundSpoilersActive && groundSpoilersActive && autopilotActive) { - antiDroopInhibited = true; - } else if (!groundSpoilersActive) { - antiDroopInhibited = false; - } - areGroundSpoilersActive = groundSpoilersActive; - - // anti-droop - if (groundSpoilersActive && !antiDroopInhibited && (-pitchAttitudeDegrees) < ANTI_DROOP_PITCH_ATTITUDE_REFERENCE && - flapsHandleIndex > 0) { - rateLimiterAntiDroop.update(ANTI_DROOP_BIAS_ON, dt); - } else { - rateLimiterAntiDroop.update(ANTI_DROOP_BIAS_OFF, dt); - } - - // droop - if ((lastFlapsPosition <= DROOP_MINIMUM_FLAPS_EXTENSION && flapsPosition > DROOP_MINIMUM_FLAPS_EXTENSION) || - (lastFlapsPosition > DROOP_MINIMUM_FLAPS_EXTENSION && flapsPosition <= DROOP_MINIMUM_FLAPS_EXTENSION)) { - eventTime = simulationTime; - } - if (simulationTime - eventTime >= DROOP_TIME_SWITCH) { - if (flapsPosition > DROOP_MINIMUM_FLAPS_EXTENSION) { - targetValueDroop = DROOP_BIAS_ON; - } else { - targetValueDroop = DROOP_BIAS_OFF; - } - } - rateLimiterDroop.update(targetValueDroop, dt); - lastFlapsPosition = flapsPosition; - - // set target position - rateLimiterLeft.update(fmax(-1.0, fmin(1.0, position + rateLimiterDroop.getValue() + rateLimiterAntiDroop.getValue())), dt); - rateLimiterRight.update(fmax(-1.0, fmin(1.0, position - rateLimiterDroop.getValue() - rateLimiterAntiDroop.getValue())), dt); -} - -double AnimationAileronHandler::getPositionLeft() { - return rateLimiterLeft.getValue(); -} - -double AnimationAileronHandler::getPositionRight() { - return rateLimiterRight.getValue(); -} diff --git a/src/fbw/src/AnimationAileronHandler.h b/src/fbw/src/AnimationAileronHandler.h deleted file mode 100644 index eacb0f20188d..000000000000 --- a/src/fbw/src/AnimationAileronHandler.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "RateLimiter.h" - -class AnimationAileronHandler { - public: - AnimationAileronHandler(); - - void update(bool autopilotActive, - bool groundSpoilersActive, - double simulationTime, - double pitchAttitudeDegrees, - double flapsHandleIndex, - double flapsPosition, - double position, - double dt); - double getPositionLeft(); - double getPositionRight(); - - private: - static constexpr double AILERON_RATE = 2.00; - static constexpr double DROOP_RATE = 0.25; - static constexpr double DROOP_TIME_SWITCH = 2.0; - static constexpr double DROOP_BIAS_OFF = 0.0; - static constexpr double DROOP_BIAS_ON = 0.2; - static constexpr double DROOP_MINIMUM_FLAPS_EXTENSION = 0.01; - static constexpr double ANTI_DROOP_RATE = 1.0; - static constexpr double ANTI_DROOP_BIAS_OFF = 0.0; - static constexpr double ANTI_DROOP_BIAS_ON = -1.2; - static constexpr double ANTI_DROOP_PITCH_ATTITUDE_REFERENCE = 2.5; - - bool antiDroopInhibited = false; - bool areGroundSpoilersActive = false; - - double eventTime = 0; - - double lastFlapsPosition = 0; - double targetValueDroop = DROOP_BIAS_OFF; - - RateLimiter rateLimiterLeft; - RateLimiter rateLimiterRight; - RateLimiter rateLimiterDroop; - RateLimiter rateLimiterAntiDroop; -}; diff --git a/src/fbw/src/Arinc429.cpp b/src/fbw/src/Arinc429.cpp new file mode 100644 index 000000000000..393222d2cd89 --- /dev/null +++ b/src/fbw/src/Arinc429.cpp @@ -0,0 +1,101 @@ +#include "Arinc429.h" + +template +Arinc429Word::Arinc429Word() {} + +template +void Arinc429Word::setFromSimVar(double simVar) { + Arinc429Word convertedWord = *reinterpret_cast(&simVar); + rawSsm = convertedWord.ssm(); + rawData = convertedWord.value(); +} +template void Arinc429Word::setFromSimVar(double simVar); +template void Arinc429Word::setFromSimVar(double simVar); + +template +void Arinc429Word::setFromData(T data, Arinc429SignStatus ssm) { + rawSsm = ssm; + rawData = data; +} +template void Arinc429Word::setFromData(uint32_t data, Arinc429SignStatus ssm); +template void Arinc429Word::setFromData(float data, Arinc429SignStatus ssm); + +template +double Arinc429Word::toSimVar() { + return *reinterpret_cast(this); +} +template double Arinc429Word::toSimVar(); +template double Arinc429Word::toSimVar(); + +template +Arinc429SignStatus Arinc429Word::ssm() const { + return static_cast(rawSsm); +} +template Arinc429SignStatus Arinc429Word::ssm() const; +template Arinc429SignStatus Arinc429Word::ssm() const; + +template +void Arinc429Word::setSsm(Arinc429SignStatus ssm) { + rawSsm = static_cast(ssm); +} +template void Arinc429Word::setSsm(Arinc429SignStatus ssm); +template void Arinc429Word::setSsm(Arinc429SignStatus ssm); + +template +void Arinc429Word::setData(T data) { + rawData = data; +} +template void Arinc429Word::setData(uint32_t data); +template void Arinc429Word::setData(float data); + +template +bool Arinc429Word::isFw() const { + return static_cast(rawSsm) == Arinc429SignStatus::FailureWarning; +} +template bool Arinc429Word::isFw() const; +template bool Arinc429Word::isFw() const; + +template +bool Arinc429Word::isNo() const { + return static_cast(rawSsm) == Arinc429SignStatus::NormalOperation; +} +template bool Arinc429Word::isNo() const; +template bool Arinc429Word::isNo() const; + +template +T Arinc429Word::value() const { + return rawData; +} +template uint32_t Arinc429Word::value() const; +template float Arinc429Word::value() const; + +template +T Arinc429Word::valueOr(T defaultVal) const { + if (rawSsm == NormalOperation || rawSsm == FunctionalTest) { + return rawData; + } else { + return defaultVal; + } +} +template uint32_t Arinc429Word::valueOr(uint32_t defaultVal) const; +template float Arinc429Word::valueOr(float defaultVal) const; + +Arinc429DiscreteWord::Arinc429DiscreteWord() {} + +bool Arinc429DiscreteWord::bitFromValue(int bit) const { + return (static_cast(rawData) >> (bit - 1)) & 0x01; +} + +bool Arinc429DiscreteWord::bitFromValueOr(int bit, bool defaultVal) const { + if (rawSsm == NormalOperation || rawSsm == FunctionalTest) { + return (static_cast(rawData) >> (bit - 1)) & 0x01; + } else { + return defaultVal; + } +} + +void Arinc429DiscreteWord::setBit(int bit, bool value) { + rawData = static_cast((static_cast(rawData) & ~(1 << (bit - 1))) | (value << (bit - 1))); +} + +Arinc429NumericWord::Arinc429NumericWord() {} diff --git a/src/fbw/src/Arinc429.h b/src/fbw/src/Arinc429.h new file mode 100644 index 000000000000..2cf9b492379d --- /dev/null +++ b/src/fbw/src/Arinc429.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +enum Arinc429SignStatus { + FailureWarning = 0b00, + NoComputedData = 0b01, + FunctionalTest = 0b10, + NormalOperation = 0b11, +}; + +template +class Arinc429Word { + protected: + Arinc429Word(); + + uint32_t rawSsm; + + T rawData; + + public: + void setFromSimVar(double simVar); + + void setFromData(T data, Arinc429SignStatus ssm); + + double toSimVar(); + + Arinc429SignStatus ssm() const; + + void setSsm(Arinc429SignStatus ssm); + + void setData(T data); + + bool isFw() const; + + bool isNo() const; + + T value() const; + + T valueOr(T defaultVal) const; +}; + +class Arinc429DiscreteWord : public Arinc429Word { + public: + Arinc429DiscreteWord(); + + bool bitFromValue(int bit) const; + + bool bitFromValueOr(int bit, bool defaultVal) const; + + void setBit(int bit, bool value); +}; + +class Arinc429NumericWord : public Arinc429Word { + public: + Arinc429NumericWord(); +}; diff --git a/src/fbw/src/Arinc429Utils.cpp b/src/fbw/src/Arinc429Utils.cpp new file mode 100644 index 000000000000..a7ddfd5e8a13 --- /dev/null +++ b/src/fbw/src/Arinc429Utils.cpp @@ -0,0 +1,43 @@ +#include "Arinc429Utils.h" + +base_arinc_429 Arinc429Utils::fromSimVar(double simVar) { + return *reinterpret_cast(&simVar); +} + +double Arinc429Utils::toSimVar(base_arinc_429 word) { + return *reinterpret_cast(&word); +} + +bool Arinc429Utils::isFw(base_arinc_429 word) { + return static_cast(word.SSM) == SignStatusMatrix::FailureWarning; +} + +bool Arinc429Utils::isNo(base_arinc_429 word) { + return static_cast(word.SSM) == SignStatusMatrix::NormalOperation; +} + +float Arinc429Utils::valueOr(base_arinc_429 word, float defaultVal) { + auto castSsm = static_cast(word.SSM); + if (castSsm == SignStatusMatrix::NormalOperation || castSsm == SignStatusMatrix::FunctionalTest) { + return word.Data; + } else { + return defaultVal; + } +} + +bool Arinc429Utils::bitFromValue(base_arinc_429 word, int bit) { + return (static_cast(word.Data) >> (bit - 1)) & 0x01; +} + +bool Arinc429Utils::bitFromValueOr(base_arinc_429 word, int bit, bool defaultVal) { + auto castSsm = static_cast(word.SSM); + if (castSsm == SignStatusMatrix::NormalOperation || castSsm == SignStatusMatrix::FunctionalTest) { + return (static_cast(word.Data) >> (bit - 1)) & 0x01; + } else { + return defaultVal; + } +} + +void Arinc429Utils::setBit(base_arinc_429& word, int bit, bool value) { + word.Data = static_cast((static_cast(word.Data) & ~(1 << (bit - 1))) | (value << (bit - 1))); +} diff --git a/src/fbw/src/Arinc429Utils.h b/src/fbw/src/Arinc429Utils.h new file mode 100644 index 000000000000..cc6f7603fbcc --- /dev/null +++ b/src/fbw/src/Arinc429Utils.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include "model/ElacComputer_types.h" + +namespace Arinc429Utils { +base_arinc_429 fromSimVar(double simVar); + +double toSimVar(base_arinc_429 word); + +bool isFw(base_arinc_429 word); + +bool isNo(base_arinc_429 word); + +float valueOr(base_arinc_429 word, float defaultVal); + +bool bitFromValue(base_arinc_429 word, int bit); + +bool bitFromValueOr(base_arinc_429 word, int bit, bool defaultVal); + +void setBit(base_arinc_429& word, int bit, bool value); +}; // namespace Arinc429Utils diff --git a/src/fbw/src/FlightDataRecorder.cpp b/src/fbw/src/FlightDataRecorder.cpp index 569e0784a14c..0b7f87133898 100644 --- a/src/fbw/src/FlightDataRecorder.cpp +++ b/src/fbw/src/FlightDataRecorder.cpp @@ -41,7 +41,6 @@ void FlightDataRecorder::initialize() { void FlightDataRecorder::update(AutopilotStateMachineModelClass* autopilotStateMachine, AutopilotLawsModelClass* autopilotLaws, AutothrustModelClass* autoThrust, - FlyByWireModelClass* flyByWire, const EngineData& engineData, const AdditionalData& additionalData) { // check if enabled @@ -56,7 +55,6 @@ void FlightDataRecorder::update(AutopilotStateMachineModelClass* autopilotStateM fileStream->write((char*)(&autopilotStateMachine->getExternalOutputs().out), sizeof(autopilotStateMachine->getExternalOutputs().out)); fileStream->write((char*)(&autopilotLaws->getExternalOutputs().out.output), sizeof(autopilotLaws->getExternalOutputs().out.output)); fileStream->write((char*)(&autoThrust->getExternalOutputs().out), sizeof(autoThrust->getExternalOutputs().out)); - fileStream->write((char*)(&flyByWire->getExternalOutputs().out), sizeof(flyByWire->getExternalOutputs().out)); fileStream->write((char*)(&engineData), sizeof(engineData)); fileStream->write((char*)(&additionalData), sizeof(additionalData)); } diff --git a/src/fbw/src/FlightDataRecorder.h b/src/fbw/src/FlightDataRecorder.h index 0103762e7a56..9dbac11a7175 100644 --- a/src/fbw/src/FlightDataRecorder.h +++ b/src/fbw/src/FlightDataRecorder.h @@ -7,20 +7,18 @@ #include "AutopilotStateMachine.h" #include "Autothrust.h" #include "EngineData.h" -#include "FlyByWire.h" #include "zfstream.h" class FlightDataRecorder { public: // IMPORTANT: this constant needs to increased with every interface change - const uint64_t INTERFACE_VERSION = 21; + const uint64_t INTERFACE_VERSION = 23; void initialize(); void update(AutopilotStateMachineModelClass* autopilotStateMachine, AutopilotLawsModelClass* autopilotLaws, AutothrustModelClass* autoThrust, - FlyByWireModelClass* flyByWire, const EngineData& engineData, const AdditionalData& additionalData); diff --git a/src/fbw/src/FlyByWireInterface.cpp b/src/fbw/src/FlyByWireInterface.cpp index 4f11c0bc52c7..2bb519d86068 100644 --- a/src/fbw/src/FlyByWireInterface.cpp +++ b/src/fbw/src/FlyByWireInterface.cpp @@ -4,6 +4,7 @@ #include #include +#include "Arinc429Utils.h" #include "FlyByWireInterface.h" #include "SimConnectData.h" @@ -20,22 +21,22 @@ bool FlyByWireInterface::connect() { // setup handlers spoilersHandler = make_shared(); elevatorTrimHandler = make_shared(); - rudderTrimHandler = make_shared(); - animationAileronHandler = make_shared(); + + // initialize failures handler + failuresConsumer.initialize(); // initialize model autopilotStateMachine.initialize(); autopilotLaws.initialize(); autoThrust.initialize(); - flyByWire.initialize(); // initialize flight data recorder flightDataRecorder.initialize(); // connect to sim connect - return simConnectInterface.connect(clientDataEnabled, autopilotStateMachineEnabled, autopilotLawsEnabled, flyByWireEnabled, throttleAxis, - spoilersHandler, elevatorTrimHandler, rudderTrimHandler, flightControlsKeyChangeAileron, - flightControlsKeyChangeElevator, flightControlsKeyChangeRudder, + return simConnectInterface.connect(clientDataEnabled, autopilotStateMachineEnabled, autopilotLawsEnabled, flyByWireEnabled, elacDisabled, + secDisabled, facDisabled, throttleAxis, spoilersHandler, elevatorTrimHandler, + flightControlsKeyChangeAileron, flightControlsKeyChangeElevator, flightControlsKeyChangeRudder, disableXboxCompatibilityRudderAxisPlusMinus, idMinimumSimulationRate->get(), idMaximumSimulationRate->get(), limitSimulationRateByPerformance); } @@ -47,7 +48,6 @@ void FlyByWireInterface::disconnect() { // terminate model autopilotStateMachine.terminate(); autopilotLaws.terminate(); - flyByWire.terminate(); // terminate flight data recorder flightDataRecorder.terminate(); @@ -62,6 +62,9 @@ void FlyByWireInterface::disconnect() { bool FlyByWireInterface::update(double sampleTime) { bool result = true; + // update failures handler + failuresConsumer.update(); + // get data & inputs result &= readDataAndLocalVariables(sampleTime); @@ -100,6 +103,40 @@ bool FlyByWireInterface::update(double sampleTime) { // get throttle data and process it result &= updateAutothrust(calculatedSampleTime); + for (int i = 0; i < 2; i++) { + result &= updateRa(i); + } + + for (int i = 0; i < 2; i++) { + result &= updateLgciu(i); + } + + for (int i = 0; i < 2; i++) { + result &= updateSfcc(i); + } + + for (int i = 0; i < 3; i++) { + result &= updateAdirs(i); + } + + for (int i = 0; i < 2; i++) { + result &= updateElac(calculatedSampleTime, i); + } + + for (int i = 0; i < 3; i++) { + result &= updateSec(calculatedSampleTime, i); + } + + for (int i = 0; i < 2; i++) { + result &= updateFac(calculatedSampleTime, i); + } + + for (int i = 0; i < 2; i++) { + result &= updateFcdc(calculatedSampleTime, i); + } + + result &= updateServoSolenoidStatus(); + // update additional recording data result &= updateAdditionalData(calculatedSampleTime); @@ -109,8 +146,11 @@ bool FlyByWireInterface::update(double sampleTime) { // update spoilers result &= updateSpoilers(calculatedSampleTime); + // update FO side with FO Sync ON + result &= updateFoSide(calculatedSampleTime); + // update flight data recorder - flightDataRecorder.update(&autopilotStateMachine, &autopilotLaws, &autoThrust, &flyByWire, engineData, additionalData); + flightDataRecorder.update(&autopilotStateMachine, &autopilotLaws, &autoThrust, engineData, additionalData); // if default AP is on -> disconnect it if (simConnectInterface.getSimData().autopilot_master_on) { @@ -136,10 +176,14 @@ void FlyByWireInterface::loadConfiguration() { autopilotLawsEnabled = INITypeConversion::getBoolean(iniStructure, "MODEL", "AUTOPILOT_LAWS_ENABLED", true); autoThrustEnabled = INITypeConversion::getBoolean(iniStructure, "MODEL", "AUTOTHRUST_ENABLED", true); flyByWireEnabled = INITypeConversion::getBoolean(iniStructure, "MODEL", "FLY_BY_WIRE_ENABLED", true); + elacDisabled = INITypeConversion::getInteger(iniStructure, "MODEL", "ELAC_DISABLED", -1); + secDisabled = INITypeConversion::getInteger(iniStructure, "MODEL", "SEC_DISABLED", -1); + facDisabled = INITypeConversion::getInteger(iniStructure, "MODEL", "FAC_DISABLED", -1); tailstrikeProtectionEnabled = INITypeConversion::getBoolean(iniStructure, "MODEL", "TAILSTRIKE_PROTECTION_ENABLED", false); // if any model is deactivated we need to enable client data - clientDataEnabled = (!autopilotStateMachineEnabled || !autopilotLawsEnabled || !autoThrustEnabled || !flyByWireEnabled); + clientDataEnabled = (elacDisabled != -1 || secDisabled != -1 || facDisabled != -1 || !autopilotStateMachineEnabled || + !autopilotLawsEnabled || !autoThrustEnabled || !flyByWireEnabled); // print configuration into console cout << "WASM: MODEL : CLIENT_DATA_ENABLED (auto) = " << clientDataEnabled << endl; @@ -147,6 +191,9 @@ void FlyByWireInterface::loadConfiguration() { cout << "WASM: MODEL : AUTOPILOT_LAWS_ENABLED = " << autopilotLawsEnabled << endl; cout << "WASM: MODEL : AUTOTHRUST_ENABLED = " << autoThrustEnabled << endl; cout << "WASM: MODEL : FLY_BY_WIRE_ENABLED = " << flyByWireEnabled << endl; + cout << "WASM: MODEL : ELAC_DISABLED = " << elacDisabled << endl; + cout << "WASM: MODEL : SEC_DISABLED = " << secDisabled << endl; + cout << "WASM: MODEL : FAC_DISABLED = " << facDisabled << endl; cout << "WASM: MODEL : TAILSTRIKE_PROTECTION_ENABLED = " << tailstrikeProtectionEnabled << endl; // -------------------------------------------------------------------------- @@ -245,6 +292,7 @@ void FlyByWireInterface::setupLocalVariables() { idPerformanceWarningActive = make_unique("A32NX_PERFORMANCE_WARNING_ACTIVE"); // register L variable for external override + idTrackingMode = make_unique("A32NX_FLIGHT_CONTROLS_TRACKING_MODE"); idExternalOverride = make_unique("A32NX_EXTERNAL_OVERRIDE"); // register L variable for FDR event @@ -279,9 +327,6 @@ void FlyByWireInterface::setupLocalVariables() { idFlightDirectorPitch = make_unique("A32NX_FLIGHT_DIRECTOR_PITCH"); idFlightDirectorYaw = make_unique("A32NX_FLIGHT_DIRECTOR_YAW"); - idBetaTarget = make_unique("A32NX_BETA_TARGET"); - idBetaTargetActive = make_unique("A32NX_BETA_TARGET_ACTIVE"); - // register L variables for autoland warning idAutopilotAutolandWarning = make_unique("A32NX_AUTOPILOT_AUTOLAND_WARNING"); @@ -295,12 +340,6 @@ void FlyByWireInterface::setupLocalVariables() { idAutopilotAutothrustMode = make_unique("A32NX_AUTOPILOT_AUTOTHRUST_MODE"); - // speeds - idSpeedAlphaProtection = make_unique("A32NX_SPEEDS_ALPHA_PROTECTION"); - idSpeedAlphaMax = make_unique("A32NX_SPEEDS_ALPHA_MAX"); - - idAlphaMaxPercentage = make_unique("A32NX_ALPHA_MAX_PERCENTAGE"); - // register L variables for flight guidance idFwcFlightPhase = make_unique("A32NX_FWC_FLIGHT_PHASE"); idFmgcFlightPhase = make_unique("A32NX_FMGC_FLIGHT_PHASE"); @@ -433,12 +472,8 @@ void FlyByWireInterface::setupLocalVariables() { idSpoilersArmed = make_unique("A32NX_SPOILERS_ARMED"); idSpoilersHandlePosition = make_unique("A32NX_SPOILERS_HANDLE_POSITION"); - idSpoilersGroundSpoilersActive = make_unique("A32NX_SPOILERS_GROUND_SPOILERS_ACTIVE"); - idSpoilersPositionLeft = make_unique("A32NX_SPOILERS_LEFT_DEFLECTION_DEMAND"); - idSpoilersPositionRight = make_unique("A32NX_SPOILERS_RIGHT_DEFLECTION_DEMAND"); - idAileronPositionLeft = make_unique("A32NX_AILERON_LEFT_DEFLECTION_DEMAND"); - idAileronPositionRight = make_unique("A32NX_AILERON_RIGHT_DEFLECTION_DEMAND"); + idRudderPosition = make_unique("A32NX_RUDDER_DEFLECTION_DEMAND"); idRadioReceiverUsageEnabled = make_unique("A32NX_RADIO_RECEIVER_USAGE_ENABLED"); idRadioReceiverLocalizerValid = make_unique("A32NX_RADIO_RECEIVER_LOC_IS_VALID"); @@ -450,6 +485,229 @@ void FlyByWireInterface::setupLocalVariables() { idRealisticTillerEnabled = make_unique("A32NX_REALISTIC_TILLER_ENABLED"); idTillerHandlePosition = make_unique("A32NX_TILLER_HANDLE_POSITION"); idNoseWheelPosition = make_unique("A32NX_NOSE_WHEEL_POSITION"); + + idSyncFoEfisEnabled = make_unique("A32NX_FO_SYNC_EFIS_ENABLED"); + + idLs1Active = make_unique("BTN_LS_1_FILTER_ACTIVE"); + idLs2Active = make_unique("BTN_LS_2_FILTER_ACTIVE"); + idIsisLsActive = make_unique("A32NX_ISIS_LS_ACTIVE"); + + for (int i = 0; i < 2; i++) { + string idString = std::to_string(i + 1); + idRadioAltimeterHeight[i] = make_unique("A32NX_RA_" + idString + "_RADIO_ALTITUDE"); + } + + for (int i = 0; i < 2; i++) { + string idString = std::to_string(i + 1); + idLgciuNoseGearCompressed[i] = make_unique("A32NX_LGCIU_" + idString + "_NOSE_GEAR_COMPRESSED"); + idLgciuLeftMainGearCompressed[i] = make_unique("A32NX_LGCIU_" + idString + "_LEFT_GEAR_COMPRESSED"); + idLgciuRightMainGearCompressed[i] = make_unique("A32NX_LGCIU_" + idString + "_RIGHT_GEAR_COMPRESSED"); + idLgciuDiscreteWord1[i] = make_unique("A32NX_LGCIU_" + idString + "_DISCRETE_WORD_1"); + idLgciuDiscreteWord2[i] = make_unique("A32NX_LGCIU_" + idString + "_DISCRETE_WORD_2"); + idLgciuDiscreteWord3[i] = make_unique("A32NX_LGCIU_" + idString + "_DISCRETE_WORD_3"); + } + + idSfccSlatFlapComponentStatusWord = make_unique("A32NX_SFCC_SLAT_FLAP_COMPONENT_STATUS_WORD"); + idSfccSlatFlapSystemStatusWord = make_unique("A32NX_SFCC_SLAT_FLAP_SYSTEM_STATUS_WORD"); + idSfccSlatFlapActualPositionWord = make_unique("A32NX_SFCC_SLAT_FLAP_ACTUAL_POSITION_WORD"); + idSfccSlatActualPositionWord = make_unique("A32NX_SFCC_SLAT_ACTUAL_POSITION_WORD"); + idSfccFlapActualPositionWord = make_unique("A32NX_SFCC_FLAP_ACTUAL_POSITION_WORD"); + + for (int i = 0; i < 3; i++) { + string idString = std::to_string(i + 1); + idAdrAltitudeCorrected[i] = make_unique("A32NX_ADIRS_ADR_" + idString + "_ALTITUDE"); + idAdrMach[i] = make_unique("A32NX_ADIRS_ADR_" + idString + "_MACH"); + idAdrAirspeedComputed[i] = make_unique("A32NX_ADIRS_ADR_" + idString + "_COMPUTED_AIRSPEED"); + idAdrAirspeedTrue[i] = make_unique("A32NX_ADIRS_ADR_" + idString + "_TRUE_AIRSPEED"); + idAdrVerticalSpeed[i] = make_unique("A32NX_ADIRS_ADR_" + idString + "_BAROMETRIC_VERTICAL_SPEED"); + idAdrAoaCorrected[i] = make_unique("A32NX_ADIRS_ADR_" + idString + "_ANGLE_OF_ATTACK"); + idAdrCorrectedAverageStaticPressure[i] = + make_unique("A32NX_ADIRS_ADR_" + idString + "_CORRECTED_AVERAGE_STATIC_PRESSURE"); + + idIrLatitude[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_LATITUDE"); + idIrLongitude[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_LONGITUDE"); + idIrGroundSpeed[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_GROUND_SPEED"); + idIrWindSpeed[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_WIND_VELOCITY"); + idIrWindDirectionTrue[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_WIND_DIRECTION"); + idIrTrackAngleMagnetic[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_TRACK"); + idIrHeadingMagnetic[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_HEADING"); + idIrDriftAngle[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_DRIFT_ANGLE"); + idIrFlightPathAngle[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_FLIGHT_PATH_ANGLE"); + idIrPitchAngle[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_PITCH"); + idIrRollAngle[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_ROLL"); + idIrBodyPitchRate[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_BODY_PITCH_RATE"); + idIrBodyRollRate[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_BODY_ROLL_RATE"); + idIrBodyYawRate[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_BODY_YAW_RATE"); + idIrBodyLongAccel[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_BODY_LONGITUDINAL_ACC"); + idIrBodyLatAccel[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_BODY_LATERAL_ACC"); + idIrBodyNormalAccel[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_BODY_NORMAL_ACC"); + idIrTrackAngleRate[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_HEADING_RATE"); + idIrPitchAttRate[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_PITCH_ATT_RATE"); + idIrRollAttRate[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_ROLL_ATT_RATE"); + idIrInertialVerticalSpeed[i] = make_unique("A32NX_ADIRS_IR_" + idString + "_VERTICAL_SPEED"); + } + + for (int i = 0; i < 2; i++) { + string idString = std::to_string(i + 1); + + idFcdcDiscreteWord1[i] = make_unique("A32NX_FCDC_" + idString + "_DISCRETE_WORD_1"); + idFcdcDiscreteWord2[i] = make_unique("A32NX_FCDC_" + idString + "_DISCRETE_WORD_2"); + idFcdcDiscreteWord3[i] = make_unique("A32NX_FCDC_" + idString + "_DISCRETE_WORD_3"); + idFcdcDiscreteWord4[i] = make_unique("A32NX_FCDC_" + idString + "_DISCRETE_WORD_4"); + idFcdcDiscreteWord5[i] = make_unique("A32NX_FCDC_" + idString + "_DISCRETE_WORD_5"); + idFcdcCaptRollCommand[i] = make_unique("A32NX_FCDC_" + idString + "_CAPT_ROLL_COMMAND"); + idFcdcFoRollCommand[i] = make_unique("A32NX_FCDC_" + idString + "_FO_ROLL_COMMAND"); + idFcdcCaptPitchCommand[i] = make_unique("A32NX_FCDC_" + idString + "_CAPT_PITCH_COMMAND"); + idFcdcFoPitchCommand[i] = make_unique("A32NX_FCDC_" + idString + "_FO_PITCH_COMMAND"); + idFcdcRudderPedalPos[i] = make_unique("A32NX_FCDC_" + idString + "_RUDDER_PEDAL_POS"); + idFcdcAileronLeftPos[i] = make_unique("A32NX_FCDC_" + idString + "_AILERON_LEFT_POS"); + idFcdcElevatorLeftPos[i] = make_unique("A32NX_FCDC_" + idString + "_ELEVATOR_LEFT_POS"); + idFcdcAileronRightPos[i] = make_unique("A32NX_FCDC_" + idString + "_AILERON_RIGHT_POS"); + idFcdcElevatorRightPos[i] = make_unique("A32NX_FCDC_" + idString + "_ELEVATOR_RIGHT_POS"); + idFcdcElevatorTrimPos[i] = make_unique("A32NX_FCDC_" + idString + "_ELEVATOR_TRIM_POS"); + idFcdcSpoilerLeft1Pos[i] = make_unique("A32NX_FCDC_" + idString + "_SPOILER_LEFT_1_POS"); + idFcdcSpoilerLeft2Pos[i] = make_unique("A32NX_FCDC_" + idString + "_SPOILER_LEFT_2_POS"); + idFcdcSpoilerLeft3Pos[i] = make_unique("A32NX_FCDC_" + idString + "_SPOILER_LEFT_3_POS"); + idFcdcSpoilerLeft4Pos[i] = make_unique("A32NX_FCDC_" + idString + "_SPOILER_LEFT_4_POS"); + idFcdcSpoilerLeft5Pos[i] = make_unique("A32NX_FCDC_" + idString + "_SPOILER_LEFT_5_POS"); + idFcdcSpoilerRight1Pos[i] = make_unique("A32NX_FCDC_" + idString + "_SPOILER_RIGHT_1_POS"); + idFcdcSpoilerRight2Pos[i] = make_unique("A32NX_FCDC_" + idString + "_SPOILER_RIGHT_2_POS"); + idFcdcSpoilerRight3Pos[i] = make_unique("A32NX_FCDC_" + idString + "_SPOILER_RIGHT_3_POS"); + idFcdcSpoilerRight4Pos[i] = make_unique("A32NX_FCDC_" + idString + "_SPOILER_RIGHT_4_POS"); + idFcdcSpoilerRight5Pos[i] = make_unique("A32NX_FCDC_" + idString + "_SPOILER_RIGHT_5_POS"); + + idFcdcPriorityCaptGreen[i] = make_unique("A32NX_FCDC_" + idString + "_PRIORITY_LIGHT_CAPT_GREEN_ON"); + idFcdcPriorityCaptRed[i] = make_unique("A32NX_FCDC_" + idString + "_PRIORITY_LIGHT_CAPT_RED_ON"); + idFcdcPriorityFoGreen[i] = make_unique("A32NX_FCDC_" + idString + "_PRIORITY_LIGHT_FO_GREEN_ON"); + idFcdcPriorityFoRed[i] = make_unique("A32NX_FCDC_" + idString + "_PRIORITY_LIGHT_FO_RED_ON"); + } + + idThsOverrideActive = make_unique("A32NX_HYD_THS_TRIM_MANUAL_OVERRIDE"); + + for (int i = 0; i < 2; i++) { + string idString = std::to_string(i + 1); + + idElacPushbuttonPressed[i] = make_unique("A32NX_ELAC_" + idString + "_PUSHBUTTON_PRESSED"); + idElacDigitalOpValidated[i] = make_unique("A32NX_ELAC_" + idString + "_DIGITAL_OP_VALIDATED"); + } + + for (int i = 0; i < 3; i++) { + string idString = std::to_string(i + 1); + + idSecPushbuttonPressed[i] = make_unique("A32NX_SEC_" + idString + "_PUSHBUTTON_PRESSED"); + idSecFaultLightOn[i] = make_unique("A32NX_SEC_" + idString + "_FAULT_LIGHT_ON"); + idSecGroundSpoilersOut[i] = make_unique("A32NX_SEC_" + idString + "_GROUND_SPOILER_OUT"); + } + + for (int i = 0; i < 2; i++) { + string idString = std::to_string(i + 1); + + idFacPushbuttonPressed[i] = make_unique("A32NX_FAC_" + idString + "_PUSHBUTTON_PRESSED"); + idFacHealthy[i] = make_unique("A32NX_FAC_" + idString + "_HEALTHY"); + + idFacDiscreteWord1[i] = make_unique("A32NX_FAC_" + idString + "_DISCRETE_WORD_1"); + idFacGammaA[i] = make_unique("A32NX_FAC_" + idString + "_GAMMA_A"); + idFacGammaT[i] = make_unique("A32NX_FAC_" + idString + "_GAMMA_T"); + idFacWeight[i] = make_unique("A32NX_FAC_" + idString + "_WEIGHT"); + idFacCenterOfGravity[i] = make_unique("A32NX_FAC_" + idString + "_CENTER_OF_GRAVITY"); + idFacSideslipTarget[i] = make_unique("A32NX_FAC_" + idString + "_SIDESLIP_TARGET"); + idFacSlatAngle[i] = make_unique("A32NX_FAC_" + idString + "_SLATS_ANGLE"); + idFacFlapAngle[i] = make_unique("A32NX_FAC_" + idString + "_FLAPS_ANGLE"); + idFacDiscreteWord2[i] = make_unique("A32NX_FAC_" + idString + "_DISCRETE_WORD_2"); + idFacRudderTravelLimitCommand[i] = make_unique("A32NX_FAC_" + idString + "_RUDDER_TRAVEL_LIMIT_COMMAND"); + idFacDeltaRYawDamperVoted[i] = make_unique("A32NX_FAC_" + idString + "_DELTA_R_YAW_DAMPER"); + idFacEstimatedSideslip[i] = make_unique("A32NX_FAC_" + idString + "_ESTIMATED_SIDESLIP"); + idFacVAlphaLim[i] = make_unique("A32NX_FAC_" + idString + "_V_ALPHA_LIM"); + idFacVLs[i] = make_unique("A32NX_FAC_" + idString + "_V_LS"); + idFacVStall[i] = make_unique("A32NX_FAC_" + idString + "_V_STALL_1G"); + idFacVAlphaProt[i] = make_unique("A32NX_FAC_" + idString + "_V_ALPHA_PROT"); + idFacVStallWarn[i] = make_unique("A32NX_FAC_" + idString + "_V_STALL_WARN"); + idFacSpeedTrend[i] = make_unique("A32NX_FAC_" + idString + "_SPEED_TREND"); + idFacV3[i] = make_unique("A32NX_FAC_" + idString + "_V_3"); + idFacV4[i] = make_unique("A32NX_FAC_" + idString + "_V_4"); + idFacVMan[i] = make_unique("A32NX_FAC_" + idString + "_V_MAN"); + idFacVMax[i] = make_unique("A32NX_FAC_" + idString + "_V_MAX"); + idFacVFeNext[i] = make_unique("A32NX_FAC_" + idString + "_V_FE_NEXT"); + idFacDiscreteWord3[i] = make_unique("A32NX_FAC_" + idString + "_DISCRETE_WORD_3"); + idFacDiscreteWord4[i] = make_unique("A32NX_FAC_" + idString + "_DISCRETE_WORD_4"); + idFacDiscreteWord5[i] = make_unique("A32NX_FAC_" + idString + "_DISCRETE_WORD_5"); + idFacDeltaRRudderTrim[i] = make_unique("A32NX_FAC_" + idString + "_DELTA_R_RUDDER_TRIM"); + idFacRudderTrimPos[i] = make_unique("A32NX_FAC_" + idString + "_RUDDER_TRIM_POS"); + } + + for (int i = 0; i < 2; i++) { + string aileronStringLeft = i == 0 ? "BLUE" : "GREEN"; + string aileronStringRight = i == 0 ? "GREEN" : "BLUE"; + string elevatorStringLeft = i == 0 ? "BLUE" : "GREEN"; + string elevatorStringRight = i == 0 ? "BLUE" : "YELLOW"; + string yawDamperString = i == 0 ? "GREEN" : "YELLOW"; + string idString = std::to_string(i + 1); + + idLeftAileronSolenoidEnergized[i] = make_unique("A32NX_LEFT_AIL_" + aileronStringLeft + "_SERVO_SOLENOID_ENERGIZED"); + idLeftAileronCommandedPosition[i] = make_unique("A32NX_LEFT_AIL_" + aileronStringLeft + "_COMMANDED_POSITION"); + idRightAileronSolenoidEnergized[i] = make_unique("A32NX_RIGHT_AIL_" + aileronStringRight + "_SERVO_SOLENOID_ENERGIZED"); + idRightAileronCommandedPosition[i] = make_unique("A32NX_RIGHT_AIL_" + aileronStringRight + "_COMMANDED_POSITION"); + idLeftElevatorSolenoidEnergized[i] = make_unique("A32NX_LEFT_ELEV_" + elevatorStringLeft + "_SERVO_SOLENOID_ENERGIZED"); + idLeftElevatorCommandedPosition[i] = make_unique("A32NX_LEFT_ELEV_" + elevatorStringLeft + "_COMMANDED_POSITION"); + idRightElevatorSolenoidEnergized[i] = + make_unique("A32NX_RIGHT_ELEV_" + elevatorStringRight + "_SERVO_SOLENOID_ENERGIZED"); + idRightElevatorCommandedPosition[i] = make_unique("A32NX_RIGHT_ELEV_" + elevatorStringRight + "_COMMANDED_POSITION"); + + idYawDamperSolenoidEnergized[i] = make_unique("A32NX_YAW_DAMPER_" + yawDamperString + "_SERVO_SOLENOID_ENERGIZED"); + idYawDamperCommandedPosition[i] = make_unique("A32NX_YAW_DAMPER_" + yawDamperString + "_COMMANDED_POSITION"); + idRudderTrimActiveModeCommanded[i] = make_unique("A32NX_RUDDER_TRIM_" + idString + "_ACTIVE_MODE_COMMANDED"); + idRudderTrimCommandedPosition[i] = make_unique("A32NX_RUDDER_TRIM_" + idString + "_COMMANDED_POSITION"); + idRudderTravelLimitActiveModeCommanded[i] = + make_unique("A32NX_RUDDER_TRAVEL_LIM_" + idString + "_ACTIVE_MODE_COMMANDED"); + idRudderTravelLimCommandedPosition[i] = make_unique("A32NX_RUDDER_TRAVEL_LIM_" + idString + "_COMMANDED_POSITION"); + } + + for (int i = 0; i < 3; i++) { + string idString = std::to_string(i + 1); + + idTHSActiveModeCommanded[i] = make_unique("A32NX_THS_" + idString + "_ACTIVE_MODE_COMMANDED"); + idTHSCommandedPosition[i] = make_unique("A32NX_THS_" + idString + "_COMMANDED_POSITION"); + } + + for (int i = 0; i < 2; i++) { + string idString = std::to_string(i + 1); + idElevFaultLeft[i] = make_unique("A32NX_LEFT_ELEV_SERVO_" + idString + "_FAILED"); + idElevFaultRight[i] = make_unique("A32NX_RIGHT_ELEV_SERVO_" + idString + "_FAILED"); + idAilFaultLeft[i] = make_unique("A32NX_LEFT_AIL_SERVO_" + idString + "_FAILED"); + idAilFaultRight[i] = make_unique("A32NX_RIGHT_AIL_SERVO_" + idString + "_FAILED"); + } + + for (int i = 0; i < 5; i++) { + string idString = std::to_string(i + 1); + idLeftSpoilerCommandedPosition[i] = make_unique("A32NX_LEFT_SPOILER_" + idString + "_COMMANDED_POSITION"); + idRightSpoilerCommandedPosition[i] = make_unique("A32NX_RIGHT_SPOILER_" + idString + "_COMMANDED_POSITION"); + + idLeftSpoilerPosition[i] = make_unique("A32NX_HYD_SPOILER_" + idString + "_LEFT_DEFLECTION"); + idRightSpoilerPosition[i] = make_unique("A32NX_HYD_SPOILER_" + idString + "_RIGHT_DEFLECTION"); + + idSplrFaultLeft[i] = make_unique("A32NX_LEFT_SPLR_" + idString + "_SERVO_FAILED"); + idSplrFaultRight[i] = make_unique("A32NX_RIGHT_SPLR_" + idString + "_SERVO_FAILED"); + } + + idLeftAileronPosition = make_unique("A32NX_HYD_AILERON_LEFT_DEFLECTION"); + idRightAileronPosition = make_unique("A32NX_HYD_AILERON_RIGHT_DEFLECTION"); + idLeftElevatorPosition = make_unique("A32NX_HYD_ELEVATOR_LEFT_DEFLECTION"); + idRightElevatorPosition = make_unique("A32NX_HYD_ELEVATOR_RIGHT_DEFLECTION"); + + idElecDcBus2Powered = make_unique("A32NX_ELEC_DC_2_BUS_IS_POWERED"); + idElecDcEssShedBusPowered = make_unique("A32NX_ELEC_DC_ESS_SHED_BUS_IS_POWERED"); + idElecDcEssBusPowered = make_unique("A32NX_ELEC_DC_ESS_BUS_IS_POWERED"); + idElecBat1HotBusPowered = make_unique("A32NX_ELEC_DC_HOT_1_BUS_IS_POWERED"); + + idHydYellowSystemPressure = make_unique("A32NX_HYD_YELLOW_SYSTEM_1_SECTION_PRESSURE"); + idHydGreenSystemPressure = make_unique("A32NX_HYD_GREEN_SYSTEM_1_SECTION_PRESSURE"); + idHydBlueSystemPressure = make_unique("A32NX_HYD_BLUE_SYSTEM_1_SECTION_PRESSURE"); + idHydYellowPressurised = make_unique("A32NX_HYD_YELLOW_SYSTEM_1_SECTION_PRESSURE_SWITCH"); + idHydGreenPressurised = make_unique("A32NX_HYD_GREEN_SYSTEM_1_SECTION_PRESSURE_SWITCH"); + idHydBluePressurised = make_unique("A32NX_HYD_BLUE_SYSTEM_1_SECTION_PRESSURE_SWITCH"); + + idCaptPriorityButtonPressed = make_unique("A32NX_PRIORITY_TAKEOVER:1"); + idFoPriorityButtonPressed = make_unique("A32NX_PRIORITY_TAKEOVER:2"); } bool FlyByWireInterface::handleFcuInitialization(double sampleTime) { @@ -516,6 +774,8 @@ bool FlyByWireInterface::readDataAndLocalVariables(double sampleTime) { // reset input simConnectInterface.resetSimInputAutopilot(); + simConnectInterface.resetSimInputRudderTrim(); + // set logging options simConnectInterface.setLoggingFlightControlsEnabled(idLoggingFlightControlsEnabled->get() == 1); simConnectInterface.setLoggingThrottlesEnabled(idLoggingThrottlesEnabled->get() == 1); @@ -593,6 +853,8 @@ bool FlyByWireInterface::readDataAndLocalVariables(double sampleTime) { // calculate delta time (and ensure it does not get 0 -> max 500 fps) calculatedSampleTime = max(0.002, simData.simulationTime - previousSimulationTime); + monotonicTime += calculatedSampleTime; + // store previous simulation time previousSimulationTime = simData.simulationTime; @@ -663,11 +925,13 @@ bool FlyByWireInterface::handleSimulationRate(double sampleTime) { return true; } + bool elac1ProtActive = false; + bool elac2ProtActive = false; + // check if simulation rate should be reduced if (idPerformanceWarningActive->get() == 1 || abs(simConnectInterface.getSimData().Phi_deg) > 33 || - simConnectInterface.getSimData().Theta_deg < -20 || simConnectInterface.getSimData().Theta_deg > 10 || - flyByWireOutput.sim.data_computed.high_aoa_prot_active == 1 || flyByWireOutput.sim.data_computed.high_speed_prot_active == 1 || - autopilotStateMachineOutput.speed_protection_mode == 1) { + simConnectInterface.getSimData().Theta_deg < -20 || simConnectInterface.getSimData().Theta_deg > 10 || elac1ProtActive || + elac2ProtActive || autopilotStateMachineOutput.speed_protection_mode == 1) { // set target simulation rate targetSimulationRateModified = true; targetSimulationRate = max(1, simData.simulation_rate / 2); @@ -732,7 +996,8 @@ bool FlyByWireInterface::updateAdditionalData(double sampleTime) { additionalData.spoilers_handle_pos = idSpoilersHandlePosition->get(); additionalData.spoilers_armed = idSpoilersArmed->get(); additionalData.spoilers_handle_sim_pos = simData.spoilers_handle_position; - additionalData.ground_spoilers_active = idSpoilersGroundSpoilersActive->get(); + additionalData.ground_spoilers_active = + idSecGroundSpoilersOut[0]->get() || idSecGroundSpoilersOut[1]->get() || idSecGroundSpoilersOut[2]->get(); additionalData.flaps_handle_percent = idFlapsHandlePercent->get(); additionalData.flaps_handle_index = idFlapsHandleIndex->get(); additionalData.flaps_handle_configuration_index = flapsHandleIndexFlapConf->get(); @@ -753,6 +1018,12 @@ bool FlyByWireInterface::updateAdditionalData(double sampleTime) { additionalData.tillerHandlePosition = idTillerHandlePosition->get(); additionalData.noseWheelPosition = idNoseWheelPosition->get(); + additionalData.syncFoEfisEnabled = idSyncFoEfisEnabled->get(); + + additionalData.ls1Active = idLs1Active->get(); + additionalData.ls2Active = idLs2Active->get(); + additionalData.IsisLsActive = idIsisLsActive->get(); + return true; } @@ -810,6 +1081,620 @@ bool FlyByWireInterface::updateEngineData(double sampleTime) { return true; } +bool FlyByWireInterface::updateRa(int raIndex) { + raBusOutputs[raIndex].radio_height_ft = Arinc429Utils::fromSimVar(idRadioAltimeterHeight[raIndex]->get()); + + if (clientDataEnabled) { + simConnectInterface.setClientDataRa(raBusOutputs[raIndex], raIndex); + } + + return true; +} + +bool FlyByWireInterface::updateLgciu(int lgciuIndex) { + lgciuBusOutputs[lgciuIndex].discrete_word_1 = Arinc429Utils::fromSimVar(idLgciuDiscreteWord1[lgciuIndex]->get()); + lgciuBusOutputs[lgciuIndex].discrete_word_2 = Arinc429Utils::fromSimVar(idLgciuDiscreteWord2[lgciuIndex]->get()); + lgciuBusOutputs[lgciuIndex].discrete_word_3 = Arinc429Utils::fromSimVar(idLgciuDiscreteWord3[lgciuIndex]->get()); + lgciuBusOutputs[lgciuIndex].discrete_word_4.SSM = Arinc429SignStatus::NormalOperation; + lgciuBusOutputs[lgciuIndex].discrete_word_4.Data = 0; + + if (clientDataEnabled) { + simConnectInterface.setClientDataLgciu(lgciuBusOutputs[lgciuIndex], lgciuIndex); + } + + return true; +} + +bool FlyByWireInterface::updateSfcc(int sfccIndex) { + sfccBusOutputs[sfccIndex].slat_flap_component_status_word = Arinc429Utils::fromSimVar(idSfccSlatFlapComponentStatusWord->get()); + sfccBusOutputs[sfccIndex].slat_flap_system_status_word = Arinc429Utils::fromSimVar(idSfccSlatFlapSystemStatusWord->get()); + sfccBusOutputs[sfccIndex].slat_flap_actual_position_word = Arinc429Utils::fromSimVar(idSfccSlatFlapActualPositionWord->get()); + sfccBusOutputs[sfccIndex].slat_actual_position_deg = Arinc429Utils::fromSimVar(idSfccSlatActualPositionWord->get()); + sfccBusOutputs[sfccIndex].flap_actual_position_deg = Arinc429Utils::fromSimVar(idSfccFlapActualPositionWord->get()); + + if (clientDataEnabled) { + simConnectInterface.setClientDataSfcc(sfccBusOutputs[sfccIndex], sfccIndex); + } + + return true; +} + +bool FlyByWireInterface::updateAdirs(int adirsIndex) { + adrBusOutputs[adirsIndex].altitude_corrected_ft = Arinc429Utils::fromSimVar(idAdrAltitudeCorrected[adirsIndex]->get()); + adrBusOutputs[adirsIndex].mach = Arinc429Utils::fromSimVar(idAdrMach[adirsIndex]->get()); + adrBusOutputs[adirsIndex].airspeed_computed_kn = Arinc429Utils::fromSimVar(idAdrAirspeedComputed[adirsIndex]->get()); + adrBusOutputs[adirsIndex].airspeed_true_kn = Arinc429Utils::fromSimVar(idAdrAirspeedTrue[adirsIndex]->get()); + adrBusOutputs[adirsIndex].vertical_speed_ft_min = Arinc429Utils::fromSimVar(idAdrVerticalSpeed[adirsIndex]->get()); + adrBusOutputs[adirsIndex].aoa_corrected_deg = Arinc429Utils::fromSimVar(idAdrAoaCorrected[adirsIndex]->get()); + adrBusOutputs[adirsIndex].corrected_average_static_pressure = + Arinc429Utils::fromSimVar(idAdrCorrectedAverageStaticPressure[adirsIndex]->get()); + + irBusOutputs[adirsIndex].latitude_deg = Arinc429Utils::fromSimVar(idIrLatitude[adirsIndex]->get()); + irBusOutputs[adirsIndex].longitude_deg = Arinc429Utils::fromSimVar(idIrLongitude[adirsIndex]->get()); + irBusOutputs[adirsIndex].ground_speed_kn = Arinc429Utils::fromSimVar(idIrGroundSpeed[adirsIndex]->get()); + irBusOutputs[adirsIndex].wind_speed_kn = Arinc429Utils::fromSimVar(idIrWindSpeed[adirsIndex]->get()); + irBusOutputs[adirsIndex].wind_direction_true_deg = Arinc429Utils::fromSimVar(idIrWindDirectionTrue[adirsIndex]->get()); + irBusOutputs[adirsIndex].track_angle_magnetic_deg = Arinc429Utils::fromSimVar(idIrTrackAngleMagnetic[adirsIndex]->get()); + irBusOutputs[adirsIndex].heading_magnetic_deg = Arinc429Utils::fromSimVar(idIrHeadingMagnetic[adirsIndex]->get()); + irBusOutputs[adirsIndex].drift_angle_deg = Arinc429Utils::fromSimVar(idIrDriftAngle[adirsIndex]->get()); + irBusOutputs[adirsIndex].flight_path_angle_deg = Arinc429Utils::fromSimVar(idIrFlightPathAngle[adirsIndex]->get()); + irBusOutputs[adirsIndex].pitch_angle_deg = Arinc429Utils::fromSimVar(idIrPitchAngle[adirsIndex]->get()); + irBusOutputs[adirsIndex].roll_angle_deg = Arinc429Utils::fromSimVar(idIrRollAngle[adirsIndex]->get()); + irBusOutputs[adirsIndex].body_pitch_rate_deg_s = Arinc429Utils::fromSimVar(idIrBodyPitchRate[adirsIndex]->get()); + irBusOutputs[adirsIndex].body_roll_rate_deg_s = Arinc429Utils::fromSimVar(idIrBodyRollRate[adirsIndex]->get()); + irBusOutputs[adirsIndex].body_yaw_rate_deg_s = Arinc429Utils::fromSimVar(idIrBodyYawRate[adirsIndex]->get()); + irBusOutputs[adirsIndex].body_long_accel_g = Arinc429Utils::fromSimVar(idIrBodyLongAccel[adirsIndex]->get()); + irBusOutputs[adirsIndex].body_lat_accel_g = Arinc429Utils::fromSimVar(idIrBodyLatAccel[adirsIndex]->get()); + irBusOutputs[adirsIndex].body_normal_accel_g = Arinc429Utils::fromSimVar(idIrBodyNormalAccel[adirsIndex]->get()); + irBusOutputs[adirsIndex].track_angle_rate_deg_s = Arinc429Utils::fromSimVar(idIrTrackAngleRate[adirsIndex]->get()); + irBusOutputs[adirsIndex].pitch_att_rate_deg_s = Arinc429Utils::fromSimVar(idIrPitchAttRate[adirsIndex]->get()); + irBusOutputs[adirsIndex].roll_att_rate_deg_s = Arinc429Utils::fromSimVar(idIrRollAttRate[adirsIndex]->get()); + irBusOutputs[adirsIndex].inertial_vertical_speed_ft_s = Arinc429Utils::fromSimVar(idIrInertialVerticalSpeed[adirsIndex]->get()); + + if (clientDataEnabled) { + simConnectInterface.setClientDataAdr(adrBusOutputs[adirsIndex], adirsIndex); + simConnectInterface.setClientDataIr(irBusOutputs[adirsIndex], adirsIndex); + } + + return true; +} + +bool FlyByWireInterface::updateElac(double sampleTime, int elacIndex) { + const int oppElacIndex = elacIndex == 0 ? 1 : 0; + SimData simData = simConnectInterface.getSimData(); + SimInput simInput = simConnectInterface.getSimInput(); + + elacs[elacIndex].modelInputs.in.time.dt = sampleTime; + elacs[elacIndex].modelInputs.in.time.simulation_time = simData.simulationTime; + elacs[elacIndex].modelInputs.in.time.monotonic_time = monotonicTime; + + elacs[elacIndex].modelInputs.in.sim_data.slew_on = wasInSlew; + elacs[elacIndex].modelInputs.in.sim_data.pause_on = pauseDetected; + elacs[elacIndex].modelInputs.in.sim_data.tracking_mode_on_override = idExternalOverride->get() == 1; + elacs[elacIndex].modelInputs.in.sim_data.tailstrike_protection_on = tailstrikeProtectionEnabled; + + elacs[elacIndex].modelInputs.in.discrete_inputs.ground_spoilers_active_1 = secsDiscreteOutputs[0].ground_spoiler_out; + elacs[elacIndex].modelInputs.in.discrete_inputs.ground_spoilers_active_2 = + elacIndex == 0 ? secsDiscreteOutputs[1].ground_spoiler_out : secsDiscreteOutputs[2].ground_spoiler_out; + elacs[elacIndex].modelInputs.in.discrete_inputs.is_unit_1 = elacIndex == 0; + elacs[elacIndex].modelInputs.in.discrete_inputs.is_unit_2 = elacIndex == 1; + elacs[elacIndex].modelInputs.in.discrete_inputs.opp_axis_pitch_failure = !elacsDiscreteOutputs[oppElacIndex].pitch_axis_ok; + elacs[elacIndex].modelInputs.in.discrete_inputs.ap_1_disengaged = !autopilotStateMachineOutput.enabled_AP1; + elacs[elacIndex].modelInputs.in.discrete_inputs.ap_2_disengaged = !autopilotStateMachineOutput.enabled_AP2; + elacs[elacIndex].modelInputs.in.discrete_inputs.opp_left_aileron_lost = !elacsDiscreteOutputs[oppElacIndex].left_aileron_ok; + elacs[elacIndex].modelInputs.in.discrete_inputs.opp_right_aileron_lost = !elacsDiscreteOutputs[oppElacIndex].right_aileron_ok; + elacs[elacIndex].modelInputs.in.discrete_inputs.fac_1_yaw_control_lost = !facsDiscreteOutputs[0].yaw_damper_avail_for_norm_law; + elacs[elacIndex].modelInputs.in.discrete_inputs.lgciu_1_nose_gear_pressed = idLgciuNoseGearCompressed[0]->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.lgciu_2_nose_gear_pressed = idLgciuNoseGearCompressed[1]->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.fac_2_yaw_control_lost = !facsDiscreteOutputs[1].yaw_damper_avail_for_norm_law; + elacs[elacIndex].modelInputs.in.discrete_inputs.lgciu_1_right_main_gear_pressed = idLgciuRightMainGearCompressed[0]->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.lgciu_2_right_main_gear_pressed = idLgciuRightMainGearCompressed[1]->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.lgciu_1_left_main_gear_pressed = idLgciuLeftMainGearCompressed[0]->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.lgciu_2_left_main_gear_pressed = idLgciuLeftMainGearCompressed[1]->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.ths_motor_fault = false; + elacs[elacIndex].modelInputs.in.discrete_inputs.sfcc_1_slats_out = false; + elacs[elacIndex].modelInputs.in.discrete_inputs.sfcc_2_slats_out = false; + elacs[elacIndex].modelInputs.in.discrete_inputs.l_ail_servo_failed = idAilFaultLeft[elacIndex]->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.l_elev_servo_failed = idElevFaultLeft[elacIndex]->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.r_ail_servo_failed = idAilFaultRight[elacIndex]->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.r_elev_servo_failed = idElevFaultRight[elacIndex]->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.ths_override_active = idThsOverrideActive->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.yellow_low_pressure = !idHydYellowPressurised->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.capt_priority_takeover_pressed = idCaptPriorityButtonPressed->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.fo_priority_takeover_pressed = idFoPriorityButtonPressed->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.blue_low_pressure = !idHydBluePressurised->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.green_low_pressure = !idHydGreenPressurised->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.elac_engaged_from_switch = idElacPushbuttonPressed[elacIndex]->get(); + elacs[elacIndex].modelInputs.in.discrete_inputs.normal_powersupply_lost = false; + + elacs[elacIndex].modelInputs.in.analog_inputs.capt_pitch_stick_pos = -simInput.inputs[0]; + elacs[elacIndex].modelInputs.in.analog_inputs.fo_pitch_stick_pos = 0; + elacs[elacIndex].modelInputs.in.analog_inputs.capt_roll_stick_pos = -simInput.inputs[1]; + elacs[elacIndex].modelInputs.in.analog_inputs.fo_roll_stick_pos = 0; + double leftElevPos = -idLeftElevatorPosition->get(); + double rightElevPos = -idRightElevatorPosition->get(); + elacs[elacIndex].modelInputs.in.analog_inputs.left_elevator_pos_deg = leftElevPos > 0 ? leftElevPos * 17 : leftElevPos * 30; + elacs[elacIndex].modelInputs.in.analog_inputs.right_elevator_pos_deg = rightElevPos > 0 ? rightElevPos * 17 : rightElevPos * 30; + elacs[elacIndex].modelInputs.in.analog_inputs.ths_pos_deg = -simData.eta_trim_deg; + elacs[elacIndex].modelInputs.in.analog_inputs.left_aileron_pos_deg = idLeftAileronPosition->get() * 25; + elacs[elacIndex].modelInputs.in.analog_inputs.right_aileron_pos_deg = -idRightAileronPosition->get() * 25; + elacs[elacIndex].modelInputs.in.analog_inputs.rudder_pedal_pos = -simInput.inputs[2]; + elacs[elacIndex].modelInputs.in.analog_inputs.load_factor_acc_1_g = 0; + elacs[elacIndex].modelInputs.in.analog_inputs.load_factor_acc_2_g = 0; + elacs[elacIndex].modelInputs.in.analog_inputs.blue_hyd_pressure_psi = idHydBlueSystemPressure->get(); + elacs[elacIndex].modelInputs.in.analog_inputs.green_hyd_pressure_psi = idHydGreenSystemPressure->get(); + elacs[elacIndex].modelInputs.in.analog_inputs.yellow_hyd_pressure_psi = idHydYellowSystemPressure->get(); + + elacs[elacIndex].modelInputs.in.bus_inputs.adr_1_bus = adrBusOutputs[0]; + elacs[elacIndex].modelInputs.in.bus_inputs.adr_2_bus = adrBusOutputs[1]; + elacs[elacIndex].modelInputs.in.bus_inputs.adr_3_bus = adrBusOutputs[2]; + elacs[elacIndex].modelInputs.in.bus_inputs.ir_1_bus = irBusOutputs[0]; + elacs[elacIndex].modelInputs.in.bus_inputs.ir_2_bus = irBusOutputs[1]; + elacs[elacIndex].modelInputs.in.bus_inputs.ir_3_bus = irBusOutputs[2]; + elacs[elacIndex].modelInputs.in.bus_inputs.fmgc_1_bus = fmgcBBusOutputs; + elacs[elacIndex].modelInputs.in.bus_inputs.fmgc_2_bus = fmgcBBusOutputs; + elacs[elacIndex].modelInputs.in.bus_inputs.ra_1_bus = raBusOutputs[0]; + elacs[elacIndex].modelInputs.in.bus_inputs.ra_2_bus = raBusOutputs[1]; + elacs[elacIndex].modelInputs.in.bus_inputs.sfcc_1_bus = sfccBusOutputs[0]; + elacs[elacIndex].modelInputs.in.bus_inputs.sfcc_2_bus = sfccBusOutputs[1]; + elacs[elacIndex].modelInputs.in.bus_inputs.fcdc_1_bus = fcdcsBusOutputs[0]; + elacs[elacIndex].modelInputs.in.bus_inputs.fcdc_2_bus = fcdcsBusOutputs[1]; + elacs[elacIndex].modelInputs.in.bus_inputs.sec_1_bus = secsBusOutputs[0]; + elacs[elacIndex].modelInputs.in.bus_inputs.sec_2_bus = secsBusOutputs[1]; + elacs[elacIndex].modelInputs.in.bus_inputs.elac_opp_bus = elacsBusOutputs[oppElacIndex]; + + if (elacIndex == elacDisabled) { + simConnectInterface.setClientDataElacDiscretes(elacs[elacIndex].modelInputs.in.discrete_inputs); + simConnectInterface.setClientDataElacAnalog(elacs[elacIndex].modelInputs.in.analog_inputs); + + elacsDiscreteOutputs[elacIndex] = simConnectInterface.getClientDataElacDiscretesOutput(); + elacsAnalogOutputs[elacIndex] = simConnectInterface.getClientDataElacAnalogsOutput(); + elacsBusOutputs[elacIndex] = simConnectInterface.getClientDataElacBusOutput(); + } else { + bool powerSupplyAvailable = false; + if (elacIndex == 0) { + powerSupplyAvailable = + idElecDcEssBusPowered->get() || (elacsDiscreteOutputs[elacIndex].batt_power_supply ? idElecBat1HotBusPowered->get() : false); + } else { + powerSupplyAvailable = idElecDcBus2Powered->get(); + } + + elacs[elacIndex].update(sampleTime, simData.simulationTime, + failuresConsumer.isActive(elacIndex == 0 ? Failures::Elac1 : Failures::Elac2), powerSupplyAvailable); + + elacsDiscreteOutputs[elacIndex] = elacs[elacIndex].getDiscreteOutputs(); + elacsAnalogOutputs[elacIndex] = elacs[elacIndex].getAnalogOutputs(); + elacsBusOutputs[elacIndex] = elacs[elacIndex].getBusOutputs(); + } + + if (oppElacIndex == elacDisabled || secDisabled != -1 || facDisabled != -1) { + simConnectInterface.setClientDataElacBusInput(elacsBusOutputs[elacIndex], elacIndex); + } + + idElacDigitalOpValidated[elacIndex]->set(elacsDiscreteOutputs[elacIndex].digital_output_validated); + + return true; +} + +bool FlyByWireInterface::updateSec(double sampleTime, int secIndex) { + const int oppSecIndex = secIndex == 0 ? 1 : 0; + SimData simData = simConnectInterface.getSimData(); + SimInput simInput = simConnectInterface.getSimInput(); + + secs[secIndex].modelInputs.in.time.dt = sampleTime; + secs[secIndex].modelInputs.in.time.simulation_time = simData.simulationTime; + secs[secIndex].modelInputs.in.time.monotonic_time = monotonicTime; + + secs[secIndex].modelInputs.in.sim_data.slew_on = wasInSlew; + secs[secIndex].modelInputs.in.sim_data.pause_on = pauseDetected; + secs[secIndex].modelInputs.in.sim_data.tracking_mode_on_override = idExternalOverride->get() == 1; + secs[secIndex].modelInputs.in.sim_data.tailstrike_protection_on = tailstrikeProtectionEnabled; + + secs[secIndex].modelInputs.in.discrete_inputs.sec_engaged_from_switch = idSecPushbuttonPressed[secIndex]->get(); + secs[secIndex].modelInputs.in.discrete_inputs.sec_in_emergency_powersupply = false; + secs[secIndex].modelInputs.in.discrete_inputs.is_unit_1 = secIndex == 0; + secs[secIndex].modelInputs.in.discrete_inputs.is_unit_2 = secIndex == 1; + secs[secIndex].modelInputs.in.discrete_inputs.is_unit_3 = secIndex == 2; + if (secIndex < 2) { + secs[secIndex].modelInputs.in.discrete_inputs.pitch_not_avail_elac_1 = !elacsDiscreteOutputs[0].pitch_axis_ok; + secs[secIndex].modelInputs.in.discrete_inputs.pitch_not_avail_elac_2 = !elacsDiscreteOutputs[1].pitch_axis_ok; + secs[secIndex].modelInputs.in.discrete_inputs.left_elev_not_avail_sec_opp = !secsDiscreteOutputs[oppSecIndex].left_elevator_ok; + secs[secIndex].modelInputs.in.discrete_inputs.right_elev_not_avail_sec_opp = !secsDiscreteOutputs[oppSecIndex].right_elevator_ok; + secs[secIndex].modelInputs.in.discrete_inputs.ths_motor_fault = false; + secs[secIndex].modelInputs.in.discrete_inputs.l_elev_servo_failed = idElevFaultLeft[secIndex]->get(); + secs[secIndex].modelInputs.in.discrete_inputs.r_elev_servo_failed = idElevFaultRight[secIndex]->get(); + secs[secIndex].modelInputs.in.discrete_inputs.ths_override_active = idThsOverrideActive->get(); + } else { + secs[secIndex].modelInputs.in.discrete_inputs.pitch_not_avail_elac_1 = false; + secs[secIndex].modelInputs.in.discrete_inputs.pitch_not_avail_elac_2 = false; + secs[secIndex].modelInputs.in.discrete_inputs.left_elev_not_avail_sec_opp = false; + secs[secIndex].modelInputs.in.discrete_inputs.right_elev_not_avail_sec_opp = false; + secs[secIndex].modelInputs.in.discrete_inputs.ths_motor_fault = false; + secs[secIndex].modelInputs.in.discrete_inputs.l_elev_servo_failed = false; + secs[secIndex].modelInputs.in.discrete_inputs.r_elev_servo_failed = false; + secs[secIndex].modelInputs.in.discrete_inputs.ths_override_active = false; + } + + secs[secIndex].modelInputs.in.discrete_inputs.digital_output_failed_elac_1 = !elacsDiscreteOutputs[0].digital_output_validated; + secs[secIndex].modelInputs.in.discrete_inputs.digital_output_failed_elac_2 = !elacsDiscreteOutputs[1].digital_output_validated; + secs[secIndex].modelInputs.in.discrete_inputs.green_low_pressure = !idHydGreenPressurised->get(); + secs[secIndex].modelInputs.in.discrete_inputs.blue_low_pressure = !idHydBluePressurised->get(); + secs[secIndex].modelInputs.in.discrete_inputs.yellow_low_pressure = !idHydYellowPressurised->get(); + secs[secIndex].modelInputs.in.discrete_inputs.sfcc_1_slats_out = false; + secs[secIndex].modelInputs.in.discrete_inputs.sfcc_2_slats_out = false; + + int splrIndex = secIndex == 2 ? 0 : (secIndex == 0 ? 2 : 4); + + secs[secIndex].modelInputs.in.discrete_inputs.l_spoiler_1_servo_failed = idSplrFaultLeft[splrIndex]->get(); + secs[secIndex].modelInputs.in.discrete_inputs.r_spoiler_1_servo_failed = idSplrFaultRight[splrIndex]->get(); + if (secIndex != 1) { + secs[secIndex].modelInputs.in.discrete_inputs.l_spoiler_2_servo_failed = idSplrFaultLeft[splrIndex + 1]->get(); + secs[secIndex].modelInputs.in.discrete_inputs.r_spoiler_2_servo_failed = idSplrFaultRight[splrIndex + 1]->get(); + } else { + secs[secIndex].modelInputs.in.discrete_inputs.l_spoiler_2_servo_failed = false; + secs[secIndex].modelInputs.in.discrete_inputs.r_spoiler_2_servo_failed = false; + } + + secs[secIndex].modelInputs.in.discrete_inputs.capt_priority_takeover_pressed = idCaptPriorityButtonPressed->get(); + secs[secIndex].modelInputs.in.discrete_inputs.fo_priority_takeover_pressed = idFoPriorityButtonPressed->get(); + + if (secIndex < 2) { + secs[secIndex].modelInputs.in.analog_inputs.capt_pitch_stick_pos = -simInput.inputs[0]; + secs[secIndex].modelInputs.in.analog_inputs.fo_pitch_stick_pos = 0; + double leftElevPos = -idLeftElevatorPosition->get(); + double rightElevPos = -idRightElevatorPosition->get(); + secs[secIndex].modelInputs.in.analog_inputs.left_elevator_pos_deg = leftElevPos > 0 ? leftElevPos * 17 : leftElevPos * 30; + secs[secIndex].modelInputs.in.analog_inputs.right_elevator_pos_deg = rightElevPos > 0 ? rightElevPos * 17 : rightElevPos * 30; + secs[secIndex].modelInputs.in.analog_inputs.ths_pos_deg = -simData.eta_trim_deg; + secs[secIndex].modelInputs.in.analog_inputs.load_factor_acc_1_g = 0; + secs[secIndex].modelInputs.in.analog_inputs.load_factor_acc_2_g = 0; + } else { + secs[secIndex].modelInputs.in.analog_inputs.capt_pitch_stick_pos = 0; + secs[secIndex].modelInputs.in.analog_inputs.fo_pitch_stick_pos = 0; + secs[secIndex].modelInputs.in.analog_inputs.left_elevator_pos_deg = 0; + secs[secIndex].modelInputs.in.analog_inputs.right_elevator_pos_deg = 0; + secs[secIndex].modelInputs.in.analog_inputs.ths_pos_deg = 0; + secs[secIndex].modelInputs.in.analog_inputs.load_factor_acc_1_g = 0; + secs[secIndex].modelInputs.in.analog_inputs.load_factor_acc_2_g = 0; + } + secs[secIndex].modelInputs.in.analog_inputs.capt_roll_stick_pos = -simInput.inputs[1]; + secs[secIndex].modelInputs.in.analog_inputs.fo_roll_stick_pos = 0; + secs[secIndex].modelInputs.in.analog_inputs.spd_brk_lever_pos = + spoilersHandler->getIsArmed() ? -0.05 : spoilersHandler->getHandlePosition(); + secs[secIndex].modelInputs.in.analog_inputs.thr_lever_1_pos = thrustLeverAngle_1->get(); + secs[secIndex].modelInputs.in.analog_inputs.thr_lever_2_pos = thrustLeverAngle_2->get(); + secs[secIndex].modelInputs.in.analog_inputs.left_spoiler_1_pos_deg = -idLeftSpoilerPosition[splrIndex]->get() * 50; + secs[secIndex].modelInputs.in.analog_inputs.right_spoiler_1_pos_deg = -idRightSpoilerPosition[splrIndex]->get() * 50; + secs[secIndex].modelInputs.in.analog_inputs.left_spoiler_2_pos_deg = -idLeftSpoilerPosition[splrIndex + 1]->get() * 50; + secs[secIndex].modelInputs.in.analog_inputs.right_spoiler_2_pos_deg = -idRightSpoilerPosition[splrIndex + 1]->get() * 50; + secs[secIndex].modelInputs.in.analog_inputs.wheel_speed_left = simData.wheelRpmLeft * 0.118921; + secs[secIndex].modelInputs.in.analog_inputs.wheel_speed_right = simData.wheelRpmRight * 0.118921; + + if (secIndex == 0) { + secs[secIndex].modelInputs.in.bus_inputs.adr_1_bus = adrBusOutputs[0]; + secs[secIndex].modelInputs.in.bus_inputs.adr_2_bus = adrBusOutputs[2]; + secs[secIndex].modelInputs.in.bus_inputs.ir_1_bus = irBusOutputs[0]; + secs[secIndex].modelInputs.in.bus_inputs.ir_2_bus = irBusOutputs[2]; + } else if (secIndex == 1) { + secs[secIndex].modelInputs.in.bus_inputs.adr_1_bus = adrBusOutputs[0]; + secs[secIndex].modelInputs.in.bus_inputs.adr_2_bus = adrBusOutputs[1]; + secs[secIndex].modelInputs.in.bus_inputs.ir_1_bus = irBusOutputs[0]; + secs[secIndex].modelInputs.in.bus_inputs.ir_2_bus = irBusOutputs[1]; + } else if (secIndex == 2) { + secs[secIndex].modelInputs.in.bus_inputs.adr_1_bus = adrBusOutputs[1]; + secs[secIndex].modelInputs.in.bus_inputs.adr_2_bus = adrBusOutputs[2]; + secs[secIndex].modelInputs.in.bus_inputs.ir_1_bus = irBusOutputs[1]; + secs[secIndex].modelInputs.in.bus_inputs.ir_2_bus = irBusOutputs[2]; + } + + secs[secIndex].modelInputs.in.bus_inputs.fcdc_1_bus = fcdcsBusOutputs[0]; + secs[secIndex].modelInputs.in.bus_inputs.fcdc_2_bus = fcdcsBusOutputs[1]; + secs[secIndex].modelInputs.in.bus_inputs.elac_1_bus = elacsBusOutputs[0]; + secs[secIndex].modelInputs.in.bus_inputs.elac_2_bus = elacsBusOutputs[1]; + secs[secIndex].modelInputs.in.bus_inputs.sfcc_1_bus = sfccBusOutputs[0]; + secs[secIndex].modelInputs.in.bus_inputs.sfcc_2_bus = sfccBusOutputs[1]; + secs[secIndex].modelInputs.in.bus_inputs.lgciu_1_bus = lgciuBusOutputs[0]; + secs[secIndex].modelInputs.in.bus_inputs.lgciu_2_bus = lgciuBusOutputs[1]; + + if (secIndex == secDisabled) { + simConnectInterface.setClientDataSecDiscretes(secs[secIndex].modelInputs.in.discrete_inputs); + simConnectInterface.setClientDataSecAnalog(secs[secIndex].modelInputs.in.analog_inputs); + + secsDiscreteOutputs[secIndex] = simConnectInterface.getClientDataSecDiscretesOutput(); + secsAnalogOutputs[secIndex] = simConnectInterface.getClientDataSecAnalogsOutput(); + secsBusOutputs[secIndex] = simConnectInterface.getClientDataSecBusOutput(); + } else { + bool powerSupplyAvailable = false; + if (secIndex == 0) { + powerSupplyAvailable = + idElecDcEssBusPowered->get() || (secsDiscreteOutputs[secIndex].batt_power_supply ? idElecBat1HotBusPowered->get() : false); + } else { + powerSupplyAvailable = idElecDcBus2Powered->get(); + } + + Failures failureIndex = secIndex == 0 ? Failures::Sec1 : (secIndex == 1 ? Failures::Sec2 : Failures::Sec3); + secs[secIndex].update(sampleTime, simData.simulationTime, failuresConsumer.isActive(failureIndex), powerSupplyAvailable); + + secsDiscreteOutputs[secIndex] = secs[secIndex].getDiscreteOutputs(); + secsAnalogOutputs[secIndex] = secs[secIndex].getAnalogOutputs(); + secsBusOutputs[secIndex] = secs[secIndex].getBusOutputs(); + } + + if (elacDisabled != -1 && secIndex < 2) { + simConnectInterface.setClientDataSecBus(secsBusOutputs[secIndex], secIndex); + } + + idSecFaultLightOn[secIndex]->set(secsDiscreteOutputs[secIndex].sec_failed); + idSecGroundSpoilersOut[secIndex]->set(secsDiscreteOutputs[secIndex].ground_spoiler_out); + + return true; +} + +bool FlyByWireInterface::updateFcdc(double sampleTime, int fcdcIndex) { + const int oppFcdcIndex = fcdcIndex == 0 ? 1 : 0; + + fcdcs[fcdcIndex].discreteInputs.elac1Off = !idElacPushbuttonPressed[0]->get(); + fcdcs[fcdcIndex].discreteInputs.elac1Valid = elacsDiscreteOutputs[0].digital_output_validated; + fcdcs[fcdcIndex].discreteInputs.elac2Valid = elacsDiscreteOutputs[1].digital_output_validated; + fcdcs[fcdcIndex].discreteInputs.sec1Off = !idSecPushbuttonPressed[0]->get(); + fcdcs[fcdcIndex].discreteInputs.sec1Valid = !secsDiscreteOutputs[0].sec_failed; + fcdcs[fcdcIndex].discreteInputs.sec2Valid = !secsDiscreteOutputs[1].sec_failed; + fcdcs[fcdcIndex].discreteInputs.eng1NotOnGroundAndNotLowOilPress = false; + fcdcs[fcdcIndex].discreteInputs.eng2NotOnGroundAndNotLowOilPress = false; + fcdcs[fcdcIndex].discreteInputs.noseGearPressed = idLgciuNoseGearCompressed[0]->get(); + fcdcs[fcdcIndex].discreteInputs.oppFcdcFailed = !fcdcsDiscreteOutputs[oppFcdcIndex].fcdcValid; + fcdcs[fcdcIndex].discreteInputs.sec3Off = !idSecPushbuttonPressed[2]->get(); + fcdcs[fcdcIndex].discreteInputs.sec3Valid = !secsDiscreteOutputs[2].sec_failed; + fcdcs[fcdcIndex].discreteInputs.elac2Off = !idElacPushbuttonPressed[1]->get(); + fcdcs[fcdcIndex].discreteInputs.sec2Off = !idSecPushbuttonPressed[1]->get(); + + fcdcs[fcdcIndex].busInputs.elac1 = elacsBusOutputs[0]; + fcdcs[fcdcIndex].busInputs.sec1 = secsBusOutputs[0]; + fcdcs[fcdcIndex].busInputs.fcdcOpp = fcdcsBusOutputs[oppFcdcIndex]; + fcdcs[fcdcIndex].busInputs.elac2 = elacsBusOutputs[1]; + fcdcs[fcdcIndex].busInputs.sec2 = secsBusOutputs[1]; + fcdcs[fcdcIndex].busInputs.sec3 = secsBusOutputs[2]; + + fcdcs[fcdcIndex].update(sampleTime, failuresConsumer.isActive(fcdcIndex == 0 ? Failures::Fcdc1 : Failures::Fcdc2), + fcdcIndex == 0 ? idElecDcEssShedBusPowered->get() : idElecDcBus2Powered->get()); + + fcdcsDiscreteOutputs[fcdcIndex] = fcdcs[fcdcIndex].getDiscreteOutputs(); + FcdcBus bus = fcdcs[fcdcIndex].getBusOutputs(); + fcdcsBusOutputs[fcdcIndex] = *reinterpret_cast(&bus); + + idFcdcDiscreteWord1[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].efcs_status_word_1)); + idFcdcDiscreteWord2[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].efcs_status_word_2)); + idFcdcDiscreteWord3[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].efcs_status_word_3)); + idFcdcDiscreteWord4[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].efcs_status_word_4)); + idFcdcDiscreteWord5[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].efcs_status_word_5)); + idFcdcCaptRollCommand[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].capt_roll_command_deg)); + idFcdcFoRollCommand[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].fo_roll_command_deg)); + idFcdcCaptPitchCommand[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].capt_pitch_command_deg)); + idFcdcFoPitchCommand[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].fo_pitch_command_deg)); + idFcdcRudderPedalPos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].rudder_pedal_position_deg)); + idFcdcAileronLeftPos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].aileron_left_pos_deg)); + idFcdcElevatorLeftPos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].elevator_left_pos_deg)); + idFcdcAileronRightPos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].aileron_right_pos_deg)); + idFcdcElevatorRightPos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].elevator_right_pos_deg)); + idFcdcElevatorTrimPos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].horiz_stab_trim_pos_deg)); + idFcdcSpoilerLeft1Pos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].spoiler_1_left_pos_deg)); + idFcdcSpoilerLeft2Pos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].spoiler_2_left_pos_deg)); + idFcdcSpoilerLeft3Pos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].spoiler_3_left_pos_deg)); + idFcdcSpoilerLeft4Pos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].spoiler_4_left_pos_deg)); + idFcdcSpoilerLeft5Pos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].spoiler_5_left_pos_deg)); + idFcdcSpoilerRight1Pos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].spoiler_1_right_pos_deg)); + idFcdcSpoilerRight2Pos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].spoiler_2_right_pos_deg)); + idFcdcSpoilerRight3Pos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].spoiler_3_right_pos_deg)); + idFcdcSpoilerRight4Pos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].spoiler_4_right_pos_deg)); + idFcdcSpoilerRight5Pos[fcdcIndex]->set(Arinc429Utils::toSimVar(fcdcsBusOutputs[fcdcIndex].spoiler_5_right_pos_deg)); + + idFcdcPriorityCaptGreen[fcdcIndex]->set(fcdcsDiscreteOutputs[fcdcIndex].captGreenPriorityLightOn); + idFcdcPriorityCaptRed[fcdcIndex]->set(fcdcsDiscreteOutputs[fcdcIndex].captRedPriorityLightOn); + idFcdcPriorityFoGreen[fcdcIndex]->set(fcdcsDiscreteOutputs[fcdcIndex].foGreenPriorityLightOn); + idFcdcPriorityFoRed[fcdcIndex]->set(fcdcsDiscreteOutputs[fcdcIndex].foRedPriorityLightOn); + + return true; +} + +bool FlyByWireInterface::updateFac(double sampleTime, int facIndex) { + const int oppFacIndex = facIndex == 0 ? 1 : 0; + SimData simData = simConnectInterface.getSimData(); + SimInputRudderTrim trimInput = simConnectInterface.getSimInputRudderTrim(); + + facs[facIndex].modelInputs.in.time.dt = sampleTime; + facs[facIndex].modelInputs.in.time.simulation_time = simData.simulationTime; + facs[facIndex].modelInputs.in.time.monotonic_time = monotonicTime; + + facs[facIndex].modelInputs.in.sim_data.slew_on = wasInSlew; + facs[facIndex].modelInputs.in.sim_data.pause_on = pauseDetected; + facs[facIndex].modelInputs.in.sim_data.tracking_mode_on_override = idExternalOverride->get() == 1; + facs[facIndex].modelInputs.in.sim_data.tailstrike_protection_on = tailstrikeProtectionEnabled; + + facs[facIndex].modelInputs.in.discrete_inputs.ap_own_engaged = + facIndex == 0 ? autopilotStateMachineOutput.enabled_AP1 : autopilotStateMachineOutput.enabled_AP2; + facs[facIndex].modelInputs.in.discrete_inputs.ap_opp_engaged = + facIndex == 0 ? autopilotStateMachineOutput.enabled_AP2 : autopilotStateMachineOutput.enabled_AP1; + facs[facIndex].modelInputs.in.discrete_inputs.yaw_damper_opp_engaged = facsDiscreteOutputs[oppFacIndex].yaw_damper_engaged; + facs[facIndex].modelInputs.in.discrete_inputs.rudder_trim_opp_engaged = facsDiscreteOutputs[oppFacIndex].rudder_trim_engaged; + facs[facIndex].modelInputs.in.discrete_inputs.rudder_travel_lim_opp_engaged = facsDiscreteOutputs[oppFacIndex].rudder_travel_lim_engaged; + facs[facIndex].modelInputs.in.discrete_inputs.elac_1_healthy = elacsDiscreteOutputs[0].digital_output_validated; + facs[facIndex].modelInputs.in.discrete_inputs.elac_2_healthy = elacsDiscreteOutputs[1].digital_output_validated; + facs[facIndex].modelInputs.in.discrete_inputs.engine_1_stopped = true; + facs[facIndex].modelInputs.in.discrete_inputs.engine_2_stopped = true; + facs[facIndex].modelInputs.in.discrete_inputs.rudder_trim_switch_left = trimInput.rudderTrimSwitchLeft; + facs[facIndex].modelInputs.in.discrete_inputs.rudder_trim_switch_right = trimInput.rudderTrimSwitchRight; + facs[facIndex].modelInputs.in.discrete_inputs.rudder_trim_reset_button = trimInput.rudderTrimReset; + facs[facIndex].modelInputs.in.discrete_inputs.fac_engaged_from_switch = idFacPushbuttonPressed[facIndex]->get(); + facs[facIndex].modelInputs.in.discrete_inputs.fac_opp_healthy = facsDiscreteOutputs[oppFacIndex].fac_healthy; + facs[facIndex].modelInputs.in.discrete_inputs.is_unit_1 = facIndex == 0; + facs[facIndex].modelInputs.in.discrete_inputs.rudder_trim_actuator_healthy = true; + facs[facIndex].modelInputs.in.discrete_inputs.rudder_travel_lim_actuator_healthy = true; + facs[facIndex].modelInputs.in.discrete_inputs.slats_extended = false; + facs[facIndex].modelInputs.in.discrete_inputs.nose_gear_pressed = idLgciuNoseGearCompressed[facIndex]->get(); + facs[facIndex].modelInputs.in.discrete_inputs.ir_3_switch = false; + facs[facIndex].modelInputs.in.discrete_inputs.adr_3_switch = false; + facs[facIndex].modelInputs.in.discrete_inputs.yaw_damper_has_hyd_press = + facIndex == 0 ? idHydGreenPressurised->get() : idHydYellowPressurised->get(); + + facs[facIndex].modelInputs.in.analog_inputs.yaw_damper_position_deg = 0; + facs[facIndex].modelInputs.in.analog_inputs.rudder_trim_position_deg = -simData.zeta_trim_pos * 20; + facs[facIndex].modelInputs.in.analog_inputs.rudder_travel_lim_position_deg = rudderTravelLimiterPosition; + + facs[facIndex].modelInputs.in.bus_inputs.fac_opp_bus = facsBusOutputs[oppFacIndex]; + facs[facIndex].modelInputs.in.bus_inputs.adr_own_bus = facIndex == 0 ? adrBusOutputs[0] : adrBusOutputs[1]; + facs[facIndex].modelInputs.in.bus_inputs.adr_opp_bus = facIndex == 0 ? adrBusOutputs[1] : adrBusOutputs[0]; + facs[facIndex].modelInputs.in.bus_inputs.adr_3_bus = adrBusOutputs[2]; + facs[facIndex].modelInputs.in.bus_inputs.ir_own_bus = facIndex == 0 ? irBusOutputs[0] : irBusOutputs[1]; + facs[facIndex].modelInputs.in.bus_inputs.ir_opp_bus = facIndex == 0 ? irBusOutputs[1] : irBusOutputs[0]; + facs[facIndex].modelInputs.in.bus_inputs.ir_3_bus = irBusOutputs[2]; + facs[facIndex].modelInputs.in.bus_inputs.fmgc_own_bus = {}; + facs[facIndex].modelInputs.in.bus_inputs.fmgc_opp_bus = {}; + facs[facIndex].modelInputs.in.bus_inputs.sfcc_own_bus = sfccBusOutputs[facIndex]; + facs[facIndex].modelInputs.in.bus_inputs.lgciu_own_bus = lgciuBusOutputs[facIndex]; + facs[facIndex].modelInputs.in.bus_inputs.elac_1_bus = elacsBusOutputs[0]; + facs[facIndex].modelInputs.in.bus_inputs.elac_2_bus = elacsBusOutputs[1]; + + if (facIndex == facDisabled) { + simConnectInterface.setClientDataFacDiscretes(facs[facIndex].modelInputs.in.discrete_inputs); + simConnectInterface.setClientDataFacAnalog(facs[facIndex].modelInputs.in.analog_inputs); + + facsDiscreteOutputs[facIndex] = simConnectInterface.getClientDataFacDiscretesOutput(); + facsAnalogOutputs[facIndex] = simConnectInterface.getClientDataFacAnalogsOutput(); + facsBusOutputs[facIndex] = simConnectInterface.getClientDataFacBusOutput(); + } else { + facs[facIndex].update(sampleTime, simData.simulationTime, failuresConsumer.isActive(facIndex == 0 ? Failures::Fac1 : Failures::Fac2), + facIndex == 0 ? idElecDcEssShedBusPowered->get() : idElecDcBus2Powered->get()); + + facsDiscreteOutputs[facIndex] = facs[facIndex].getDiscreteOutputs(); + facsAnalogOutputs[facIndex] = facs[facIndex].getAnalogOutputs(); + facsBusOutputs[facIndex] = facs[facIndex].getBusOutputs(); + } + + if (oppFacIndex == facDisabled) { + simConnectInterface.setClientDataFacBus(facsBusOutputs[facIndex], facIndex); + } + + idFacHealthy[facIndex]->set(facsDiscreteOutputs[facIndex].fac_healthy); + + idFacDiscreteWord1[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].discrete_word_1)); + idFacGammaA[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].gamma_a_deg)); + idFacGammaT[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].gamma_t_deg)); + idFacWeight[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].total_weight_lbs)); + idFacCenterOfGravity[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].center_of_gravity_pos_percent)); + idFacSideslipTarget[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].sideslip_target_deg)); + idFacSlatAngle[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].fac_slat_angle_deg)); + idFacFlapAngle[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].fac_flap_angle)); + idFacDiscreteWord2[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].discrete_word_2)); + idFacRudderTravelLimitCommand[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].rudder_travel_limit_command_deg)); + idFacDeltaRYawDamperVoted[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].delta_r_yaw_damper_deg)); + idFacEstimatedSideslip[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].estimated_sideslip_deg)); + idFacVAlphaLim[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].v_alpha_lim_kn)); + idFacVLs[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].v_ls_kn)); + idFacVStall[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].v_stall_kn)); + idFacVAlphaProt[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].v_alpha_prot_kn)); + idFacVStallWarn[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].v_stall_warn_kn)); + idFacSpeedTrend[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].speed_trend_kn)); + idFacV3[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].v_3_kn)); + idFacV4[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].v_4_kn)); + idFacVMan[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].v_man_kn)); + idFacVMax[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].v_max_kn)); + idFacVFeNext[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].v_fe_next_kn)); + idFacDiscreteWord3[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].discrete_word_3)); + idFacDiscreteWord4[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].discrete_word_4)); + idFacDiscreteWord5[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].discrete_word_5)); + idFacDeltaRRudderTrim[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].delta_r_rudder_trim_deg)); + idFacRudderTrimPos[facIndex]->set(Arinc429Utils::toSimVar(facsBusOutputs[facIndex].rudder_trim_pos_deg)); + + return true; +} + +bool FlyByWireInterface::updateServoSolenoidStatus() { + idLeftAileronSolenoidEnergized[0]->set(elacsDiscreteOutputs[0].left_aileron_active_mode); + idLeftAileronCommandedPosition[0]->set(elacsAnalogOutputs[0].left_aileron_pos_order); + idRightAileronSolenoidEnergized[0]->set(elacsDiscreteOutputs[0].right_aileron_active_mode); + idRightAileronCommandedPosition[0]->set(-elacsAnalogOutputs[0].right_aileron_pos_order); + idLeftAileronSolenoidEnergized[1]->set(elacsDiscreteOutputs[1].left_aileron_active_mode); + idLeftAileronCommandedPosition[1]->set(elacsAnalogOutputs[1].left_aileron_pos_order); + idRightAileronSolenoidEnergized[1]->set(elacsDiscreteOutputs[1].right_aileron_active_mode); + idRightAileronCommandedPosition[1]->set(-elacsAnalogOutputs[1].right_aileron_pos_order); + + idLeftSpoilerCommandedPosition[0]->set(-secsAnalogOutputs[2].left_spoiler_1_pos_order_deg); + idRightSpoilerCommandedPosition[0]->set(-secsAnalogOutputs[2].right_spoiler_1_pos_order_deg); + idLeftSpoilerCommandedPosition[1]->set(-secsAnalogOutputs[2].left_spoiler_2_pos_order_deg); + idRightSpoilerCommandedPosition[1]->set(-secsAnalogOutputs[2].right_spoiler_2_pos_order_deg); + idLeftSpoilerCommandedPosition[2]->set(-secsAnalogOutputs[0].left_spoiler_1_pos_order_deg); + idRightSpoilerCommandedPosition[2]->set(-secsAnalogOutputs[0].right_spoiler_1_pos_order_deg); + idLeftSpoilerCommandedPosition[3]->set(-secsAnalogOutputs[0].left_spoiler_2_pos_order_deg); + idRightSpoilerCommandedPosition[3]->set(-secsAnalogOutputs[0].right_spoiler_2_pos_order_deg); + idLeftSpoilerCommandedPosition[4]->set(-secsAnalogOutputs[1].left_spoiler_1_pos_order_deg); + idRightSpoilerCommandedPosition[4]->set(-secsAnalogOutputs[1].right_spoiler_1_pos_order_deg); + + idLeftElevatorSolenoidEnergized[0]->set(elacsDiscreteOutputs[1].left_elevator_damping_mode || + secsDiscreteOutputs[1].left_elevator_damping_mode); + idLeftElevatorCommandedPosition[0]->set(elacsAnalogOutputs[0].left_elev_pos_order_deg + secsAnalogOutputs[0].left_elev_pos_order_deg); + idRightElevatorSolenoidEnergized[0]->set(elacsDiscreteOutputs[1].right_elevator_damping_mode || + secsDiscreteOutputs[1].right_elevator_damping_mode); + idRightElevatorCommandedPosition[0]->set(elacsAnalogOutputs[0].right_elev_pos_order_deg + secsAnalogOutputs[0].right_elev_pos_order_deg); + idLeftElevatorSolenoidEnergized[1]->set(elacsDiscreteOutputs[0].left_elevator_damping_mode || + secsDiscreteOutputs[0].left_elevator_damping_mode); + idLeftElevatorCommandedPosition[1]->set(elacsAnalogOutputs[1].left_elev_pos_order_deg + secsAnalogOutputs[1].left_elev_pos_order_deg); + idRightElevatorSolenoidEnergized[1]->set(elacsDiscreteOutputs[0].right_elevator_damping_mode || + secsDiscreteOutputs[0].right_elevator_damping_mode); + idRightElevatorCommandedPosition[1]->set(elacsAnalogOutputs[1].right_elev_pos_order_deg + secsAnalogOutputs[1].right_elev_pos_order_deg); + + idTHSActiveModeCommanded[0]->set(elacsDiscreteOutputs[1].ths_active); + idTHSCommandedPosition[0]->set(-elacsAnalogOutputs[1].ths_pos_order); + idTHSActiveModeCommanded[1]->set(elacsDiscreteOutputs[0].ths_active || secsDiscreteOutputs[0].ths_active); + idTHSCommandedPosition[1]->set(-elacsAnalogOutputs[0].ths_pos_order - secsAnalogOutputs[0].ths_pos_order_deg); + idTHSActiveModeCommanded[2]->set(secsDiscreteOutputs[1].ths_active); + idTHSCommandedPosition[2]->set(-secsAnalogOutputs[1].ths_pos_order_deg); + + idYawDamperSolenoidEnergized[0]->set(facsDiscreteOutputs[0].yaw_damper_engaged); + idYawDamperCommandedPosition[0]->set(facsAnalogOutputs[0].yaw_damper_order_deg); + idYawDamperSolenoidEnergized[1]->set(facsDiscreteOutputs[1].yaw_damper_engaged); + idYawDamperCommandedPosition[1]->set(facsAnalogOutputs[1].yaw_damper_order_deg); + idRudderTrimActiveModeCommanded[0]->set(facsDiscreteOutputs[0].rudder_trim_engaged); + idRudderTravelLimCommandedPosition[0]->set(facsAnalogOutputs[0].rudder_trim_order_deg); + idRudderTrimActiveModeCommanded[1]->set(facsDiscreteOutputs[1].rudder_trim_engaged); + idRudderTravelLimCommandedPosition[1]->set(facsAnalogOutputs[1].rudder_trim_order_deg); + idRudderTravelLimitActiveModeCommanded[0]->set(facsDiscreteOutputs[0].rudder_travel_lim_engaged); + idRudderTravelLimCommandedPosition[0]->set(facsAnalogOutputs[0].rudder_travel_limit_order_deg); + idRudderTravelLimitActiveModeCommanded[1]->set(facsDiscreteOutputs[1].rudder_travel_lim_engaged); + idRudderTravelLimCommandedPosition[1]->set(facsAnalogOutputs[1].rudder_travel_limit_order_deg); + + SimInput simInput = simConnectInterface.getSimInput(); + + idRudderPosition->set(-simInput.inputs[2] - (facsAnalogOutputs[0].yaw_damper_order_deg + facsAnalogOutputs[1].yaw_damper_order_deg) / 30); + + SimOutputZetaTrim outputZetaTrim = {}; + outputZetaTrim.zeta_trim_pos = -(facsAnalogOutputs[0].rudder_trim_order_deg + facsAnalogOutputs[1].rudder_trim_order_deg) / 20; + if (facsDiscreteOutputs[0].rudder_trim_engaged || facsDiscreteOutputs[1].rudder_trim_engaged) { + if (!simConnectInterface.sendData(outputZetaTrim)) { + cout << "WASM: Write data failed!" << endl; + return false; + } + } + + if (facsDiscreteOutputs[0].rudder_travel_lim_engaged || facsDiscreteOutputs[1].rudder_travel_lim_engaged) { + rudderTravelLimiterPosition = facsAnalogOutputs[0].rudder_travel_limit_order_deg + facsAnalogOutputs[1].rudder_travel_limit_order_deg; + } + + double totalSpoilersLeftDeflection = idLeftSpoilerPosition[0]->get() + idLeftSpoilerPosition[1]->get() + idLeftSpoilerPosition[2]->get() + + idLeftSpoilerPosition[3]->get() + idLeftSpoilerPosition[4]->get(); + double totalSpoilersRightDeflection = idRightSpoilerPosition[0]->get() + idRightSpoilerPosition[1]->get() + + idRightSpoilerPosition[2]->get() + idRightSpoilerPosition[3]->get() + + idRightSpoilerPosition[4]->get(); + totalSpoilersLeftDeflection /= 5; + totalSpoilersRightDeflection /= 5; + double totalSpoilerDeflection = (totalSpoilersLeftDeflection + totalSpoilersRightDeflection) / 2; + double totalAssymmetricSpoilerDeflection = fabs(totalSpoilersLeftDeflection - totalSpoilersRightDeflection) / 2; + + SimOutputSpoilers out = {fmax(totalSpoilerDeflection - totalAssymmetricSpoilerDeflection, 0)}; + simConnectInterface.sendData(out); + + return true; +} + bool FlyByWireInterface::updateAutopilotStateMachine(double sampleTime) { // get data from interface ------------------------------------------------------------------------------------------ SimData simData = simConnectInterface.getSimData(); @@ -820,8 +1705,7 @@ bool FlyByWireInterface::updateAutopilotStateMachine(double sampleTime) { bool doDisconnect = false; if (autopilotStateMachineOutput.enabled_AP1 || autopilotStateMachineOutput.enabled_AP2) { - doDisconnect = fabs(simInput.inputs[0]) > 0.5 || fabs(simInput.inputs[1]) > 0.5 || fabs(simInput.inputs[2]) > 0.4 || - flyByWireOutput.sim.data_computed.protection_ap_disc; + doDisconnect = !(elacsDiscreteOutputs[0].ap_1_authorised || elacsDiscreteOutputs[1].ap_1_authorised); } // update state machine --------------------------------------------------------------------------------------------- @@ -1321,6 +2205,27 @@ bool FlyByWireInterface::updateAutopilotLaws(double sampleTime) { autopilotLawsOutput.flare_law.condition_Flare = clientDataLaws.conditionFlare; } + base_arinc_429 raToUse; + if (raBusOutputs[0].radio_height_ft.SSM != Arinc429SignStatus::FailureWarning) { + raToUse = raBusOutputs[0].radio_height_ft; + } else { + raToUse = raBusOutputs[1].radio_height_ft; + } + + fmgcBBusOutputs.fg_radio_height_ft = raToUse; + fmgcBBusOutputs.delta_p_ail_cmd_deg.SSM = Arinc429SignStatus::NormalOperation; + fmgcBBusOutputs.delta_p_ail_cmd_deg.Data = autopilotLawsOutput.autopilot.Phi_c_deg; + fmgcBBusOutputs.delta_p_splr_cmd_deg.SSM = Arinc429SignStatus::NormalOperation; + fmgcBBusOutputs.delta_p_splr_cmd_deg.Data = 0; + fmgcBBusOutputs.delta_r_cmd_deg.SSM = Arinc429SignStatus::NormalOperation; + fmgcBBusOutputs.delta_r_cmd_deg.Data = autopilotLawsOutput.autopilot.Beta_c_deg; + fmgcBBusOutputs.delta_q_cmd_deg.SSM = Arinc429SignStatus::NormalOperation; + fmgcBBusOutputs.delta_q_cmd_deg.Data = autopilotLawsOutput.autopilot.Theta_c_deg; + + if (elacDisabled != -1 || facDisabled != -1) { + simConnectInterface.setClientDataFmgcB(fmgcBBusOutputs, 0); + } + // update flight director ------------------------------------------------------------------------------------------- idFlightDirectorPitch->set(-autopilotLawsOutput.flight_director.Theta_c_deg); idFlightDirectorBank->set(-autopilotLawsOutput.flight_director.Phi_c_deg); @@ -1344,141 +2249,6 @@ bool FlyByWireInterface::updateFlyByWire(double sampleTime) { SimData simData = simConnectInterface.getSimData(); SimInput simInput = simConnectInterface.getSimInput(); - // update fly-by-wire ----------------------------------------------------------------------------------------------- - if (flyByWireEnabled) { - // fill time into model ------------------------------------------------------------------------------------------- - flyByWireInput.in.time.dt = sampleTime; - flyByWireInput.in.time.simulation_time = simData.simulationTime; - - // fill data into model ------------------------------------------------------------------------------------------- - flyByWireInput.in.data.nz_g = simData.nz_g; - flyByWireInput.in.data.Theta_deg = simData.Theta_deg; - flyByWireInput.in.data.Phi_deg = simData.Phi_deg; - flyByWireInput.in.data.q_rad_s = simData.bodyRotationVelocity.x; - flyByWireInput.in.data.r_rad_s = simData.bodyRotationVelocity.y; - flyByWireInput.in.data.p_rad_s = simData.bodyRotationVelocity.z; - flyByWireInput.in.data.q_dot_rad_s2 = simData.bodyRotationAcceleration.x; - flyByWireInput.in.data.r_dot_rad_s2 = simData.bodyRotationAcceleration.y; - flyByWireInput.in.data.p_dot_rad_s2 = simData.bodyRotationAcceleration.z; - flyByWireInput.in.data.psi_magnetic_deg = simData.Psi_magnetic_deg; - flyByWireInput.in.data.psi_true_deg = simData.Psi_true_deg; - flyByWireInput.in.data.eta_pos = simData.eta_pos; - flyByWireInput.in.data.eta_trim_deg = simData.eta_trim_deg; - flyByWireInput.in.data.xi_pos = simData.xi_pos; - flyByWireInput.in.data.zeta_pos = simData.zeta_pos; - flyByWireInput.in.data.zeta_trim_pos = simData.zeta_trim_pos; - flyByWireInput.in.data.alpha_deg = simData.alpha_deg; - flyByWireInput.in.data.beta_deg = simData.beta_deg; - flyByWireInput.in.data.beta_dot_deg_s = simData.beta_dot_deg_s; - flyByWireInput.in.data.V_ias_kn = simData.V_ias_kn; - flyByWireInput.in.data.V_tas_kn = simData.V_tas_kn; - flyByWireInput.in.data.V_mach = simData.V_mach; - flyByWireInput.in.data.VLS_kn = idFmgcV_LS->get(); - flyByWireInput.in.data.H_ft = simData.H_ft; - flyByWireInput.in.data.H_ind_ft = simData.H_ind_ft; - flyByWireInput.in.data.H_radio_ft = simData.H_radio_ft; - flyByWireInput.in.data.CG_percent_MAC = simData.CG_percent_MAC; - flyByWireInput.in.data.total_weight_kg = simData.total_weight_kg; - flyByWireInput.in.data.gear_animation_pos_0 = simData.gear_animation_pos_0; - flyByWireInput.in.data.gear_animation_pos_1 = simData.gear_animation_pos_1; - flyByWireInput.in.data.gear_animation_pos_2 = simData.gear_animation_pos_2; - flyByWireInput.in.data.flaps_handle_index = flapsHandleIndexFlapConf->get(); - flyByWireInput.in.data.spoilers_left_pos = simData.spoilers_left_pos; - flyByWireInput.in.data.spoilers_right_pos = simData.spoilers_right_pos; - flyByWireInput.in.data.autopilot_master_on = simData.autopilot_master_on; - flyByWireInput.in.data.slew_on = simData.slew_on; - flyByWireInput.in.data.pause_on = pauseDetected; - flyByWireInput.in.data.autopilot_custom_on = autopilotLawsOutput.ap_on; - flyByWireInput.in.data.autopilot_custom_Theta_c_deg = autopilotLawsOutput.autopilot.Theta_c_deg; - flyByWireInput.in.data.autopilot_custom_Phi_c_deg = autopilotLawsOutput.autopilot.Phi_c_deg; - flyByWireInput.in.data.autopilot_custom_Beta_c_deg = autopilotLawsOutput.autopilot.Beta_c_deg; - flyByWireInput.in.data.tracking_mode_on_override = idExternalOverride->get() == 1; - flyByWireInput.in.data.simulation_rate = simData.simulation_rate; - flyByWireInput.in.data.ice_structure_percent = simData.ice_structure_percent; - flyByWireInput.in.data.linear_cl_alpha_per_deg = simData.linear_cl_alpha_per_deg; - flyByWireInput.in.data.alpha_stall_deg = simData.alpha_stall_deg; - flyByWireInput.in.data.alpha_zero_lift_deg = simData.alpha_zero_lift_deg; - flyByWireInput.in.data.ambient_density_kg_per_m3 = simData.ambient_density_kg_per_m3; - flyByWireInput.in.data.ambient_pressure_mbar = simData.ambient_pressure_mbar; - flyByWireInput.in.data.ambient_temperature_celsius = simData.ambient_temperature_celsius; - flyByWireInput.in.data.ambient_wind_x_kn = simData.ambient_wind_x_kn; - flyByWireInput.in.data.ambient_wind_y_kn = simData.ambient_wind_y_kn; - flyByWireInput.in.data.ambient_wind_z_kn = simData.ambient_wind_z_kn; - flyByWireInput.in.data.ambient_wind_velocity_kn = simData.ambient_wind_velocity_kn; - flyByWireInput.in.data.ambient_wind_direction_deg = simData.ambient_wind_direction_deg; - flyByWireInput.in.data.total_air_temperature_celsius = simData.total_air_temperature_celsius; - flyByWireInput.in.data.latitude_deg = simData.latitude_deg; - flyByWireInput.in.data.longitude_deg = simData.longitude_deg; - flyByWireInput.in.data.engine_1_thrust_lbf = simData.engine_1_thrust_lbf; - flyByWireInput.in.data.engine_2_thrust_lbf = simData.engine_2_thrust_lbf; - flyByWireInput.in.data.thrust_lever_1_pos = thrustLeverAngle_1->get(); - flyByWireInput.in.data.thrust_lever_2_pos = thrustLeverAngle_2->get(); - flyByWireInput.in.data.tailstrike_protection_on = tailstrikeProtectionEnabled; - - // set inputs ----------------------------------------------------------------------------------------------------- - flyByWireInput.in.input.delta_eta_pos = simInput.inputs[0]; - flyByWireInput.in.input.delta_xi_pos = simInput.inputs[1]; - flyByWireInput.in.input.delta_zeta_pos = simInput.inputs[2]; - - // step the model ------------------------------------------------------------------------------------------------- - flyByWire.setExternalInputs(&flyByWireInput); - flyByWire.step(); - - // when tracking mode is on do not write anything ----------------------------------------------------------------- - flyByWireOutput = flyByWire.getExternalOutputs().out; - - // write client data if necessary - if (!autopilotStateMachineEnabled) { - ClientDataFlyByWire clientDataFlyByWire = { - flyByWireOutput.output.eta_pos, - flyByWireOutput.output.xi_pos, - flyByWireOutput.output.zeta_pos, - flyByWireOutput.output.eta_trim_deg_should_write, - flyByWireOutput.output.eta_trim_deg, - flyByWireOutput.output.zeta_trim_pos_should_write, - flyByWireOutput.output.zeta_trim_pos, - flyByWireOutput.sim.data_computed.alpha_floor_command, - flyByWireOutput.sim.data_computed.protection_ap_disc, - flyByWireOutput.sim.data_speeds_aoa.v_alpha_prot_kn, - flyByWireOutput.sim.data_speeds_aoa.v_alpha_max_kn, - flyByWireOutput.roll.data_computed.beta_target_deg, - }; - simConnectInterface.setClientDataFlyByWire(clientDataFlyByWire); - } - } else { - // send data to client data to be read by simulink - ClientDataFlyByWireInput clientDataFlyByWireInput = { - simInput.inputs[0], - simInput.inputs[1], - simInput.inputs[2], - }; - simConnectInterface.setClientDataFlyByWireInput(clientDataFlyByWireInput); - - ClientDataAutopilotLaws clientDataLaws = {autopilotLawsOutput.ap_on, - autopilotLawsOutput.flight_director.Theta_c_deg, - autopilotLawsOutput.autopilot.Theta_c_deg, - autopilotLawsOutput.flight_director.Phi_c_deg, - autopilotLawsOutput.autopilot.Phi_c_deg, - autopilotLawsOutput.autopilot.Beta_c_deg}; - simConnectInterface.setClientDataAutopilotLaws(clientDataLaws); - - // read data - auto clientDataFlyByWire = simConnectInterface.getClientDataFlyByWire(); - flyByWireOutput.output.eta_pos = clientDataFlyByWire.eta_pos; - flyByWireOutput.output.xi_pos = clientDataFlyByWire.xi_pos; - flyByWireOutput.output.zeta_pos = clientDataFlyByWire.zeta_pos; - flyByWireOutput.output.eta_trim_deg_should_write = clientDataFlyByWire.eta_trim_deg_should_write; - flyByWireOutput.output.eta_trim_deg = clientDataFlyByWire.eta_trim_deg; - flyByWireOutput.output.zeta_trim_pos_should_write = clientDataFlyByWire.zeta_trim_pos_should_write; - flyByWireOutput.output.zeta_trim_pos = clientDataFlyByWire.zeta_trim_pos; - flyByWireOutput.sim.data_computed.tracking_mode_on = simData.slew_on || pauseDetected || idExternalOverride->get() == 1; - flyByWireOutput.sim.data_computed.alpha_floor_command = clientDataFlyByWire.alpha_floor_command; - flyByWireOutput.sim.data_computed.protection_ap_disc = clientDataFlyByWire.protection_ap_disc; - flyByWireOutput.sim.data_speeds_aoa.v_alpha_prot_kn = clientDataFlyByWire.v_alpha_prot_kn; - flyByWireOutput.sim.data_speeds_aoa.v_alpha_max_kn = clientDataFlyByWire.v_alpha_max_kn; - flyByWireOutput.roll.data_computed.beta_target_deg = clientDataFlyByWire.beta_target_deg; - } - // write sidestick position idSideStickPositionX->set(-1.0 * simInput.inputs[1]); idSideStickPositionY->set(-1.0 * simInput.inputs[0]); @@ -1487,90 +2257,16 @@ bool FlyByWireInterface::updateFlyByWire(double sampleTime) { idRudderPedalPosition->set(max(-100, min(100, (-100.0 * simInput.inputs[2])))); idRudderPedalAnimationPosition->set(max(-100, min(100, (-100.0 * simInput.inputs[2]) + (100.0 * simData.zeta_trim_pos)))); - // set outputs - if (!flyByWireOutput.sim.data_computed.tracking_mode_on) { - // object to write with trim - SimOutput output = {flyByWireOutput.output.eta_pos, flyByWireOutput.output.xi_pos, flyByWireOutput.output.zeta_pos}; - - // send data via sim connect - if (!simConnectInterface.sendData(output)) { - cout << "WASM: Write data failed!" << endl; - return false; - } - } + // provide tracking mode state + idTrackingMode->set(wasInSlew || pauseDetected || idExternalOverride->get()); // determine if nosewheel demand shall be set - if (!flyByWireOutput.sim.data_computed.tracking_mode_on) { + if (!(wasInSlew || pauseDetected || idExternalOverride->get())) { idAutopilotNosewheelDemand->set(autopilotLawsOutput.Nosewheel_c); } else { idAutopilotNosewheelDemand->set(0); } - // set trim values - SimOutputEtaTrim outputEtaTrim = {}; - if (flyByWireOutput.output.eta_trim_deg_should_write) { - outputEtaTrim.eta_trim_deg = flyByWireOutput.output.eta_trim_deg; - elevatorTrimHandler->synchronizeValue(outputEtaTrim.eta_trim_deg); - } else { - outputEtaTrim.eta_trim_deg = elevatorTrimHandler->getPosition(); - } - if (!flyByWireOutput.sim.data_computed.tracking_mode_on) { - if (!simConnectInterface.sendData(outputEtaTrim)) { - cout << "WASM: Write data failed!" << endl; - return false; - } - } - - SimOutputZetaTrim outputZetaTrim = {}; - rudderTrimHandler->update(sampleTime); - if (flyByWireOutput.output.zeta_trim_pos_should_write) { - outputZetaTrim.zeta_trim_pos = flyByWireOutput.output.zeta_trim_pos; - rudderTrimHandler->synchronizeValue(outputZetaTrim.zeta_trim_pos); - } else { - outputZetaTrim.zeta_trim_pos = rudderTrimHandler->getPosition(); - } - if (!flyByWireOutput.sim.data_computed.tracking_mode_on) { - if (!simConnectInterface.sendData(outputZetaTrim)) { - cout << "WASM: Write data failed!" << endl; - return false; - } - } - - // calculate alpha max percentage - if (flyByWireOutput.sim.data_computed.on_ground) { - idAlphaMaxPercentage->set(0); - } else { - idAlphaMaxPercentage->set(flyByWireOutput.sim.data_speeds_aoa.alpha_filtered_deg / flyByWireOutput.sim.data_speeds_aoa.alpha_max_deg); - } - - // update speeds - idSpeedAlphaProtection->set(flyByWireOutput.sim.data_speeds_aoa.v_alpha_prot_kn); - idSpeedAlphaMax->set(flyByWireOutput.sim.data_speeds_aoa.v_alpha_max_kn); - - // update aileron positions - animationAileronHandler->update(idAutopilotActiveAny->get(), spoilersHandler->getIsGroundSpoilersActive(), simData.simulationTime, - simData.Theta_deg, flapsHandleIndexFlapConf->get(), flapsPosition->get(), - idExternalOverride->get() == 1 ? simData.xi_pos : flyByWireOutput.output.xi_pos, sampleTime); - idAileronPositionLeft->set(animationAileronHandler->getPositionLeft()); - idAileronPositionRight->set(animationAileronHandler->getPositionRight()); - - // determine if beta target needs to be active (blue) - bool conditionDifferenceEngineN1Larger35 = (abs(simData.engine_N1_1_percent - simData.engine_N1_2_percent) > 35); - bool conditionConfigruation123 = (flapsHandleIndexFlapConf->get() > 0 && flapsHandleIndexFlapConf->get() < 4); - bool conditionAnyEngineN1Above80 = (simData.engine_N1_1_percent > 80 || simData.engine_N1_2_percent > 80); - bool conditionAnyThrustLeverAboveMct = (thrustLeverAngle_1->get() > 35 || thrustLeverAngle_2->get() > 35); - bool conditionAnyThrustLeverInFlex = ((thrustLeverAngle_1->get() >= 35 || thrustLeverAngle_2->get() >= 35) && - autoThrustOutput.thrust_limit_type == athr_thrust_limit_type_FLEX); - - if (conditionDifferenceEngineN1Larger35 && conditionConfigruation123 && - (conditionAnyEngineN1Above80 || conditionAnyThrustLeverAboveMct || conditionAnyThrustLeverInFlex)) { - idBetaTargetActive->set(1); - idBetaTarget->set(flyByWireOutput.roll.data_computed.beta_target_deg); - } else { - idBetaTargetActive->set(0); - idBetaTarget->set(0); - } - // success ---------------------------------------------------------------------------------------------------------- return true; } @@ -1580,7 +2276,8 @@ bool FlyByWireInterface::updateAutothrust(double sampleTime) { SimData simData = simConnectInterface.getSimData(); // set ground / flight for throttle handling - if (flyByWireOutput.sim.data_computed.on_ground) { + if (idLgciuLeftMainGearCompressed[0]->get() || idLgciuLeftMainGearCompressed[1]->get() || idLgciuRightMainGearCompressed[0]->get() || + idLgciuRightMainGearCompressed[1]->get()) { throttleAxis[0]->setOnGround(); throttleAxis[1]->setOnGround(); } else { @@ -1611,7 +2308,8 @@ bool FlyByWireInterface::updateAutothrust(double sampleTime) { idFmgcFlexTemperature->get(), autopilotStateMachineOutput.autothrust_mode, simData.is_mach_mode_active, - flyByWireOutput.sim.data_computed.alpha_floor_command, + reinterpret_cast(&facsBusOutputs[0].discrete_word_5)->bitFromValueOr(29, false) || + reinterpret_cast(&facsBusOutputs[1].discrete_word_5)->bitFromValueOr(29, false), autopilotStateMachineOutput.vertical_mode >= 30 && autopilotStateMachineOutput.vertical_mode <= 34, autopilotStateMachineOutput.vertical_mode == 40, autopilotStateMachineOutput.vertical_mode == 41, @@ -1676,7 +2374,9 @@ bool FlyByWireInterface::updateAutothrust(double sampleTime) { autoThrustInput.in.input.flex_temperature_degC = idFmgcFlexTemperature->get(); autoThrustInput.in.input.mode_requested = autopilotStateMachineOutput.autothrust_mode; autoThrustInput.in.input.is_mach_mode_active = simData.is_mach_mode_active; - autoThrustInput.in.input.alpha_floor_condition = flyByWireOutput.sim.data_computed.alpha_floor_command; + autoThrustInput.in.input.alpha_floor_condition = + reinterpret_cast(&facsBusOutputs[0].discrete_word_5)->bitFromValueOr(29, false) || + reinterpret_cast(&facsBusOutputs[1].discrete_word_5)->bitFromValueOr(29, false); autoThrustInput.in.input.is_approach_mode_active = (autopilotStateMachineOutput.vertical_mode >= 30 && autopilotStateMachineOutput.vertical_mode <= 34) || autopilotStateMachineOutput.vertical_mode == 24; @@ -1770,29 +2470,9 @@ bool FlyByWireInterface::updateSpoilers(double sampleTime) { spoilersHandler->setInitialPosition(idSpoilersArmed->get(), simData.spoilers_handle_position); } - // update simulation variables - spoilersHandler->setSimulationVariables( - simData.simulationTime, autopilotStateMachineOutput.enabled_AP1 == 1 || autopilotStateMachineOutput.enabled_AP2 == 1, - simData.V_gnd_kn, thrustLeverAngle_1->get(), thrustLeverAngle_2->get(), simData.gear_animation_pos_1, simData.gear_animation_pos_2, - flapsHandleIndexFlapConf->get(), flyByWireOutput.sim.data_computed.high_aoa_prot_active == 1, flyByWireOutput.output.xi_pos); - - // update sim position - spoilersHandler->updateSimPosition(sampleTime); - - // check state of spoilers and adapt if necessary - if (spoilersHandler->getSimPosition() != simData.spoilers_handle_position) { - SimOutputSpoilers out = {spoilersHandler->getSimPosition()}; - simConnectInterface.sendData(out); - } - // set 3D handle position idSpoilersArmed->set(spoilersHandler->getIsArmed() ? 1 : 0); idSpoilersHandlePosition->set(spoilersHandler->getHandlePosition()); - idSpoilersGroundSpoilersActive->set(spoilersHandler->getIsGroundSpoilersActive() ? 1 : 0); - - // set spoiler demand as input for hydraulics - idSpoilersPositionLeft->set(spoilersHandler->getLeftPosition()); - idSpoilersPositionRight->set(spoilersHandler->getRightPosition()); // result return true; @@ -1812,6 +2492,46 @@ bool FlyByWireInterface::updateAltimeterSetting(double sampleTime) { return true; } +bool FlyByWireInterface::updateFoSide(double sampleTime) { + // get sim data + auto simData = simConnectInterface.getSimData(); + + // FD Button + if (additionalData.syncFoEfisEnabled && simData.ap_fd_1_active != simData.ap_fd_2_active) { + if (last_fd1_active != simData.ap_fd_1_active) { + simConnectInterface.sendEvent(SimConnectInterface::Events::TOGGLE_FLIGHT_DIRECTOR, 2); + } + + if (last_fd2_active != simData.ap_fd_2_active) { + simConnectInterface.sendEvent(SimConnectInterface::Events::TOGGLE_FLIGHT_DIRECTOR, 1); + } + } + last_fd1_active = simData.ap_fd_1_active; + last_fd2_active = simData.ap_fd_2_active; + + // LS Button + if (additionalData.syncFoEfisEnabled && additionalData.ls1Active != additionalData.ls2Active) { + if (last_ls1_active != additionalData.ls1Active) { + idLs2Active->set(additionalData.ls1Active); + } + + if (last_ls2_active != additionalData.ls2Active) { + idLs1Active->set(additionalData.ls2Active); + } + } + last_ls1_active = additionalData.ls1Active; + last_ls2_active = additionalData.ls2Active; + + // inHg/hPa switch + // Currently synced already + + // STD Button + // Currently synced already + + // result + return true; +} + double FlyByWireInterface::getTcasModeAvailable() { auto state = idTcasMode->get(); auto isTaOnly = idTcasTaOnly->get(); diff --git a/src/fbw/src/FlyByWireInterface.h b/src/fbw/src/FlyByWireInterface.h index 2e2aff319063..46848c01816c 100644 --- a/src/fbw/src/FlyByWireInterface.h +++ b/src/fbw/src/FlyByWireInterface.h @@ -4,7 +4,6 @@ #include #include "AdditionalData.h" -#include "AnimationAileronHandler.h" #include "AutopilotLaws.h" #include "AutopilotStateMachine.h" #include "Autothrust.h" @@ -12,14 +11,19 @@ #include "ElevatorTrimHandler.h" #include "EngineData.h" #include "FlightDataRecorder.h" -#include "FlyByWire.h" #include "InterpolatingLookupTable.h" #include "LocalVariable.h" #include "RateLimiter.h" -#include "RudderTrimHandler.h" #include "SimConnectInterface.h" #include "SpoilersHandler.h" #include "ThrottleAxisMapping.h" +#include "elac/Elac.h" +#include "fac/Fac.h" +#include "failures/FailuresConsumer.h" +#include "fcdc/Fcdc.h" +#include "sec/Sec.h" + +#include "utils/HysteresisNode.h" class FlyByWireInterface { public: @@ -39,6 +43,8 @@ class FlyByWireInterface { double previousSimulationTime = 0; double calculatedSampleTime = 0; + double monotonicTime = 0; + int currentApproachCapability = 0; double previousApproachCapabilityUpdateTime = 0; @@ -51,6 +57,9 @@ class FlyByWireInterface { bool autopilotStateMachineEnabled = false; bool autopilotLawsEnabled = false; bool flyByWireEnabled = false; + int elacDisabled = -1; + int secDisabled = -1; + int facDisabled = -1; bool autoThrustEnabled = false; bool tailstrikeProtectionEnabled = true; @@ -73,17 +82,23 @@ class FlyByWireInterface { double flightControlsKeyChangeElevator = 0.0; double flightControlsKeyChangeRudder = 0.0; + double rudderTravelLimiterPosition = 25; + bool disableXboxCompatibilityRudderAxisPlusMinus = false; bool clientDataEnabled = false; + bool last_fd1_active = false; + bool last_fd2_active = false; + + bool last_ls1_active = false; + bool last_ls2_active = false; + FlightDataRecorder flightDataRecorder; SimConnectInterface simConnectInterface; - FlyByWireModelClass flyByWire; - FlyByWireModelClass::ExternalInputs_FlyByWire_T flyByWireInput = {}; - fbw_output flyByWireOutput; + FailuresConsumer failuresConsumer; AutopilotStateMachineModelClass autopilotStateMachine; AutopilotStateMachineModelClass::ExternalInputs_AutopilotStateMachine_T autopilotStateMachineInput = {}; @@ -97,6 +112,36 @@ class FlyByWireInterface { AutothrustModelClass::ExternalInputs_Autothrust_T autoThrustInput = {}; athr_output autoThrustOutput; + base_ra_bus raBusOutputs[2] = {}; + + base_lgciu_bus lgciuBusOutputs[2] = {}; + + base_sfcc_bus sfccBusOutputs[2] = {}; + + base_fmgc_b_bus fmgcBBusOutputs = {}; + + base_adr_bus adrBusOutputs[3] = {}; + base_ir_bus irBusOutputs[3] = {}; + + Elac elacs[2] = {Elac(true), Elac(false)}; + base_elac_discrete_outputs elacsDiscreteOutputs[2] = {}; + base_elac_analog_outputs elacsAnalogOutputs[2] = {}; + base_elac_out_bus elacsBusOutputs[2] = {}; + + Sec secs[3] = {Sec(true, false), Sec(false, false), Sec(false, true)}; + base_sec_discrete_outputs secsDiscreteOutputs[3] = {}; + base_sec_analog_outputs secsAnalogOutputs[3] = {}; + base_sec_out_bus secsBusOutputs[3] = {}; + + Fcdc fcdcs[2] = {Fcdc(true), Fcdc(false)}; + FcdcDiscreteOutputs fcdcsDiscreteOutputs[2] = {}; + base_fcdc_bus fcdcsBusOutputs[2] = {}; + + Fac facs[2] = {Fac(true), Fac(false)}; + base_fac_discrete_outputs facsDiscreteOutputs[2] = {}; + base_fac_analog_outputs facsAnalogOutputs[2] = {}; + base_fac_bus facsBusOutputs[2] = {}; + InterpolatingLookupTable throttleLookupTable; RadioReceiver radioReceiver; @@ -123,6 +168,7 @@ class FlyByWireInterface { std::unique_ptr idPerformanceWarningActive; + std::unique_ptr idTrackingMode; std::unique_ptr idExternalOverride; std::unique_ptr idFdrEvent; @@ -133,11 +179,6 @@ class FlyByWireInterface { std::unique_ptr idRudderPedalAnimationPosition; std::unique_ptr idAutopilotNosewheelDemand; - std::unique_ptr idSpeedAlphaProtection; - std::unique_ptr idSpeedAlphaMax; - - std::unique_ptr idAlphaMaxPercentage; - std::unique_ptr idFmaLateralMode; std::unique_ptr idFmaLateralArmed; std::unique_ptr idFmaVerticalMode; @@ -158,9 +199,6 @@ class FlyByWireInterface { std::unique_ptr idFlightDirectorPitch; std::unique_ptr idFlightDirectorYaw; - std::unique_ptr idBetaTarget; - std::unique_ptr idBetaTargetActive; - std::unique_ptr idAutopilotAutolandWarning; std::unique_ptr idAutopilotActiveAny; @@ -306,17 +344,11 @@ class FlyByWireInterface { std::unique_ptr idSpoilersArmed; std::unique_ptr idSpoilersHandlePosition; - std::unique_ptr idSpoilersGroundSpoilersActive; std::shared_ptr spoilersHandler; - std::unique_ptr idSpoilersPositionLeft; - std::unique_ptr idSpoilersPositionRight; std::shared_ptr elevatorTrimHandler; - std::shared_ptr rudderTrimHandler; - std::unique_ptr idAileronPositionLeft; - std::unique_ptr idAileronPositionRight; - std::shared_ptr animationAileronHandler; + std::unique_ptr idRudderPosition; std::unique_ptr idRadioReceiverUsageEnabled; std::unique_ptr idRadioReceiverLocalizerValid; @@ -329,6 +361,195 @@ class FlyByWireInterface { std::unique_ptr idTillerHandlePosition; std::unique_ptr idNoseWheelPosition; + std::unique_ptr idSyncFoEfisEnabled; + + std::unique_ptr idLs1Active; + std::unique_ptr idLs2Active; + std::unique_ptr idIsisLsActive; + + // RA bus inputs + std::unique_ptr idRadioAltimeterHeight[2]; + + // LGCIU inputs + std::unique_ptr idLgciuNoseGearCompressed[2]; + std::unique_ptr idLgciuLeftMainGearCompressed[2]; + std::unique_ptr idLgciuRightMainGearCompressed[2]; + std::unique_ptr idLgciuDiscreteWord1[2]; + std::unique_ptr idLgciuDiscreteWord2[2]; + std::unique_ptr idLgciuDiscreteWord3[2]; + + // SFCC inputs + std::unique_ptr idSfccSlatFlapComponentStatusWord; + std::unique_ptr idSfccSlatFlapSystemStatusWord; + std::unique_ptr idSfccSlatFlapActualPositionWord; + std::unique_ptr idSfccSlatActualPositionWord; + std::unique_ptr idSfccFlapActualPositionWord; + + // ADR bus inputs + std::unique_ptr idAdrAltitudeCorrected[3]; + std::unique_ptr idAdrMach[3]; + std::unique_ptr idAdrAirspeedComputed[3]; + std::unique_ptr idAdrAirspeedTrue[3]; + std::unique_ptr idAdrVerticalSpeed[3]; + std::unique_ptr idAdrAoaCorrected[3]; + std::unique_ptr idAdrCorrectedAverageStaticPressure[3]; + + // IR bus inputs + std::unique_ptr idIrLatitude[3]; + std::unique_ptr idIrLongitude[3]; + std::unique_ptr idIrGroundSpeed[3]; + std::unique_ptr idIrWindSpeed[3]; + std::unique_ptr idIrWindDirectionTrue[3]; + std::unique_ptr idIrTrackAngleMagnetic[3]; + std::unique_ptr idIrHeadingMagnetic[3]; + std::unique_ptr idIrDriftAngle[3]; + std::unique_ptr idIrFlightPathAngle[3]; + std::unique_ptr idIrPitchAngle[3]; + std::unique_ptr idIrRollAngle[3]; + std::unique_ptr idIrBodyPitchRate[3]; + std::unique_ptr idIrBodyRollRate[3]; + std::unique_ptr idIrBodyYawRate[3]; + std::unique_ptr idIrBodyLongAccel[3]; + std::unique_ptr idIrBodyLatAccel[3]; + std::unique_ptr idIrBodyNormalAccel[3]; + std::unique_ptr idIrTrackAngleRate[3]; + std::unique_ptr idIrPitchAttRate[3]; + std::unique_ptr idIrRollAttRate[3]; + std::unique_ptr idIrInertialVerticalSpeed[3]; + + // FCDC bus label Lvars + std::unique_ptr idFcdcDiscreteWord1[2]; + std::unique_ptr idFcdcDiscreteWord2[2]; + std::unique_ptr idFcdcDiscreteWord3[2]; + std::unique_ptr idFcdcDiscreteWord4[2]; + std::unique_ptr idFcdcDiscreteWord5[2]; + std::unique_ptr idFcdcCaptRollCommand[2]; + std::unique_ptr idFcdcFoRollCommand[2]; + std::unique_ptr idFcdcCaptPitchCommand[2]; + std::unique_ptr idFcdcFoPitchCommand[2]; + std::unique_ptr idFcdcRudderPedalPos[2]; + std::unique_ptr idFcdcAileronLeftPos[2]; + std::unique_ptr idFcdcElevatorLeftPos[2]; + std::unique_ptr idFcdcAileronRightPos[2]; + std::unique_ptr idFcdcElevatorRightPos[2]; + std::unique_ptr idFcdcElevatorTrimPos[2]; + std::unique_ptr idFcdcSpoilerLeft1Pos[2]; + std::unique_ptr idFcdcSpoilerLeft2Pos[2]; + std::unique_ptr idFcdcSpoilerLeft3Pos[2]; + std::unique_ptr idFcdcSpoilerLeft4Pos[2]; + std::unique_ptr idFcdcSpoilerLeft5Pos[2]; + std::unique_ptr idFcdcSpoilerRight1Pos[2]; + std::unique_ptr idFcdcSpoilerRight2Pos[2]; + std::unique_ptr idFcdcSpoilerRight3Pos[2]; + std::unique_ptr idFcdcSpoilerRight4Pos[2]; + std::unique_ptr idFcdcSpoilerRight5Pos[2]; + + // FCDC discrete output Lvars + std::unique_ptr idFcdcPriorityCaptGreen[2]; + std::unique_ptr idFcdcPriorityCaptRed[2]; + std::unique_ptr idFcdcPriorityFoGreen[2]; + std::unique_ptr idFcdcPriorityFoRed[2]; + + // fault input Lvars + std::unique_ptr idElevFaultLeft[2]; + std::unique_ptr idElevFaultRight[2]; + std::unique_ptr idAilFaultLeft[2]; + std::unique_ptr idAilFaultRight[2]; + std::unique_ptr idSplrFaultLeft[5]; + std::unique_ptr idSplrFaultRight[5]; + + // THS Override Signal LVar + std::unique_ptr idThsOverrideActive; + + // ELAC discrete input Lvars + std::unique_ptr idElacPushbuttonPressed[2]; + + // ELAC discrete output Lvars + std::unique_ptr idElacDigitalOpValidated[2]; + + // SEC discrete input Lvars + std::unique_ptr idSecPushbuttonPressed[3]; + + // SEC discrete output Lvars + std::unique_ptr idSecFaultLightOn[3]; + std::unique_ptr idSecGroundSpoilersOut[3]; + + // Flight controls solenoid valve energization Lvars + std::unique_ptr idLeftAileronSolenoidEnergized[2]; + std::unique_ptr idLeftAileronCommandedPosition[2]; + std::unique_ptr idRightAileronSolenoidEnergized[2]; + std::unique_ptr idRightAileronCommandedPosition[2]; + std::unique_ptr idLeftSpoilerCommandedPosition[5]; + std::unique_ptr idRightSpoilerCommandedPosition[5]; + std::unique_ptr idLeftElevatorSolenoidEnergized[2]; + std::unique_ptr idLeftElevatorCommandedPosition[2]; + std::unique_ptr idRightElevatorSolenoidEnergized[2]; + std::unique_ptr idRightElevatorCommandedPosition[2]; + std::unique_ptr idTHSActiveModeCommanded[3]; + std::unique_ptr idTHSCommandedPosition[3]; + std::unique_ptr idYawDamperSolenoidEnergized[2]; + std::unique_ptr idYawDamperCommandedPosition[2]; + std::unique_ptr idRudderTrimActiveModeCommanded[2]; + std::unique_ptr idRudderTrimCommandedPosition[2]; + std::unique_ptr idRudderTravelLimitActiveModeCommanded[2]; + std::unique_ptr idRudderTravelLimCommandedPosition[2]; + + // FAC discrete input Lvars + std::unique_ptr idFacPushbuttonPressed[2]; + // FAC discrete output Lvars + std::unique_ptr idFacHealthy[2]; + + std::unique_ptr idFacDiscreteWord1[2]; + std::unique_ptr idFacGammaA[2]; + std::unique_ptr idFacGammaT[2]; + std::unique_ptr idFacWeight[2]; + std::unique_ptr idFacCenterOfGravity[2]; + std::unique_ptr idFacSideslipTarget[2]; + std::unique_ptr idFacSlatAngle[2]; + std::unique_ptr idFacFlapAngle[2]; + std::unique_ptr idFacDiscreteWord2[2]; + std::unique_ptr idFacRudderTravelLimitCommand[2]; + std::unique_ptr idFacDeltaRYawDamperVoted[2]; + std::unique_ptr idFacEstimatedSideslip[2]; + std::unique_ptr idFacVAlphaLim[2]; + std::unique_ptr idFacVLs[2]; + std::unique_ptr idFacVStall[2]; + std::unique_ptr idFacVAlphaProt[2]; + std::unique_ptr idFacVStallWarn[2]; + std::unique_ptr idFacSpeedTrend[2]; + std::unique_ptr idFacV3[2]; + std::unique_ptr idFacV4[2]; + std::unique_ptr idFacVMan[2]; + std::unique_ptr idFacVMax[2]; + std::unique_ptr idFacVFeNext[2]; + std::unique_ptr idFacDiscreteWord3[2]; + std::unique_ptr idFacDiscreteWord4[2]; + std::unique_ptr idFacDiscreteWord5[2]; + std::unique_ptr idFacDeltaRRudderTrim[2]; + std::unique_ptr idFacRudderTrimPos[2]; + + std::unique_ptr idLeftAileronPosition; + std::unique_ptr idRightAileronPosition; + std::unique_ptr idLeftElevatorPosition; + std::unique_ptr idRightElevatorPosition; + std::unique_ptr idLeftSpoilerPosition[5]; + std::unique_ptr idRightSpoilerPosition[5]; + + std::unique_ptr idElecDcBus2Powered; + std::unique_ptr idElecDcEssShedBusPowered; + std::unique_ptr idElecDcEssBusPowered; + std::unique_ptr idElecBat1HotBusPowered; + + std::unique_ptr idHydYellowSystemPressure; + std::unique_ptr idHydGreenSystemPressure; + std::unique_ptr idHydBlueSystemPressure; + std::unique_ptr idHydYellowPressurised; + std::unique_ptr idHydGreenPressurised; + std::unique_ptr idHydBluePressurised; + + std::unique_ptr idCaptPriorityButtonPressed; + std::unique_ptr idFoPriorityButtonPressed; + void loadConfiguration(); void setupLocalVariables(); @@ -349,8 +570,28 @@ class FlyByWireInterface { bool updateFlyByWire(double sampleTime); bool updateAutothrust(double sampleTime); + bool updateRa(int raIndex); + + bool updateLgciu(int lgciuIndex); + + bool updateSfcc(int sfccIndex); + + bool updateAdirs(int adirsIndex); + + bool updateElac(double sampleTime, int elacIndex); + + bool updateSec(double sampleTime, int secIndex); + + bool updateFcdc(double sampleTime, int fcdcIndex); + + bool updateFac(double sampleTime, int facIndex); + + bool updateServoSolenoidStatus(); + bool updateSpoilers(double sampleTime); + bool updateFoSide(double sampleTime); + bool updateAltimeterSetting(double sampleTime); double getTcasModeAvailable(); diff --git a/src/fbw/src/RudderTrimHandler.cpp b/src/fbw/src/RudderTrimHandler.cpp deleted file mode 100644 index eec7e6de432b..000000000000 --- a/src/fbw/src/RudderTrimHandler.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "RudderTrimHandler.h" -#include - -RudderTrimHandler::RudderTrimHandler() { - rateLimiter.setRate(RATE_LEFT_RIGHT); -} - -void RudderTrimHandler::synchronizeValue(double value) { - targetValue = value; - rateLimiter.reset(value); -} - -void RudderTrimHandler::onEventRudderTrimLeft(double dt) { - rateLimiter.setRate(RATE_LEFT_RIGHT); - if (targetValue == POSITION_RESET) { - targetValue = rateLimiter.getValue(); - } - targetValue = fmax(POSITION_MAX_LEFT, targetValue - (RATE_LEFT_RIGHT * dt)); -} - -void RudderTrimHandler::onEventRudderTrimReset() { - rateLimiter.setRate(RATE_RESET); - targetValue = POSITION_RESET; -} - -void RudderTrimHandler::onEventRudderTrimRight(double dt) { - rateLimiter.setRate(RATE_LEFT_RIGHT); - if (targetValue == POSITION_RESET) { - targetValue = rateLimiter.getValue(); - } - targetValue = fmin(POSITION_MAX_RIGHT, targetValue + (RATE_LEFT_RIGHT * dt)); -} - -void RudderTrimHandler::onEventRudderTrimSet(double value) { - rateLimiter.setRate(RATE_LEFT_RIGHT); - targetValue = fmin(POSITION_MAX_RIGHT, fmax(POSITION_MAX_LEFT, value / SET_EVENT_DIVIDER)); -} - -void RudderTrimHandler::update(double dt) { - rateLimiter.update(targetValue, dt); -} - -double RudderTrimHandler::getPosition() { - return rateLimiter.getValue(); -} - -double RudderTrimHandler::getTargetPosition() { - return targetValue; -} diff --git a/src/fbw/src/RudderTrimHandler.h b/src/fbw/src/RudderTrimHandler.h deleted file mode 100644 index 727d860bf6a2..000000000000 --- a/src/fbw/src/RudderTrimHandler.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "RateLimiter.h" - -class RudderTrimHandler { - public: - RudderTrimHandler(); - - void synchronizeValue(double value); - - void onEventRudderTrimLeft(double dt); - void onEventRudderTrimReset(); - void onEventRudderTrimRight(double dt); - void onEventRudderTrimSet(double value); - - void update(double dt); - double getPosition(); - double getTargetPosition(); - - private: - static constexpr double SET_EVENT_DIVIDER = 16384.0; - static constexpr double POSITION_RESET = 0.0; - static constexpr double POSITION_MAX_LEFT = -1.0; - static constexpr double POSITION_MAX_RIGHT = +1.0; - static constexpr double RATE_LEFT_RIGHT = 0.05; - static constexpr double RATE_RESET = 0.075; - - double targetValue = 0; - RateLimiter rateLimiter; -}; diff --git a/src/fbw/src/SpoilersHandler.cpp b/src/fbw/src/SpoilersHandler.cpp index e9ceb70a118d..a26d81f21fa5 100644 --- a/src/fbw/src/SpoilersHandler.cpp +++ b/src/fbw/src/SpoilersHandler.cpp @@ -14,286 +14,67 @@ bool SpoilersHandler::getIsArmed() const { return isArmed; } -bool SpoilersHandler::getIsGroundSpoilersActive() const { - return isGroundSpoilersActive; -} - double SpoilersHandler::getHandlePosition() const { return handlePosition; } -double SpoilersHandler::getSimPosition() const { - return rateLimiter.getValue(); -} - -double SpoilersHandler::getLeftPosition() const { - return rateLimiterLeft.getValue(); -} - -double SpoilersHandler::getRightPosition() const { - return rateLimiterRight.getValue(); -} - void SpoilersHandler::setInitialPosition(bool isArmed, double position) { if (isInitialized) { return; } - update(isArmed, fmin(1.0, fmax(0.0, position))); - rateLimiter.setRate(DEFLECTION_RATE); - rateLimiter.reset(targetSimPosition); + this->isArmed = isArmed; + handlePosition = fmin(1.0, fmax(0.0, position)); isInitialized = true; } -void SpoilersHandler::setSimulationVariables(double simulationTime_new, - bool isAutopilotEngaged_new, - double groundSpeed_new, - double thrustLeverAngle_1_new, - double thrustLeverAngle_2_new, - double landingGearCompression_1_new, - double landingGearCompression_2_new, - double flapsHandleIndex_new, - bool isAngleOfAttackProtectionActive_new, - double aileronPosition_new) { - update(simulationTime_new, isArmed, handlePosition, isAutopilotEngaged_new, groundSpeed_new, thrustLeverAngle_1_new, - thrustLeverAngle_2_new, getGearStrutCompressionFromAnimation(landingGearCompression_1_new), - getGearStrutCompressionFromAnimation(landingGearCompression_2_new), flapsHandleIndex_new, isAngleOfAttackProtectionActive_new, - aileronPosition_new); -} - void SpoilersHandler::onEventSpoilersOn() { - update(isArmed, POSITION_FULL); + isArmed = false; + handlePosition = POSITION_FULL; } void SpoilersHandler::onEventSpoilersOff() { - update(isArmed, POSITION_RETRACTED); + isArmed = false; + handlePosition = POSITION_RETRACTED; } void SpoilersHandler::onEventSpoilersToggle() { - update(isArmed, handlePosition > 0 ? POSITION_RETRACTED : POSITION_FULL); + isArmed = false; + handlePosition = handlePosition > 0 ? POSITION_RETRACTED : POSITION_FULL; } void SpoilersHandler::onEventSpoilersSet(double value) { - update(isArmed, fmin(1.0, fmax(0.0, value / 16384.0))); + handlePosition = fmin(1.0, fmax(0.0, value / 16384.0)); + if (handlePosition > 0) { + isArmed = false; + } } void SpoilersHandler::onEventSpoilersAxisSet(double value) { - update(isArmed, fmin(1.0, fmax(0.0, 0.5 + (value / 32768.0)))); + handlePosition = fmin(1.0, fmax(0.0, 0.5 + (value / 32768.0))); + if (handlePosition > 0) { + isArmed = false; + } } void SpoilersHandler::onEventSpoilersArmOn() { - update(true, handlePosition); + isArmed = true; + handlePosition = 0; } void SpoilersHandler::onEventSpoilersArmOff() { - update(false, handlePosition); + isArmed = false; } void SpoilersHandler::onEventSpoilersArmToggle() { - update(!isArmed, handlePosition); -} - -void SpoilersHandler::onEventSpoilersArmSet(bool value) { - update(value, handlePosition); -} - -void SpoilersHandler::update(bool isArmed_new, double handlePosition_new) { - update(simulationTime, isArmed_new, handlePosition_new, isAutopilotEngaged, groundSpeed, thrustLeverAngle_1, thrustLeverAngle_2, - landingGearCompression_1, landingGearCompression_2, flapsHandleIndex, isAngleOfAttackProtectionActive, aileronPosition); -} - -void SpoilersHandler::update(double simulationTime_new, - bool isArmed_new, - double handlePosition_new, - bool isAutopilotEngaged_new, - double groundSpeed_new, - double thrustLeverAngle_1_new, - double thrustLeverAngle_2_new, - double landingGearCompression_1_new, - double landingGearCompression_2_new, - double flapsHandleIndex_new, - bool isAngleOfAttackProtectionActive_new, - double aileronPosition_new) { - // inhibit condition ------------------------------------------------------------------------------------------------- - - if ((flapsHandleIndex == FLAPS_HANDLE_INDEX_FULL) || areAboveMct(thrustLeverAngle_1_new, thrustLeverAngle_2_new) || - isAngleOfAttackProtectionActive_new) { - if (!conditionInhibit) { - targetSimPosition = POSITION_RETRACTED; - } - timeInhibitReset = 0; - conditionInhibit = true; - } else if (conditionInhibit && handlePosition_new == POSITION_RETRACTED) { - if (timeInhibitReset == 0) { - timeInhibitReset = simulationTime_new; - } else if (simulationTime_new - timeInhibitReset >= INHIBIT_COOLDOWN_TIME) { - timeInhibitReset = 0; - conditionInhibit = false; - } - } - - // deployment ------------------------------------------------------------------------------------------------------- - - if (isArmed != isArmed_new || handlePosition != handlePosition_new || isAutopilotEngaged != isAutopilotEngaged_new) { - // ensure ground spoilers are only armed when handle is in retracted position - isArmed = isArmed_new && (handlePosition_new == POSITION_RETRACTED); - // remember handle position - handlePosition = handlePosition_new; - // set sim position - if (!conditionInhibit || handlePosition_new == POSITION_RETRACTED) { - if (isAutopilotEngaged_new) { - targetSimPosition = fmin(POSITION_LIMIT_AUTOPILOT, handlePosition_new); - } else { - targetSimPosition = handlePosition_new; - } - isGroundSpoilersActive = false; - } - } - - // store simulation variables ---------------------------------------------------------------------------------------- - - simulationTime = simulationTime_new; - isAutopilotEngaged = isAutopilotEngaged_new; - groundSpeed = groundSpeed_new; - thrustLeverAngle_1 = thrustLeverAngle_1_new; - thrustLeverAngle_2 = thrustLeverAngle_2_new; - landingGearCompression_1 = landingGearCompression_1_new; - landingGearCompression_2 = landingGearCompression_2_new; - flapsHandleIndex = flapsHandleIndex_new; - isAngleOfAttackProtectionActive = isAngleOfAttackProtectionActive_new; - - // conditions -------------------------------------------------------------------------------------------------------- - - // determine conditions - double numberOfMainLandingGearsOnGround = numberOfLandingGearsOnGround(landingGearCompression_1, landingGearCompression_2); - bool areThrustLeversAtOrBelowIdle = areAtOrBelowIdle(thrustLeverAngle_1, thrustLeverAngle_2); - - // detect landing condition - if (conditionLanding) { - if (numberOfMainLandingGearsOnGround == 2 && groundSpeed < CONDITION_GROUND_SPEED) { - conditionLanding = false; - timeAirborne = 0; - } - } else { - if (timeAirborne == 0.0 && numberOfMainLandingGearsOnGround == 0.0) { - timeAirborne = simulationTime; - } else if (getTimeSinceAirborne(simulationTime, timeAirborne) >= MINIMUM_AIRBORNE_TIME) { - conditionLanding = true; - } - } - - // detect take-off condition - if (numberOfMainLandingGearsOnGround == 2 && groundSpeed > CONDITION_GROUND_SPEED) { - conditionTakeOff = true; - } else if (groundSpeed < CONDITION_GROUND_SPEED || numberOfMainLandingGearsOnGround == 0) { - conditionTakeOff = false; - } - - // take-off phase ---------------------------------------------------------------------------------------------------- - - if (conditionTakeOff) { - if ((isArmed && areThrustLeversAtOrBelowIdle) || isAtLeastOneInReverseAndOtherAtOrBelowIdle(thrustLeverAngle_1, thrustLeverAngle_2)) { - targetSimPosition = POSITION_FULL; - isGroundSpoilersActive = true; - } + isArmed = !isArmed; + if (isArmed) { + handlePosition = 0; } - - // landing phase ----------------------------------------------------------------------------------------------------- - - if (conditionLanding) { - // determine conditions - bool areThrustLeversBelowClimb = areBelowClimb(thrustLeverAngle_1_new, thrustLeverAngle_2_new); - bool isAtLeastOneThrustLeverInReverseAndOtherBelowMct = - isAtLeastOneInReverseAndOtherBelowMct(thrustLeverAngle_1_new, thrustLeverAngle_2_new); - - // armed *or* lever *not* retracted - if (isArmed || handlePosition > POSITION_RETRACTED) { - if (numberOfMainLandingGearsOnGround == 2) { - if (areThrustLeversAtOrBelowIdle || isAtLeastOneThrustLeverInReverseAndOtherBelowMct) { - // full deployment - targetSimPosition = POSITION_FULL; - isGroundSpoilersActive = true; - } else if (isArmed && areThrustLeversBelowClimb) { - // partial deployment - targetSimPosition = POSITION_PARTIAL; - } - } else if (numberOfMainLandingGearsOnGround >= 1 && areThrustLeversAtOrBelowIdle) { - // partial deployment - targetSimPosition = fmax(handlePosition, POSITION_PARTIAL); - } - } - - // *not* armed *and* lever retracted - if (!isArmed && handlePosition == POSITION_RETRACTED) { - if (isAtLeastOneThrustLeverInReverseAndOtherBelowMct) { - if (numberOfMainLandingGearsOnGround == 2) { - // full deployment - targetSimPosition = POSITION_FULL; - isGroundSpoilersActive = true; - } else if (numberOfMainLandingGearsOnGround >= 1) { - // partial deployment - targetSimPosition = POSITION_PARTIAL; - } - } - } - - // on touch & go retract spoilers when at least one thrust lever is > 20° - if (numberOfMainLandingGearsOnGround > 0 && - (thrustLeverAngle_1 > TLA_CONDITION_TOUCH_GO || thrustLeverAngle_2 > TLA_CONDITION_TOUCH_GO)) { - targetSimPosition = fmax(handlePosition, POSITION_RETRACTED); - isGroundSpoilersActive = false; - } - } - - // spoilerons ------------------------------------------------------------------------------------------------------- - - aileronPosition = aileronPosition_new; - double spoileronsDeflection = fmax(0.0, fmin(1.0, SPOILERONS_AILERON_GAIN * (fabs(aileronPosition) - SPOILERONS_MIN_AILERON_POSITION))); - targetLeftPosition = fmax(0.0, fmin(1.0, targetSimPosition - copysign(spoileronsDeflection, aileronPosition))); - targetRightPosition = fmax(0.0, fmin(1.0, targetSimPosition + copysign(spoileronsDeflection, aileronPosition))); -} - -void SpoilersHandler::updateSimPosition(double dt) { - rateLimiter.update(targetSimPosition, dt); - rateLimiterLeft.update(targetLeftPosition, dt); - rateLimiterRight.update(targetRightPosition, dt); -} - -double SpoilersHandler::getGearStrutCompressionFromAnimation(double animationPosition) { - return fmin(1.0, fmax(0.0, 2 * (animationPosition - 0.5))); -} - -double SpoilersHandler::getTimeSinceAirborne(double simulationTime, double timeAirborne) { - return timeAirborne > 0 ? simulationTime - timeAirborne : 0; } -double SpoilersHandler::numberOfLandingGearsOnGround(double landingGearCompression_1, double landingGearCompression_2) { - double numberOnGround = 0.0; - if (landingGearCompression_1 > 0.1) { - numberOnGround += 1.0; - } - if (landingGearCompression_2 > 0.1) { - numberOnGround += 1.0; +void SpoilersHandler::onEventSpoilersArmSet(bool value) { + isArmed = value; + if (isArmed) { + handlePosition = 0; } - return numberOnGround; -} - -bool SpoilersHandler::areAtOrBelowIdle(double thrustLeverAngle_1, double thrustLeverAngle_2) { - return thrustLeverAngle_1 <= TLA_IDLE && thrustLeverAngle_2 <= TLA_IDLE; -} - -bool SpoilersHandler::areBelowClimb(double thrustLeverAngle_1, double thrustLeverAngle_2) { - return thrustLeverAngle_1 < TLA_CLB && thrustLeverAngle_2 < TLA_CLB; -} - -bool SpoilersHandler::areAboveMct(double thrustLeverAngle_1, double thrustLeverAngle_2) { - return thrustLeverAngle_1 > TLA_MCT && thrustLeverAngle_2 > TLA_MCT; -} - -bool SpoilersHandler::isAtLeastOneInReverseAndOtherAtOrBelowIdle(double thrustLeverAngle_1, double thrustLeverAngle_2) { - return (thrustLeverAngle_1 < TLA_IDLE && thrustLeverAngle_2 <= TLA_IDLE) || - (thrustLeverAngle_2 < TLA_IDLE && thrustLeverAngle_1 <= TLA_IDLE); -} - -bool SpoilersHandler::isAtLeastOneInReverseAndOtherBelowMct(double thrustLeverAngle_1, double thrustLeverAngle_2) { - return (thrustLeverAngle_1 < TLA_IDLE && thrustLeverAngle_2 < TLA_MCT) || (thrustLeverAngle_2 < TLA_IDLE && thrustLeverAngle_1 < TLA_MCT); } diff --git a/src/fbw/src/SpoilersHandler.h b/src/fbw/src/SpoilersHandler.h index 62112dfba9fa..49d70851cdf4 100644 --- a/src/fbw/src/SpoilersHandler.h +++ b/src/fbw/src/SpoilersHandler.h @@ -7,24 +7,8 @@ class SpoilersHandler { bool getIsInitialized() const; bool getIsArmed() const; double getHandlePosition() const; - double getSimPosition() const; - bool getIsGroundSpoilersActive() const; - - double getLeftPosition() const; - double getRightPosition() const; void setInitialPosition(bool isArmed, double position); - void setSimulationVariables(double simulationTime_new, - bool isAutopilotEngaged_new, - double groundSpeed_new, - double thrustLeverAngle_1_new, - double thrustLeverAngle_2_new, - double landingGearCompression_1_new, - double landingGearCompression_2_new, - double flapsHandleIndex_new, - bool isAngleOfAttackProtectionActive_new, - double aileronPosition_new); - void updateSimPosition(double dt); void onEventSpoilersOn(); void onEventSpoilersOff(); @@ -37,85 +21,10 @@ class SpoilersHandler { void onEventSpoilersArmToggle(); void onEventSpoilersArmSet(bool value); - private: - static constexpr double DEFLECTION_RATE = 0.66; - static constexpr double POSITION_RETRACTED = 0.0; - static constexpr double POSITION_PARTIAL = 0.25; - static constexpr double POSITION_LIMIT_AUTOPILOT = 0.5; static constexpr double POSITION_FULL = 1.0; - static constexpr double MINIMUM_AIRBORNE_TIME = 5.0; - static constexpr double CONDITION_GROUND_SPEED = 72.0; - - static constexpr double TLA_IDLE = 0.0; - static constexpr double TLA_CLB = 25.0; - static constexpr double TLA_MCT = 35.0; - static constexpr double TLA_CONDITION_TOUCH_GO = 20.0; - - static constexpr double FLAPS_HANDLE_INDEX_FULL = 5; - static constexpr double INHIBIT_COOLDOWN_TIME = 10.0; - - static constexpr double SPOILERONS_MIN_AILERON_POSITION = 0.08; - static constexpr double SPOILERONS_AILERON_GAIN = 0.3571; - bool isInitialized = false; bool isArmed = false; double handlePosition = 0.0; - double targetSimPosition = 0.0; - double targetLeftPosition = 0.0; - double targetRightPosition = 0.0; - - RateLimiter rateLimiter; - RateLimiter rateLimiterLeft; - RateLimiter rateLimiterRight; - - bool conditionInhibit = false; - double timeInhibitReset = 0.0; - bool conditionLanding = false; - bool conditionTakeOff = false; - double simulationTime = 0.0; - double timeAirborne = 0.0; - bool isAutopilotEngaged = false; - double groundSpeed = 0.0; - double thrustLeverAngle_1 = 0.0; - double thrustLeverAngle_2 = 0.0; - double landingGearCompression_1 = 0.0; - double landingGearCompression_2 = 0.0; - double flapsHandleIndex = 0.0; - double aileronPosition = 0.0; - bool isAngleOfAttackProtectionActive = false; - - bool isGroundSpoilersActive = false; - - void update(bool isArmed_new, double handlePosition_new); - - void update(double simulationTime_new, - bool isArmed_new, - double handlePosition_new, - bool isAutopilotEngaged_new, - double groundSpeed_new, - double thrustLeverAngle_1_new, - double thrustLeverAngle_2_new, - double landingGearCompression_1_new, - double landingGearCompression_2_new, - double flapsHandleIndex_new, - bool isAngleOfAttackProtectionActive_new, - double aileronPosition_new); - - static double getGearStrutCompressionFromAnimation(double animationPosition); - - static double getTimeSinceAirborne(double simulationTime, double timeAirborne); - - static double numberOfLandingGearsOnGround(double landingGearCompression_1, double landingGearCompression_2); - - static bool areAtOrBelowIdle(double thrustLeverAngle_1, double thrustLeverAngle_2); - - static bool areBelowClimb(double thrustLeverAngle_1, double thrustLeverAngle_2); - - static bool areAboveMct(double thrustLeverAngle_1, double thrustLeverAngle_2); - - static bool isAtLeastOneInReverseAndOtherAtOrBelowIdle(double thrustLeverAngle_1, double thrustLeverAngle_2); - - static bool isAtLeastOneInReverseAndOtherBelowMct(double thrustLeverAngle_1, double thrustLeverAngle_2); }; diff --git a/src/fbw/src/busStructures/busStructures.h b/src/fbw/src/busStructures/busStructures.h new file mode 100644 index 000000000000..4a053582a05f --- /dev/null +++ b/src/fbw/src/busStructures/busStructures.h @@ -0,0 +1,610 @@ +#pragma once + +#include "../Arinc429.h" +#include "../model/ElacComputer_types.h" +#include "../model/SecComputer_types.h" + +enum class LateralLaw { + NormalLaw, + DirectLaw, + None, +}; + +enum class PitchLaw { + NormalLaw, + AlternateLaw1, + AlternateLaw2, + DirectLaw, + None, +}; + +struct IlsBus { + // Label 33 + Arinc429NumericWord ilsFreq; + // Label 17 + Arinc429NumericWord runwayHeading; + // Label 173 + Arinc429NumericWord locDeviation; + // Label 174 + Arinc429NumericWord glideDeviation; +}; + +struct DmeBus {}; + +struct RaBus { + // Label 164 + Arinc429NumericWord radioHeight; +}; + +struct FcdcBus { + // Label 040 + Arinc429DiscreteWord efcsStatus1; + // Label 041 + Arinc429DiscreteWord efcsStatus2; + // Label 042 + Arinc429DiscreteWord efcsStatus3; + // Label 043 + Arinc429DiscreteWord efcsStatus4; + // Label 044 + Arinc429DiscreteWord efcsStatus5; + // Label 301 + Arinc429NumericWord captRollCommand; + // Label 302 + Arinc429NumericWord foRollCommand; + // Label 304 + Arinc429NumericWord rudderPedalPosition; + // Label 305 + Arinc429NumericWord captPitchCommand; + // Label 306 + Arinc429NumericWord foPitchCommand; + // Label 310 + Arinc429NumericWord aileronLeftPos; + // Label 314 + Arinc429NumericWord elevatorLeftPos; + // Label 330 + Arinc429NumericWord aileronRightPos; + // Label 334 + Arinc429NumericWord elevatorRightPos; + // Label 335 and/or 315, not sure + Arinc429NumericWord horizStabTrimPos; + // Label 361 + Arinc429NumericWord spoilerLeft1Pos; + // Label 362 + Arinc429NumericWord spoilerLeft2Pos; + // Label 363 + Arinc429NumericWord spoilerLeft3Pos; + // Label 364 + Arinc429NumericWord spoilerLeft4Pos; + // Label 365 + Arinc429NumericWord spoilerLeft5Pos; + // Label 371 + Arinc429NumericWord spoilerRight1Pos; + // Label 372 + Arinc429NumericWord spoilerRight2Pos; + // Label 373 + Arinc429NumericWord spoilerRight3Pos; + // Label 374 + Arinc429NumericWord spoilerRight4Pos; + // Label 375 + Arinc429NumericWord spoilerRight5Pos; +}; + +// These EFCS interconnect buses are only educated guesses. +// Here is a bit table for the ELAC discrete Words: + +// +=======+=====================================+=======+ +// | Label | Parameter Definition | Bits | +// +=======+=====================================+=======+ +// | 1 | Discrete Word 1 | | +// +-------+-------------------------------------+-------+ +// | | Left Aileron Fault | 11 | +// +-------+-------------------------------------+-------+ +// | | Right Aileron Fault | 12 | +// +-------+-------------------------------------+-------+ +// | | Left Elevator Fault | 13 | +// +-------+-------------------------------------+-------+ +// | | Right Elevator Fault | 14 | +// +-------+-------------------------------------+-------+ +// | | Left Aileron Avail | 15 | +// +-------+-------------------------------------+-------+ +// | | Right Aileron Avail | 16 | +// +-------+-------------------------------------+-------+ +// | | Left Elevator Avail | 17 | +// +-------+-------------------------------------+-------+ +// | | Right Elevator Avail | 18 | +// +-------+-------------------------------------+-------+ +// | | Computer Engaged in Pitch | 19 | +// +-------+-------------------------------------+-------+ +// | | Computer Engaged in Roll | 20 | +// +-------+-------------------------------------+-------+ +// | | Computer Pitch Fault | 21 | +// +-------+-------------------------------------+-------+ +// | | Computer Roll Fault | 22 | +// +-------+-------------------------------------+-------+ +// | | Active Pitch Law | 23-25 | +// +-------+-------------------------------------+-------+ +// | | 1 0 0 - Normal law | | +// +-------+-------------------------------------+-------+ +// | | 0 1 0 - Alternate law 1 | | +// +-------+-------------------------------------+-------+ +// | | 1 1 0 - Alternate law 2 | | +// +-------+-------------------------------------+-------+ +// | | 0 0 1 - Direct law | | +// +-------+-------------------------------------+-------+ +// | | 0 0 0 - None | | +// +-------+-------------------------------------+-------+ +// | | Active Lateral Law | 26-27 | +// +-------+-------------------------------------+-------+ +// | | 1 0 - Normal law | | +// +-------+-------------------------------------+-------+ +// | | 0 1 - Direct Law | | +// +-------+-------------------------------------+-------+ +// | | 0 0 - None | | +// +-------+-------------------------------------+-------+ +// | | Spoiler Pair 3 Roll Active Command | 28 | +// +-------+-------------------------------------+-------+ +// | | Spoiler Pair 2 Roll Active Command | 29 | +// +-------+-------------------------------------+-------+ +// | | | | +// +-------+-------------------------------------+-------+ +// | 2 | Discrete Word 2 | | +// +-------+-------------------------------------+-------+ +// | | Pitch law Capability | 11-12 | +// +-------+-------------------------------------+-------+ +// | | 1 0 - Normal law | | +// +-------+-------------------------------------+-------+ +// | | 0 1 - Alternate law | | +// +-------+-------------------------------------+-------+ +// | | 1 1 - Direct law | | +// +-------+-------------------------------------+-------+ +// | | 0 0 - None | | +// +-------+-------------------------------------+-------+ +// | | Lateral law Capability | 13-14 | +// +-------+-------------------------------------+-------+ +// | | 1 0 - Normal law | | +// +-------+-------------------------------------+-------+ +// | | 0 1 - Direct Law | | +// +-------+-------------------------------------+-------+ +// | | 0 0 - None | | +// +-------+-------------------------------------+-------+ +// | | Left Sidestick Fault | 15 | +// +-------+-------------------------------------+-------+ +// | | Right Sidestick Fault | 16 | +// +-------+-------------------------------------+-------+ +// | | Left sidestick disabled (priority) | 17 | +// +-------+-------------------------------------+-------+ +// | | Right sidestick disabled (priority) | 18 | +// +-------+-------------------------------------+-------+ +// | | Left sidestick priority locked | 19 | +// +-------+-------------------------------------+-------+ +// | | Right sidestick priority locked | 20 | +// +-------+-------------------------------------+-------+ +// | | Aileron Droop active | 21 | +// +-------+-------------------------------------+-------+ +// | | Any AP engaged | 22 | +// +-------+-------------------------------------+-------+ +// | | Alpha protection active | 23 | +// +-------+-------------------------------------+-------+ + +struct ElacOutBus { + // Surface positions + Arinc429NumericWord leftAileronPosition; + + Arinc429NumericWord rightAileronPosition; + + Arinc429NumericWord leftElevatorPosition; + + Arinc429NumericWord rightElevatorPosition; + + Arinc429NumericWord thsPosition; + // Sidestick/Rudder pedal posititons; + Arinc429NumericWord leftSidestickPitchCommand; + + Arinc429NumericWord rightSidestickPitchCommand; + + Arinc429NumericWord leftSidestickRollCommand; + + Arinc429NumericWord rightSidestickRollCommand; + + Arinc429NumericWord rudderPedalPosition; + // Aileron command for cross-ELAC roll command execution + Arinc429NumericWord aileronCommand; + + Arinc429NumericWord rollSpoilerCommand; + // Yaw Damper command for the FACs + Arinc429NumericWord yawDamperCommand; + + Arinc429NumericWord elevatorDualPressurizationCommand; + // Discrete status words + Arinc429DiscreteWord discreteStatusWord1; + + Arinc429DiscreteWord discreteStatusWord2; +}; + +// Bit table for SEC discrete words + +// +=======+=====================================+=======+ +// | Label | Parameter Definition | Bits | +// +=======+=====================================+=======+ +// | 1 | Discrete Word 1 | | +// +-------+-------------------------------------+-------+ +// | | Spoiler 1 Fault | 11 | +// +-------+-------------------------------------+-------+ +// | | Spoiler 2 Fault | 12 | +// +-------+-------------------------------------+-------+ +// | | Left Elevator Fault | 13 | +// +-------+-------------------------------------+-------+ +// | | Right Elevator Fault | 14 | +// +-------+-------------------------------------+-------+ +// | | Spoiler 1 Avail | 15 | +// +-------+-------------------------------------+-------+ +// | | Spoiler 2 Avail | 16 | +// +-------+-------------------------------------+-------+ +// | | Left Elevator Avail | 17 | +// +-------+-------------------------------------+-------+ +// | | Right Elevator Avail | 18 | +// +-------+-------------------------------------+-------+ +// | | Active Pitch Law | 19-21 | +// +-------+-------------------------------------+-------+ +// | | 0 1 0 - Alternate law 1 | | +// +-------+-------------------------------------+-------+ +// | | 1 1 0 - Alternate law 2 | | +// +-------+-------------------------------------+-------+ +// | | 0 0 1 - Direct law | | +// +-------+-------------------------------------+-------+ +// | | 0 0 0 - None | | +// +-------+-------------------------------------+-------+ +// | | Computer Engaged in Lateral | 22 | +// +-------+-------------------------------------+-------+ +// | | Computer Engaged in Pitch | 23 | +// +-------+-------------------------------------+-------+ +// | | Ground Spoiler Fault | 24 | +// +-------+-------------------------------------+-------+ +// | | Ground Spoiler Out | 25 | +// +-------+-------------------------------------+-------+ +// | | Ground Spoiler Armed | 26 | +// +-------+-------------------------------------+-------+ +// | | | | +// +-------+-------------------------------------+-------+ +// | 2 | Discrete Word 2 | | +// +-------+-------------------------------------+-------+ +// | | Left Sidestick Fault | 11 | +// +-------+-------------------------------------+-------+ +// | | Right Sidestick Fault | 12 | +// +-------+-------------------------------------+-------+ +// | | Left sidestick disabled (priority) | 13 | +// +-------+-------------------------------------+-------+ +// | | Right sidestick disabled (priority) | 14 | +// +-------+-------------------------------------+-------+ +// | | Left sidestick priority locked | 15 | +// +-------+-------------------------------------+-------+ +// | | Right sidestick priority locked | 16 | +// +-------+-------------------------------------+-------+ +// | | LGCIU Uplock Disagree or Fault | 17 | +// +-------+-------------------------------------+-------+ +// | | Any L/G not uplocked | 18 | +// +-------+-------------------------------------+-------+ + +struct SecOutBus { + // Surface positions + Arinc429NumericWord leftSpoiler1Position; + + Arinc429NumericWord rightSpoiler1Position; + + Arinc429NumericWord leftSpoiler2Position; + + Arinc429NumericWord rightSpoiler2Position; + + Arinc429NumericWord leftElevatorPosition; + + Arinc429NumericWord rightElevatorPosition; + + Arinc429NumericWord thsPosition; + + // Sidestick / Speed brake lever positions + // SEC 1&2 only + Arinc429NumericWord leftSidestickPitchCommand; + // SEC 1&2 only + Arinc429NumericWord rightSidestickPitchCommand; + + Arinc429NumericWord leftSidestickRollCommand; + + Arinc429NumericWord rightSidestickRollCommand; + + Arinc429NumericWord speedBrakeLeverCommand; + + // Thrust Lever Angles, for ELAC and FCDC + Arinc429NumericWord thrustLeverAngle1; + + Arinc429NumericWord thrustLeverAngle2; + + // Discrete status words + Arinc429DiscreteWord discreteStatusWord1; + + Arinc429DiscreteWord discreteStatusWord2; +}; + +struct FmgcABus { + // Label 140 + Arinc429NumericWord rollFdCommand; + // Label 141 + Arinc429NumericWord pitchFdCommand; + // Label 143 + Arinc429NumericWord yawFdCommand; + // Label 144 + Arinc429NumericWord accuracyInput; + // Label 145 + Arinc429DiscreteWord discreteWord5; + // Label 146 + Arinc429DiscreteWord discreteWord4; + // Label 270 + Arinc429DiscreteWord discreteWordAthr; + // Label 271 + Arinc429DiscreteWord discreteWordAthrFma; + // Label 273 + Arinc429DiscreteWord discreteWord3; + // Label 274 + Arinc429DiscreteWord discreteWord1; + // Label 275 + Arinc429DiscreteWord discreteWord2; + // Label 276 + Arinc429DiscreteWord discreteWord6; + // Label 277 + Arinc429DiscreteWord discreteWord7; + // Label 317 + Arinc429NumericWord track; + // Label 320 + Arinc429NumericWord heading; + // Label 206 + Arinc429NumericWord cas; + // Label 205 + Arinc429NumericWord mach; + // Label 365 + Arinc429NumericWord vs; + // Label 322 + Arinc429NumericWord fpa; +}; + +struct FmgcBBus { + // Label 164 + Arinc429NumericWord fgRadioHeight; + // Label 310 + Arinc429DiscreteWord deltaPAileronCmd; + // Label 311 + Arinc429DiscreteWord deltaPSpoilerCmd; + // Label 312 + Arinc429DiscreteWord deltaRCmd; + // Label 313 + Arinc429DiscreteWord deltaQCmd; +}; + +struct FacBus { + // Label 47 + Arinc429DiscreteWord discreteWord1; + // Label 70 + Arinc429NumericWord gammaA; + // Label 71 + Arinc429NumericWord gammaT; + // Label 74 + Arinc429NumericWord weight; + // Label 76 + Arinc429NumericWord centerOfGravity; + // Label 77 + Arinc429NumericWord sideslipTarget; + // Label 127 + Arinc429NumericWord facSlatAngle; + // Label 137 + Arinc429NumericWord facFlapAngle; + // Label 146 + Arinc429DiscreteWord discreteWord2; + // Label 167 + Arinc429NumericWord rudderTravelLimitCommand; + // Label 172 + Arinc429NumericWord deltaRYawDamperVoted; + // Label 226 + Arinc429NumericWord estimatedSideslip; + // Label 243 + Arinc429NumericWord vAlphaLim; + // Label 245 + Arinc429NumericWord vLs; + // Label 246 + Arinc429NumericWord vStall; + // Label 247 + Arinc429NumericWord vAlphaProt; + // Label 256 + Arinc429NumericWord vStallWarn; + // Label 262 + Arinc429NumericWord speedTrend; + // Label 263 + Arinc429NumericWord v3; + // Label 264 + Arinc429NumericWord v4; + // Label 265 + Arinc429NumericWord vMan; + // Label 266 + Arinc429NumericWord vMax; + // Label 267 + Arinc429NumericWord vFeNext; + // Label 271 + Arinc429DiscreteWord discreteWord3; + // Label 273 + Arinc429DiscreteWord discreteWord4; + // Label 274 + Arinc429DiscreteWord discreteWord5; + // Label 312 + Arinc429DiscreteWord deltaRRudderTrim; + // Label 313 + Arinc429DiscreteWord rudderTrimPos; +}; + +struct FadecBus { + Arinc429NumericWord thrustLeverAngle; + + Arinc429NumericWord n1Max; + + Arinc429NumericWord n1Limit; + + Arinc429NumericWord n1Idle; + + Arinc429DiscreteWord statusWord2; +}; + +struct FcuBus { + // Label 101 + Arinc429NumericWord selectedHeading; + // Label 102 + Arinc429NumericWord selectedAltitude; + // Label 103 + Arinc429NumericWord selectedAirspeed; + // Label 104 + Arinc429NumericWord selectedVerticalSpeed; + // Label 106 + Arinc429NumericWord selectedMach; + // Label 114 + Arinc429NumericWord selectedTrack; + // Label 115 + Arinc429NumericWord selectedFpa; + // Label 214 + Arinc429NumericWord flexToTemp; + // Label 234 + Arinc429NumericWord baroCorrectionHpaLeft; + // Label 235 + Arinc429NumericWord baroCorrectionInhgLeft; + // Label 236 + Arinc429NumericWord baroCorrectionHpaRight; + // Label 237 + Arinc429NumericWord baroCorrectionInhgRight; + // Label 343 + Arinc429NumericWord n1AthrCommand; + // Label 270 + Arinc429DiscreteWord fcuAtsWord; + // Label 147 + Arinc429DiscreteWord fcuAtsFmaWord; + // Label 271 + Arinc429DiscreteWord fcuEisWord1Left; + // Label 271 + Arinc429DiscreteWord fcuEisWord1Right; + // Label 272 + Arinc429DiscreteWord fcuEisWord2Left; + // Label 272 + Arinc429DiscreteWord fcuEisWord2Right; + // Label 273 + Arinc429DiscreteWord fcuDiscreteWord2; + // Label 274 + Arinc429DiscreteWord fcuDiscreteWord1; +}; + +// These are not the complete Bus contents, there are +// some more labels that I have left out for now +struct AirDataBus { + // Label 203 + Arinc429NumericWord altitudeStandard; + // Label 204 + Arinc429NumericWord altitudeCorrected; + // Label 205 + Arinc429NumericWord mach; + // Label 206 + Arinc429NumericWord airspeedComputed; + // Label 210 + Arinc429NumericWord airspeedTrue; + // Label 212 + Arinc429NumericWord verticalSpeed; + // Label 241 + Arinc429NumericWord aoaCorrected; +}; + +// Same here +struct InertialReferenceBus { + // Label 270 + Arinc429DiscreteWord discreteWord1; + // Label 310 + Arinc429NumericWord latitude; + // Label 311 + Arinc429NumericWord longitude; + // Label 312 + Arinc429NumericWord groundspeed; + // Label 313 + Arinc429NumericWord trackAngleTrue; + // Label 314 + Arinc429NumericWord headingTrue; + // Label 315 + Arinc429NumericWord windspeed; + // Label 316 + Arinc429NumericWord windDirectionTrue; + // Label 317 + Arinc429NumericWord trackAngleMagnetic; + // Label 320 + Arinc429NumericWord headingMagnetic; + // Label 321 + Arinc429NumericWord driftAngle; + // Label 322 + Arinc429NumericWord flightPathAngle; + // Label 323 + Arinc429NumericWord flightPathAcceleration; + // Label 324 + Arinc429NumericWord pitchAngle; + // Label 325 + Arinc429NumericWord rollAngle; + // Label 326 + Arinc429NumericWord bodyPitchRate; + // Label 327 + Arinc429NumericWord bodyRollRate; + // Label 330 + Arinc429NumericWord bodyYawRate; + // Label 331 + Arinc429NumericWord bodyLongAccel; + // Label 332 + Arinc429NumericWord bodyLatAccel; + // Label 333 + Arinc429NumericWord bodyNormalAccel; + // Label 335 + Arinc429NumericWord trackAngleRate; + // Label 336 + Arinc429NumericWord pitchAttRate; + // Label 337 + Arinc429NumericWord rollAttRate; + // Label 361 + Arinc429NumericWord inertialAlt; + // Label 362 + Arinc429NumericWord alongTrackHorizAcc; + // Label 363 + Arinc429NumericWord crossTrackHorizAcc; + // Label 364 + Arinc429NumericWord verticalAccel; + // Label 365 + Arinc429NumericWord inertialVerticalSpeed; + // Label 366 + Arinc429NumericWord northSouthVelocity; + // Label 367 + Arinc429NumericWord eastWestVelocity; +}; + +struct AdirsBusses { + AirDataBus adrBus; + + InertialReferenceBus irsBus; +}; + +struct SfccBus { + // Label 45 + Arinc429DiscreteWord slatFlapComponentStatus; + // Label 46 + Arinc429DiscreteWord slatFlapSystemStatus; + // Label 47 + Arinc429DiscreteWord slatFlapActualPosition; + // Label 127 + Arinc429NumericWord slatActualPosition; + // Label 137 + Arinc429NumericWord flapActualPosition; +}; + +struct LgciuBus { + // Label 20 + Arinc429DiscreteWord discreteWord1; + // Label 21 + Arinc429DiscreteWord discreteWord2; + // Label 22 + Arinc429DiscreteWord discreteWord3; + // Label 23 + Arinc429DiscreteWord discreteWord4; +}; diff --git a/src/fbw/src/elac/Elac.cpp b/src/fbw/src/elac/Elac.cpp new file mode 100644 index 000000000000..d754406e706c --- /dev/null +++ b/src/fbw/src/elac/Elac.cpp @@ -0,0 +1,161 @@ +#include "Elac.h" +#include + +Elac::Elac(bool isUnit1) : isUnit1(isUnit1) { + elacComputer.initialize(); +} + +Elac::Elac(const Elac& obj) : isUnit1(obj.isUnit1) { + elacComputer.initialize(); +} + +void Elac::clearMemory() {} + +// If the power supply is valid, perform the self-test-sequence. +// If at least one hydraulic source is pressurised, perform a short test. +// If no hydraulic supply is pressurised, and the outage was more than 3 seconds (or the switch was turned off), +// perform a long selft-test. +// Else, perform a short self-test. +void Elac::initSelfTests(bool viaPushButton) { + if (powerSupplyFault) + return; + + if (modelInputs.in.discrete_inputs.green_low_pressure && modelInputs.in.discrete_inputs.blue_low_pressure && + modelInputs.in.discrete_inputs.yellow_low_pressure && (powerSupplyOutageTime > 3 || viaPushButton)) { + selfTestTimer = longSelfTestDuration; + } else { + selfTestTimer = shortSelfTestDuration; + } +} + +// Main update cycle. Surface position through parameters here is temporary. +void Elac::update(double deltaTime, double simulationTime, bool faultActive, bool isPowered) { + monitorPowerSupply(deltaTime, isPowered); + monitorButtonStatus(); + + updateSelfTest(deltaTime); + monitorSelf(faultActive); + + elacComputer.setExternalInputs(&modelInputs); + modelInputs.in.sim_data.computer_running = monitoringHealthy; + elacComputer.step(); + modelOutputs = elacComputer.getExternalOutputs().out; +} + +// Perform self monitoring +void Elac::monitorSelf(bool faultActive) { + if (faultActive || powerSupplyFault || !selfTestComplete || !modelInputs.in.discrete_inputs.elac_engaged_from_switch) { + monitoringHealthy = false; + } else { + monitoringHealthy = true; + } +} + +// Monitor the overhead button position. If the button was switched off, and is now on, +// begin self-tests. +void Elac::monitorButtonStatus() { + if (modelInputs.in.discrete_inputs.elac_engaged_from_switch && !prevEngageButtonWasPressed) { + initSelfTests(true); + } + prevEngageButtonWasPressed = modelInputs.in.discrete_inputs.elac_engaged_from_switch; +} + +// Monitor the power supply and record the outage time (used for self test and healthy logic). +// If an outage lasts more than 10ms, stop the program execution. +// If the power has been restored after an outage that lasted longer than 10ms, reset the RAM and +// perform the startup sequence. +void Elac::monitorPowerSupply(double deltaTime, bool isPowered) { + if (!isPowered) { + powerSupplyOutageTime += deltaTime; + } + if (powerSupplyOutageTime > minimumPowerOutageTimeForFailure) { + powerSupplyFault = true; + } + if (isPowered && powerSupplyFault) { + powerSupplyFault = false; + initSelfTests(false); + powerSupplyOutageTime = 0; + } +} + +// Update the Self-test-Sequence +void Elac::updateSelfTest(double deltaTime) { + if (selfTestTimer > 0) { + selfTestTimer -= deltaTime; + } + if (selfTestTimer <= 0) { + selfTestComplete = true; + } else { + selfTestComplete = false; + } +} + +// Write the bus output data and return it. +base_elac_out_bus Elac::getBusOutputs() { + base_elac_out_bus output = {}; + + if (!monitoringHealthy) { + output.left_aileron_position_deg.SSM = Arinc429SignStatus::FailureWarning; + output.right_aileron_position_deg.SSM = Arinc429SignStatus::FailureWarning; + output.left_elevator_position_deg.SSM = Arinc429SignStatus::FailureWarning; + output.right_elevator_position_deg.SSM = Arinc429SignStatus::FailureWarning; + output.ths_position_deg.SSM = Arinc429SignStatus::FailureWarning; + output.left_sidestick_pitch_command_deg.SSM = Arinc429SignStatus::FailureWarning; + output.right_sidestick_pitch_command_deg.SSM = Arinc429SignStatus::FailureWarning; + output.left_sidestick_roll_command_deg.SSM = Arinc429SignStatus::FailureWarning; + output.right_sidestick_roll_command_deg.SSM = Arinc429SignStatus::FailureWarning; + output.rudder_pedal_position_deg.SSM = Arinc429SignStatus::FailureWarning; + output.aileron_command_deg.SSM = Arinc429SignStatus::FailureWarning; + output.roll_spoiler_command_deg.SSM = Arinc429SignStatus::FailureWarning; + output.yaw_damper_command_deg.SSM = Arinc429SignStatus::FailureWarning; + output.discrete_status_word_1.SSM = Arinc429SignStatus::FailureWarning; + output.discrete_status_word_2.SSM = Arinc429SignStatus::FailureWarning; + + return output; + } + + output = modelOutputs.bus_outputs; + + return output; +} + +// Write the discrete output data and return it. +base_elac_discrete_outputs Elac::getDiscreteOutputs() { + base_elac_discrete_outputs output = {}; + + output.digital_output_validated = monitoringHealthy; + if (!monitoringHealthy) { + output.pitch_axis_ok = false; + output.left_aileron_ok = false; + output.right_aileron_ok = false; + output.ap_1_authorised = false; + output.ap_2_authorised = false; + output.left_aileron_active_mode = false; + output.right_aileron_active_mode = false; + output.left_elevator_damping_mode = false; + output.right_elevator_damping_mode = false; + output.ths_active = false; + output.batt_power_supply = false; + } else { + output = modelOutputs.discrete_outputs; + } + + return output; +} + +// Write the analog outputs and return it. +base_elac_analog_outputs Elac::getAnalogOutputs() { + base_elac_analog_outputs output = {}; + + if (!monitoringHealthy) { + output.left_elev_pos_order_deg = 0; + output.right_elev_pos_order_deg = 0; + output.ths_pos_order = 0; + output.left_aileron_pos_order = 0; + output.right_aileron_pos_order = 0; + } else { + output = modelOutputs.analog_outputs; + } + + return output; +} diff --git a/src/fbw/src/elac/Elac.h b/src/fbw/src/elac/Elac.h new file mode 100644 index 000000000000..e8db38344450 --- /dev/null +++ b/src/fbw/src/elac/Elac.h @@ -0,0 +1,66 @@ +#pragma once + +#include "ElacIO.h" + +#include "../Arinc429.h" +#include "../model/ElacComputer.h" +#include "../utils/ConfirmNode.h" +#include "../utils/HysteresisNode.h" +#include "../utils/PulseNode.h" +#include "../utils/SRFlipFlop.h" + +class Elac { + public: + Elac(bool isUnit1); + + Elac(const Elac&); + + void update(double deltaTime, double simulationTime, bool faultActive, bool isPowered); + + base_elac_out_bus getBusOutputs(); + + base_elac_discrete_outputs getDiscreteOutputs(); + + base_elac_analog_outputs getAnalogOutputs(); + + ElacComputer::ExternalInputs_ElacComputer_T modelInputs = {}; + + private: + void initSelfTests(bool viaPushButton); + + void clearMemory(); + + void monitorButtonStatus(); + + void monitorPowerSupply(double deltaTime, bool isPowered); + + void monitorSelf(bool faultActive); + + void updateSelfTest(double deltaTime); + + // Model + ElacComputer elacComputer; + elac_outputs modelOutputs; + + // Computer Self-monitoring vars + bool monitoringHealthy; + + bool prevEngageButtonWasPressed; + + // Power Supply monitoring + double powerSupplyOutageTime; + + bool powerSupplyFault; + + // Selftest vars + double selfTestTimer; + + bool selfTestComplete; + + // Constants + const bool isUnit1; + + const double minimumPowerOutageTimeForFailure = 0.02; + const double shortSelfTestDuration = 1; + const double longSelfTestDuration = 3; +}; diff --git a/src/fbw/src/elac/ElacIO.h b/src/fbw/src/elac/ElacIO.h new file mode 100644 index 000000000000..da46fbcbbf25 --- /dev/null +++ b/src/fbw/src/elac/ElacIO.h @@ -0,0 +1,169 @@ +#pragma once + +#include "../busStructures/busStructures.h" + +struct ElacAnalogInputs { + // ANI 1-1 + double capPitchStickPos; + // ANI 1-2 + double foPitchStickPos; + // ANI 1-3 + double capRollStickPos; + // ANI 1-4 + double foRollStickPos; + // ANI 2-1 + double leftElevatorPos; + // ANI 2-2 + double rightElevatorPos; + // ANI 2-3 + double thsPos; + // ANI 2-4 + double leftAileronPos; + // ANI 2-5 + double rightAileronPos; + // ANI 2-6 + double rudderPedalPos; + // ANI 4-1 + double loadFactorAcc1; + // ANI 4-2 + double loadFactorAcc2; + // ANI 4-3 + double blueHydPressure; + // ANI 4-4 + double greenHydPressure; + // ANI 4-5 + double yellowHydPressure; +}; + +struct ElacDiscreteInputs { + // DSI 3 + bool groundSpoilersActive1; + // DSI 3 + bool groundSpoilersActive2; + // DSI 6 + bool oppPitchAxisFailure; + // DSI 7 + bool ap1Disengaged; + // DSI 10 + bool ap2Disengaged; + // DSI 11 + bool oppLeftAileronLost; + // DSI 14 + bool oppRightAileronLost; + // DSI 15 + bool fac1YawControlLost; + // DSI 16 + bool lgciu1NoseGearPressed; + // DSI 16 + bool lgciu2NoseGearPressed; + // DSI 18 + bool fac2YawControlLost; + // DSI 19 + bool lgciu1RightMainGearPressed; + // DSI 19 + bool lgciu2RightMainGearPressed; + // DSI 21 + bool lgciu1LeftMainGearPressed; + // DSI 21 + bool lgciu2LeftMainGearPressed; + // DSI 24 + bool thsMotorFault; + // DSI 26 + bool sfcc1SlatsOut; + // DSI 26 + bool sfcc2SlatsOut; + // DSI 27 + bool lAilServoFailed; + // DSI 27 + bool lElevServoFailed; + // DSI 28 + bool rAilServoFailed; + // DSI 28 + bool rElevServoFailed; + // DSI 31 + bool thsOverrideActive; + // DSI 32 + bool yellowLowPressure; + // DSI 35 + bool captPriorityTakeoverPressed; + // DSI 36 + bool foPriorityTakeoverPressed; + // DSI 40 + bool blueLowPressure; + // DSI 42 + bool greenLowPressure; + // DSI 50 + bool elacEngagedFromSwitch; + // DSI 53 + bool normalPowersupplyLost; +}; + +struct ElacAnalogOutputs { + // ANO 1 + double leftElevPosOrder; + // ANO 2 + double rightElevPosOrder; + // ANO 3 + double thsPosOrder; + // ANO 4 + double leftAileronPosOrder; + // ANO 5 + double rightAileronPosOrder; +}; + +struct ElacDiscreteOutputs { + // DSO 1 + bool pitchAxisOk; + // DSO 2 + bool leftAileronOk; + // DSO 3 + bool rightAileronOk; + // DSO 6 + bool digitalOperationValidated; + // DSO 7 + bool ap1Authorised; + // DSO 8 + bool ap2Authorised; + + // Relays + // K5-1 + bool leftAileronActiveMode; + // K4-1 + bool rightAileronActiveMode; + + bool leftElevatorDampingMode; + + bool rightElevatorDampingMode; + + bool thsActive; +}; + +struct ElacBusInputs { + AdirsBusses adirs1; + + AdirsBusses adirs2; + + AdirsBusses adirs3; + + FmgcABus fmgc1; + + FmgcABus fmgc2; + + RaBus ra1; + + RaBus ra2; + + SfccBus sfcc1; + + SfccBus sfcc2; + + FcdcBus fcdc1; + + FcdcBus fcdc2; + + SecOutBus sec1; + + SecOutBus sec2; + + ElacOutBus elacOpp; +}; diff --git a/src/fbw/src/fac/Fac.cpp b/src/fbw/src/fac/Fac.cpp new file mode 100644 index 000000000000..21e5ef78b87d --- /dev/null +++ b/src/fbw/src/fac/Fac.cpp @@ -0,0 +1,155 @@ +#include "Fac.h" + +Fac::Fac(bool isUnit1) : isUnit1(isUnit1) { + facComputer.initialize(); +} + +Fac::Fac(const Fac& obj) : isUnit1(obj.isUnit1) { + facComputer.initialize(); +} + +// Erase all data in RAM +void Fac::clearMemory() {} + +// Main update cycle. Surface position through parameters here is temporary. +void Fac::update(double deltaTime, double simulationTime, bool faultActive, bool isPowered) { + monitorPowerSupply(deltaTime, isPowered); + + updateSelfTest(deltaTime); + monitorSelf(faultActive); + + if (!shortPowerFailure) { + facComputer.setExternalInputs(&modelInputs); + facComputer.step(); + modelOutputs = facComputer.getExternalOutputs().out; + } +} + +// Software reset logic. After a reset, start self-test if on ground and engines off, and reset RAM. +void Fac::initSelfTests() { + if (modelInputs.in.discrete_inputs.nose_gear_pressed && modelInputs.in.discrete_inputs.engine_1_stopped && + modelInputs.in.discrete_inputs.engine_2_stopped && powerSupplyOutageTime > 4) { + selfTestTimer = selfTestDuration; + } +} + +// Perform self monitoring. This is implemented as hard-wired circuitry. +// If on ground and a fault or long power failure occurs, reset automatically at power restoration. +// If in flight, a manual reset via the FAC pushbutton has to occure for a reset. +void Fac::monitorSelf(bool faultActive) { + bool softwareHealthy = !faultActive && selfTestComplete; + bool softwareResetCondition = !facHealthy && pushbuttonPulse.update(modelInputs.in.discrete_inputs.fac_engaged_from_switch); + + // If the hardware signal is given to reset, then clear the memory. + if (softwareResetCondition) { + clearMemory(); + } + + facHealthy = facHealthyFlipFlop.update(softwareHealthy && (softwareResetCondition || modelInputs.in.discrete_inputs.nose_gear_pressed), + longPowerFailure || !softwareHealthy); + + modelInputs.in.sim_data.computer_running = facHealthy; +} + +// Monitor the power supply and record the outage time (healthy logic). +// If an outage lasts more than 10ms but less than 200ms, stop the program execution, +// but return to normal operation at power restoration. +// If an outage lasts more than 200ms, stop program execution. In this case, the computer +// memory is lost. +void Fac::monitorPowerSupply(double deltaTime, bool isPowered) { + if (!isPowered) { + powerSupplyOutageTime += deltaTime; + } + shortPowerFailure = powerSupplyOutageTime > shortPowerFailureTime; + longPowerFailure = powerSupplyOutageTime > longPowerFailureTime; + + if (isPowered && powerSupplyOutageTime > 0) { + if (powerSupplyOutageTime > longPowerFailureTime) { + clearMemory(); + } + initSelfTests(); + powerSupplyOutageTime = 0; + } +} + +// Update the Self-test-Sequence +void Fac::updateSelfTest(double deltaTime) { + if (selfTestTimer > 0) { + selfTestTimer -= deltaTime; + selfTestComplete = false; + } else { + selfTestComplete = true; + } +} + +base_fac_bus Fac::getBusOutputs() { + base_fac_bus output = {}; + + if (!facHealthy) { + output.discrete_word_1.SSM = Arinc429SignStatus::FailureWarning; + output.discrete_word_2.SSM = Arinc429SignStatus::FailureWarning; + output.discrete_word_3.SSM = Arinc429SignStatus::FailureWarning; + output.discrete_word_4.SSM = Arinc429SignStatus::FailureWarning; + output.discrete_word_5.SSM = Arinc429SignStatus::FailureWarning; + output.gamma_a_deg.SSM = Arinc429SignStatus::FailureWarning; + output.gamma_t_deg.SSM = Arinc429SignStatus::FailureWarning; + output.total_weight_lbs.SSM = Arinc429SignStatus::FailureWarning; + output.center_of_gravity_pos_percent.SSM = Arinc429SignStatus::FailureWarning; + output.sideslip_target_deg.SSM = Arinc429SignStatus::FailureWarning; + output.fac_slat_angle_deg.SSM = Arinc429SignStatus::FailureWarning; + output.fac_flap_angle.SSM = Arinc429SignStatus::FailureWarning; + output.rudder_travel_limit_command_deg.SSM = Arinc429SignStatus::FailureWarning; + output.delta_r_yaw_damper_deg.SSM = Arinc429SignStatus::FailureWarning; + output.estimated_sideslip_deg.SSM = Arinc429SignStatus::FailureWarning; + output.v_alpha_lim_kn.SSM = Arinc429SignStatus::FailureWarning; + output.v_ls_kn.SSM = Arinc429SignStatus::FailureWarning; + output.v_stall_kn.SSM = Arinc429SignStatus::FailureWarning; + output.v_alpha_prot_kn.SSM = Arinc429SignStatus::FailureWarning; + output.v_stall_warn_kn.SSM = Arinc429SignStatus::FailureWarning; + output.speed_trend_kn.SSM = Arinc429SignStatus::FailureWarning; + output.v_3_kn.SSM = Arinc429SignStatus::FailureWarning; + output.v_4_kn.SSM = Arinc429SignStatus::FailureWarning; + output.v_man_kn.SSM = Arinc429SignStatus::FailureWarning; + output.v_max_kn.SSM = Arinc429SignStatus::FailureWarning; + output.v_fe_next_kn.SSM = Arinc429SignStatus::FailureWarning; + output.delta_r_rudder_trim_deg.SSM = Arinc429SignStatus::FailureWarning; + output.rudder_trim_pos_deg.SSM = Arinc429SignStatus::FailureWarning; + + return output; + } + + output = modelOutputs.bus_outputs; + + return output; +} + +base_fac_discrete_outputs Fac::getDiscreteOutputs() { + base_fac_discrete_outputs output = {}; + + output.fac_healthy = facHealthy; + + if (!facHealthy) { + output.yaw_damper_engaged = false; + output.rudder_trim_engaged = false; + output.rudder_travel_lim_engaged = false; + output.yaw_damper_avail_for_norm_law = false; + } else { + output = modelOutputs.discrete_outputs; + } + + return output; +} + +base_fac_analog_outputs Fac::getAnalogOutputs() { + base_fac_analog_outputs output = {}; + + if (!facHealthy) { + output.yaw_damper_order_deg = 0; + output.rudder_trim_order_deg = 0; + output.rudder_travel_limit_order_deg = 0; + } else { + output = modelOutputs.analog_outputs; + } + + return output; +} diff --git a/src/fbw/src/fac/Fac.h b/src/fbw/src/fac/Fac.h new file mode 100644 index 000000000000..d2f701014029 --- /dev/null +++ b/src/fbw/src/fac/Fac.h @@ -0,0 +1,65 @@ +#pragma once + +#include "../Arinc429.h" +#include "../model/FacComputer.h" +#include "../utils/PulseNode.h" +#include "../utils/SRFlipFlop.h" +#include "FacIO.h" + +class Fac { + public: + Fac(bool isUnit1); + + Fac(const Fac&); + + void update(double deltaTime, double simulationTime, bool faultActive, bool isPowered); + + base_fac_bus getBusOutputs(); + + base_fac_discrete_outputs getDiscreteOutputs(); + + base_fac_analog_outputs getAnalogOutputs(); + + FacComputer::ExternalInputs_FacComputer_T modelInputs = {}; + + private: + void initSelfTests(); + + void clearMemory(); + + void monitorPowerSupply(double deltaTime, bool isPowered); + + void monitorSelf(bool faultActive); + + void updateSelfTest(double deltaTime); + + // Model + FacComputer facComputer; + fac_outputs modelOutputs; + + // Computer Self-monitoring vars + bool facHealthy; + + SRFlipFlop facHealthyFlipFlop = SRFlipFlop(false); + + PulseNode pushbuttonPulse = PulseNode(true); + + // Power Supply monitoring + double powerSupplyOutageTime; + + bool longPowerFailure; + + bool shortPowerFailure; + + // Selftest vars + double selfTestTimer; + + bool selfTestComplete; + + // Constants + const bool isUnit1; + + const double longPowerFailureTime = 0.2; + const double shortPowerFailureTime = 0.01; + const double selfTestDuration = 10; +}; diff --git a/src/fbw/src/fac/FacIO.h b/src/fbw/src/fac/FacIO.h new file mode 100644 index 000000000000..bb0ff87fe793 --- /dev/null +++ b/src/fbw/src/fac/FacIO.h @@ -0,0 +1,95 @@ +#pragma once + +#include "../busStructures/busStructures.h" + +struct FacAnalogInputs { + double yawDamperPosition; + + double rudderTrimPosition; + + double rudderTravelLimPosition; +}; + +struct FacDiscreteInputs { + bool apOwnEngaged; + + bool apOppEngaged; + + bool yawDamperOppEngaged; + + bool rudderTrimOppEngaged; + + bool rudderTravelLimOppEngaged; + + bool elac1Healthy; + + bool elac2Healthy; + + bool engine1Stopped; + + bool engine2Stopped; + + bool rudderTrimSwitchLeft; + + bool rudderTrimSwitchRight; + + bool rudderTrimButtonReset; + + bool facEngagedFromSwitch; + + bool rudderTrimActuatorHealthy; + + bool rudderTravelLimActuatorHealthy; + + bool slatsExtended; + + bool noseGearPressed; + + bool ir3Switch; + + bool adr3Switch; + + bool yawDamperHasHydPress; +}; + +struct FacAnalogOutputs { + double yawDamperOrder; + + double rudderTrimOrder; + + double rudderTravelLimOrder; +}; + +struct FacDiscreteOutputs { + bool facHealthy; + + bool yawDamperEngaged; + + bool rudderTrimEngaged; + + bool rudderTravelLimEngaged; + + bool yawDamperNormalLawAvail; +}; + +struct FacBusInputs { + FacBus facOpp; + + FmgcABus fmgcOwn; + + FmgcABus fmgcOpp; + + AdirsBusses adirsOwn; + + AdirsBusses adirsOpp; + + AdirsBusses adirs3; + + base_elac_out_bus elac1; + + base_elac_out_bus elac2; + + SfccBus sfccOwn; + + LgciuBus lgciuOwn; +}; diff --git a/src/fbw/src/failures/FailureList.h b/src/fbw/src/failures/FailureList.h new file mode 100644 index 000000000000..5bf13cd015b1 --- /dev/null +++ b/src/fbw/src/failures/FailureList.h @@ -0,0 +1,13 @@ +#pragma once + +enum class Failures { + Fac1 = 22000, + Fac2 = 22001, + Elac1 = 27000, + Elac2 = 27001, + Sec1 = 27002, + Sec2 = 27003, + Sec3 = 27004, + Fcdc1 = 27005, + Fcdc2 = 27006, +}; diff --git a/src/fbw/src/failures/FailuresConsumer.cpp b/src/fbw/src/failures/FailuresConsumer.cpp new file mode 100644 index 000000000000..58bc4a1c0729 --- /dev/null +++ b/src/fbw/src/failures/FailuresConsumer.cpp @@ -0,0 +1,52 @@ +#include "FailuresConsumer.h" +#include + +FailuresConsumer::FailuresConsumer() { + activeFailures.emplace(std::make_pair(Failures::Elac1, false)); + activeFailures.emplace(std::make_pair(Failures::Elac2, false)); + activeFailures.emplace(std::make_pair(Failures::Sec1, false)); + activeFailures.emplace(std::make_pair(Failures::Sec2, false)); + activeFailures.emplace(std::make_pair(Failures::Sec3, false)); + activeFailures.emplace(std::make_pair(Failures::Fac1, false)); + activeFailures.emplace(std::make_pair(Failures::Fac2, false)); + activeFailures.emplace(std::make_pair(Failures::Fcdc1, false)); + activeFailures.emplace(std::make_pair(Failures::Fcdc2, false)); +} + +void FailuresConsumer::initialize() { + activateLvar = std::make_unique("A32NX_FAILURE_ACTIVATE"); + deactivateLvar = std::make_unique("A32NX_FAILURE_DEACTIVATE"); +} + +void FailuresConsumer::update() { + updateActivate(); + updateDeactivate(); +} + +bool FailuresConsumer::isActive(Failures failure) { + return (*activeFailures.find(failure)).second; +} + +void FailuresConsumer::updateActivate() { + double index = activateLvar->get(); + if (setIfFound(index, true)) { + activateLvar->set(0); + } +} + +void FailuresConsumer::updateDeactivate() { + double index = deactivateLvar->get(); + if (setIfFound(index, false)) { + deactivateLvar->set(0); + } +} + +bool FailuresConsumer::setIfFound(double identifier, bool value) { + auto pair = activeFailures.find(static_cast(identifier)); + if (pair == activeFailures.end()) { + return false; + } else { + (*pair).second = value; + return true; + } +} diff --git a/src/fbw/src/failures/FailuresConsumer.h b/src/fbw/src/failures/FailuresConsumer.h new file mode 100644 index 000000000000..d09000cd581c --- /dev/null +++ b/src/fbw/src/failures/FailuresConsumer.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include "../LocalVariable.h" +#include "FailureList.h" + +class FailuresConsumer { + public: + FailuresConsumer(); + + void update(); + + bool isActive(Failures failure); + + void initialize(); + + private: + std::map activeFailures; + + void updateActivate(); + + void updateDeactivate(); + + bool setIfFound(double identifier, bool value); + + std::unique_ptr activateLvar; + + std::unique_ptr deactivateLvar; +}; diff --git a/src/fbw/src/fcdc/Fcdc.cpp b/src/fbw/src/fcdc/Fcdc.cpp new file mode 100644 index 000000000000..b657865eff7b --- /dev/null +++ b/src/fbw/src/fcdc/Fcdc.cpp @@ -0,0 +1,797 @@ +#include "Fcdc.h" +#include +#include "../Arinc429Utils.h" + +using namespace Arinc429Utils; + +Fcdc::Fcdc(bool isUnit1) : isUnit1(isUnit1) {} + +// Perform the startup sequence, i.e.: Clear the memory, and initialize the self-test sequence. +// If the power supply outage was lower than 3 seconds, or the aircraft is in the air or on ground an moving, +// perform a short self-test. +// Else, perform a long self-test. +void Fcdc::startup() { + if (powerSupplyOutageTime <= 3.0 || (!discreteInputs.noseGearPressed && !discreteInputs.eng1NotOnGroundAndNotLowOilPress && + !discreteInputs.eng2NotOnGroundAndNotLowOilPress)) { + selfTestTimer = 0.5; + } else { + selfTestTimer = 3; + } + powerSupplyOutageTime = 0.0; +} + +// Main update cycle +void Fcdc::update(double deltaTime, bool faultActive, bool isPowered) { + monitorPowerSupply(deltaTime, isPowered); + + updateSelfTest(deltaTime); + monitorSelf(faultActive); + + if (monitoringHealthy) { + computeComputerEngagements(); + computeActiveSystemLaws(); + consolidatePositionData(); + computeSidestickPriorityLights(deltaTime); + } +} + +// Perform self monitoring +void Fcdc::monitorSelf(bool faultActive) { + if (faultActive || powerSupplyFault || !selfTestComplete) { + monitoringHealthy = false; + } else { + monitoringHealthy = true; + } +} + +// Monitor the power supply and record the outage time (used for self test and healthy logic). +// If an outage lasts more than 10ms, stop the program execution. +// If the power has been restored after an outage that lasted longer than 10ms, reset the RAM and +// perform the startup sequence. +void Fcdc::monitorPowerSupply(double deltaTime, bool isPowered) { + if (!isPowered) { + powerSupplyOutageTime += deltaTime; + } + if (powerSupplyOutageTime > minimumPowerOutageTimeForFailure) { + powerSupplyFault = true; + } + if (isPowered && powerSupplyFault) { + powerSupplyFault = false; + startup(); + } +} + +// Update the Self-test-Sequence +void Fcdc::updateSelfTest(double deltaTime) { + if (selfTestTimer > 0) { + selfTestTimer -= deltaTime; + } + if (selfTestTimer <= 0) { + selfTestComplete = true; + } else { + selfTestComplete = false; + } +} + +LateralLaw Fcdc::getLateralLawStatusFromBits(bool bit1, bool bit2, bool bit3) { + if (bit1) { + return LateralLaw::NormalLaw; + } else if (bit2) { + return LateralLaw::DirectLaw; + } else { + return LateralLaw::None; + } +} + +PitchLaw Fcdc::getPitchLawStatusFromBits(bool bit1, bool bit2, bool bit3) { + if (bit1 && !bit2 && !bit3) { + return PitchLaw::NormalLaw; + } else if (!bit1 && bit2 && !bit3) { + return PitchLaw::AlternateLaw1; + } else if (bit1 && bit2 && !bit3) { + return PitchLaw::AlternateLaw2; + } else if (!bit1 && !bit2 && bit3) { + return PitchLaw::DirectLaw; + } else { + return PitchLaw::None; + } +} + +void Fcdc::computeComputerEngagements() { + elac1EngagedInRoll = bitFromValueOr(busInputs.elac1.discrete_status_word_1, 20, false); + elac2EngagedInRoll = bitFromValueOr(busInputs.elac2.discrete_status_word_1, 20, false); + sec1EngagedInRoll = bitFromValueOr(busInputs.sec1.discrete_status_word_1, 22, false); + sec2EngagedInRoll = bitFromValueOr(busInputs.sec2.discrete_status_word_1, 22, false); + sec3EngagedInRoll = bitFromValueOr(busInputs.sec3.discrete_status_word_1, 22, false); + + elac1EngagedInPitch = bitFromValueOr(busInputs.elac1.discrete_status_word_1, 19, false); + elac2EngagedInPitch = bitFromValueOr(busInputs.elac2.discrete_status_word_1, 19, false); + sec1EngagedInPitch = bitFromValueOr(busInputs.sec1.discrete_status_word_1, 23, false); + sec2EngagedInPitch = bitFromValueOr(busInputs.sec2.discrete_status_word_1, 23, false); +} + +void Fcdc::consolidatePositionData() { + // Compute Aileron Data. Look at each side individually: if the data from the ELAC that is + // engaged in roll is valid, use that data. If not, take the data from the other ELAC. + // If neither are valid, set the respective aileron position as invalid. + leftAileronPosValid = true; + if (elac1EngagedInRoll && isNo(busInputs.elac1.left_aileron_position_deg)) { + leftAileronPos = busInputs.elac1.left_aileron_position_deg.Data; + } else if (elac2EngagedInRoll && isNo(busInputs.elac2.left_aileron_position_deg)) { + leftAileronPos = busInputs.elac2.left_aileron_position_deg.Data; + } else if (isNo(busInputs.elac1.left_aileron_position_deg)) { + leftAileronPos = busInputs.elac1.left_aileron_position_deg.Data; + } else if (isNo(busInputs.elac2.left_aileron_position_deg)) { + leftAileronPos = busInputs.elac2.left_aileron_position_deg.Data; + } else { + leftAileronPos = 0; + leftAileronPosValid = false; + } + + rightAileronPosValid = true; + if (elac1EngagedInRoll && isNo(busInputs.elac1.right_aileron_position_deg)) { + rightAileronPos = busInputs.elac1.right_aileron_position_deg.Data; + } else if (elac2EngagedInRoll && isNo(busInputs.elac2.right_aileron_position_deg)) { + rightAileronPos = busInputs.elac2.right_aileron_position_deg.Data; + } else if (isNo(busInputs.elac1.right_aileron_position_deg)) { + rightAileronPos = busInputs.elac1.right_aileron_position_deg.Data; + } else if (isNo(busInputs.elac2.right_aileron_position_deg)) { + rightAileronPos = busInputs.elac2.right_aileron_position_deg.Data; + } else { + rightAileronPos = 0; + rightAileronPosValid = false; + } + + // Compute the sidestick positions in roll. Always take the sidestick data + // from the ELAC that is engaged in roll axis. If no ELAC is engaged in roll, + // then all the SECs are engaged seperately in the roll axis. In that case, simply + // choose the first one that has valid stick positions. + // If no computer is engaged in roll, choose the first computer that outputs valid stick positions. + // If no computer output valid stick positions, set them as invalid. + if (elac1EngagedInRoll) { + rollSidestickPosCapt = busInputs.elac1.left_sidestick_roll_command_deg.Data; + rollSidestickPosCaptValid = isNo(busInputs.elac1.left_sidestick_roll_command_deg); + rollSidestickPosFo = busInputs.elac1.right_sidestick_roll_command_deg.Data; + rollSidestickPosFoValid = isNo(busInputs.elac1.right_sidestick_roll_command_deg); + } else if (elac2EngagedInRoll) { + rollSidestickPosCapt = busInputs.elac2.left_sidestick_roll_command_deg.Data; + rollSidestickPosCaptValid = isNo(busInputs.elac2.left_sidestick_roll_command_deg); + rollSidestickPosFo = busInputs.elac2.right_sidestick_roll_command_deg.Data; + rollSidestickPosFoValid = isNo(busInputs.elac2.right_sidestick_roll_command_deg); + } else if (sec1EngagedInRoll && + (isNo(busInputs.sec1.left_sidestick_roll_command_deg) || isNo(busInputs.sec1.right_sidestick_roll_command_deg))) { + rollSidestickPosCapt = busInputs.sec1.left_sidestick_roll_command_deg.Data; + rollSidestickPosCaptValid = isNo(busInputs.sec1.left_sidestick_roll_command_deg); + rollSidestickPosFo = busInputs.sec1.right_sidestick_roll_command_deg.Data; + rollSidestickPosFoValid = isNo(busInputs.sec1.right_sidestick_roll_command_deg); + } else if (sec2EngagedInRoll && + (isNo(busInputs.sec2.left_sidestick_roll_command_deg) || isNo(busInputs.sec2.right_sidestick_roll_command_deg))) { + rollSidestickPosCapt = busInputs.sec2.left_sidestick_roll_command_deg.Data; + rollSidestickPosCaptValid = isNo(busInputs.sec2.left_sidestick_roll_command_deg); + rollSidestickPosFo = busInputs.sec2.right_sidestick_roll_command_deg.Data; + rollSidestickPosFoValid = isNo(busInputs.sec2.right_sidestick_roll_command_deg); + } else if (sec3EngagedInRoll && + (isNo(busInputs.sec3.left_sidestick_roll_command_deg) || isNo(busInputs.sec3.right_sidestick_roll_command_deg))) { + rollSidestickPosCapt = busInputs.sec3.left_sidestick_roll_command_deg.Data; + rollSidestickPosCaptValid = isNo(busInputs.sec3.left_sidestick_roll_command_deg); + rollSidestickPosFo = busInputs.sec3.right_sidestick_roll_command_deg.Data; + rollSidestickPosFoValid = isNo(busInputs.sec3.right_sidestick_roll_command_deg); + } else if (isNo(busInputs.elac1.left_sidestick_roll_command_deg) || isNo(busInputs.elac1.right_sidestick_roll_command_deg)) { + rollSidestickPosCapt = busInputs.elac1.left_sidestick_roll_command_deg.Data; + rollSidestickPosCaptValid = isNo(busInputs.elac1.left_sidestick_roll_command_deg); + rollSidestickPosFo = busInputs.elac1.right_sidestick_roll_command_deg.Data; + rollSidestickPosFoValid = isNo(busInputs.elac1.right_sidestick_roll_command_deg); + } else if (isNo(busInputs.elac2.left_sidestick_roll_command_deg) || isNo(busInputs.elac2.right_sidestick_roll_command_deg)) { + rollSidestickPosCapt = busInputs.elac2.left_sidestick_roll_command_deg.Data; + rollSidestickPosCaptValid = isNo(busInputs.elac2.left_sidestick_roll_command_deg); + rollSidestickPosFo = busInputs.elac2.right_sidestick_roll_command_deg.Data; + rollSidestickPosFoValid = isNo(busInputs.elac2.right_sidestick_roll_command_deg); + } else if (isNo(busInputs.sec1.left_sidestick_roll_command_deg) || isNo(busInputs.sec1.right_sidestick_roll_command_deg)) { + rollSidestickPosCapt = busInputs.sec1.left_sidestick_roll_command_deg.Data; + rollSidestickPosCaptValid = isNo(busInputs.sec1.left_sidestick_roll_command_deg); + rollSidestickPosFo = busInputs.sec1.right_sidestick_roll_command_deg.Data; + rollSidestickPosFoValid = isNo(busInputs.sec1.right_sidestick_roll_command_deg); + } else if (isNo(busInputs.sec2.left_sidestick_roll_command_deg) || isNo(busInputs.sec2.right_sidestick_roll_command_deg)) { + rollSidestickPosCapt = busInputs.sec2.left_sidestick_roll_command_deg.Data; + rollSidestickPosCaptValid = isNo(busInputs.sec2.left_sidestick_roll_command_deg); + rollSidestickPosFo = busInputs.sec2.right_sidestick_roll_command_deg.Data; + rollSidestickPosFoValid = isNo(busInputs.sec2.right_sidestick_roll_command_deg); + } else if (isNo(busInputs.sec3.left_sidestick_roll_command_deg) || isNo(busInputs.sec3.right_sidestick_roll_command_deg)) { + rollSidestickPosCapt = busInputs.sec3.left_sidestick_roll_command_deg.Data; + rollSidestickPosCaptValid = isNo(busInputs.sec3.left_sidestick_roll_command_deg); + rollSidestickPosFo = busInputs.sec3.right_sidestick_roll_command_deg.Data; + rollSidestickPosFoValid = isNo(busInputs.sec3.right_sidestick_roll_command_deg); + } else { + rollSidestickPosCapt = 0; + rollSidestickPosCaptValid = false; + rollSidestickPosFo = 0; + rollSidestickPosFoValid = false; + } + + // Compute the rudder pedal position. First check if an ELAC is engaged in roll. + // If one is, take that data. If none is engaged in roll, take the first valid data. + // If none is valid, set data as invalid. + if (elac1EngagedInRoll) { + rudderPedalPos = busInputs.elac1.rudder_pedal_position_deg.Data; + rudderPedalPosValid = isNo(busInputs.elac1.rudder_pedal_position_deg); + } else if (elac2EngagedInRoll) { + rudderPedalPos = busInputs.elac2.rudder_pedal_position_deg.Data; + rudderPedalPosValid = isNo(busInputs.elac2.rudder_pedal_position_deg); + } else if (isNo(busInputs.elac1.rudder_pedal_position_deg)) { + rudderPedalPos = busInputs.elac1.rudder_pedal_position_deg.Data; + rudderPedalPosValid = isNo(busInputs.elac1.rudder_pedal_position_deg); + } else if (isNo(busInputs.elac2.rudder_pedal_position_deg)) { + rudderPedalPos = busInputs.elac2.rudder_pedal_position_deg.Data; + rudderPedalPosValid = isNo(busInputs.elac2.rudder_pedal_position_deg); + } else { + rudderPedalPos = 0; + rudderPedalPosValid = false; + } + + // Compute Elevator/THS data. Look at each surface individually: if the data from the computer that is + // engaged in roll is valid, use that data. If not, take the data from the other computers. + // If neither are valid, set the respective position as invalid. + leftElevatorPosValid = true; + if (elac2EngagedInPitch && isNo(busInputs.elac2.left_elevator_position_deg)) { + leftElevatorPos = busInputs.elac2.left_elevator_position_deg.Data; + } else if (elac1EngagedInPitch && isNo(busInputs.elac1.left_elevator_position_deg)) { + leftElevatorPos = busInputs.elac1.left_elevator_position_deg.Data; + } else if (sec2EngagedInPitch && isNo(busInputs.sec2.left_elevator_position_deg)) { + leftElevatorPos = busInputs.sec2.left_elevator_position_deg.Data; + } else if (sec1EngagedInPitch && isNo(busInputs.sec1.left_elevator_position_deg)) { + leftElevatorPos = busInputs.sec1.left_elevator_position_deg.Data; + } else if (isNo(busInputs.elac2.left_elevator_position_deg)) { + leftElevatorPos = busInputs.elac2.left_elevator_position_deg.Data; + } else if (isNo(busInputs.elac1.left_elevator_position_deg)) { + leftElevatorPos = busInputs.elac1.left_elevator_position_deg.Data; + } else if (isNo(busInputs.sec2.left_elevator_position_deg)) { + leftElevatorPos = busInputs.sec2.left_elevator_position_deg.Data; + } else if (isNo(busInputs.sec1.left_elevator_position_deg)) { + leftElevatorPos = busInputs.sec1.left_elevator_position_deg.Data; + } else { + leftElevatorPos = 0; + leftElevatorPosValid = false; + } + + rightElevatorPosValid = true; + if (elac2EngagedInPitch && isNo(busInputs.elac2.right_elevator_position_deg)) { + rightElevatorPos = busInputs.elac2.right_elevator_position_deg.Data; + } else if (elac1EngagedInPitch && isNo(busInputs.elac1.right_elevator_position_deg)) { + rightElevatorPos = busInputs.elac1.right_elevator_position_deg.Data; + } else if (sec2EngagedInPitch && isNo(busInputs.sec2.right_elevator_position_deg)) { + rightElevatorPos = busInputs.sec2.right_elevator_position_deg.Data; + } else if (sec1EngagedInPitch && isNo(busInputs.sec1.right_elevator_position_deg)) { + rightElevatorPos = busInputs.sec1.right_elevator_position_deg.Data; + } else if (isNo(busInputs.elac2.right_elevator_position_deg)) { + rightElevatorPos = busInputs.elac2.right_elevator_position_deg.Data; + } else if (isNo(busInputs.elac1.right_elevator_position_deg)) { + rightElevatorPos = busInputs.elac1.right_elevator_position_deg.Data; + } else if (isNo(busInputs.sec2.right_elevator_position_deg)) { + rightElevatorPos = busInputs.sec2.right_elevator_position_deg.Data; + } else if (isNo(busInputs.sec1.right_elevator_position_deg)) { + rightElevatorPos = busInputs.sec1.right_elevator_position_deg.Data; + } else { + rightElevatorPos = 0; + rightElevatorPosValid = false; + } + + thsPosValid = true; + if (elac2EngagedInPitch && isNo(busInputs.elac2.ths_position_deg)) { + thsPos = busInputs.elac2.ths_position_deg.Data; + } else if (elac1EngagedInPitch && isNo(busInputs.elac1.ths_position_deg)) { + thsPos = busInputs.elac1.ths_position_deg.Data; + } else if (sec2EngagedInPitch && isNo(busInputs.sec2.ths_position_deg)) { + thsPos = busInputs.sec2.ths_position_deg.Data; + } else if (sec1EngagedInPitch && isNo(busInputs.sec1.ths_position_deg)) { + thsPos = busInputs.sec1.ths_position_deg.Data; + } else if (isNo(busInputs.elac2.ths_position_deg)) { + thsPos = busInputs.elac2.ths_position_deg.Data; + } else if (isNo(busInputs.elac1.ths_position_deg)) { + thsPos = busInputs.elac1.ths_position_deg.Data; + } else if (isNo(busInputs.sec2.ths_position_deg)) { + thsPos = busInputs.sec2.ths_position_deg.Data; + } else if (isNo(busInputs.sec1.ths_position_deg)) { + thsPos = busInputs.sec1.ths_position_deg.Data; + } else { + thsPos = 0; + thsPosValid = false; + } + + // Compute the sidestick positions in pitch. Always take the sidestick data + // from the computer that is engaged in pitch axis. + // If none is engaged in pitch, take the first valid data. + // If none is valid, set data as invalid. + if (elac2EngagedInPitch) { + pitchSidestickPosCapt = busInputs.elac2.left_sidestick_pitch_command_deg.Data; + pitchSidestickPosCaptValid = isNo(busInputs.elac2.left_sidestick_pitch_command_deg); + pitchSidestickPosFo = busInputs.elac2.right_sidestick_pitch_command_deg.Data; + pitchSidestickPosFoValid = isNo(busInputs.elac2.right_sidestick_pitch_command_deg); + } else if (elac1EngagedInPitch) { + pitchSidestickPosCapt = busInputs.elac1.left_sidestick_pitch_command_deg.Data; + pitchSidestickPosCaptValid = isNo(busInputs.elac1.left_sidestick_pitch_command_deg); + pitchSidestickPosFo = busInputs.elac1.right_sidestick_pitch_command_deg.Data; + pitchSidestickPosFoValid = isNo(busInputs.elac1.right_sidestick_pitch_command_deg); + } else if (sec2EngagedInPitch) { + pitchSidestickPosCapt = busInputs.sec2.left_sidestick_pitch_command_deg.Data; + pitchSidestickPosCaptValid = isNo(busInputs.sec2.left_sidestick_pitch_command_deg); + pitchSidestickPosFo = busInputs.sec2.right_sidestick_pitch_command_deg.Data; + pitchSidestickPosFoValid = isNo(busInputs.sec2.right_sidestick_pitch_command_deg); + } else if (sec1EngagedInPitch) { + pitchSidestickPosCapt = busInputs.sec1.left_sidestick_pitch_command_deg.Data; + pitchSidestickPosCaptValid = isNo(busInputs.sec1.left_sidestick_pitch_command_deg); + pitchSidestickPosFo = busInputs.sec1.right_sidestick_pitch_command_deg.Data; + pitchSidestickPosFoValid = isNo(busInputs.sec1.right_sidestick_pitch_command_deg); + } else if (isNo(busInputs.elac2.left_sidestick_pitch_command_deg) || isNo(busInputs.elac2.right_sidestick_pitch_command_deg)) { + pitchSidestickPosCapt = busInputs.elac2.left_sidestick_pitch_command_deg.Data; + pitchSidestickPosCaptValid = isNo(busInputs.elac2.left_sidestick_pitch_command_deg); + pitchSidestickPosFo = busInputs.elac2.right_sidestick_pitch_command_deg.Data; + pitchSidestickPosFoValid = isNo(busInputs.elac2.right_sidestick_pitch_command_deg); + } else if (isNo(busInputs.elac1.left_sidestick_pitch_command_deg) || isNo(busInputs.elac1.right_sidestick_pitch_command_deg)) { + pitchSidestickPosCapt = busInputs.elac1.left_sidestick_pitch_command_deg.Data; + pitchSidestickPosCaptValid = isNo(busInputs.elac1.left_sidestick_pitch_command_deg); + pitchSidestickPosFo = busInputs.elac1.right_sidestick_pitch_command_deg.Data; + pitchSidestickPosFoValid = isNo(busInputs.elac1.right_sidestick_pitch_command_deg); + } else if (isNo(busInputs.sec2.left_sidestick_pitch_command_deg) || isNo(busInputs.sec2.right_sidestick_pitch_command_deg)) { + pitchSidestickPosCapt = busInputs.sec2.left_sidestick_pitch_command_deg.Data; + pitchSidestickPosCaptValid = isNo(busInputs.sec2.left_sidestick_pitch_command_deg); + pitchSidestickPosFo = busInputs.sec2.right_sidestick_pitch_command_deg.Data; + pitchSidestickPosFoValid = isNo(busInputs.sec2.right_sidestick_pitch_command_deg); + } else if (isNo(busInputs.sec1.left_sidestick_pitch_command_deg) || isNo(busInputs.sec1.right_sidestick_pitch_command_deg)) { + pitchSidestickPosCapt = busInputs.sec1.left_sidestick_pitch_command_deg.Data; + pitchSidestickPosCaptValid = isNo(busInputs.sec1.left_sidestick_pitch_command_deg); + pitchSidestickPosFo = busInputs.sec1.right_sidestick_pitch_command_deg.Data; + pitchSidestickPosFoValid = isNo(busInputs.sec1.right_sidestick_pitch_command_deg); + } else { + pitchSidestickPosCapt = 0; + pitchSidestickPosCaptValid = false; + pitchSidestickPosFo = 0; + pitchSidestickPosFoValid = false; + } +} + +// Consolidate all the active law data from the different computers, and then compute +// the overall active system law from the law status of the computers that are engaged in the +// respective axes. +void Fcdc::computeActiveSystemLaws() { + if (elac1EngagedInRoll) { + systemLateralLaw = getLateralLawStatusFromBits(bitFromValue(busInputs.elac1.discrete_status_word_1, 26), + bitFromValue(busInputs.elac1.discrete_status_word_1, 27), + bitFromValue(busInputs.elac1.discrete_status_word_1, 28)); + } else if (elac2EngagedInRoll) { + systemLateralLaw = getLateralLawStatusFromBits(bitFromValue(busInputs.elac2.discrete_status_word_1, 26), + bitFromValue(busInputs.elac2.discrete_status_word_1, 27), + bitFromValue(busInputs.elac2.discrete_status_word_1, 28)); + } else if (sec1EngagedInRoll || sec2EngagedInRoll || sec3EngagedInRoll) { + systemLateralLaw = LateralLaw::DirectLaw; + } else { + systemLateralLaw = LateralLaw::None; + } + + if (elac1EngagedInPitch) { + systemPitchLaw = getPitchLawStatusFromBits(bitFromValue(busInputs.elac1.discrete_status_word_1, 23), + bitFromValue(busInputs.elac1.discrete_status_word_1, 24), + bitFromValue(busInputs.elac1.discrete_status_word_1, 25)); + } else if (elac2EngagedInPitch) { + systemPitchLaw = getPitchLawStatusFromBits(bitFromValue(busInputs.elac2.discrete_status_word_1, 23), + bitFromValue(busInputs.elac2.discrete_status_word_1, 24), + bitFromValue(busInputs.elac2.discrete_status_word_1, 25)); + } else if (sec1EngagedInPitch) { + systemPitchLaw = getPitchLawStatusFromBits(bitFromValue(busInputs.sec1.discrete_status_word_1, 19), + bitFromValue(busInputs.sec1.discrete_status_word_1, 20), + bitFromValue(busInputs.sec1.discrete_status_word_1, 21)); + } else if (sec2EngagedInPitch) { + systemPitchLaw = getPitchLawStatusFromBits(bitFromValue(busInputs.sec2.discrete_status_word_1, 19), + bitFromValue(busInputs.sec2.discrete_status_word_1, 20), + bitFromValue(busInputs.sec2.discrete_status_word_1, 21)); + } else { + systemPitchLaw = PitchLaw::None; + } +} + +void Fcdc::computeSidestickPriorityLights(double deltaTime) { + bool leftSidestickDisabledRoll; + bool rightSidestickDisabledRoll; + bool leftSidestickDisabledPitch; + bool rightSidestickDisabledPitch; + bool leftSidestickPriorityLockedRoll; + bool rightSidestickPriorityLockedRoll; + bool leftSidestickPriorityLockedPitch; + bool rightSidestickPriorityLockedPitch; + + // Compute if a sidestick has lost priority (per computer). Use the computer that is engaged in the respective axis. + if (elac1EngagedInRoll) { + leftSidestickDisabledRoll = bitFromValue(busInputs.elac1.discrete_status_word_2, 17); + rightSidestickDisabledRoll = bitFromValue(busInputs.elac1.discrete_status_word_2, 18); + leftSidestickPriorityLockedRoll = bitFromValue(busInputs.elac1.discrete_status_word_2, 19); + rightSidestickPriorityLockedRoll = bitFromValue(busInputs.elac1.discrete_status_word_2, 20); + } else if (elac2EngagedInRoll) { + leftSidestickDisabledRoll = bitFromValue(busInputs.elac2.discrete_status_word_2, 17); + rightSidestickDisabledRoll = bitFromValue(busInputs.elac2.discrete_status_word_2, 18); + leftSidestickPriorityLockedRoll = bitFromValue(busInputs.elac2.discrete_status_word_2, 19); + rightSidestickPriorityLockedRoll = bitFromValue(busInputs.elac2.discrete_status_word_2, 20); + } else if (sec1EngagedInRoll || sec2EngagedInRoll || sec3EngagedInRoll) { + leftSidestickDisabledRoll = bitFromValue(busInputs.sec1.discrete_status_word_2, 13) || + bitFromValue(busInputs.sec2.discrete_status_word_2, 13) || + bitFromValue(busInputs.sec3.discrete_status_word_2, 13); + rightSidestickDisabledRoll = bitFromValue(busInputs.sec1.discrete_status_word_2, 14) || + bitFromValue(busInputs.sec2.discrete_status_word_2, 14) || + bitFromValue(busInputs.sec3.discrete_status_word_2, 14); + leftSidestickPriorityLockedRoll = bitFromValue(busInputs.sec1.discrete_status_word_2, 15) || + bitFromValue(busInputs.sec2.discrete_status_word_2, 15) || + bitFromValue(busInputs.sec3.discrete_status_word_2, 15); + rightSidestickPriorityLockedRoll = bitFromValue(busInputs.sec1.discrete_status_word_2, 16) || + bitFromValue(busInputs.sec2.discrete_status_word_2, 16) || + bitFromValue(busInputs.sec3.discrete_status_word_2, 16); + } + + if (elac2EngagedInPitch) { + leftSidestickDisabledPitch = bitFromValue(busInputs.elac2.discrete_status_word_2, 17); + rightSidestickDisabledPitch = bitFromValue(busInputs.elac2.discrete_status_word_2, 18); + leftSidestickPriorityLockedPitch = bitFromValue(busInputs.elac2.discrete_status_word_2, 19); + rightSidestickPriorityLockedPitch = bitFromValue(busInputs.elac2.discrete_status_word_2, 20); + } else if (elac1EngagedInPitch) { + leftSidestickDisabledPitch = bitFromValue(busInputs.elac1.discrete_status_word_2, 17); + rightSidestickDisabledPitch = bitFromValue(busInputs.elac1.discrete_status_word_2, 18); + leftSidestickPriorityLockedPitch = bitFromValue(busInputs.elac1.discrete_status_word_2, 19); + rightSidestickPriorityLockedPitch = bitFromValue(busInputs.elac1.discrete_status_word_2, 20); + } else if (sec2EngagedInPitch) { + leftSidestickDisabledPitch = bitFromValue(busInputs.sec2.discrete_status_word_2, 13); + rightSidestickDisabledPitch = bitFromValue(busInputs.sec2.discrete_status_word_2, 14); + leftSidestickPriorityLockedPitch = bitFromValue(busInputs.sec2.discrete_status_word_2, 15); + rightSidestickPriorityLockedPitch = bitFromValue(busInputs.sec2.discrete_status_word_2, 16); + } else if (sec1EngagedInPitch) { + leftSidestickDisabledPitch = bitFromValue(busInputs.sec1.discrete_status_word_2, 13); + rightSidestickDisabledPitch = bitFromValue(busInputs.sec1.discrete_status_word_2, 14); + leftSidestickPriorityLockedPitch = bitFromValue(busInputs.sec1.discrete_status_word_2, 15); + rightSidestickPriorityLockedPitch = bitFromValue(busInputs.sec1.discrete_status_word_2, 16); + } + + // Compute the overall sidestick priority status. A sidestick has lost priority, + // if at least one of the computers that is engaged in one axis has computed it as disabled. + leftSidestickDisabled = leftSidestickDisabledPitch || leftSidestickDisabledRoll; + rightSidestickDisabled = rightSidestickDisabledPitch || rightSidestickDisabledRoll; + leftSidestickPriorityLocked = leftSidestickPriorityLockedPitch || leftSidestickPriorityLockedRoll; + rightSidestickPriorityLocked = rightSidestickPriorityLockedPitch || rightSidestickPriorityLockedRoll; + + // Update light flashing clock + if (priorityLightFlashingClock > 2 * LIGHT_FLASHING_PERIOD) { + priorityLightFlashingClock = 0; + } else { + priorityLightFlashingClock += deltaTime; + } + bool flashingLightActive = priorityLightFlashingClock < LIGHT_FLASHING_PERIOD; + + // The red arrow light comes on in front of the pilot who has lost priority. + leftRedPriorityLightOn = leftSidestickDisabled; + rightRedPriorityLightOn = rightSidestickDisabled; + + bool leftSidestickPitchDeflected = std::abs(pitchSidestickPosCapt) >= 2 && pitchSidestickPosCaptValid; + bool leftSidestickRollDeflected = std::abs(rollSidestickPosCapt) >= 2 && rollSidestickPosCaptValid; + bool rightSidestickPitchDeflected = std::abs(pitchSidestickPosFo) >= 2 && pitchSidestickPosFoValid; + bool rightSidestickRollDeflected = std::abs(rollSidestickPosFo) >= 2 && rollSidestickPosFoValid; + // The green light comes on, in case of dual input, in which case it flashes, + // or, if the other sidestick, which has lost priority, is deflected. + if (!leftRedPriorityLightOn && !rightRedPriorityLightOn) { + // This is the case where no side has taken priority, so check for dual input + + if ((leftSidestickPitchDeflected || leftSidestickRollDeflected) && (rightSidestickPitchDeflected || rightSidestickRollDeflected)) { + leftGreenPriorityLightOn = flashingLightActive; + rightGreenPriorityLightOn = flashingLightActive; + } else { + leftGreenPriorityLightOn = false; + rightGreenPriorityLightOn = false; + } + } else { + // This is the case where one sidestick has lost priority, so check if this sidestick is deflected. + // If it is, illuminate the other light. + if (leftSidestickDisabled && (leftSidestickPitchDeflected || leftSidestickRollDeflected)) { + leftGreenPriorityLightOn = false; + rightGreenPriorityLightOn = true; + } else if (rightSidestickDisabled && (rightSidestickPitchDeflected || rightSidestickRollDeflected)) { + leftGreenPriorityLightOn = true; + rightGreenPriorityLightOn = false; + } else { + leftGreenPriorityLightOn = false; + rightGreenPriorityLightOn = false; + } + } +} + +// Write the bus output data and return it. +FcdcBus Fcdc::getBusOutputs() { + FcdcBus output = {}; + + if (!monitoringHealthy) { + output.efcsStatus1.setSsm(Arinc429SignStatus::FailureWarning); + output.efcsStatus2.setSsm(Arinc429SignStatus::FailureWarning); + output.efcsStatus3.setSsm(Arinc429SignStatus::FailureWarning); + output.efcsStatus4.setSsm(Arinc429SignStatus::FailureWarning); + output.efcsStatus5.setSsm(Arinc429SignStatus::FailureWarning); + output.captRollCommand.setSsm(Arinc429SignStatus::FailureWarning); + output.foRollCommand.setSsm(Arinc429SignStatus::FailureWarning); + output.rudderPedalPosition.setSsm(Arinc429SignStatus::FailureWarning); + output.captPitchCommand.setSsm(Arinc429SignStatus::FailureWarning); + output.foPitchCommand.setSsm(Arinc429SignStatus::FailureWarning); + output.aileronLeftPos.setSsm(Arinc429SignStatus::FailureWarning); + output.elevatorLeftPos.setSsm(Arinc429SignStatus::FailureWarning); + output.aileronRightPos.setSsm(Arinc429SignStatus::FailureWarning); + output.elevatorRightPos.setSsm(Arinc429SignStatus::FailureWarning); + output.horizStabTrimPos.setSsm(Arinc429SignStatus::FailureWarning); + output.spoilerLeft1Pos.setSsm(Arinc429SignStatus::FailureWarning); + output.spoilerLeft2Pos.setSsm(Arinc429SignStatus::FailureWarning); + output.spoilerLeft3Pos.setSsm(Arinc429SignStatus::FailureWarning); + output.spoilerLeft4Pos.setSsm(Arinc429SignStatus::FailureWarning); + output.spoilerLeft5Pos.setSsm(Arinc429SignStatus::FailureWarning); + output.spoilerRight1Pos.setSsm(Arinc429SignStatus::FailureWarning); + output.spoilerRight2Pos.setSsm(Arinc429SignStatus::FailureWarning); + output.spoilerRight3Pos.setSsm(Arinc429SignStatus::FailureWarning); + output.spoilerRight4Pos.setSsm(Arinc429SignStatus::FailureWarning); + output.spoilerRight5Pos.setSsm(Arinc429SignStatus::FailureWarning); + + return output; + } + + output.efcsStatus1.setSsm(Arinc429SignStatus::NormalOperation); + output.efcsStatus1.setBit(11, systemPitchLaw == PitchLaw::NormalLaw); + output.efcsStatus1.setBit(12, systemPitchLaw == PitchLaw::AlternateLaw1); + output.efcsStatus1.setBit(13, systemPitchLaw == PitchLaw::AlternateLaw2); + output.efcsStatus1.setBit(15, systemPitchLaw == PitchLaw::DirectLaw); + output.efcsStatus1.setBit(16, systemLateralLaw == LateralLaw::NormalLaw); + output.efcsStatus1.setBit(17, systemLateralLaw == LateralLaw::DirectLaw); + output.efcsStatus1.setBit(18, false); // No idea what this bit is supposed to mean + output.efcsStatus1.setBit(19, bitFromValueOr(busInputs.elac1.discrete_status_word_1, 21, true)); + output.efcsStatus1.setBit(20, bitFromValueOr(busInputs.elac1.discrete_status_word_1, 22, true)); + output.efcsStatus1.setBit(21, bitFromValueOr(busInputs.elac2.discrete_status_word_1, 21, true)); + output.efcsStatus1.setBit(22, bitFromValueOr(busInputs.elac2.discrete_status_word_1, 22, true)); + output.efcsStatus1.setBit(23, !discreteInputs.elac1Valid); + output.efcsStatus1.setBit(24, !discreteInputs.elac2Valid); + output.efcsStatus1.setBit(25, !discreteInputs.sec1Valid); + output.efcsStatus1.setBit(26, !discreteInputs.sec2Valid); + output.efcsStatus1.setBit(27, false); + output.efcsStatus1.setBit(28, discreteInputs.oppFcdcFailed); + output.efcsStatus1.setBit(29, !discreteInputs.sec3Valid); + + bool leftElev1Fault = (!discreteInputs.elac1Valid || bitFromValueOr(busInputs.elac1.discrete_status_word_1, 21, false)) && + (!discreteInputs.sec1Valid || bitFromValueOr(busInputs.sec1.discrete_status_word_1, 13, false)); + bool leftElev2Fault = (!discreteInputs.elac2Valid || bitFromValueOr(busInputs.elac2.discrete_status_word_1, 21, false)) && + (!discreteInputs.sec2Valid || bitFromValueOr(busInputs.sec2.discrete_status_word_1, 13, false)); + bool rightElev1Fault = (!discreteInputs.elac1Valid || bitFromValueOr(busInputs.elac1.discrete_status_word_1, 21, false)) && + (!discreteInputs.sec1Valid || bitFromValueOr(busInputs.sec1.discrete_status_word_1, 14, false)); + bool rightElev2Fault = (!discreteInputs.elac2Valid || bitFromValueOr(busInputs.elac2.discrete_status_word_1, 21, false)) && + (!discreteInputs.sec2Valid || bitFromValueOr(busInputs.sec2.discrete_status_word_1, 14, false)); + + output.efcsStatus2.setSsm(Arinc429SignStatus::NormalOperation); + output.efcsStatus2.setBit(11, bitFromValueOr(busInputs.elac1.discrete_status_word_1, 11, true)); + output.efcsStatus2.setBit(12, bitFromValueOr(busInputs.elac2.discrete_status_word_1, 11, true)); + output.efcsStatus2.setBit(13, bitFromValueOr(busInputs.elac1.discrete_status_word_1, 12, true)); + output.efcsStatus2.setBit(14, bitFromValueOr(busInputs.elac2.discrete_status_word_1, 12, true)); + output.efcsStatus2.setBit(15, leftElev1Fault); + output.efcsStatus2.setBit(16, leftElev2Fault); + output.efcsStatus2.setBit(17, rightElev1Fault); + output.efcsStatus2.setBit(18, rightElev2Fault); + output.efcsStatus2.setBit(19, rightSidestickPriorityLocked); + output.efcsStatus2.setBit(20, leftSidestickPriorityLocked); + output.efcsStatus2.setBit(21, false); + output.efcsStatus2.setBit(22, false); + output.efcsStatus2.setBit(23, false); + output.efcsStatus2.setBit(24, false); + output.efcsStatus2.setBit(25, false); + output.efcsStatus2.setBit(26, false); + output.efcsStatus2.setBit(27, false); + output.efcsStatus2.setBit(28, leftSidestickDisabled); + output.efcsStatus2.setBit(29, rightSidestickDisabled); + + output.efcsStatus3.setSsm(Arinc429SignStatus::NormalOperation); + output.efcsStatus3.setBit(11, bitFromValueOr(busInputs.elac1.discrete_status_word_1, 15, false)); + output.efcsStatus3.setBit(12, bitFromValueOr(busInputs.elac2.discrete_status_word_1, 15, false)); + output.efcsStatus3.setBit(13, bitFromValueOr(busInputs.elac1.discrete_status_word_1, 16, false)); + output.efcsStatus3.setBit(14, bitFromValueOr(busInputs.elac2.discrete_status_word_1, 16, false)); + output.efcsStatus3.setBit(15, bitFromValueOr(busInputs.elac1.discrete_status_word_1, 17, false) || + bitFromValueOr(busInputs.sec1.discrete_status_word_1, 17, false)); + output.efcsStatus3.setBit(16, bitFromValueOr(busInputs.elac2.discrete_status_word_1, 17, false) || + bitFromValueOr(busInputs.sec2.discrete_status_word_1, 17, false)); + output.efcsStatus3.setBit(17, bitFromValueOr(busInputs.elac1.discrete_status_word_1, 18, false) || + bitFromValueOr(busInputs.sec1.discrete_status_word_1, 18, false)); + output.efcsStatus3.setBit(18, bitFromValueOr(busInputs.elac2.discrete_status_word_1, 18, false) || + bitFromValueOr(busInputs.sec2.discrete_status_word_1, 18, false)); + output.efcsStatus3.setBit(19, discreteInputs.elac1Off); + output.efcsStatus3.setBit(20, discreteInputs.elac2Off); + output.efcsStatus3.setBit(21, bitFromValueOr(busInputs.sec3.discrete_status_word_1, 15, false)); + output.efcsStatus3.setBit(22, bitFromValueOr(busInputs.sec3.discrete_status_word_1, 16, false)); + output.efcsStatus3.setBit(23, bitFromValueOr(busInputs.sec1.discrete_status_word_1, 15, false)); + output.efcsStatus3.setBit(24, bitFromValueOr(busInputs.sec1.discrete_status_word_1, 16, false)); + output.efcsStatus3.setBit(25, bitFromValueOr(busInputs.sec2.discrete_status_word_1, 15, false)); + output.efcsStatus3.setBit(26, false); + output.efcsStatus3.setBit(27, discreteInputs.sec1Off); + output.efcsStatus3.setBit(28, discreteInputs.sec2Off); + output.efcsStatus3.setBit(29, discreteInputs.sec3Off); + + output.efcsStatus4.setSsm(Arinc429SignStatus::NormalOperation); + output.efcsStatus4.setBit(11, valueOr(busInputs.sec3.left_spoiler_1_position_deg, 0) < -2.5); + output.efcsStatus4.setBit(12, valueOr(busInputs.sec3.right_spoiler_1_position_deg, 0) < -2.5); + output.efcsStatus4.setBit(13, valueOr(busInputs.sec3.left_spoiler_2_position_deg, 0) < -2.5); + output.efcsStatus4.setBit(14, valueOr(busInputs.sec3.right_spoiler_2_position_deg, 0) < -2.5); + output.efcsStatus4.setBit(15, valueOr(busInputs.sec1.left_spoiler_1_position_deg, 0) < -2.5); + output.efcsStatus4.setBit(16, valueOr(busInputs.sec1.right_spoiler_1_position_deg, 0) < -2.5); + output.efcsStatus4.setBit(17, valueOr(busInputs.sec1.left_spoiler_2_position_deg, 0) < -2.5); + output.efcsStatus4.setBit(18, valueOr(busInputs.sec1.right_spoiler_2_position_deg, 0) < -2.5); + output.efcsStatus4.setBit(19, valueOr(busInputs.sec2.left_spoiler_1_position_deg, 0) < -2.5); + output.efcsStatus4.setBit(20, valueOr(busInputs.sec2.right_spoiler_1_position_deg, 0) < -2.5); + output.efcsStatus4.setBit(21, isNo(busInputs.sec3.left_spoiler_1_position_deg) && isNo(busInputs.sec3.right_spoiler_1_position_deg)); + output.efcsStatus4.setBit(22, isNo(busInputs.sec3.left_spoiler_2_position_deg) && isNo(busInputs.sec3.right_spoiler_2_position_deg)); + output.efcsStatus4.setBit(23, isNo(busInputs.sec1.left_spoiler_1_position_deg) && isNo(busInputs.sec1.right_spoiler_1_position_deg)); + output.efcsStatus4.setBit(24, isNo(busInputs.sec1.left_spoiler_2_position_deg) && isNo(busInputs.sec1.right_spoiler_2_position_deg)); + output.efcsStatus4.setBit(25, isNo(busInputs.sec2.left_spoiler_1_position_deg) && isNo(busInputs.sec2.right_spoiler_1_position_deg)); + output.efcsStatus4.setBit(26, bitFromValueOr(busInputs.sec1.discrete_status_word_1, 25, false) || + bitFromValueOr(busInputs.sec2.discrete_status_word_1, 25, false) || + bitFromValueOr(busInputs.sec3.discrete_status_word_1, 25, false)); + output.efcsStatus4.setBit(27, bitFromValueOr(busInputs.sec1.discrete_status_word_1, 26, false) || + bitFromValueOr(busInputs.sec2.discrete_status_word_1, 26, false) || + bitFromValueOr(busInputs.sec3.discrete_status_word_1, 26, false)); + output.efcsStatus4.setBit(28, valueOr(busInputs.sec1.speed_brake_lever_command_deg, 0) > 1.5 || + valueOr(busInputs.sec2.speed_brake_lever_command_deg, 0) > 1.5 || + valueOr(busInputs.sec3.speed_brake_lever_command_deg, 0) > 1.5); + output.efcsStatus4.setBit(29, bitFromValueOr(busInputs.elac1.discrete_status_word_2, 21, false) || + bitFromValueOr(busInputs.elac2.discrete_status_word_2, 21, false)); + + output.efcsStatus5.setSsm(Arinc429SignStatus::NormalOperation); + output.efcsStatus5.setBit(11, !isNo(busInputs.sec1.speed_brake_lever_command_deg) && discreteInputs.sec1Valid); + output.efcsStatus5.setBit(12, !isNo(busInputs.sec2.speed_brake_lever_command_deg) && discreteInputs.sec2Valid); + output.efcsStatus5.setBit(13, !isNo(busInputs.sec3.speed_brake_lever_command_deg) && discreteInputs.sec3Valid); + output.efcsStatus5.setBit(14, bitFromValueOr(busInputs.sec1.discrete_status_word_1, 24, false)); + output.efcsStatus5.setBit(15, bitFromValueOr(busInputs.sec2.discrete_status_word_1, 24, false)); + output.efcsStatus5.setBit(16, bitFromValueOr(busInputs.sec3.discrete_status_word_1, 24, false)); + output.efcsStatus5.setBit(17, false); + output.efcsStatus5.setBit(18, false); + output.efcsStatus5.setBit(19, false); + output.efcsStatus5.setBit(20, false); + output.efcsStatus5.setBit(21, bitFromValueOr(busInputs.sec3.discrete_status_word_1, 11, false)); + output.efcsStatus5.setBit(22, bitFromValueOr(busInputs.sec3.discrete_status_word_1, 12, false)); + output.efcsStatus5.setBit(23, bitFromValueOr(busInputs.sec1.discrete_status_word_1, 11, false)); + output.efcsStatus5.setBit(24, bitFromValueOr(busInputs.sec1.discrete_status_word_1, 12, false)); + output.efcsStatus5.setBit(25, bitFromValueOr(busInputs.sec2.discrete_status_word_1, 11, false)); + output.efcsStatus5.setBit(26, false); + output.efcsStatus5.setBit(27, false); + output.efcsStatus5.setBit(28, false); + output.efcsStatus5.setBit(29, false); + + // Roll Data + if (leftAileronPosValid) { + output.aileronLeftPos.setFromData(leftAileronPos, Arinc429SignStatus::NormalOperation); + } else { + output.aileronLeftPos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + if (rightAileronPosValid) { + output.aileronRightPos.setFromData(rightAileronPos, Arinc429SignStatus::NormalOperation); + } else { + output.aileronRightPos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + if (rollSidestickPosCaptValid) { + output.captRollCommand.setFromData(rollSidestickPosCapt, Arinc429SignStatus::NormalOperation); + } else { + output.captRollCommand.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + if (rollSidestickPosFoValid) { + output.foRollCommand.setFromData(rollSidestickPosFo, Arinc429SignStatus::NormalOperation); + } else { + output.foRollCommand.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + if (rudderPedalPosValid) { + output.rudderPedalPosition.setFromData(rudderPedalPos, Arinc429SignStatus::NormalOperation); + } else { + output.rudderPedalPosition.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + // Pitch Data + if (leftElevatorPosValid) { + output.elevatorLeftPos.setFromData(leftElevatorPos, Arinc429SignStatus::NormalOperation); + } else { + output.elevatorLeftPos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + if (rightElevatorPosValid) { + output.elevatorRightPos.setFromData(rightElevatorPos, Arinc429SignStatus::NormalOperation); + } else { + output.elevatorRightPos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + if (thsPosValid) { + output.horizStabTrimPos.setFromData(thsPos, Arinc429SignStatus::NormalOperation); + } else { + output.horizStabTrimPos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + if (pitchSidestickPosCaptValid) { + output.captPitchCommand.setFromData(pitchSidestickPosCapt, Arinc429SignStatus::NormalOperation); + } else { + output.captPitchCommand.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + if (pitchSidestickPosFoValid) { + output.foPitchCommand.setFromData(pitchSidestickPosFo, Arinc429SignStatus::NormalOperation); + } else { + output.foPitchCommand.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + if (isNo(busInputs.sec3.left_spoiler_1_position_deg)) { + output.spoilerLeft1Pos.setFromData(busInputs.sec3.left_spoiler_1_position_deg.Data, Arinc429SignStatus::NormalOperation); + } else { + output.spoilerLeft1Pos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + if (isNo(busInputs.sec3.right_spoiler_1_position_deg)) { + output.spoilerRight1Pos.setFromData(busInputs.sec3.right_spoiler_1_position_deg.Data, Arinc429SignStatus::NormalOperation); + } else { + output.spoilerRight1Pos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + if (isNo(busInputs.sec3.left_spoiler_2_position_deg)) { + output.spoilerLeft2Pos.setFromData(busInputs.sec3.left_spoiler_2_position_deg.Data, Arinc429SignStatus::NormalOperation); + } else { + output.spoilerLeft2Pos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + if (isNo(busInputs.sec3.right_spoiler_2_position_deg)) { + output.spoilerRight2Pos.setFromData(busInputs.sec3.right_spoiler_2_position_deg.Data, Arinc429SignStatus::NormalOperation); + } else { + output.spoilerRight2Pos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + if (isNo(busInputs.sec1.left_spoiler_1_position_deg)) { + output.spoilerLeft3Pos.setFromData(busInputs.sec1.left_spoiler_1_position_deg.Data, Arinc429SignStatus::NormalOperation); + } else { + output.spoilerLeft3Pos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + if (isNo(busInputs.sec1.right_spoiler_1_position_deg)) { + output.spoilerRight3Pos.setFromData(busInputs.sec1.right_spoiler_1_position_deg.Data, Arinc429SignStatus::NormalOperation); + } else { + output.spoilerRight3Pos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + if (isNo(busInputs.sec1.left_spoiler_2_position_deg)) { + output.spoilerLeft4Pos.setFromData(busInputs.sec1.left_spoiler_2_position_deg.Data, Arinc429SignStatus::NormalOperation); + } else { + output.spoilerLeft4Pos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + if (isNo(busInputs.sec1.right_spoiler_2_position_deg)) { + output.spoilerRight4Pos.setFromData(busInputs.sec1.right_spoiler_2_position_deg.Data, Arinc429SignStatus::NormalOperation); + } else { + output.spoilerRight4Pos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + if (isNo(busInputs.sec2.left_spoiler_1_position_deg)) { + output.spoilerLeft5Pos.setFromData(busInputs.sec2.left_spoiler_1_position_deg.Data, Arinc429SignStatus::NormalOperation); + } else { + output.spoilerLeft5Pos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + if (isNo(busInputs.sec2.right_spoiler_1_position_deg)) { + output.spoilerRight5Pos.setFromData(busInputs.sec2.right_spoiler_1_position_deg.Data, Arinc429SignStatus::NormalOperation); + } else { + output.spoilerRight5Pos.setFromData(0, Arinc429SignStatus::FailureWarning); + } + + return output; +} + +// Write the discrete output data and return it. +FcdcDiscreteOutputs Fcdc::getDiscreteOutputs() { + FcdcDiscreteOutputs output = {}; + + if (!monitoringHealthy) { + output.captRedPriorityLightOn = false; + output.captGreenPriorityLightOn = false; + output.fcdcValid = false; + output.foRedPriorityLightOn = false; + output.foGreenPriorityLightOn = false; + } else { + output.captRedPriorityLightOn = leftRedPriorityLightOn; + output.captGreenPriorityLightOn = leftGreenPriorityLightOn; + output.fcdcValid = true; + output.foRedPriorityLightOn = rightRedPriorityLightOn; + output.foGreenPriorityLightOn = rightGreenPriorityLightOn; + } + + return output; +} diff --git a/src/fbw/src/fcdc/Fcdc.h b/src/fbw/src/fcdc/Fcdc.h new file mode 100644 index 000000000000..b670821e305b --- /dev/null +++ b/src/fbw/src/fcdc/Fcdc.h @@ -0,0 +1,141 @@ +#pragma once + +#include "FcdcIO.h" + +const double LIGHT_FLASHING_PERIOD = 0.25; + +class Fcdc { + public: + Fcdc(bool isUnit1); + + void update(double deltaTime, bool faultActive, bool isPowered); + + FcdcBus getBusOutputs(); + + FcdcDiscreteOutputs getDiscreteOutputs(); + + FcdcDiscreteInputs discreteInputs; + + FcdcBusInputs busInputs; + + private: + void startup(); + + void monitorPowerSupply(double deltaTime, bool isPowered); + + void monitorSelf(bool faultActive); + + void updateSelfTest(double deltaTime); + + void computeActiveSystemLaws(); + + void consolidatePositionData(); + + PitchLaw getPitchLawStatusFromBits(bool bit1, bool bit2, bool bit3); + + LateralLaw getLateralLawStatusFromBits(bool bit1, bool bit2, bool bit3); + + void computeComputerEngagements(); + + void computeSidestickPriorityLights(double deltaTime); + + // Computer axis engagement vars + bool elac1EngagedInRoll; + + bool elac2EngagedInRoll; + + bool sec1EngagedInRoll; + + bool sec2EngagedInRoll; + + bool sec3EngagedInRoll; + + bool elac1EngagedInPitch; + + bool elac2EngagedInPitch; + + bool sec1EngagedInPitch; + + bool sec2EngagedInPitch; + + // Data concentration and computation vars + + PitchLaw systemPitchLaw; + + LateralLaw systemLateralLaw; + + double leftAileronPos; + + bool leftAileronPosValid; + + double rightAileronPos; + + bool rightAileronPosValid; + + double leftElevatorPos; + + bool leftElevatorPosValid; + + double rightElevatorPos; + + bool rightElevatorPosValid; + + double thsPos; + + bool thsPosValid; + + double rollSidestickPosCapt; + + bool rollSidestickPosCaptValid; + + double rollSidestickPosFo; + + bool rollSidestickPosFoValid; + + double pitchSidestickPosCapt; + + bool pitchSidestickPosCaptValid; + + double pitchSidestickPosFo; + + bool pitchSidestickPosFoValid; + + double rudderPedalPos; + + bool rudderPedalPosValid; + + // Sidestick priority vars + bool leftSidestickDisabled; + + bool rightSidestickDisabled; + + bool leftSidestickPriorityLocked; + + bool rightSidestickPriorityLocked; + + bool leftRedPriorityLightOn; + + bool rightRedPriorityLightOn; + + bool leftGreenPriorityLightOn; + + bool rightGreenPriorityLightOn; + + double priorityLightFlashingClock; + + // Computer monitoring and self-test vars + + bool monitoringHealthy; + + double powerSupplyOutageTime; + + bool powerSupplyFault; + + double selfTestTimer; + + bool selfTestComplete; + + const bool isUnit1; + + const double minimumPowerOutageTimeForFailure = 0.01; +}; diff --git a/src/fbw/src/fcdc/FcdcIO.h b/src/fbw/src/fcdc/FcdcIO.h new file mode 100644 index 000000000000..b0cd31bc7b8f --- /dev/null +++ b/src/fbw/src/fcdc/FcdcIO.h @@ -0,0 +1,59 @@ +#pragma once + +#include "../busStructures/busStructures.h" + +struct FcdcDiscreteInputs { + bool elac1Off; + + bool elac1Valid; + + bool elac2Valid; + + bool sec1Off; + + bool sec1Valid; + + bool sec2Valid; + + bool eng1NotOnGroundAndNotLowOilPress; + + bool eng2NotOnGroundAndNotLowOilPress; + + bool noseGearPressed; + + bool oppFcdcFailed; + + bool sec3Off; + + bool sec3Valid; + + bool elac2Off; + + bool sec2Off; +}; + +struct FcdcDiscreteOutputs { + bool captRedPriorityLightOn; + + bool captGreenPriorityLightOn; + + bool fcdcValid; + + bool foRedPriorityLightOn; + + bool foGreenPriorityLightOn; +}; + +struct FcdcBusInputs { + base_elac_out_bus elac1; + + base_sec_out_bus sec1; + + base_fcdc_bus fcdcOpp; + + base_elac_out_bus elac2; + + base_sec_out_bus sec2; + + base_sec_out_bus sec3; +}; diff --git a/src/fbw/src/interface/SimConnectData.h b/src/fbw/src/interface/SimConnectData.h index 5b17b1ded746..9dbfb98bd239 100644 --- a/src/fbw/src/interface/SimConnectData.h +++ b/src/fbw/src/interface/SimConnectData.h @@ -129,12 +129,20 @@ struct SimData { unsigned long long assistanceLandingEnabled; unsigned long long aiAutoTrimActive; unsigned long long aiControlsActive; + double wheelRpmLeft; + double wheelRpmRight; }; struct SimInput { double inputs[3]; }; +struct SimInputRudderTrim { + bool rudderTrimSwitchLeft; + bool rudderTrimSwitchRight; + bool rudderTrimReset; +}; + struct SimInputAutopilot { double AP_engage; double AP_1_push; @@ -158,16 +166,6 @@ struct SimInputThrottles { double ATHR_reset_disable; }; -struct SimOutput { - double eta; - double xi; - double zeta; -}; - -struct SimOutputEtaTrim { - double eta_trim_deg; -}; - struct SimOutputZetaTrim { double zeta_trim_pos; }; diff --git a/src/fbw/src/interface/SimConnectInterface.cpp b/src/fbw/src/interface/SimConnectInterface.cpp index eb4ac5f8ffa7..e3d78e80b259 100644 --- a/src/fbw/src/interface/SimConnectInterface.cpp +++ b/src/fbw/src/interface/SimConnectInterface.cpp @@ -17,10 +17,12 @@ bool SimConnectInterface::connect(bool clientDataEnabled, bool autopilotStateMachineEnabled, bool autopilotLawsEnabled, bool flyByWireEnabled, + int elacDisabled, + int secDisabled, + int facDisabled, const std::vector>& throttleAxis, std::shared_ptr spoilersHandler, std::shared_ptr elevatorTrimHandler, - std::shared_ptr rudderTrimHandler, double keyChangeAileron, double keyChangeElevator, double keyChangeRudder, @@ -44,14 +46,15 @@ bool SimConnectInterface::connect(bool clientDataEnabled, this->spoilersHandler = spoilersHandler; // store elevator trim handler this->elevatorTrimHandler = elevatorTrimHandler; - // store rudder trim handler - this->rudderTrimHandler = rudderTrimHandler; // store maximum allowed simulation rate this->minSimulationRate = minSimulationRate; this->maxSimulationRate = maxSimulationRate; this->limitSimulationRateByPerformance = limitSimulationRateByPerformance; // store is client data is enabled this->clientDataEnabled = clientDataEnabled; + this->elacDisabled = elacDisabled; + this->secDisabled = secDisabled; + this->facDisabled = facDisabled; // store key change value for each axis flightControlsKeyChangeAileron = keyChangeAileron; flightControlsKeyChangeElevator = keyChangeElevator; @@ -245,6 +248,8 @@ bool SimConnectInterface::prepareSimDataSimConnectDataDefinitions() { result &= addDataDefinition(hSimConnect, 0, SIMCONNECT_DATATYPE_INT64, "ASSISTANCE LANDING ENABLED", "BOOL"); result &= addDataDefinition(hSimConnect, 0, SIMCONNECT_DATATYPE_INT64, "AI AUTOTRIM ACTIVE", "BOOL"); result &= addDataDefinition(hSimConnect, 0, SIMCONNECT_DATATYPE_INT64, "AI CONTROLS", "BOOL"); + result &= addDataDefinition(hSimConnect, 0, SIMCONNECT_DATATYPE_FLOAT64, "WHEEL RPM:1", "RPM"); + result &= addDataDefinition(hSimConnect, 0, SIMCONNECT_DATATYPE_FLOAT64, "WHEEL RPM:2", "RPM"); return result; } @@ -279,11 +284,6 @@ bool SimConnectInterface::prepareSimInputSimConnectDataDefinitions() { result &= addInputDataDefinition(hSimConnect, 0, Events::ELEV_DOWN, "ELEV_DOWN", true); result &= addInputDataDefinition(hSimConnect, 0, Events::ELEV_UP, "ELEV_UP", true); - result &= addInputDataDefinition(hSimConnect, 0, Events::ELEV_TRIM_DN, "ELEV_TRIM_DN", true); - result &= addInputDataDefinition(hSimConnect, 0, Events::ELEV_TRIM_UP, "ELEV_TRIM_UP", true); - result &= addInputDataDefinition(hSimConnect, 0, Events::ELEVATOR_TRIM_SET, "ELEVATOR_TRIM_SET", true); - result &= addInputDataDefinition(hSimConnect, 0, Events::AXIS_ELEV_TRIM_SET, "AXIS_ELEV_TRIM_SET", true); - result &= addInputDataDefinition(hSimConnect, 0, Events::AP_MASTER, "AP_MASTER", true); result &= addInputDataDefinition(hSimConnect, 0, Events::AUTOPILOT_OFF, "AUTOPILOT_OFF", false); result &= addInputDataDefinition(hSimConnect, 0, Events::AUTOPILOT_ON, "AUTOPILOT_ON", true); @@ -436,10 +436,6 @@ bool SimConnectInterface::prepareSimInputSimConnectDataDefinitions() { bool SimConnectInterface::prepareSimOutputSimConnectDataDefinitions() { bool result = true; - result &= addDataDefinition(hSimConnect, 1, SIMCONNECT_DATATYPE_FLOAT64, "ELEVATOR POSITION", "POSITION"); - result &= addDataDefinition(hSimConnect, 1, SIMCONNECT_DATATYPE_FLOAT64, "AILERON POSITION", "POSITION"); - result &= addDataDefinition(hSimConnect, 1, SIMCONNECT_DATATYPE_FLOAT64, "RUDDER POSITION", "POSITION"); - result &= addDataDefinition(hSimConnect, 2, SIMCONNECT_DATATYPE_FLOAT64, "ELEVATOR TRIM POSITION", "DEGREE"); result &= addDataDefinition(hSimConnect, 3, SIMCONNECT_DATATYPE_FLOAT64, "RUDDER TRIM PCT", "PERCENT OVER 100"); @@ -608,54 +604,338 @@ bool SimConnectInterface::prepareClientDataDefinitions() { // ------------------------------------------------------------------------------------------------------------------ // map client id - result &= SimConnect_MapClientDataNameToID(hSimConnect, "A32NX_CLIENT_DATA_FLY_BY_WIRE_INPUT", ClientData::FLY_BY_WIRE_INPUT); + result &= SimConnect_MapClientDataNameToID(hSimConnect, "A32NX_CLIENT_DATA_ELAC_DISCRETE_INPUT", ClientData::ELAC_DISCRETE_INPUTS); // create client data - result &= SimConnect_CreateClientData(hSimConnect, ClientData::FLY_BY_WIRE_INPUT, sizeof(ClientDataFlyByWireInput), + result &= SimConnect_CreateClientData(hSimConnect, ClientData::ELAC_DISCRETE_INPUTS, sizeof(base_elac_discrete_inputs), SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); // add data definitions - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE_INPUT, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE_INPUT, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE_INPUT, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); + for (int i = 0; i < 32; i++) { + result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::ELAC_DISCRETE_INPUTS, SIMCONNECT_CLIENTDATAOFFSET_AUTO, + SIMCONNECT_CLIENTDATATYPE_INT8); + } // ------------------------------------------------------------------------------------------------------------------ // map client id - result &= SimConnect_MapClientDataNameToID(hSimConnect, "A32NX_CLIENT_DATA_FLY_BY_WIRE", ClientData::FLY_BY_WIRE); + result &= SimConnect_MapClientDataNameToID(hSimConnect, "A32NX_CLIENT_DATA_ELAC_ANALOG_INPUT", ClientData::ELAC_ANALOG_INPUTS); // create client data - result &= SimConnect_CreateClientData(hSimConnect, ClientData::FLY_BY_WIRE, sizeof(ClientDataFlyByWire), + result &= SimConnect_CreateClientData(hSimConnect, ClientData::ELAC_ANALOG_INPUTS, sizeof(base_elac_analog_inputs), SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); // add data definitions - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); - result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FLY_BY_WIRE, SIMCONNECT_CLIENTDATAOFFSET_AUTO, - SIMCONNECT_CLIENTDATATYPE_FLOAT64); + for (int i = 0; i < 15; i++) { + result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::ELAC_ANALOG_INPUTS, SIMCONNECT_CLIENTDATAOFFSET_AUTO, + SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + + // ------------------------------------------------------------------------------------------------------------------ + + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, "A32NX_CLIENT_DATA_ELAC_DISCRETES_OUTPUT", ClientData::ELAC_DISCRETE_OUTPUTS); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, ClientData::ELAC_DISCRETE_OUTPUTS, sizeof(base_elac_discrete_outputs), + SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 12; i++) { + result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::ELAC_DISCRETE_OUTPUTS, SIMCONNECT_CLIENTDATAOFFSET_AUTO, + SIMCONNECT_CLIENTDATATYPE_INT8); + } // request data to be updated when set - result &= SimConnect_RequestClientData(hSimConnect, ClientData::FLY_BY_WIRE, ClientData::FLY_BY_WIRE, ClientData::FLY_BY_WIRE, - SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET); + result &= SimConnect_RequestClientData(hSimConnect, ClientData::ELAC_DISCRETE_OUTPUTS, ClientData::ELAC_DISCRETE_OUTPUTS, + ClientData::ELAC_DISCRETE_OUTPUTS, SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET); + + // ------------------------------------------------------------------------------------------------------------------ + + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, "A32NX_CLIENT_DATA_ELAC_ANALOGS_OUTPUT", ClientData::ELAC_ANALOG_OUTPUTS); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, ClientData::ELAC_ANALOG_OUTPUTS, sizeof(base_elac_analog_outputs), + SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 5; i++) { + result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::ELAC_ANALOG_OUTPUTS, SIMCONNECT_CLIENTDATAOFFSET_AUTO, + SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + + // request data to be updated when set + result &= SimConnect_RequestClientData(hSimConnect, ClientData::ELAC_ANALOG_OUTPUTS, ClientData::ELAC_ANALOG_OUTPUTS, + ClientData::ELAC_ANALOG_OUTPUTS, SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET); + + // ------------------------------------------------------------------------------------------------------------------ + + for (int i = 0; i < 2; i++) { + auto defineId = ClientData::ELAC_1_BUS_OUTPUT + i; + + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, ("A32NX_CLIENT_DATA_ELAC_" + std::to_string(i + 1) + "_BUS").c_str(), defineId); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, defineId, sizeof(base_elac_out_bus), SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 16; i++) { + result &= + SimConnect_AddToClientDataDefinition(hSimConnect, defineId, SIMCONNECT_CLIENTDATAOFFSET_AUTO, SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + + // request data to be updated when set + if (i == elacDisabled) { + result &= SimConnect_RequestClientData(hSimConnect, defineId, defineId, defineId, SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET); + } + } + + // ------------------------------------------------------------------------------------------------------------------ + + for (int i = 0; i < 3; i++) { + auto defineId = ClientData::ADR_1_INPUTS + i; + + // map client id + result &= + SimConnect_MapClientDataNameToID(hSimConnect, ("A32NX_CLIENT_DATA_ADR_" + std::to_string(i + 1) + "_INPUT").c_str(), defineId); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, defineId, sizeof(base_adr_bus), SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 8; i++) { + result &= + SimConnect_AddToClientDataDefinition(hSimConnect, defineId, SIMCONNECT_CLIENTDATAOFFSET_AUTO, SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + } + + // ------------------------------------------------------------------------------------------------------------------ + + for (int i = 0; i < 3; i++) { + auto defineId = ClientData::IR_1_INPUTS + i; + + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, ("A32NX_CLIENT_DATA_IR_" + std::to_string(i + 1) + "_INPUT").c_str(), defineId); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, defineId, sizeof(base_ir_bus), SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 31; i++) { + result &= + SimConnect_AddToClientDataDefinition(hSimConnect, defineId, SIMCONNECT_CLIENTDATAOFFSET_AUTO, SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + } + + // ------------------------------------------------------------------------------------------------------------------ + + for (int i = 0; i < 2; i++) { + auto defineId = ClientData::RA_1_BUS + i; + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, ("A32NX_CLIENT_DATA_RA_" + std::to_string(i + 1) + "_BUS").c_str(), defineId); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, defineId, sizeof(base_ra_bus), SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + result &= + SimConnect_AddToClientDataDefinition(hSimConnect, defineId, SIMCONNECT_CLIENTDATAOFFSET_AUTO, SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + + // ------------------------------------------------------------------------------------------------------------------ + + for (int i = 0; i < 2; i++) { + auto defineId = ClientData::LGCIU_1_BUS + i; + // map client id + result &= + SimConnect_MapClientDataNameToID(hSimConnect, ("A32NX_CLIENT_DATA_LGCIU_" + std::to_string(i + 1) + "_BUS").c_str(), defineId); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, defineId, sizeof(base_lgciu_bus), SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 4; i++) { + result &= + SimConnect_AddToClientDataDefinition(hSimConnect, defineId, SIMCONNECT_CLIENTDATAOFFSET_AUTO, SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + } + + // ------------------------------------------------------------------------------------------------------------------ + + for (int i = 0; i < 2; i++) { + auto defineId = ClientData::SFCC_1_BUS + i; + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, ("A32NX_CLIENT_DATA_SFCC_" + std::to_string(i + 1) + "_BUS").c_str(), defineId); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, defineId, sizeof(base_sfcc_bus), SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 5; i++) { + result &= + SimConnect_AddToClientDataDefinition(hSimConnect, defineId, SIMCONNECT_CLIENTDATAOFFSET_AUTO, SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + } + + // ------------------------------------------------------------------------------------------------------------------ + + for (int i = 0; i < 2; i++) { + auto defineId = ClientData::FMGC_1_B_BUS + i; + // map client id + result &= + SimConnect_MapClientDataNameToID(hSimConnect, ("A32NX_CLIENT_DATA_FMGC_" + std::to_string(i + 1) + "_B_BUS").c_str(), defineId); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, defineId, sizeof(base_fmgc_b_bus), SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 18; i++) { + result &= + SimConnect_AddToClientDataDefinition(hSimConnect, defineId, SIMCONNECT_CLIENTDATAOFFSET_AUTO, SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + } + + // ------------------------------------------------------------------------------------------------------------------ + + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, "A32NX_CLIENT_DATA_SEC_DISCRETE_INPUT", ClientData::SEC_DISCRETE_INPUTS); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, ClientData::SEC_DISCRETE_INPUTS, sizeof(base_sec_discrete_inputs), + SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 26; i++) { + result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::SEC_DISCRETE_INPUTS, SIMCONNECT_CLIENTDATAOFFSET_AUTO, + SIMCONNECT_CLIENTDATATYPE_INT8); + } + + // ------------------------------------------------------------------------------------------------------------------ + + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, "A32NX_CLIENT_DATA_SEC_ANALOG_INPUT", ClientData::SEC_ANALOG_INPUTS); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, ClientData::SEC_ANALOG_INPUTS, sizeof(base_sec_analog_inputs), + SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 18; i++) { + result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::SEC_ANALOG_INPUTS, SIMCONNECT_CLIENTDATAOFFSET_AUTO, + SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + + // ------------------------------------------------------------------------------------------------------------------ + + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, "A32NX_CLIENT_DATA_SEC_DISCRETES_OUTPUT", ClientData::SEC_DISCRETE_OUTPUTS); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, ClientData::SEC_DISCRETE_OUTPUTS, sizeof(base_sec_discrete_outputs), + SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 9; i++) { + result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::SEC_DISCRETE_OUTPUTS, SIMCONNECT_CLIENTDATAOFFSET_AUTO, + SIMCONNECT_CLIENTDATATYPE_INT8); + } + + // request data to be updated when set + result &= SimConnect_RequestClientData(hSimConnect, ClientData::SEC_DISCRETE_OUTPUTS, ClientData::SEC_DISCRETE_OUTPUTS, + ClientData::SEC_DISCRETE_OUTPUTS, SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET); + + // ------------------------------------------------------------------------------------------------------------------ + + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, "A32NX_CLIENT_DATA_SEC_ANALOGS_OUTPUT", ClientData::SEC_ANALOG_OUTPUTS); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, ClientData::SEC_ANALOG_OUTPUTS, sizeof(base_sec_analog_outputs), + SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 7; i++) { + result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::SEC_ANALOG_OUTPUTS, SIMCONNECT_CLIENTDATAOFFSET_AUTO, + SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + + // request data to be updated when set + result &= SimConnect_RequestClientData(hSimConnect, ClientData::SEC_ANALOG_OUTPUTS, ClientData::SEC_ANALOG_OUTPUTS, + ClientData::SEC_ANALOG_OUTPUTS, SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET); + + // ------------------------------------------------------------------------------------------------------------------ + + for (int i = 0; i < 2; i++) { + auto defineId = ClientData::SEC_1_BUS_OUTPUT + i; + + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, ("A32NX_CLIENT_DATA_SEC_" + std::to_string(i + 1) + "_BUS").c_str(), defineId); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, defineId, sizeof(base_sec_out_bus), SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 16; i++) { + result &= + SimConnect_AddToClientDataDefinition(hSimConnect, defineId, SIMCONNECT_CLIENTDATAOFFSET_AUTO, SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + + // request data to be updated when set + if (i == secDisabled) { + result &= SimConnect_RequestClientData(hSimConnect, defineId, defineId, defineId, SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET); + } + } + + // ------------------------------------------------------------------------------------------------------------------ + + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, "A32NX_CLIENT_DATA_FAC_DISCRETE_INPUT", ClientData::FAC_DISCRETE_INPUTS); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, ClientData::FAC_DISCRETE_INPUTS, sizeof(base_fac_discrete_inputs), + SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 22; i++) { + result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FAC_DISCRETE_INPUTS, SIMCONNECT_CLIENTDATAOFFSET_AUTO, + SIMCONNECT_CLIENTDATATYPE_INT8); + } + + // ------------------------------------------------------------------------------------------------------------------ + + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, "A32NX_CLIENT_DATA_FAC_ANALOG_INPUT", ClientData::FAC_ANALOG_INPUTS); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, ClientData::FAC_ANALOG_INPUTS, sizeof(base_fac_analog_inputs), + SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 3; i++) { + result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FAC_ANALOG_INPUTS, SIMCONNECT_CLIENTDATAOFFSET_AUTO, + SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + + // ------------------------------------------------------------------------------------------------------------------ + + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, "A32NX_CLIENT_DATA_FAC_DISCRETES_OUTPUT", ClientData::FAC_DISCRETE_OUTPUTS); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, ClientData::FAC_DISCRETE_OUTPUTS, sizeof(base_fac_discrete_outputs), + SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 6; i++) { + result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FAC_DISCRETE_OUTPUTS, SIMCONNECT_CLIENTDATAOFFSET_AUTO, + SIMCONNECT_CLIENTDATATYPE_INT8); + } + + // request data to be updated when set + result &= SimConnect_RequestClientData(hSimConnect, ClientData::FAC_DISCRETE_OUTPUTS, ClientData::FAC_DISCRETE_OUTPUTS, + ClientData::FAC_DISCRETE_OUTPUTS, SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET); + + // ------------------------------------------------------------------------------------------------------------------ + + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, "A32NX_CLIENT_DATA_FAC_ANALOGS_OUTPUT", ClientData::FAC_ANALOG_OUTPUTS); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, ClientData::FAC_ANALOG_OUTPUTS, sizeof(base_fac_analog_outputs), + SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 3; i++) { + result &= SimConnect_AddToClientDataDefinition(hSimConnect, ClientData::FAC_ANALOG_OUTPUTS, SIMCONNECT_CLIENTDATAOFFSET_AUTO, + SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + + // request data to be updated when set + result &= SimConnect_RequestClientData(hSimConnect, ClientData::FAC_ANALOG_OUTPUTS, ClientData::FAC_ANALOG_OUTPUTS, + ClientData::FAC_ANALOG_OUTPUTS, SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET); + + // ------------------------------------------------------------------------------------------------------------------ + + for (int i = 0; i < 2; i++) { + auto defineId = ClientData::FAC_1_BUS_OUTPUT + i; + + // map client id + result &= SimConnect_MapClientDataNameToID(hSimConnect, ("A32NX_CLIENT_DATA_FAC_" + std::to_string(i + 1) + "_BUS").c_str(), defineId); + // create client data + result &= SimConnect_CreateClientData(hSimConnect, defineId, sizeof(base_fac_bus), SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + // add data definitions + for (int i = 0; i < 28; i++) { + result &= + SimConnect_AddToClientDataDefinition(hSimConnect, defineId, SIMCONNECT_CLIENTDATAOFFSET_AUTO, SIMCONNECT_CLIENTDATATYPE_FLOAT64); + } + + // request data to be updated when set + if (i == facDisabled) { + result &= SimConnect_RequestClientData(hSimConnect, defineId, defineId, defineId, SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET); + } + } // ------------------------------------------------------------------------------------------------------------------ @@ -864,16 +1144,6 @@ bool SimConnectInterface::readData() { return true; } -bool SimConnectInterface::sendData(SimOutput output) { - // write data and return result - return sendData(1, sizeof(output), &output); -} - -bool SimConnectInterface::sendData(SimOutputEtaTrim output) { - // write data and return result - return sendData(2, sizeof(output), &output); -} - bool SimConnectInterface::sendData(SimOutputZetaTrim output) { // write data and return result return sendData(3, sizeof(output), &output); @@ -943,6 +1213,10 @@ SimInputAutopilot SimConnectInterface::getSimInputAutopilot() { return simInputAutopilot; } +SimInputRudderTrim SimConnectInterface::getSimInputRudderTrim() { + return simInputRudderTrim; +} + SimInputThrottles SimConnectInterface::getSimInputThrottles() { return simInputThrottles; } @@ -964,6 +1238,12 @@ void SimConnectInterface::resetSimInputAutopilot() { simInputAutopilot.DIR_TO_trigger = 0; } +void SimConnectInterface::resetSimInputRudderTrim() { + simInputRudderTrim.rudderTrimSwitchLeft = false; + simInputRudderTrim.rudderTrimSwitchRight = false; + simInputRudderTrim.rudderTrimReset = false; +} + void SimConnectInterface::resetSimInputThrottles() { simInputThrottles.ATHR_push = 0; simInputThrottles.ATHR_disconnect = 0; @@ -992,18 +1272,108 @@ ClientDataAutothrust SimConnectInterface::getClientDataAutothrust() { return clientDataAutothrust; } -bool SimConnectInterface::setClientDataFlyByWireInput(ClientDataFlyByWireInput output) { - // write data and return result - return sendClientData(ClientData::FLY_BY_WIRE_INPUT, sizeof(output), &output); +ClientDataFlyByWire SimConnectInterface::getClientDataFlyByWire() { + return clientDataFlyByWire; } -bool SimConnectInterface::setClientDataFlyByWire(ClientDataFlyByWire output) { - // write data and return result - return sendClientData(ClientData::FLY_BY_WIRE, sizeof(output), &output); +bool SimConnectInterface::setClientDataElacDiscretes(base_elac_discrete_inputs output) { + return sendClientData(ClientData::ELAC_DISCRETE_INPUTS, sizeof(output), &output); } -ClientDataFlyByWire SimConnectInterface::getClientDataFlyByWire() { - return clientDataFlyByWire; +bool SimConnectInterface::setClientDataElacAnalog(base_elac_analog_inputs output) { + return sendClientData(ClientData::ELAC_ANALOG_INPUTS, sizeof(output), &output); +} + +bool SimConnectInterface::setClientDataElacBusInput(base_elac_out_bus output, int elacIndex) { + return sendClientData(ClientData::ELAC_1_BUS_OUTPUT + elacIndex, sizeof(output), &output); +} + +bool SimConnectInterface::setClientDataSecDiscretes(base_sec_discrete_inputs output) { + return sendClientData(ClientData::SEC_DISCRETE_INPUTS, sizeof(output), &output); +} + +bool SimConnectInterface::setClientDataSecAnalog(base_sec_analog_inputs output) { + return sendClientData(ClientData::SEC_ANALOG_INPUTS, sizeof(output), &output); +} + +bool SimConnectInterface::setClientDataSecBus(base_sec_out_bus output, int secIndex) { + if (secIndex < 2) { + return sendClientData(ClientData::SEC_1_BUS_OUTPUT + secIndex, sizeof(output), &output); + } else { + return false; + } +} + +bool SimConnectInterface::setClientDataFacDiscretes(base_fac_discrete_inputs output) { + return sendClientData(ClientData::FAC_DISCRETE_INPUTS, sizeof(output), &output); +} + +bool SimConnectInterface::setClientDataFacAnalog(base_fac_analog_inputs output) { + return sendClientData(ClientData::FAC_ANALOG_INPUTS, sizeof(output), &output); +} + +bool SimConnectInterface::setClientDataFacBus(base_fac_bus output, int facIndex) { + return sendClientData(ClientData::FAC_1_BUS_OUTPUT + facIndex, sizeof(output), &output); +} + +base_elac_discrete_outputs SimConnectInterface::getClientDataElacDiscretesOutput() { + return clientDataElacDiscreteOutputs; +} + +base_elac_analog_outputs SimConnectInterface::getClientDataElacAnalogsOutput() { + return clientDataElacAnalogOutputs; +} + +base_elac_out_bus SimConnectInterface::getClientDataElacBusOutput() { + return clientDataElacBusOutputs; +} + +base_sec_discrete_outputs SimConnectInterface::getClientDataSecDiscretesOutput() { + return clientDataSecDiscreteOutputs; +} + +base_sec_analog_outputs SimConnectInterface::getClientDataSecAnalogsOutput() { + return clientDataSecAnalogOutputs; +} + +base_sec_out_bus SimConnectInterface::getClientDataSecBusOutput() { + return clientDataSecBusOutputs; +} + +base_fac_discrete_outputs SimConnectInterface::getClientDataFacDiscretesOutput() { + return clientDataFacDiscreteOutputs; +} + +base_fac_analog_outputs SimConnectInterface::getClientDataFacAnalogsOutput() { + return clientDataFacAnalogOutputs; +} + +base_fac_bus SimConnectInterface::getClientDataFacBusOutput() { + return clientDataFacBusOutputs; +} + +bool SimConnectInterface::setClientDataAdr(base_adr_bus output, int adrIndex) { + return sendClientData(ClientData::ADR_1_INPUTS + adrIndex, sizeof(output), &output); +} + +bool SimConnectInterface::setClientDataIr(base_ir_bus output, int irIndex) { + return sendClientData(ClientData::IR_1_INPUTS + irIndex, sizeof(output), &output); +} + +bool SimConnectInterface::setClientDataRa(base_ra_bus output, int raIndex) { + return sendClientData(ClientData::RA_1_BUS + raIndex, sizeof(output), &output); +} + +bool SimConnectInterface::setClientDataLgciu(base_lgciu_bus output, int lgciuIndex) { + return sendClientData(ClientData::LGCIU_1_BUS + lgciuIndex, sizeof(output), &output); +} + +bool SimConnectInterface::setClientDataSfcc(base_sfcc_bus output, int sfccIndex) { + return sendClientData(ClientData::SFCC_1_BUS + sfccIndex, sizeof(output), &output); +} + +bool SimConnectInterface::setClientDataFmgcB(base_fmgc_b_bus output, int fmgcIndex) { + return sendClientData(ClientData::FMGC_1_B_BUS + fmgcIndex, sizeof(output), &output); } void SimConnectInterface::setLoggingFlightControlsEnabled(bool enabled) { @@ -1217,60 +1587,48 @@ void SimConnectInterface::simConnectProcessEvent(const SIMCONNECT_RECV_EVENT* ev } case Events::RUDDER_TRIM_LEFT: { - rudderTrimHandler->onEventRudderTrimLeft(sampleTime); + simInputRudderTrim.rudderTrimSwitchLeft = true; if (loggingFlightControlsEnabled) { cout << "WASM: RUDDER_TRIM_LEFT: "; cout << "(no data)"; - cout << " -> "; - cout << rudderTrimHandler->getTargetPosition(); cout << endl; } break; } case Events::RUDDER_TRIM_RESET: { - rudderTrimHandler->onEventRudderTrimReset(); + simInputRudderTrim.rudderTrimReset = true; if (loggingFlightControlsEnabled) { cout << "WASM: RUDDER_TRIM_RESET: "; cout << "(no data)"; - cout << " -> "; - cout << rudderTrimHandler->getTargetPosition(); cout << endl; } break; } case Events::RUDDER_TRIM_RIGHT: { - rudderTrimHandler->onEventRudderTrimRight(sampleTime); + simInputRudderTrim.rudderTrimSwitchRight = true; if (loggingFlightControlsEnabled) { cout << "WASM: RUDDER_TRIM_RIGHT: "; cout << "(no data)"; - cout << " -> "; - cout << rudderTrimHandler->getTargetPosition(); cout << endl; } break; } case Events::RUDDER_TRIM_SET: { - rudderTrimHandler->onEventRudderTrimSet(static_cast(event->dwData)); if (loggingFlightControlsEnabled) { cout << "WASM: RUDDER_TRIM_SET: "; cout << static_cast(event->dwData); - cout << " -> "; - cout << rudderTrimHandler->getTargetPosition(); cout << endl; } break; } case Events::RUDDER_TRIM_SET_EX1: { - rudderTrimHandler->onEventRudderTrimSet(static_cast(event->dwData)); if (loggingFlightControlsEnabled) { cout << "WASM: RUDDER_TRIM_SET_EX1: "; cout << static_cast(event->dwData); - cout << " -> "; - cout << rudderTrimHandler->getTargetPosition(); cout << endl; } break; @@ -1363,54 +1721,6 @@ void SimConnectInterface::simConnectProcessEvent(const SIMCONNECT_RECV_EVENT* ev break; } - case Events::ELEV_TRIM_DN: { - elevatorTrimHandler->onEventElevatorTrimDown(); - if (loggingFlightControlsEnabled) { - cout << "WASM: ELEV_TRIM_DN: "; - cout << "(no data)"; - cout << " -> "; - cout << elevatorTrimHandler->getPosition(); - cout << endl; - } - break; - } - - case Events::ELEV_TRIM_UP: { - elevatorTrimHandler->onEventElevatorTrimUp(); - if (loggingFlightControlsEnabled) { - cout << "WASM: ELEV_TRIM_UP: "; - cout << "(no data)"; - cout << " -> "; - cout << elevatorTrimHandler->getPosition(); - cout << endl; - } - break; - } - - case Events::ELEVATOR_TRIM_SET: { - elevatorTrimHandler->onEventElevatorTrimSet(static_cast(event->dwData)); - if (loggingFlightControlsEnabled) { - cout << "WASM: ELEVATOR_TRIM_SET: "; - cout << static_cast(event->dwData); - cout << " -> "; - cout << elevatorTrimHandler->getPosition(); - cout << endl; - } - break; - } - - case Events::AXIS_ELEV_TRIM_SET: { - elevatorTrimHandler->onEventElevatorTrimAxisSet(static_cast(event->dwData)); - if (loggingFlightControlsEnabled) { - cout << "WASM: AXIS_ELEV_TRIM_SET: "; - cout << static_cast(event->dwData); - cout << " -> "; - cout << elevatorTrimHandler->getPosition(); - cout << endl; - } - break; - } - case Events::AUTOPILOT_OFF: { simInputAutopilot.AP_disconnect = 1; cout << "WASM: event triggered: AUTOPILOT_OFF" << endl; @@ -1616,7 +1926,8 @@ void SimConnectInterface::simConnectProcessEvent(const SIMCONNECT_RECV_EVENT* ev } else { execute_calculator_code( "3 (A:AUTOPILOT ALTITUDE LOCK VAR:3, feet) (L:XMLVAR_Autopilot_Altitude_Increment) - (L:XMLVAR_Autopilot_Altitude_Increment) " - "(A:AUTOPILOT ALTITUDE LOCK VAR:3, feet) (L:XMLVAR_Autopilot_Altitude_Increment) % - (L:XMLVAR_Autopilot_Altitude_Increment) % " + "(A:AUTOPILOT ALTITUDE LOCK VAR:3, feet) (L:XMLVAR_Autopilot_Altitude_Increment) % - (L:XMLVAR_Autopilot_Altitude_Increment) " + "% " "+ 100 max (>K:2:AP_ALT_VAR_SET_ENGLISH) (>H:AP_KNOB_Down) (>H:A320_Neo_CDU_AP_DEC_ALT)", nullptr, nullptr, nullptr); } @@ -2447,9 +2758,64 @@ void SimConnectInterface::simConnectProcessClientData(const SIMCONNECT_RECV_CLIE clientDataAutothrust = *((ClientDataAutothrust*)&data->dwData); return; - case ClientData::FLY_BY_WIRE: + case ClientData::ELAC_DISCRETE_OUTPUTS: + // store aircraft data + clientDataElacDiscreteOutputs = *((base_elac_discrete_outputs*)&data->dwData); + return; + + case ClientData::ELAC_ANALOG_OUTPUTS: + // store aircraft data + clientDataElacAnalogOutputs = *((base_elac_analog_outputs*)&data->dwData); + return; + + case ClientData::ELAC_1_BUS_OUTPUT: + // store aircraft data + clientDataElacBusOutputs = *((base_elac_out_bus*)&data->dwData); + return; + + case ClientData::ELAC_2_BUS_OUTPUT: + // store aircraft data + clientDataElacBusOutputs = *((base_elac_out_bus*)&data->dwData); + return; + + case ClientData::SEC_DISCRETE_OUTPUTS: + // store aircraft data + clientDataSecDiscreteOutputs = *((base_sec_discrete_outputs*)&data->dwData); + return; + + case ClientData::SEC_ANALOG_OUTPUTS: + // store aircraft data + clientDataSecAnalogOutputs = *((base_sec_analog_outputs*)&data->dwData); + return; + + case ClientData::SEC_1_BUS_OUTPUT: + // store aircraft data + clientDataSecBusOutputs = *((base_sec_out_bus*)&data->dwData); + return; + + case ClientData::SEC_2_BUS_OUTPUT: + // store aircraft data + clientDataSecBusOutputs = *((base_sec_out_bus*)&data->dwData); + return; + + case ClientData::FAC_DISCRETE_OUTPUTS: + // store aircraft data + clientDataFacDiscreteOutputs = *((base_fac_discrete_outputs*)&data->dwData); + return; + + case ClientData::FAC_ANALOG_OUTPUTS: + // store aircraft data + clientDataFacAnalogOutputs = *((base_fac_analog_outputs*)&data->dwData); + return; + + case ClientData::FAC_1_BUS_OUTPUT: + // store aircraft data + clientDataFacBusOutputs = *((base_fac_bus*)&data->dwData); + return; + + case ClientData::FAC_2_BUS_OUTPUT: // store aircraft data - clientDataFlyByWire = *((ClientDataFlyByWire*)&data->dwData); + clientDataFacBusOutputs = *((base_fac_bus*)&data->dwData); return; default: diff --git a/src/fbw/src/interface/SimConnectInterface.h b/src/fbw/src/interface/SimConnectInterface.h index b7495f09d6cb..29583eac0392 100644 --- a/src/fbw/src/interface/SimConnectInterface.h +++ b/src/fbw/src/interface/SimConnectInterface.h @@ -7,11 +7,14 @@ #include "../ElevatorTrimHandler.h" #include "../LocalVariable.h" -#include "../RudderTrimHandler.h" #include "../SpoilersHandler.h" #include "../ThrottleAxisMapping.h" #include "SimConnectData.h" +#include "../model/ElacComputer_types.h" +#include "../model/FacComputer_types.h" +#include "../model/SecComputer_types.h" + class SimConnectInterface { public: enum Events { @@ -36,10 +39,6 @@ class SimConnectInterface { ELEVATOR_SET, ELEV_DOWN, ELEV_UP, - ELEV_TRIM_DN, - ELEV_TRIM_UP, - ELEVATOR_TRIM_SET, - AXIS_ELEV_TRIM_SET, AP_MASTER, AUTOPILOT_OFF, AUTOPILOT_ON, @@ -187,10 +186,12 @@ class SimConnectInterface { bool autopilotStateMachineEnabled, bool autopilotLawsEnabled, bool flyByWireEnabled, + int elacDisabled, + int secDisabled, + int facDisabled, const std::vector>& throttleAxis, std::shared_ptr spoilersHandler, std::shared_ptr elevatorTrimHandler, - std::shared_ptr rudderTrimHandler, double keyChangeAileron, double keyChangeElevator, double keyChangeRudder, @@ -209,10 +210,6 @@ class SimConnectInterface { bool readData(); - bool sendData(SimOutput output); - - bool sendData(SimOutputEtaTrim output); - bool sendData(SimOutputZetaTrim output); bool sendData(SimOutputThrottles output); @@ -233,6 +230,8 @@ class SimConnectInterface { bool setClientDataLocalVariablesAutothrust(ClientDataLocalVariablesAutothrust output); + void resetSimInputRudderTrim(); + void resetSimInputAutopilot(); void resetSimInputThrottles(); @@ -243,6 +242,8 @@ class SimConnectInterface { SimInputAutopilot getSimInputAutopilot(); + SimInputRudderTrim getSimInputRudderTrim(); + SimInputThrottles getSimInputThrottles(); bool setClientDataAutopilotStateMachine(ClientDataAutopilotStateMachine output); @@ -258,6 +259,37 @@ class SimConnectInterface { bool setClientDataFlyByWire(ClientDataFlyByWire output); ClientDataFlyByWire getClientDataFlyByWire(); + bool setClientDataElacDiscretes(base_elac_discrete_inputs output); + bool setClientDataElacAnalog(base_elac_analog_inputs output); + bool setClientDataElacBusInput(base_elac_out_bus output, int elacIndex); + + base_elac_discrete_outputs getClientDataElacDiscretesOutput(); + base_elac_analog_outputs getClientDataElacAnalogsOutput(); + base_elac_out_bus getClientDataElacBusOutput(); + + bool setClientDataSecDiscretes(base_sec_discrete_inputs output); + bool setClientDataSecAnalog(base_sec_analog_inputs output); + bool setClientDataSecBus(base_sec_out_bus output, int secIndex); + + base_sec_discrete_outputs getClientDataSecDiscretesOutput(); + base_sec_analog_outputs getClientDataSecAnalogsOutput(); + base_sec_out_bus getClientDataSecBusOutput(); + + bool setClientDataFacDiscretes(base_fac_discrete_inputs output); + bool setClientDataFacAnalog(base_fac_analog_inputs output); + bool setClientDataFacBus(base_fac_bus output, int facIndex); + + base_fac_discrete_outputs getClientDataFacDiscretesOutput(); + base_fac_analog_outputs getClientDataFacAnalogsOutput(); + base_fac_bus getClientDataFacBusOutput(); + + bool setClientDataAdr(base_adr_bus output, int adrIndex); + bool setClientDataIr(base_ir_bus output, int irIndex); + bool setClientDataRa(base_ra_bus output, int raIndex); + bool setClientDataLgciu(base_lgciu_bus output, int lgciuIndex); + bool setClientDataSfcc(base_sfcc_bus output, int sfccIndex); + bool setClientDataFmgcB(base_fmgc_b_bus output, int fmgcIndex); + void setLoggingFlightControlsEnabled(bool enabled); bool getLoggingFlightControlsEnabled(); @@ -274,8 +306,38 @@ class SimConnectInterface { AUTOPILOT_STATE_MACHINE, AUTOPILOT_LAWS, AUTOTHRUST, - FLY_BY_WIRE_INPUT, - FLY_BY_WIRE, + ELAC_DISCRETE_INPUTS, + ELAC_ANALOG_INPUTS, + ELAC_DISCRETE_OUTPUTS, + ELAC_ANALOG_OUTPUTS, + ELAC_1_BUS_OUTPUT, + ELAC_2_BUS_OUTPUT, + SEC_DISCRETE_INPUTS, + SEC_ANALOG_INPUTS, + SEC_DISCRETE_OUTPUTS, + SEC_ANALOG_OUTPUTS, + SEC_1_BUS_OUTPUT, + SEC_2_BUS_OUTPUT, + FAC_DISCRETE_INPUTS, + FAC_ANALOG_INPUTS, + FAC_DISCRETE_OUTPUTS, + FAC_ANALOG_OUTPUTS, + FAC_1_BUS_OUTPUT, + FAC_2_BUS_OUTPUT, + ADR_1_INPUTS, + ADR_2_INPUTS, + ADR_3_INPUTS, + IR_1_INPUTS, + IR_2_INPUTS, + IR_3_INPUTS, + RA_1_BUS, + RA_2_BUS, + LGCIU_1_BUS, + LGCIU_2_BUS, + SFCC_1_BUS, + SFCC_2_BUS, + FMGC_1_B_BUS, + FMGC_2_B_BUS, LOCAL_VARIABLES, LOCAL_VARIABLES_AUTOTHRUST, }; @@ -290,6 +352,10 @@ class SimConnectInterface { bool limitSimulationRateByPerformance = true; bool clientDataEnabled = false; + int elacDisabled = -1; + int secDisabled = -1; + int facDisabled = -1; + // change to non-static when aileron events can be processed via SimConnect static bool loggingFlightControlsEnabled; bool loggingThrottlesEnabled = false; @@ -297,6 +363,7 @@ class SimConnectInterface { SimData simData = {}; // change to non-static when aileron events can be processed via SimConnect static SimInput simInput; + SimInputRudderTrim simInputRudderTrim = {}; SimInputAutopilot simInputAutopilot = {}; SimInputThrottles simInputThrottles = {}; @@ -304,13 +371,24 @@ class SimConnectInterface { std::shared_ptr spoilersHandler; std::shared_ptr elevatorTrimHandler; - std::shared_ptr rudderTrimHandler; ClientDataAutopilotStateMachine clientDataAutopilotStateMachine = {}; ClientDataAutopilotLaws clientDataAutopilotLaws = {}; ClientDataAutothrust clientDataAutothrust = {}; ClientDataFlyByWire clientDataFlyByWire = {}; + base_elac_discrete_outputs clientDataElacDiscreteOutputs = {}; + base_elac_analog_outputs clientDataElacAnalogOutputs = {}; + base_elac_out_bus clientDataElacBusOutputs = {}; + + base_sec_discrete_outputs clientDataSecDiscreteOutputs = {}; + base_sec_analog_outputs clientDataSecAnalogOutputs = {}; + base_sec_out_bus clientDataSecBusOutputs = {}; + + base_fac_discrete_outputs clientDataFacDiscreteOutputs = {}; + base_fac_analog_outputs clientDataFacAnalogOutputs = {}; + base_fac_bus clientDataFacBusOutputs = {}; + // change to non-static when aileron events can be processed via SimConnect static double flightControlsKeyChangeAileron; double flightControlsKeyChangeElevator = 0.0; diff --git a/src/fbw/src/model/ElacComputer.cpp b/src/fbw/src/model/ElacComputer.cpp new file mode 100644 index 000000000000..7f952767ddc1 --- /dev/null +++ b/src/fbw/src/model/ElacComputer.cpp @@ -0,0 +1,1921 @@ +#include "ElacComputer.h" +#include "ElacComputer_types.h" +#include "rtwtypes.h" +#include +#include "look2_binlxpw.h" +#include "look1_binlxpw.h" +#include "LateralNormalLaw.h" +#include "LateralDirectLaw.h" +#include "PitchNormalLaw.h" +#include "PitchAlternateLaw.h" +#include "PitchDirectLaw.h" + +const uint8_T ElacComputer_IN_Flight{ 1U }; + +const uint8_T ElacComputer_IN_FlightToGroundTransition{ 2U }; + +const uint8_T ElacComputer_IN_Ground{ 3U }; + +const uint8_T ElacComputer_IN_NO_ACTIVE_CHILD{ 0U }; + +const uint8_T ElacComputer_IN_Flying{ 1U }; + +const uint8_T ElacComputer_IN_Landed{ 2U }; + +const uint8_T ElacComputer_IN_Landing100ft{ 3U }; + +const uint8_T ElacComputer_IN_Takeoff100ft{ 4U }; + +const real_T ElacComputer_RGND{ 0.0 }; + +void ElacComputer::ElacComputer_MATLABFunction(const base_arinc_429 *rtu_u, boolean_T *rty_y) +{ + *rty_y = (rtu_u->SSM != static_cast(SignStatusMatrix::FailureWarning)); +} + +void ElacComputer::ElacComputer_MATLABFunction_j(const base_arinc_429 *rtu_u, real_T rtu_bit, uint32_T *rty_y) +{ + real32_T tmp; + uint32_T a; + tmp = std::round(rtu_u->Data); + if (tmp < 4.2949673E+9F) { + if (tmp >= 0.0F) { + a = static_cast(tmp); + } else { + a = 0U; + } + } else { + a = MAX_uint32_T; + } + + if (-(rtu_bit - 1.0) >= 0.0) { + if (-(rtu_bit - 1.0) <= 31.0) { + a <<= static_cast(-(rtu_bit - 1.0)); + } else { + a = 0U; + } + } else if (-(rtu_bit - 1.0) >= -31.0) { + a >>= static_cast(rtu_bit - 1.0); + } else { + a = 0U; + } + + *rty_y = a & 1U; +} + +void ElacComputer::ElacComputer_RateLimiter_Reset(rtDW_RateLimiter_ElacComputer_T *localDW) +{ + localDW->pY_not_empty = false; +} + +void ElacComputer::ElacComputer_RateLimiter(real_T rtu_u, real_T rtu_up, real_T rtu_lo, real_T rtu_Ts, real_T rtu_init, + real_T *rty_Y, rtDW_RateLimiter_ElacComputer_T *localDW) +{ + if (!localDW->pY_not_empty) { + localDW->pY = rtu_init; + localDW->pY_not_empty = true; + } + + localDW->pY += std::fmax(std::fmin(rtu_u - localDW->pY, std::abs(rtu_up) * rtu_Ts), -std::abs(rtu_lo) * rtu_Ts); + *rty_Y = localDW->pY; +} + +void ElacComputer::ElacComputer_RateLimiter_o_Reset(rtDW_RateLimiter_ElacComputer_g_T *localDW) +{ + localDW->pY_not_empty = false; +} + +void ElacComputer::ElacComputer_RateLimiter_a(real_T rtu_u, real_T rtu_up, real_T rtu_lo, real_T rtu_Ts, real_T rtu_init, + boolean_T rtu_reset, real_T *rty_Y, rtDW_RateLimiter_ElacComputer_g_T *localDW) +{ + if ((!localDW->pY_not_empty) || rtu_reset) { + localDW->pY = rtu_init; + localDW->pY_not_empty = true; + } + + if (rtu_reset) { + *rty_Y = rtu_init; + } else { + *rty_Y = std::fmax(std::fmin(rtu_u - localDW->pY, std::abs(rtu_up) * rtu_Ts), -std::abs(rtu_lo) * rtu_Ts) + + localDW->pY; + } + + localDW->pY = *rty_Y; +} + +void ElacComputer::ElacComputer_MATLABFunction_o(boolean_T rtu_bit1, boolean_T rtu_bit2, boolean_T rtu_bit3, boolean_T + rtu_bit4, boolean_T rtu_bit5, boolean_T rtu_bit6, real_T *rty_handleIndex) +{ + if (rtu_bit1) { + *rty_handleIndex = 0.0; + } else if (rtu_bit2 && rtu_bit6) { + *rty_handleIndex = 1.0; + } else if (rtu_bit2 && (!rtu_bit6)) { + *rty_handleIndex = 2.0; + } else if (rtu_bit3) { + *rty_handleIndex = 3.0; + } else if (rtu_bit4) { + *rty_handleIndex = 4.0; + } else if (rtu_bit5) { + *rty_handleIndex = 5.0; + } else { + *rty_handleIndex = 0.0; + } +} + +void ElacComputer::ElacComputer_LagFilter_Reset(rtDW_LagFilter_ElacComputer_T *localDW) +{ + localDW->pY_not_empty = false; + localDW->pU_not_empty = false; +} + +void ElacComputer::ElacComputer_LagFilter(real_T rtu_U, real_T rtu_C1, real_T rtu_dt, real_T *rty_Y, + rtDW_LagFilter_ElacComputer_T *localDW) +{ + real_T ca; + real_T denom_tmp; + if ((!localDW->pY_not_empty) || (!localDW->pU_not_empty)) { + localDW->pU = rtu_U; + localDW->pU_not_empty = true; + localDW->pY = rtu_U; + localDW->pY_not_empty = true; + } + + denom_tmp = rtu_dt * rtu_C1; + ca = denom_tmp / (denom_tmp + 2.0); + *rty_Y = (2.0 - denom_tmp) / (denom_tmp + 2.0) * localDW->pY + (rtu_U * ca + localDW->pU * ca); + localDW->pY = *rty_Y; + localDW->pU = rtu_U; +} + +void ElacComputer::ElacComputer_MATLABFunction_g5_Reset(rtDW_MATLABFunction_ElacComputer_kz_T *localDW) +{ + localDW->output = false; + localDW->timeSinceCondition = 0.0; +} + +void ElacComputer::ElacComputer_MATLABFunction_c(boolean_T rtu_u, real_T rtu_Ts, boolean_T rtu_isRisingEdge, real_T + rtu_timeDelay, boolean_T *rty_y, rtDW_MATLABFunction_ElacComputer_kz_T *localDW) +{ + if (rtu_u == rtu_isRisingEdge) { + localDW->timeSinceCondition += rtu_Ts; + if (localDW->timeSinceCondition >= rtu_timeDelay) { + localDW->output = rtu_u; + } + } else { + localDW->timeSinceCondition = 0.0; + localDW->output = rtu_u; + } + + *rty_y = localDW->output; +} + +void ElacComputer::ElacComputer_MATLABFunction_k_Reset(rtDW_MATLABFunction_ElacComputer_o_T *localDW) +{ + localDW->output = false; +} + +void ElacComputer::ElacComputer_MATLABFunction_m(real_T rtu_u, real_T rtu_highTrigger, real_T rtu_lowTrigger, boolean_T * + rty_y, rtDW_MATLABFunction_ElacComputer_o_T *localDW) +{ + boolean_T output_tmp; + output_tmp = !localDW->output; + localDW->output = ((output_tmp && (rtu_u >= rtu_highTrigger)) || ((output_tmp || (rtu_u > rtu_lowTrigger)) && + localDW->output)); + *rty_y = localDW->output; +} + +void ElacComputer::ElacComputer_GetIASforMach4(real_T rtu_m, real_T rtu_m_t, real_T rtu_v, real_T *rty_v_t) +{ + *rty_v_t = rtu_v * rtu_m_t / rtu_m; +} + +void ElacComputer::ElacComputer_RateLimiter_d_Reset(rtDW_RateLimiter_ElacComputer_b_T *localDW) +{ + localDW->pY_not_empty = false; +} + +void ElacComputer::ElacComputer_RateLimiter_n(real_T rtu_u, real_T rtu_up, real_T rtu_lo, real_T rtu_Ts, boolean_T + rtu_reset, real_T *rty_Y, rtDW_RateLimiter_ElacComputer_b_T *localDW) +{ + if ((!localDW->pY_not_empty) || rtu_reset) { + localDW->pY = rtu_u; + localDW->pY_not_empty = true; + } + + if (rtu_reset) { + *rty_Y = rtu_u; + } else { + *rty_Y = std::fmax(std::fmin(rtu_u - localDW->pY, std::abs(rtu_up) * rtu_Ts), -std::abs(rtu_lo) * rtu_Ts) + + localDW->pY; + } + + localDW->pY = *rty_Y; +} + +void ElacComputer::ElacComputer_MATLABFunction_ax_Reset(rtDW_MATLABFunction_ElacComputer_b_T *localDW) +{ + localDW->previousInput_not_empty = false; +} + +void ElacComputer::ElacComputer_MATLABFunction_g(boolean_T rtu_u, boolean_T rtu_isRisingEdge, boolean_T *rty_y, + rtDW_MATLABFunction_ElacComputer_b_T *localDW) +{ + if (!localDW->previousInput_not_empty) { + localDW->previousInput = rtu_isRisingEdge; + localDW->previousInput_not_empty = true; + } + + if (rtu_isRisingEdge) { + *rty_y = (rtu_u && (!localDW->previousInput)); + } else { + *rty_y = ((!rtu_u) && localDW->previousInput); + } + + localDW->previousInput = rtu_u; +} + +void ElacComputer::ElacComputer_MATLABFunction_cw(const boolean_T rtu_u[19], real32_T *rty_y) +{ + uint32_T out; + out = 0U; + for (int32_T i{0}; i < 19; i++) { + out |= static_cast(rtu_u[i]) << (i + 10); + } + + *rty_y = static_cast(out); +} + +void ElacComputer::ElacComputer_LateralLawCaptoBits(lateral_efcs_law rtu_law, boolean_T *rty_bit1, boolean_T *rty_bit2) +{ + *rty_bit1 = (rtu_law == lateral_efcs_law::NormalLaw); + *rty_bit2 = (rtu_law == lateral_efcs_law::DirectLaw); +} + +void ElacComputer::step() +{ + real_T rtb_xi_deg; + real_T rtb_zeta_deg; + real_T rtb_eta_deg; + real_T rtb_eta_trim_dot_deg_s; + real_T rtb_eta_trim_limit_lo; + real_T rtb_eta_trim_limit_up; + real_T rtb_eta_deg_o; + real_T rtb_eta_trim_dot_deg_s_a; + real_T rtb_eta_trim_limit_lo_h; + real_T rtb_eta_trim_limit_up_d; + const base_arinc_429 *rtb_Switch1_g_0; + real_T abnormalCondition_tmp; + real_T rtb_BusAssignment_c_logic_total_sidestick_roll_command; + real_T rtb_BusAssignment_f_logic_ir_computation_data_n_z_g; + real_T rtb_BusAssignment_f_logic_ir_computation_data_theta_dot_deg_s; + real_T rtb_BusAssignment_p_logic_high_speed_prot_hi_thresh_kn; + real_T rtb_BusConversion_InsertedFor_BusAssignment_at_inport_8_BusCreator1_V_tas_kn; + real_T rtb_BusConversion_InsertedFor_BusAssignment_at_inport_8_BusCreator1_mach; + real_T rtb_DataTypeConversion3_m; + real_T rtb_DataTypeConversion5; + real_T rtb_DataTypeConversion6_g; + real_T rtb_DataTypeConversion7; + real_T rtb_DataTypeConversion8; + real_T rtb_DataTypeConversion_o; + real_T rtb_Switch3_p; + real_T rtb_Y; + real_T rtb_Y_b; + real_T rtb_Y_c; + real_T rtb_Y_i_tmp_tmp; + real_T rtb_Y_k; + real_T rtb_eta_trim_limit_lo_d; + real_T rtb_handleIndex; + real_T rtb_handleIndex_f; + real_T rtb_handleIndex_i; + real_T rtb_xi_deg_m; + real_T rtb_zeta_deg_f; + real_T u0; + real32_T rtb_tla2; + real32_T rtb_y_m; + uint32_T rtb_DataTypeConversion1_j; + uint32_T rtb_Switch18; + uint32_T rtb_y_e; + uint32_T rtb_y_h; + boolean_T rtb_VectorConcatenate[19]; + boolean_T rtb_VectorConcatenate_a[19]; + boolean_T rtb_NOT_k; + boolean_T rtb_NOT_kl; + boolean_T rtb_OR1_hu; + boolean_T rtb_OR6; + boolean_T rtb_y_a; + boolean_T rtb_y_l; + if (ElacComputer_U.in.sim_data.computer_running) { + int32_T rtb_ap_special_disc; + real32_T rtb_V_ias; + real32_T rtb_V_tas; + real32_T rtb_alpha; + real32_T rtb_elevator_double_pressurization_command_deg_Data; + real32_T rtb_mach; + real32_T rtb_n_x; + real32_T rtb_n_y; + real32_T rtb_n_z; + real32_T rtb_phi; + real32_T rtb_phi_dot; + real32_T rtb_q; + real32_T rtb_r; + real32_T rtb_raComputationValue; + real32_T rtb_theta_dot; + real32_T rtb_tla1; + boolean_T abnormalCondition_tmp_0; + boolean_T alternate1Condition_tmp; + boolean_T canEngageInRoll; + boolean_T hasPriorityInPitch; + boolean_T hasPriorityInRoll; + boolean_T leftAileronAvail; + boolean_T rightAileronAvail; + boolean_T rtb_AND1; + boolean_T rtb_AND1_h; + boolean_T rtb_AND2; + boolean_T rtb_AND3_b; + boolean_T rtb_AND3_no_tmp; + boolean_T rtb_AND4; + boolean_T rtb_BusAssignment_n_logic_is_yellow_hydraulic_power_avail; + boolean_T rtb_DataTypeConversion_by; + boolean_T rtb_OR; + boolean_T rtb_OR1; + boolean_T rtb_OR1_me; + boolean_T rtb_OR3; + boolean_T rtb_OR4; + boolean_T rtb_OR7; + boolean_T rtb_OR_e1; + boolean_T rtb_aileronAntidroopActive; + boolean_T rtb_ap_authorised; + boolean_T rtb_doubleAdrFault; + boolean_T rtb_doubleIrFault; + boolean_T rtb_isEngagedInPitch; + boolean_T rtb_isEngagedInRoll; + boolean_T rtb_leftAileronCrossCommandActive; + boolean_T rtb_rightAileronCrossCommandActive; + boolean_T rtb_thsAvail_tmp; + boolean_T rtb_tripleAdrFault; + boolean_T rtb_tripleIrFault; + lateral_efcs_law priorityPitchLateralLawCap; + lateral_efcs_law rtb_activeLateralLaw; + lateral_efcs_law rtb_lateralLawCapability; + lateral_efcs_law rtb_oppElacRollCapability; + pitch_efcs_law priorityPitchPitchLawCap; + pitch_efcs_law rtb_pitchLawCapability; + if (!ElacComputer_DWork.Runtime_MODE) { + ElacComputer_DWork.Delay_DSTATE_cc = ElacComputer_P.Delay_InitialCondition_c; + ElacComputer_DWork.Delay1_DSTATE = ElacComputer_P.Delay1_InitialCondition; + ElacComputer_DWork.Memory_PreviousInput = ElacComputer_P.SRFlipFlop_initial_condition; + ElacComputer_DWork.Delay_DSTATE = ElacComputer_P.DiscreteDerivativeVariableTs_InitialCondition; + ElacComputer_DWork.Delay_DSTATE_b = ElacComputer_P.Delay_InitialCondition; + ElacComputer_DWork.icLoad = true; + ElacComputer_LagFilter_Reset(&ElacComputer_DWork.sf_LagFilter_a); + ElacComputer_MATLABFunction_g5_Reset(&ElacComputer_DWork.sf_MATLABFunction_jz); + ElacComputer_MATLABFunction_g5_Reset(&ElacComputer_DWork.sf_MATLABFunction_lf); + ElacComputer_MATLABFunction_g5_Reset(&ElacComputer_DWork.sf_MATLABFunction_jl); + ElacComputer_DWork.ra1CoherenceRejected = false; + ElacComputer_DWork.ra2CoherenceRejected = false; + ElacComputer_DWork.configFullEventTime_not_empty = false; + ElacComputer_DWork.is_active_c30_ElacComputer = 0U; + ElacComputer_DWork.is_c30_ElacComputer = ElacComputer_IN_NO_ACTIVE_CHILD; + ElacComputer_DWork.on_ground_time = 0.0; + ElacComputer_B.in_flight = 0.0; + ElacComputer_MATLABFunction_k_Reset(&ElacComputer_DWork.sf_MATLABFunction_jg); + ElacComputer_MATLABFunction_g5_Reset(&ElacComputer_DWork.sf_MATLABFunction_cj); + ElacComputer_MATLABFunction_k_Reset(&ElacComputer_DWork.sf_MATLABFunction_mi); + ElacComputer_MATLABFunction_g5_Reset(&ElacComputer_DWork.sf_MATLABFunction_g2); + ElacComputer_MATLABFunction_k_Reset(&ElacComputer_DWork.sf_MATLABFunction_br); + ElacComputer_MATLABFunction_g5_Reset(&ElacComputer_DWork.sf_MATLABFunction_gfx); + ElacComputer_MATLABFunction_ax_Reset(&ElacComputer_DWork.sf_MATLABFunction_g4); + ElacComputer_MATLABFunction_ax_Reset(&ElacComputer_DWork.sf_MATLABFunction_nu); + ElacComputer_DWork.pLeftStickDisabled = false; + ElacComputer_DWork.pRightStickDisabled = false; + ElacComputer_MATLABFunction_g5_Reset(&ElacComputer_DWork.sf_MATLABFunction_j2); + ElacComputer_MATLABFunction_g5_Reset(&ElacComputer_DWork.sf_MATLABFunction_g24); + ElacComputer_MATLABFunction_g5_Reset(&ElacComputer_DWork.sf_MATLABFunction_nb); + ElacComputer_DWork.abnormalConditionWasActive = false; + ElacComputer_MATLABFunction_ax_Reset(&ElacComputer_DWork.sf_MATLABFunction_l0); + ElacComputer_RateLimiter_d_Reset(&ElacComputer_DWork.sf_RateLimiter_n); + ElacComputer_DWork.eventTime_not_empty_a = false; + ElacComputer_RateLimiter_d_Reset(&ElacComputer_DWork.sf_RateLimiter_m); + ElacComputer_DWork.is_active_c28_ElacComputer = 0U; + ElacComputer_DWork.is_c28_ElacComputer = ElacComputer_IN_NO_ACTIVE_CHILD; + ElacComputer_DWork.eventTime_not_empty = false; + ElacComputer_DWork.sProtActive = false; + ElacComputer_DWork.resetEventTime_not_empty = false; + ElacComputer_DWork.sProtActive_m = false; + ElacComputer_RateLimiter_Reset(&ElacComputer_DWork.sf_RateLimiter); + ElacComputer_RateLimiter_Reset(&ElacComputer_DWork.sf_RateLimiter_b); + LawMDLOBJ2.reset(); + LawMDLOBJ1.reset(); + ElacComputer_RateLimiter_o_Reset(&ElacComputer_DWork.sf_RateLimiter_a); + ElacComputer_RateLimiter_o_Reset(&ElacComputer_DWork.sf_RateLimiter_p); + ElacComputer_LagFilter_Reset(&ElacComputer_DWork.sf_LagFilter); + LawMDLOBJ5.reset(); + LawMDLOBJ3.reset(); + LawMDLOBJ4.reset(); + ElacComputer_MATLABFunction_g5_Reset(&ElacComputer_DWork.sf_MATLABFunction_fb); + ElacComputer_DWork.Runtime_MODE = true; + } + + rtb_OR1 = ((ElacComputer_U.in.bus_inputs.adr_1_bus.mach.SSM == static_cast(SignStatusMatrix:: + FailureWarning)) || (ElacComputer_U.in.bus_inputs.adr_1_bus.airspeed_computed_kn.SSM == static_cast + (SignStatusMatrix::FailureWarning)) || (ElacComputer_U.in.bus_inputs.adr_1_bus.airspeed_true_kn.SSM == + static_cast(SignStatusMatrix::FailureWarning)) || + (ElacComputer_U.in.bus_inputs.adr_1_bus.aoa_corrected_deg.SSM == static_cast(SignStatusMatrix:: + FailureWarning)) || ElacComputer_P.Constant1_Value_b || ElacComputer_P.Constant1_Value_b); + rtb_OR3 = ((ElacComputer_U.in.bus_inputs.adr_2_bus.mach.SSM == static_cast(SignStatusMatrix:: + FailureWarning)) || (ElacComputer_U.in.bus_inputs.adr_2_bus.airspeed_computed_kn.SSM == static_cast + (SignStatusMatrix::FailureWarning)) || (ElacComputer_U.in.bus_inputs.adr_2_bus.airspeed_true_kn.SSM == + static_cast(SignStatusMatrix::FailureWarning)) || + (ElacComputer_U.in.bus_inputs.adr_2_bus.aoa_corrected_deg.SSM == static_cast(SignStatusMatrix:: + FailureWarning)) || ElacComputer_P.Constant1_Value_b || ElacComputer_P.Constant1_Value_b); + rtb_OR4 = ((ElacComputer_U.in.bus_inputs.adr_3_bus.mach.SSM == static_cast(SignStatusMatrix:: + FailureWarning)) || (ElacComputer_U.in.bus_inputs.adr_3_bus.airspeed_computed_kn.SSM == static_cast + (SignStatusMatrix::FailureWarning)) || (ElacComputer_U.in.bus_inputs.adr_3_bus.airspeed_true_kn.SSM == + static_cast(SignStatusMatrix::FailureWarning)) || + (ElacComputer_U.in.bus_inputs.adr_3_bus.aoa_corrected_deg.SSM == static_cast(SignStatusMatrix:: + FailureWarning)) || ElacComputer_P.Constant1_Value_b || ElacComputer_P.Constant1_Value_b); + rtb_doubleAdrFault = ((rtb_OR1 && rtb_OR3) || (rtb_OR1 && rtb_OR4) || (rtb_OR3 && rtb_OR4)); + rtb_tripleAdrFault = (rtb_OR1 && rtb_OR3 && rtb_OR4); + rtb_OR = ((ElacComputer_U.in.bus_inputs.ir_1_bus.pitch_angle_deg.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_1_bus.roll_angle_deg.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_1_bus.body_yaw_rate_deg_s.SSM != + static_cast(SignStatusMatrix::NormalOperation)) || + (ElacComputer_U.in.bus_inputs.ir_1_bus.body_long_accel_g.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_1_bus.body_lat_accel_g.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_1_bus.body_normal_accel_g.SSM != + static_cast(SignStatusMatrix::NormalOperation)) || + (ElacComputer_U.in.bus_inputs.ir_1_bus.pitch_att_rate_deg_s.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_1_bus.roll_att_rate_deg_s.SSM != + static_cast(SignStatusMatrix::NormalOperation)) || ElacComputer_P.Constant_Value_ad); + rtb_OR6 = ((ElacComputer_U.in.bus_inputs.ir_2_bus.pitch_angle_deg.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_2_bus.roll_angle_deg.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_2_bus.body_yaw_rate_deg_s.SSM + != static_cast(SignStatusMatrix::NormalOperation)) || + (ElacComputer_U.in.bus_inputs.ir_2_bus.body_long_accel_g.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_2_bus.body_lat_accel_g.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_2_bus.body_normal_accel_g.SSM + != static_cast(SignStatusMatrix::NormalOperation)) || + (ElacComputer_U.in.bus_inputs.ir_2_bus.pitch_att_rate_deg_s.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_2_bus.roll_att_rate_deg_s.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || ElacComputer_P.Constant_Value_ad); + rtb_OR7 = ((ElacComputer_U.in.bus_inputs.ir_3_bus.pitch_angle_deg.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_3_bus.roll_angle_deg.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_3_bus.body_yaw_rate_deg_s.SSM + != static_cast(SignStatusMatrix::NormalOperation)) || + (ElacComputer_U.in.bus_inputs.ir_3_bus.body_long_accel_g.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_3_bus.body_lat_accel_g.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_3_bus.body_normal_accel_g.SSM + != static_cast(SignStatusMatrix::NormalOperation)) || + (ElacComputer_U.in.bus_inputs.ir_3_bus.pitch_att_rate_deg_s.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (ElacComputer_U.in.bus_inputs.ir_3_bus.roll_att_rate_deg_s.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || ElacComputer_P.Constant_Value_ad); + rtb_tripleIrFault = (rtb_OR && rtb_OR6); + rtb_AND2 = (rtb_OR && rtb_OR7); + rtb_doubleIrFault = (rtb_tripleIrFault || rtb_AND2 || (rtb_OR6 && rtb_OR7)); + rtb_tripleIrFault = (rtb_tripleIrFault && rtb_OR7); + rtb_ap_authorised = !rtb_OR4; + rtb_AND1 = !rtb_OR3; + if (rtb_OR1 && rtb_AND1 && rtb_ap_authorised) { + rtb_V_ias = (ElacComputer_U.in.bus_inputs.adr_2_bus.airspeed_computed_kn.Data + + ElacComputer_U.in.bus_inputs.adr_3_bus.airspeed_computed_kn.Data) / 2.0F; + rtb_V_tas = (ElacComputer_U.in.bus_inputs.adr_2_bus.airspeed_true_kn.Data + + ElacComputer_U.in.bus_inputs.adr_3_bus.airspeed_true_kn.Data) / 2.0F; + rtb_mach = (ElacComputer_U.in.bus_inputs.adr_2_bus.mach.Data + ElacComputer_U.in.bus_inputs.adr_3_bus.mach.Data) / + 2.0F; + rtb_alpha = (ElacComputer_U.in.bus_inputs.adr_2_bus.aoa_corrected_deg.Data + + ElacComputer_U.in.bus_inputs.adr_3_bus.aoa_corrected_deg.Data) / 2.0F; + } else if ((!rtb_OR1) && rtb_OR3 && rtb_ap_authorised) { + rtb_V_ias = (ElacComputer_U.in.bus_inputs.adr_1_bus.airspeed_computed_kn.Data + + ElacComputer_U.in.bus_inputs.adr_3_bus.airspeed_computed_kn.Data) / 2.0F; + rtb_V_tas = (ElacComputer_U.in.bus_inputs.adr_1_bus.airspeed_true_kn.Data + + ElacComputer_U.in.bus_inputs.adr_3_bus.airspeed_true_kn.Data) / 2.0F; + rtb_mach = (ElacComputer_U.in.bus_inputs.adr_1_bus.mach.Data + ElacComputer_U.in.bus_inputs.adr_3_bus.mach.Data) / + 2.0F; + rtb_alpha = (ElacComputer_U.in.bus_inputs.adr_1_bus.aoa_corrected_deg.Data + + ElacComputer_U.in.bus_inputs.adr_3_bus.aoa_corrected_deg.Data) / 2.0F; + } else if (((!rtb_OR1) && rtb_AND1 && rtb_ap_authorised) || ((!rtb_OR1) && rtb_AND1 && rtb_OR4)) { + rtb_V_ias = (ElacComputer_U.in.bus_inputs.adr_1_bus.airspeed_computed_kn.Data + + ElacComputer_U.in.bus_inputs.adr_2_bus.airspeed_computed_kn.Data) / 2.0F; + rtb_V_tas = (ElacComputer_U.in.bus_inputs.adr_1_bus.airspeed_true_kn.Data + + ElacComputer_U.in.bus_inputs.adr_2_bus.airspeed_true_kn.Data) / 2.0F; + rtb_mach = (ElacComputer_U.in.bus_inputs.adr_1_bus.mach.Data + ElacComputer_U.in.bus_inputs.adr_2_bus.mach.Data) / + 2.0F; + rtb_alpha = (ElacComputer_U.in.bus_inputs.adr_1_bus.aoa_corrected_deg.Data + + ElacComputer_U.in.bus_inputs.adr_2_bus.aoa_corrected_deg.Data) / 2.0F; + } else if ((!rtb_OR1) && rtb_OR3 && rtb_OR4) { + rtb_V_ias = ElacComputer_U.in.bus_inputs.adr_1_bus.airspeed_computed_kn.Data; + rtb_V_tas = ElacComputer_U.in.bus_inputs.adr_1_bus.airspeed_true_kn.Data; + rtb_mach = ElacComputer_U.in.bus_inputs.adr_1_bus.mach.Data; + rtb_alpha = ElacComputer_U.in.bus_inputs.adr_1_bus.aoa_corrected_deg.Data; + } else if (rtb_OR1 && rtb_AND1 && rtb_OR4) { + rtb_V_ias = ElacComputer_U.in.bus_inputs.adr_2_bus.airspeed_computed_kn.Data; + rtb_V_tas = ElacComputer_U.in.bus_inputs.adr_2_bus.airspeed_true_kn.Data; + rtb_mach = ElacComputer_U.in.bus_inputs.adr_2_bus.mach.Data; + rtb_alpha = ElacComputer_U.in.bus_inputs.adr_2_bus.aoa_corrected_deg.Data; + } else if (rtb_OR1 && rtb_OR3 && rtb_ap_authorised) { + rtb_V_ias = ElacComputer_U.in.bus_inputs.adr_3_bus.airspeed_computed_kn.Data; + rtb_V_tas = ElacComputer_U.in.bus_inputs.adr_3_bus.airspeed_true_kn.Data; + rtb_mach = ElacComputer_U.in.bus_inputs.adr_3_bus.mach.Data; + rtb_alpha = ElacComputer_U.in.bus_inputs.adr_3_bus.aoa_corrected_deg.Data; + } else { + rtb_V_ias = 0.0F; + rtb_V_tas = 0.0F; + rtb_mach = 0.0F; + rtb_alpha = 0.0F; + } + + ElacComputer_LagFilter(static_cast(rtb_alpha), ElacComputer_P.LagFilter_C1, ElacComputer_U.in.time.dt, + &rtb_Y, &ElacComputer_DWork.sf_LagFilter_a); + rtb_eta_trim_limit_lo_d = rtb_V_ias; + rtb_BusConversion_InsertedFor_BusAssignment_at_inport_8_BusCreator1_V_tas_kn = rtb_V_tas; + rtb_BusConversion_InsertedFor_BusAssignment_at_inport_8_BusCreator1_mach = rtb_mach; + rtb_ap_authorised = !rtb_OR6; + rtb_AND1 = !rtb_OR7; + rtb_OR1 = (rtb_OR && rtb_AND1); + if (rtb_OR1 && rtb_ap_authorised) { + rtb_alpha = (ElacComputer_U.in.bus_inputs.ir_2_bus.pitch_angle_deg.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.pitch_angle_deg.Data) / 2.0F; + rtb_phi = (ElacComputer_U.in.bus_inputs.ir_2_bus.roll_angle_deg.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.roll_angle_deg.Data) / 2.0F; + rtb_q = (ElacComputer_U.in.bus_inputs.ir_2_bus.body_pitch_rate_deg_s.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.body_pitch_rate_deg_s.Data) / 2.0F; + rtb_r = (ElacComputer_U.in.bus_inputs.ir_2_bus.body_yaw_rate_deg_s.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.body_yaw_rate_deg_s.Data) / 2.0F; + rtb_n_x = (ElacComputer_U.in.bus_inputs.ir_2_bus.body_long_accel_g.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.body_long_accel_g.Data) / 2.0F; + rtb_n_y = (ElacComputer_U.in.bus_inputs.ir_2_bus.body_lat_accel_g.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.body_lat_accel_g.Data) / 2.0F; + rtb_n_z = (ElacComputer_U.in.bus_inputs.ir_2_bus.body_normal_accel_g.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.body_normal_accel_g.Data) / 2.0F; + rtb_theta_dot = (ElacComputer_U.in.bus_inputs.ir_2_bus.pitch_att_rate_deg_s.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.pitch_att_rate_deg_s.Data) / 2.0F; + rtb_phi_dot = (ElacComputer_U.in.bus_inputs.ir_2_bus.roll_att_rate_deg_s.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.roll_att_rate_deg_s.Data) / 2.0F; + } else { + rtb_OR = !rtb_OR; + rtb_OR7 = (rtb_OR && rtb_OR7); + if (rtb_OR7 && rtb_ap_authorised) { + rtb_alpha = (ElacComputer_U.in.bus_inputs.ir_1_bus.pitch_angle_deg.Data + + ElacComputer_U.in.bus_inputs.ir_2_bus.pitch_angle_deg.Data) / 2.0F; + rtb_phi = (ElacComputer_U.in.bus_inputs.ir_1_bus.roll_angle_deg.Data + + ElacComputer_U.in.bus_inputs.ir_2_bus.roll_angle_deg.Data) / 2.0F; + rtb_q = (ElacComputer_U.in.bus_inputs.ir_1_bus.body_pitch_rate_deg_s.Data + + ElacComputer_U.in.bus_inputs.ir_2_bus.body_pitch_rate_deg_s.Data) / 2.0F; + rtb_r = (ElacComputer_U.in.bus_inputs.ir_1_bus.body_yaw_rate_deg_s.Data + + ElacComputer_U.in.bus_inputs.ir_2_bus.body_yaw_rate_deg_s.Data) / 2.0F; + rtb_n_x = (ElacComputer_U.in.bus_inputs.ir_1_bus.body_long_accel_g.Data + + ElacComputer_U.in.bus_inputs.ir_2_bus.body_long_accel_g.Data) / 2.0F; + rtb_n_y = (ElacComputer_U.in.bus_inputs.ir_1_bus.body_lat_accel_g.Data + + ElacComputer_U.in.bus_inputs.ir_2_bus.body_lat_accel_g.Data) / 2.0F; + rtb_n_z = (ElacComputer_U.in.bus_inputs.ir_1_bus.body_normal_accel_g.Data + + ElacComputer_U.in.bus_inputs.ir_2_bus.body_normal_accel_g.Data) / 2.0F; + rtb_theta_dot = (ElacComputer_U.in.bus_inputs.ir_1_bus.pitch_att_rate_deg_s.Data + + ElacComputer_U.in.bus_inputs.ir_2_bus.pitch_att_rate_deg_s.Data) / 2.0F; + rtb_phi_dot = (ElacComputer_U.in.bus_inputs.ir_1_bus.roll_att_rate_deg_s.Data + + ElacComputer_U.in.bus_inputs.ir_2_bus.roll_att_rate_deg_s.Data) / 2.0F; + } else { + rtb_AND1 = (rtb_OR && rtb_AND1); + if ((rtb_AND1 && rtb_ap_authorised) || (rtb_AND1 && rtb_OR6)) { + rtb_alpha = (ElacComputer_U.in.bus_inputs.ir_1_bus.pitch_angle_deg.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.pitch_angle_deg.Data) / 2.0F; + rtb_phi = (ElacComputer_U.in.bus_inputs.ir_1_bus.roll_angle_deg.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.roll_angle_deg.Data) / 2.0F; + rtb_q = (ElacComputer_U.in.bus_inputs.ir_1_bus.body_pitch_rate_deg_s.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.body_pitch_rate_deg_s.Data) / 2.0F; + rtb_r = (ElacComputer_U.in.bus_inputs.ir_1_bus.body_yaw_rate_deg_s.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.body_yaw_rate_deg_s.Data) / 2.0F; + rtb_n_x = (ElacComputer_U.in.bus_inputs.ir_1_bus.body_long_accel_g.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.body_long_accel_g.Data) / 2.0F; + rtb_n_y = (ElacComputer_U.in.bus_inputs.ir_1_bus.body_lat_accel_g.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.body_lat_accel_g.Data) / 2.0F; + rtb_n_z = (ElacComputer_U.in.bus_inputs.ir_1_bus.body_normal_accel_g.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.body_normal_accel_g.Data) / 2.0F; + rtb_theta_dot = (ElacComputer_U.in.bus_inputs.ir_1_bus.pitch_att_rate_deg_s.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.pitch_att_rate_deg_s.Data) / 2.0F; + rtb_phi_dot = (ElacComputer_U.in.bus_inputs.ir_1_bus.roll_att_rate_deg_s.Data + + ElacComputer_U.in.bus_inputs.ir_3_bus.roll_att_rate_deg_s.Data) / 2.0F; + } else if (rtb_OR7 && rtb_OR6) { + rtb_alpha = ElacComputer_U.in.bus_inputs.ir_1_bus.pitch_angle_deg.Data; + rtb_phi = ElacComputer_U.in.bus_inputs.ir_1_bus.roll_angle_deg.Data; + rtb_q = ElacComputer_U.in.bus_inputs.ir_1_bus.body_pitch_rate_deg_s.Data; + rtb_r = ElacComputer_U.in.bus_inputs.ir_1_bus.body_yaw_rate_deg_s.Data; + rtb_n_x = ElacComputer_U.in.bus_inputs.ir_1_bus.body_long_accel_g.Data; + rtb_n_y = ElacComputer_U.in.bus_inputs.ir_1_bus.body_lat_accel_g.Data; + rtb_n_z = ElacComputer_U.in.bus_inputs.ir_1_bus.body_normal_accel_g.Data; + rtb_theta_dot = ElacComputer_U.in.bus_inputs.ir_1_bus.pitch_att_rate_deg_s.Data; + rtb_phi_dot = ElacComputer_U.in.bus_inputs.ir_1_bus.roll_att_rate_deg_s.Data; + } else if (rtb_OR1 && rtb_OR6) { + rtb_alpha = ElacComputer_U.in.bus_inputs.ir_3_bus.pitch_angle_deg.Data; + rtb_phi = ElacComputer_U.in.bus_inputs.ir_3_bus.roll_angle_deg.Data; + rtb_q = ElacComputer_U.in.bus_inputs.ir_3_bus.body_pitch_rate_deg_s.Data; + rtb_r = ElacComputer_U.in.bus_inputs.ir_3_bus.body_yaw_rate_deg_s.Data; + rtb_n_x = ElacComputer_U.in.bus_inputs.ir_3_bus.body_long_accel_g.Data; + rtb_n_y = ElacComputer_U.in.bus_inputs.ir_3_bus.body_lat_accel_g.Data; + rtb_n_z = ElacComputer_U.in.bus_inputs.ir_3_bus.body_normal_accel_g.Data; + rtb_theta_dot = ElacComputer_U.in.bus_inputs.ir_3_bus.pitch_att_rate_deg_s.Data; + rtb_phi_dot = ElacComputer_U.in.bus_inputs.ir_3_bus.roll_att_rate_deg_s.Data; + } else if (rtb_AND2 && rtb_ap_authorised) { + rtb_alpha = ElacComputer_U.in.bus_inputs.ir_2_bus.pitch_angle_deg.Data; + rtb_phi = ElacComputer_U.in.bus_inputs.ir_2_bus.roll_angle_deg.Data; + rtb_q = ElacComputer_U.in.bus_inputs.ir_2_bus.body_pitch_rate_deg_s.Data; + rtb_r = ElacComputer_U.in.bus_inputs.ir_2_bus.body_yaw_rate_deg_s.Data; + rtb_n_x = ElacComputer_U.in.bus_inputs.ir_2_bus.body_long_accel_g.Data; + rtb_n_y = ElacComputer_U.in.bus_inputs.ir_2_bus.body_lat_accel_g.Data; + rtb_n_z = ElacComputer_U.in.bus_inputs.ir_2_bus.body_normal_accel_g.Data; + rtb_theta_dot = ElacComputer_U.in.bus_inputs.ir_2_bus.pitch_att_rate_deg_s.Data; + rtb_phi_dot = ElacComputer_U.in.bus_inputs.ir_2_bus.roll_att_rate_deg_s.Data; + } else { + rtb_alpha = 0.0F; + rtb_phi = 0.0F; + rtb_q = 0.0F; + rtb_r = 0.0F; + rtb_n_x = 0.0F; + rtb_n_y = 0.0F; + rtb_n_z = 0.0F; + rtb_theta_dot = 0.0F; + rtb_phi_dot = 0.0F; + } + } + } + + rtb_Y_i_tmp_tmp = rtb_alpha; + rtb_Y_b = rtb_alpha; + rtb_DataTypeConversion5 = rtb_phi; + rtb_DataTypeConversion8 = rtb_r; + rtb_zeta_deg_f = rtb_r; + rtb_xi_deg_m = rtb_phi_dot; + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_actual_position_word, + ElacComputer_P.BitfromLabel_bit, &rtb_y_h); + ElacComputer_MATLABFunction(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_actual_position_word, &rtb_y_a); + rtb_AND1 = ((rtb_y_h != 0U) && rtb_y_a); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_2_bus.slat_flap_actual_position_word, + ElacComputer_P.BitfromLabel1_bit, &rtb_y_h); + ElacComputer_MATLABFunction(&ElacComputer_U.in.bus_inputs.sfcc_2_bus.slat_flap_actual_position_word, &rtb_y_l); + rtb_AND2 = ((rtb_y_h != 0U) && rtb_y_l); + ElacComputer_MATLABFunction_c(std::abs(ElacComputer_U.in.bus_inputs.ra_1_bus.radio_height_ft.Data - + ElacComputer_U.in.bus_inputs.ra_2_bus.radio_height_ft.Data) > ElacComputer_P.CompareToConstant_const_ll, + ElacComputer_U.in.time.dt, ElacComputer_P.ConfirmNode_isRisingEdge, ElacComputer_P.ConfirmNode_timeDelay, + &rtb_NOT_k, &ElacComputer_DWork.sf_MATLABFunction_jz); + rtb_DataTypeConversion_by = (rtb_doubleAdrFault && ElacComputer_P.Constant1_Value_b); + rtb_OR6 = (rtb_tripleAdrFault || rtb_DataTypeConversion_by); + ElacComputer_MATLABFunction_c((ElacComputer_U.in.bus_inputs.ra_1_bus.radio_height_ft.Data > 50.0F) && + (ElacComputer_U.in.bus_inputs.ra_1_bus.radio_height_ft.SSM == static_cast(SignStatusMatrix:: + NormalOperation)) && (rtb_V_ias > 200.0F) && rtb_OR6, ElacComputer_U.in.time.dt, + ElacComputer_P.ConfirmNode2_isRisingEdge, ElacComputer_P.ConfirmNode2_timeDelay, &rtb_y_l, + &ElacComputer_DWork.sf_MATLABFunction_lf); + ElacComputer_MATLABFunction_c((ElacComputer_U.in.bus_inputs.ra_2_bus.radio_height_ft.Data > 50.0F) && + (ElacComputer_U.in.bus_inputs.ra_2_bus.radio_height_ft.SSM == static_cast(SignStatusMatrix:: + NormalOperation)) && (rtb_V_ias > 200.0F) && rtb_OR6, ElacComputer_U.in.time.dt, + ElacComputer_P.ConfirmNode1_isRisingEdge, ElacComputer_P.ConfirmNode1_timeDelay, &rtb_y_a, + &ElacComputer_DWork.sf_MATLABFunction_jl); + ElacComputer_DWork.ra1CoherenceRejected = (rtb_y_l || ElacComputer_DWork.ra1CoherenceRejected); + ElacComputer_DWork.ra2CoherenceRejected = (rtb_y_a || ElacComputer_DWork.ra2CoherenceRejected); + rtb_OR7 = ((ElacComputer_U.in.bus_inputs.ra_1_bus.radio_height_ft.SSM == static_cast(SignStatusMatrix:: + FailureWarning)) || ElacComputer_DWork.ra1CoherenceRejected); + rtb_OR = ((ElacComputer_U.in.bus_inputs.ra_2_bus.radio_height_ft.SSM == static_cast(SignStatusMatrix:: + FailureWarning)) || ElacComputer_DWork.ra2CoherenceRejected); + if (!ElacComputer_DWork.configFullEventTime_not_empty) { + ElacComputer_DWork.configFullEventTime = ElacComputer_U.in.time.simulation_time; + ElacComputer_DWork.configFullEventTime_not_empty = true; + } + + if ((!rtb_AND1) && (!rtb_AND2)) { + ElacComputer_DWork.configFullEventTime = ElacComputer_U.in.time.simulation_time; + } + + rtb_ap_authorised = !rtb_OR; + rtb_AND1 = !rtb_OR7; + if (rtb_AND1 && rtb_ap_authorised) { + if (rtb_NOT_k) { + if (ElacComputer_U.in.time.simulation_time > ElacComputer_DWork.configFullEventTime + 10.0) { + rtb_raComputationValue = std::fmin(ElacComputer_U.in.bus_inputs.ra_1_bus.radio_height_ft.Data, + ElacComputer_U.in.bus_inputs.ra_2_bus.radio_height_ft.Data); + } else { + rtb_raComputationValue = 250.0F; + } + } else { + rtb_raComputationValue = (ElacComputer_U.in.bus_inputs.ra_1_bus.radio_height_ft.Data + + ElacComputer_U.in.bus_inputs.ra_2_bus.radio_height_ft.Data) / 2.0F; + } + } else if ((rtb_OR7 && rtb_ap_authorised) || (rtb_AND1 && rtb_OR)) { + if ((rtb_V_ias > 180.0F) && rtb_OR6) { + rtb_raComputationValue = 250.0F; + } else if (rtb_OR) { + rtb_raComputationValue = ElacComputer_U.in.bus_inputs.ra_1_bus.radio_height_ft.Data; + } else { + rtb_raComputationValue = ElacComputer_U.in.bus_inputs.ra_2_bus.radio_height_ft.Data; + } + } else { + rtb_raComputationValue = 250.0F; + } + + rtb_AND1 = (rtb_OR7 && rtb_OR); + rtb_AND1_h = ((rtb_raComputationValue < ElacComputer_P.CompareToConstant_const) && (!rtb_AND1)); + rtb_OR6 = (ElacComputer_U.in.discrete_inputs.lgciu_1_left_main_gear_pressed && + ElacComputer_U.in.discrete_inputs.lgciu_1_right_main_gear_pressed); + rtb_aileronAntidroopActive = (ElacComputer_U.in.discrete_inputs.ground_spoilers_active_1 && + ElacComputer_U.in.discrete_inputs.ground_spoilers_active_2); + rtb_OR6 = ((rtb_OR6 && ElacComputer_U.in.discrete_inputs.lgciu_2_left_main_gear_pressed && + ElacComputer_U.in.discrete_inputs.lgciu_2_right_main_gear_pressed) || ((rtb_OR6 || + (ElacComputer_U.in.discrete_inputs.lgciu_2_left_main_gear_pressed && + ElacComputer_U.in.discrete_inputs.lgciu_2_right_main_gear_pressed)) && rtb_AND1_h) || (rtb_AND1_h && + rtb_aileronAntidroopActive)); + rtb_AND2 = (ElacComputer_U.in.sim_data.slew_on || ElacComputer_U.in.sim_data.pause_on || + ElacComputer_U.in.sim_data.tracking_mode_on_override); + if (ElacComputer_DWork.is_active_c30_ElacComputer == 0U) { + ElacComputer_DWork.is_active_c30_ElacComputer = 1U; + ElacComputer_DWork.is_c30_ElacComputer = ElacComputer_IN_Ground; + ElacComputer_B.in_flight = 0.0; + } else { + switch (ElacComputer_DWork.is_c30_ElacComputer) { + case ElacComputer_IN_Flight: + if (rtb_OR6 && (rtb_alpha < 2.5F)) { + ElacComputer_DWork.on_ground_time = ElacComputer_U.in.time.simulation_time; + ElacComputer_DWork.is_c30_ElacComputer = ElacComputer_IN_FlightToGroundTransition; + } else { + ElacComputer_B.in_flight = 1.0; + } + break; + + case ElacComputer_IN_FlightToGroundTransition: + if (ElacComputer_U.in.time.simulation_time - ElacComputer_DWork.on_ground_time >= 5.0) { + ElacComputer_DWork.is_c30_ElacComputer = ElacComputer_IN_Ground; + ElacComputer_B.in_flight = 0.0; + } else if ((!rtb_OR6) || (rtb_alpha >= 2.5F)) { + ElacComputer_DWork.on_ground_time = 0.0; + ElacComputer_DWork.is_c30_ElacComputer = ElacComputer_IN_Flight; + ElacComputer_B.in_flight = 1.0; + } + break; + + default: + if (((!rtb_OR6) && (rtb_alpha > 8.0F)) || (rtb_raComputationValue > 400.0F)) { + ElacComputer_DWork.on_ground_time = 0.0; + ElacComputer_DWork.is_c30_ElacComputer = ElacComputer_IN_Flight; + ElacComputer_B.in_flight = 1.0; + } else { + ElacComputer_B.in_flight = 0.0; + } + break; + } + } + + ElacComputer_MATLABFunction_m(ElacComputer_U.in.analog_inputs.yellow_hyd_pressure_psi, + ElacComputer_P.HysteresisNode2_highTrigger, ElacComputer_P.HysteresisNode2_lowTrigger, &rtb_y_l, + &ElacComputer_DWork.sf_MATLABFunction_jg); + ElacComputer_MATLABFunction_c((!ElacComputer_U.in.discrete_inputs.yellow_low_pressure) && rtb_y_l, + ElacComputer_U.in.time.dt, ElacComputer_P.ConfirmNode_isRisingEdge_k, ElacComputer_P.ConfirmNode_timeDelay_n, + &rtb_NOT_k, &ElacComputer_DWork.sf_MATLABFunction_cj); + ElacComputer_MATLABFunction_m(ElacComputer_U.in.analog_inputs.blue_hyd_pressure_psi, + ElacComputer_P.HysteresisNode1_highTrigger, ElacComputer_P.HysteresisNode1_lowTrigger, &rtb_y_l, + &ElacComputer_DWork.sf_MATLABFunction_mi); + ElacComputer_MATLABFunction_c((!ElacComputer_U.in.discrete_inputs.blue_low_pressure) && rtb_y_l, + ElacComputer_U.in.time.dt, ElacComputer_P.ConfirmNode1_isRisingEdge_i, ElacComputer_P.ConfirmNode1_timeDelay_h, + &rtb_y_a, &ElacComputer_DWork.sf_MATLABFunction_g2); + ElacComputer_MATLABFunction_m(ElacComputer_U.in.analog_inputs.green_hyd_pressure_psi, + ElacComputer_P.HysteresisNode3_highTrigger, ElacComputer_P.HysteresisNode3_lowTrigger, &rtb_y_l, + &ElacComputer_DWork.sf_MATLABFunction_br); + ElacComputer_MATLABFunction_c((!ElacComputer_U.in.discrete_inputs.green_low_pressure) && rtb_y_l, + ElacComputer_U.in.time.dt, ElacComputer_P.ConfirmNode2_isRisingEdge_j, ElacComputer_P.ConfirmNode2_timeDelay_k, + &rtb_y_l, &ElacComputer_DWork.sf_MATLABFunction_gfx); + rtb_BusAssignment_f_logic_ir_computation_data_n_z_g = rtb_n_z; + rtb_BusAssignment_f_logic_ir_computation_data_theta_dot_deg_s = rtb_theta_dot; + rtb_handleIndex_i = rtb_raComputationValue; + rtb_OR7 = rtb_y_a; + rtb_OR = rtb_y_l; + ElacComputer_MATLABFunction_g(ElacComputer_U.in.discrete_inputs.capt_priority_takeover_pressed, + ElacComputer_P.PulseNode_isRisingEdge, &rtb_y_a, &ElacComputer_DWork.sf_MATLABFunction_g4); + ElacComputer_MATLABFunction_g(ElacComputer_U.in.discrete_inputs.fo_priority_takeover_pressed, + ElacComputer_P.PulseNode1_isRisingEdge, &rtb_y_l, &ElacComputer_DWork.sf_MATLABFunction_nu); + if (rtb_y_a) { + ElacComputer_DWork.pRightStickDisabled = true; + ElacComputer_DWork.pLeftStickDisabled = false; + } else if (rtb_y_l) { + ElacComputer_DWork.pLeftStickDisabled = true; + ElacComputer_DWork.pRightStickDisabled = false; + } + + if (ElacComputer_DWork.pRightStickDisabled && ((!ElacComputer_U.in.discrete_inputs.capt_priority_takeover_pressed) && + (!ElacComputer_DWork.Delay1_DSTATE))) { + ElacComputer_DWork.pRightStickDisabled = false; + } else if (ElacComputer_DWork.pLeftStickDisabled) { + ElacComputer_DWork.pLeftStickDisabled = (ElacComputer_U.in.discrete_inputs.fo_priority_takeover_pressed || + ElacComputer_DWork.Delay_DSTATE_cc); + } + + ElacComputer_MATLABFunction_c(ElacComputer_DWork.pLeftStickDisabled && + (ElacComputer_U.in.discrete_inputs.fo_priority_takeover_pressed || ElacComputer_DWork.Delay_DSTATE_cc), + ElacComputer_U.in.time.dt, ElacComputer_P.ConfirmNode1_isRisingEdge_k, ElacComputer_P.ConfirmNode1_timeDelay_a, + &ElacComputer_DWork.Delay_DSTATE_cc, &ElacComputer_DWork.sf_MATLABFunction_j2); + ElacComputer_MATLABFunction_c(ElacComputer_DWork.pRightStickDisabled && + (ElacComputer_U.in.discrete_inputs.capt_priority_takeover_pressed || ElacComputer_DWork.Delay1_DSTATE), + ElacComputer_U.in.time.dt, ElacComputer_P.ConfirmNode_isRisingEdge_j, ElacComputer_P.ConfirmNode_timeDelay_a, + &ElacComputer_DWork.Delay1_DSTATE, &ElacComputer_DWork.sf_MATLABFunction_g24); + if (ElacComputer_DWork.pLeftStickDisabled) { + rtb_DataTypeConversion3_m = ElacComputer_P.Constant1_Value_p; + } else { + rtb_DataTypeConversion3_m = ElacComputer_U.in.analog_inputs.capt_roll_stick_pos; + } + + if (!ElacComputer_DWork.pRightStickDisabled) { + rtb_BusAssignment_p_logic_high_speed_prot_hi_thresh_kn = ElacComputer_U.in.analog_inputs.fo_roll_stick_pos; + } else { + rtb_BusAssignment_p_logic_high_speed_prot_hi_thresh_kn = ElacComputer_P.Constant1_Value_p; + } + + rtb_Y_k = rtb_BusAssignment_p_logic_high_speed_prot_hi_thresh_kn + rtb_DataTypeConversion3_m; + if (rtb_Y_k > ElacComputer_P.Saturation1_UpperSat) { + rtb_Y_k = ElacComputer_P.Saturation1_UpperSat; + } else if (rtb_Y_k < ElacComputer_P.Saturation1_LowerSat) { + rtb_Y_k = ElacComputer_P.Saturation1_LowerSat; + } + + if (ElacComputer_U.in.discrete_inputs.is_unit_1) { + rtb_OR1 = ((!ElacComputer_U.in.discrete_inputs.l_elev_servo_failed) && rtb_OR7); + rtb_OR3 = ((!ElacComputer_U.in.discrete_inputs.r_elev_servo_failed) && rtb_OR7); + } else { + rtb_OR1 = ((!ElacComputer_U.in.discrete_inputs.l_elev_servo_failed) && rtb_OR); + rtb_OR3 = ((!ElacComputer_U.in.discrete_inputs.r_elev_servo_failed) && rtb_NOT_k); + } + + rtb_thsAvail_tmp = !ElacComputer_U.in.discrete_inputs.ths_motor_fault; + rtb_OR4 = (rtb_thsAvail_tmp && (rtb_NOT_k || rtb_OR)); + if (ElacComputer_U.in.discrete_inputs.is_unit_1) { + rtb_ap_authorised = rtb_OR7; + } else { + rtb_ap_authorised = ((rtb_NOT_k && rtb_OR) || ((!rtb_OR7) && (rtb_OR || rtb_NOT_k))); + } + + rtb_thsAvail_tmp = ((!ElacComputer_U.in.discrete_inputs.r_elev_servo_failed) && + (!ElacComputer_U.in.discrete_inputs.l_elev_servo_failed) && rtb_thsAvail_tmp && + rtb_ap_authorised); + rtb_leftAileronCrossCommandActive = !ElacComputer_U.in.discrete_inputs.is_unit_1; + hasPriorityInPitch = (rtb_leftAileronCrossCommandActive || ElacComputer_U.in.discrete_inputs.opp_axis_pitch_failure); + rtb_isEngagedInPitch = (rtb_thsAvail_tmp && hasPriorityInPitch); + if (ElacComputer_U.in.discrete_inputs.is_unit_1) { + leftAileronAvail = ((!ElacComputer_U.in.discrete_inputs.l_ail_servo_failed) && rtb_OR7); + rightAileronAvail = ((!ElacComputer_U.in.discrete_inputs.r_ail_servo_failed) && rtb_OR); + } else { + leftAileronAvail = ((!ElacComputer_U.in.discrete_inputs.l_ail_servo_failed) && rtb_OR); + rightAileronAvail = ((!ElacComputer_U.in.discrete_inputs.r_ail_servo_failed) && rtb_OR7); + } + + canEngageInRoll = (leftAileronAvail || rightAileronAvail); + hasPriorityInRoll = (ElacComputer_U.in.discrete_inputs.is_unit_1 || + (ElacComputer_U.in.discrete_inputs.opp_left_aileron_lost && + ElacComputer_U.in.discrete_inputs.opp_right_aileron_lost)); + rtb_ap_authorised = !hasPriorityInRoll; + if (rtb_leftAileronCrossCommandActive && rtb_ap_authorised && + (ElacComputer_U.in.bus_inputs.elac_opp_bus.aileron_command_deg.SSM == static_cast(SignStatusMatrix:: + NormalOperation))) { + rtb_leftAileronCrossCommandActive = (ElacComputer_U.in.discrete_inputs.opp_left_aileron_lost && leftAileronAvail); + rtb_rightAileronCrossCommandActive = (ElacComputer_U.in.discrete_inputs.opp_right_aileron_lost && + rightAileronAvail); + } else { + rtb_leftAileronCrossCommandActive = false; + rtb_rightAileronCrossCommandActive = false; + } + + rtb_isEngagedInRoll = (canEngageInRoll && hasPriorityInRoll); + rtb_BusAssignment_n_logic_is_yellow_hydraulic_power_avail = rtb_NOT_k; + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sec_1_bus.discrete_status_word_1, + ElacComputer_P.BitfromLabel_bit_c, &rtb_y_h); + rtb_AND3_b = (rtb_y_h != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sec_1_bus.discrete_status_word_1, + ElacComputer_P.BitfromLabel1_bit_j, &rtb_y_h); + rtb_AND1_h = (rtb_y_h != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sec_2_bus.discrete_status_word_1, + ElacComputer_P.BitfromLabel2_bit, &rtb_y_h); + rtb_AND4 = (rtb_y_h != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.fcdc_1_bus.efcs_status_word_3, + ElacComputer_P.BitfromLabel3_bit, &rtb_y_h); + rtb_NOT_kl = (rtb_y_h != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.fcdc_2_bus.efcs_status_word_3, + ElacComputer_P.BitfromLabel5_bit, &rtb_y_h); + rtb_OR_e1 = (rtb_NOT_kl || (rtb_y_h != 0U)); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.fcdc_1_bus.efcs_status_word_3, + ElacComputer_P.BitfromLabel4_bit, &rtb_y_h); + rtb_NOT_k = (rtb_y_h != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.fcdc_2_bus.efcs_status_word_3, + ElacComputer_P.BitfromLabel6_bit, &rtb_y_h); + rtb_OR1_me = (rtb_NOT_k || (rtb_y_h != 0U)); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.fcdc_1_bus.efcs_status_word_1, + ElacComputer_P.BitfromLabel7_bit, &rtb_y_h); + rtb_NOT_k = (rtb_y_h == 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.fcdc_2_bus.efcs_status_word_1, + ElacComputer_P.BitfromLabel8_bit, &rtb_y_h); + if ((ElacComputer_U.in.discrete_inputs.fac_1_yaw_control_lost && + ElacComputer_U.in.discrete_inputs.fac_2_yaw_control_lost) || + ((ElacComputer_U.in.bus_inputs.sec_1_bus.discrete_status_word_1.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) && (ElacComputer_U.in.bus_inputs.sec_2_bus.discrete_status_word_1.SSM != + static_cast(SignStatusMatrix::NormalOperation)) && ((!rtb_NOT_k) && (rtb_y_h != 0U))) || + ((!rtb_AND3_b) && (!rtb_AND1_h) && (!rtb_AND4) && (!rtb_OR_e1) && (!rtb_OR1_me))) { + rtb_lateralLawCapability = lateral_efcs_law::DirectLaw; + } else { + rtb_lateralLawCapability = lateral_efcs_law::NormalLaw; + } + + ElacComputer_MATLABFunction(&ElacComputer_U.in.bus_inputs.sec_1_bus.discrete_status_word_2, &rtb_y_a); + ElacComputer_MATLABFunction(&ElacComputer_U.in.bus_inputs.sec_2_bus.discrete_status_word_2, &rtb_y_l); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sec_1_bus.discrete_status_word_2, + ElacComputer_P.BitfromLabel4_bit_d, &rtb_y_h); + rtb_NOT_k = (rtb_y_h != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sec_2_bus.discrete_status_word_2, + ElacComputer_P.BitfromLabel5_bit_e, &rtb_y_h); + rtb_OR1_hu = (((!rtb_y_a) && (!rtb_y_l)) || (rtb_NOT_k && (rtb_y_h != 0U))); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sec_1_bus.discrete_status_word_2, + ElacComputer_P.BitfromLabel6_bit_k, &rtb_y_h); + rtb_NOT_k = (rtb_y_h != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sec_2_bus.discrete_status_word_2, + ElacComputer_P.BitfromLabel7_bit_h, &rtb_y_h); + rtb_NOT_k = (rtb_NOT_k || (rtb_y_h != 0U)); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_actual_position_word, + ElacComputer_P.BitfromLabel_bit_a, &rtb_y_h); + ElacComputer_MATLABFunction(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_actual_position_word, &rtb_y_a); + rtb_y_a = ((rtb_y_h != 0U) && rtb_y_a); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_2_bus.slat_flap_actual_position_word, + ElacComputer_P.BitfromLabel1_bit_jr, &rtb_y_h); + ElacComputer_MATLABFunction(&ElacComputer_U.in.bus_inputs.sfcc_2_bus.slat_flap_actual_position_word, &rtb_y_l); + rtb_NOT_kl = ((rtb_y_h != 0U) && rtb_y_l); + ElacComputer_MATLABFunction_c(ElacComputer_U.in.sim_data.slew_on, ElacComputer_U.in.time.dt, + ElacComputer_P.ConfirmNode_isRisingEdge_o, ElacComputer_P.ConfirmNode_timeDelay_d, &rtb_y_l, + &ElacComputer_DWork.sf_MATLABFunction_nb); + abnormalCondition_tmp = std::abs(static_cast(rtb_phi)); + abnormalCondition_tmp_0 = !rtb_OR6; + rtb_AND1_h = !ElacComputer_P.Constant_Value_ad; + rtb_AND4 = ((!rtb_y_l) && abnormalCondition_tmp_0 && (((!rtb_tripleAdrFault) && ((rtb_mach > 0.91) || (rtb_Y < -10.0) + || (rtb_Y > 40.0) || (rtb_V_ias > 440.0F) || (rtb_V_ias < 60.0F))) || ((!rtb_tripleIrFault) && + ((!rtb_doubleIrFault) || rtb_AND1_h) && ((abnormalCondition_tmp > 125.0) || ((rtb_alpha > 50.0F) || (rtb_alpha < + -30.0F)))))); + ElacComputer_DWork.abnormalConditionWasActive = (rtb_AND4 || (abnormalCondition_tmp_0 && + ElacComputer_DWork.abnormalConditionWasActive)); + rtb_AND3_no_tmp = !rtb_OR1; + rtb_AND3_b = (rtb_AND3_no_tmp || (!rtb_OR3)); + rtb_OR_e1 = !leftAileronAvail; + rtb_OR1_me = !rightAileronAvail; + rtb_y_l = (rtb_DataTypeConversion_by || rtb_tripleAdrFault || ElacComputer_DWork.abnormalConditionWasActive || + (rtb_OR_e1 && rtb_OR1_me && rtb_AND3_b)); + alternate1Condition_tmp = !ElacComputer_P.Constant1_Value_b; + rtb_DataTypeConversion_by = ((rtb_doubleIrFault && rtb_AND1_h) || (rtb_DataTypeConversion_by && + alternate1Condition_tmp) || (rtb_doubleAdrFault && alternate1Condition_tmp && alternate1Condition_tmp) || + rtb_AND3_b); + if (rtb_tripleIrFault || ((rtb_y_l || rtb_DataTypeConversion_by || rtb_AND1 || (rtb_lateralLawCapability == + lateral_efcs_law::DirectLaw)) && ((ElacComputer_B.in_flight != 0.0) && ((rtb_NOT_k && (!rtb_OR1_hu)) || + ((rtb_y_a || rtb_NOT_kl) && rtb_OR1_hu))))) { + rtb_pitchLawCapability = pitch_efcs_law::DirectLaw; + } else if (rtb_y_l) { + rtb_pitchLawCapability = pitch_efcs_law::AlternateLaw2; + } else if (rtb_DataTypeConversion_by) { + rtb_pitchLawCapability = pitch_efcs_law::AlternateLaw1; + } else { + rtb_pitchLawCapability = pitch_efcs_law::NormalLaw; + } + + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.elac_opp_bus.discrete_status_word_2, + ElacComputer_P.BitfromLabel_bit_h, &rtb_y_e); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.elac_opp_bus.discrete_status_word_2, + ElacComputer_P.BitfromLabel1_bit_e, &rtb_Switch18); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.elac_opp_bus.discrete_status_word_2, + ElacComputer_P.BitfromLabel2_bit_k, &rtb_DataTypeConversion1_j); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.elac_opp_bus.discrete_status_word_2, + ElacComputer_P.BitfromLabel3_bit_m, &rtb_y_h); + if ((rtb_DataTypeConversion1_j != 0U) && (rtb_y_h == 0U)) { + rtb_oppElacRollCapability = lateral_efcs_law::NormalLaw; + } else if ((rtb_DataTypeConversion1_j == 0U) && (rtb_y_h != 0U)) { + rtb_oppElacRollCapability = lateral_efcs_law::DirectLaw; + } else { + rtb_oppElacRollCapability = lateral_efcs_law::None; + } + + if (hasPriorityInPitch && rtb_isEngagedInPitch) { + priorityPitchPitchLawCap = rtb_pitchLawCapability; + priorityPitchLateralLawCap = rtb_lateralLawCapability; + } else if ((!hasPriorityInPitch) || (!rtb_isEngagedInPitch)) { + if ((rtb_y_e != 0U) && (rtb_Switch18 == 0U)) { + priorityPitchPitchLawCap = pitch_efcs_law::NormalLaw; + } else if ((rtb_y_e == 0U) && (rtb_Switch18 != 0U)) { + priorityPitchPitchLawCap = pitch_efcs_law::AlternateLaw1; + } else if ((rtb_y_e != 0U) && (rtb_Switch18 != 0U)) { + priorityPitchPitchLawCap = pitch_efcs_law::DirectLaw; + } else { + priorityPitchPitchLawCap = pitch_efcs_law::None; + } + + priorityPitchLateralLawCap = rtb_oppElacRollCapability; + } else { + priorityPitchPitchLawCap = pitch_efcs_law::None; + priorityPitchLateralLawCap = lateral_efcs_law::None; + } + + if (hasPriorityInRoll && rtb_isEngagedInRoll) { + rtb_oppElacRollCapability = rtb_lateralLawCapability; + } else if ((!rtb_ap_authorised) && rtb_isEngagedInRoll) { + rtb_oppElacRollCapability = lateral_efcs_law::None; + } + + if (rtb_isEngagedInRoll) { + if ((rtb_oppElacRollCapability == lateral_efcs_law::NormalLaw) && (priorityPitchPitchLawCap == pitch_efcs_law:: + NormalLaw) && (priorityPitchLateralLawCap == lateral_efcs_law::NormalLaw)) { + rtb_activeLateralLaw = lateral_efcs_law::NormalLaw; + } else { + rtb_activeLateralLaw = lateral_efcs_law::DirectLaw; + } + } else { + rtb_activeLateralLaw = lateral_efcs_law::None; + } + + if (rtb_isEngagedInPitch) { + if ((rtb_oppElacRollCapability == lateral_efcs_law::NormalLaw) && (priorityPitchPitchLawCap == pitch_efcs_law:: + NormalLaw) && (priorityPitchLateralLawCap == lateral_efcs_law::NormalLaw)) { + priorityPitchPitchLawCap = pitch_efcs_law::NormalLaw; + } else if ((rtb_oppElacRollCapability != lateral_efcs_law::NormalLaw) && (priorityPitchPitchLawCap == + pitch_efcs_law::NormalLaw)) { + priorityPitchPitchLawCap = pitch_efcs_law::AlternateLaw1; + } else if (priorityPitchPitchLawCap != pitch_efcs_law::NormalLaw) { + priorityPitchPitchLawCap = rtb_pitchLawCapability; + } else { + priorityPitchPitchLawCap = pitch_efcs_law::DirectLaw; + } + } else { + priorityPitchPitchLawCap = pitch_efcs_law::None; + } + + if (!ElacComputer_DWork.pRightStickDisabled) { + rtb_BusAssignment_p_logic_high_speed_prot_hi_thresh_kn = ElacComputer_U.in.analog_inputs.fo_pitch_stick_pos; + } else { + rtb_BusAssignment_p_logic_high_speed_prot_hi_thresh_kn = ElacComputer_P.Constant_Value_p; + } + + if (ElacComputer_DWork.pLeftStickDisabled) { + rtb_Switch3_p = ElacComputer_P.Constant_Value_p; + } else { + rtb_Switch3_p = ElacComputer_U.in.analog_inputs.capt_pitch_stick_pos; + } + + u0 = rtb_BusAssignment_p_logic_high_speed_prot_hi_thresh_kn + rtb_Switch3_p; + if (u0 > ElacComputer_P.Saturation_UpperSat_d) { + u0 = ElacComputer_P.Saturation_UpperSat_d; + } else if (u0 < ElacComputer_P.Saturation_LowerSat_h) { + u0 = ElacComputer_P.Saturation_LowerSat_h; + } + + ElacComputer_MATLABFunction_g(ElacComputer_B.in_flight != 0.0, ElacComputer_P.PulseNode_isRisingEdge_g, &rtb_y_l, + &ElacComputer_DWork.sf_MATLABFunction_l0); + rtb_NOT_k = ((ElacComputer_U.in.discrete_inputs.is_unit_1 && rtb_OR4 && rtb_thsAvail_tmp) || + (ElacComputer_U.in.discrete_inputs.is_unit_2 && rtb_OR4 && rtb_thsAvail_tmp && + ElacComputer_U.in.discrete_inputs.opp_axis_pitch_failure)); + rtb_handleIndex_f = std::abs(ElacComputer_U.in.analog_inputs.ths_pos_deg); + ElacComputer_DWork.Memory_PreviousInput = ElacComputer_P.Logic_table[((((!rtb_NOT_k) || (rtb_handleIndex_f <= + ElacComputer_P.CompareToConstant_const_m) || ElacComputer_U.in.discrete_inputs.ths_override_active) + ( + static_cast(rtb_y_l) << 1)) << 1) + ElacComputer_DWork.Memory_PreviousInput]; + rtb_NOT_k = (rtb_NOT_k && ElacComputer_DWork.Memory_PreviousInput); + rtb_NOT_kl = ((rtb_isEngagedInPitch && (ElacComputer_B.in_flight != 0.0) && ((priorityPitchPitchLawCap != + ElacComputer_P.EnumeratedConstant_Value_i) && (!rtb_AND4))) || rtb_NOT_k); + rtb_BusAssignment_c_logic_total_sidestick_roll_command = rtb_Y_k; + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel_bit_h2, &rtb_y_h); + rtb_DataTypeConversion_by = (rtb_y_h != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel1_bit_g, &rtb_y_h); + rtb_y_l = (rtb_y_h != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel2_bit_n, &rtb_y_h); + rtb_OR1_hu = (rtb_y_h != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel3_bit_g, &rtb_y_h); + rtb_y_a = (rtb_y_h != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel4_bit_e, &rtb_y_h); + rtb_AND1_h = (rtb_y_h != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel5_bit_a, &rtb_y_h); + ElacComputer_MATLABFunction_o(rtb_DataTypeConversion_by, rtb_y_l, rtb_OR1_hu, rtb_y_a, rtb_AND1_h, rtb_y_h != 0U, + &rtb_handleIndex); + ElacComputer_RateLimiter_n(look2_binlxpw(static_cast(rtb_mach), rtb_handleIndex, + ElacComputer_P.alphamax_bp01Data, ElacComputer_P.alphamax_bp02Data, ElacComputer_P.alphamax_tableData, + ElacComputer_P.alphamax_maxIndex, 4U), ElacComputer_P.RateLimiterGenericVariableTs_up, + ElacComputer_P.RateLimiterGenericVariableTs_lo, ElacComputer_U.in.time.dt, ElacComputer_P.reset_Value, + &rtb_Switch3_p, &ElacComputer_DWork.sf_RateLimiter_n); + if (!ElacComputer_DWork.eventTime_not_empty_a) { + ElacComputer_DWork.eventTime_g = ElacComputer_U.in.time.simulation_time; + ElacComputer_DWork.eventTime_not_empty_a = true; + } + + if (rtb_OR6 || (ElacComputer_DWork.eventTime_g == 0.0)) { + ElacComputer_DWork.eventTime_g = ElacComputer_U.in.time.simulation_time; + } + + ElacComputer_RateLimiter_n(look2_binlxpw(static_cast(rtb_mach), rtb_handleIndex, + ElacComputer_P.alphaprotection_bp01Data, ElacComputer_P.alphaprotection_bp02Data, + ElacComputer_P.alphaprotection_tableData, ElacComputer_P.alphaprotection_maxIndex, 4U), + ElacComputer_P.RateLimiterGenericVariableTs1_up, ElacComputer_P.RateLimiterGenericVariableTs1_lo, + ElacComputer_U.in.time.dt, ElacComputer_P.reset_Value_j, &rtb_handleIndex_f, &ElacComputer_DWork.sf_RateLimiter_m); + if (ElacComputer_U.in.time.simulation_time - ElacComputer_DWork.eventTime_g <= + ElacComputer_P.CompareToConstant_const_l) { + rtb_handleIndex = rtb_Switch3_p; + } else { + rtb_handleIndex = rtb_handleIndex_f; + } + + ElacComputer_GetIASforMach4(static_cast(rtb_mach), ElacComputer_P.Constant6_Value_b, static_cast + (rtb_V_ias), &rtb_Y_k); + rtb_Y_k = std::fmin(ElacComputer_P.Constant5_Value_k, rtb_Y_k); + ElacComputer_GetIASforMach4(static_cast(rtb_mach), ElacComputer_P.Constant8_Value_h, static_cast + (rtb_V_ias), &rtb_DataTypeConversion3_m); + rtb_DataTypeConversion3_m = std::fmin(ElacComputer_P.Constant7_Value_g, rtb_DataTypeConversion3_m); + rtb_DataTypeConversion_by = rtb_NOT_k; + if (ElacComputer_DWork.is_active_c28_ElacComputer == 0U) { + ElacComputer_DWork.is_active_c28_ElacComputer = 1U; + ElacComputer_DWork.is_c28_ElacComputer = ElacComputer_IN_Landed; + rtb_ap_special_disc = 0; + } else { + switch (ElacComputer_DWork.is_c28_ElacComputer) { + case ElacComputer_IN_Flying: + if (rtb_raComputationValue < 100.0F) { + ElacComputer_DWork.is_c28_ElacComputer = ElacComputer_IN_Landing100ft; + rtb_ap_special_disc = 1; + } else if (rtb_OR6) { + ElacComputer_DWork.is_c28_ElacComputer = ElacComputer_IN_Landed; + rtb_ap_special_disc = 0; + } else { + rtb_ap_special_disc = 0; + } + break; + + case ElacComputer_IN_Landed: + if (!rtb_OR6) { + ElacComputer_DWork.is_c28_ElacComputer = ElacComputer_IN_Takeoff100ft; + rtb_ap_special_disc = 0; + } else { + rtb_ap_special_disc = 0; + } + break; + + case ElacComputer_IN_Landing100ft: + if (rtb_raComputationValue > 100.0F) { + ElacComputer_DWork.is_c28_ElacComputer = ElacComputer_IN_Flying; + rtb_ap_special_disc = 0; + } else if (rtb_OR6) { + ElacComputer_DWork.is_c28_ElacComputer = ElacComputer_IN_Landed; + rtb_ap_special_disc = 0; + } else { + rtb_ap_special_disc = 1; + } + break; + + default: + if (rtb_OR6) { + ElacComputer_DWork.is_c28_ElacComputer = ElacComputer_IN_Landed; + rtb_ap_special_disc = 0; + } else if (rtb_raComputationValue > 100.0F) { + ElacComputer_DWork.is_c28_ElacComputer = ElacComputer_IN_Flying; + rtb_ap_special_disc = 0; + } else { + rtb_ap_special_disc = 0; + } + break; + } + } + + rtb_handleIndex_f = rtb_alpha - std::cos(ElacComputer_P.Gain1_Gain * rtb_phi) * rtb_Y; + if (!ElacComputer_DWork.eventTime_not_empty) { + ElacComputer_DWork.eventTime = ElacComputer_U.in.time.simulation_time; + ElacComputer_DWork.eventTime_not_empty = true; + } + + rtb_BusAssignment_p_logic_high_speed_prot_hi_thresh_kn = static_cast(rtb_V_ias) / rtb_mach; + if ((rtb_V_ias <= std::fmin(365.0, (look1_binlxpw(rtb_handleIndex_f, ElacComputer_P.uDLookupTable_bp01Data, + ElacComputer_P.uDLookupTable_tableData, 3U) + 0.01) * rtb_BusAssignment_p_logic_high_speed_prot_hi_thresh_kn)) + || ((priorityPitchPitchLawCap != pitch_efcs_law::NormalLaw) && (rtb_activeLateralLaw != lateral_efcs_law:: + NormalLaw)) || (ElacComputer_DWork.eventTime == 0.0)) { + ElacComputer_DWork.eventTime = ElacComputer_U.in.time.simulation_time; + } + + rtb_NOT_k = ((priorityPitchPitchLawCap == pitch_efcs_law::NormalLaw) || (rtb_activeLateralLaw == lateral_efcs_law:: + NormalLaw)); + if (ElacComputer_U.in.discrete_inputs.ap_1_disengaged && ElacComputer_U.in.discrete_inputs.ap_2_disengaged && + (rtb_V_ias > std::fmin(look1_binlxpw(rtb_handleIndex_f, ElacComputer_P.uDLookupTable1_bp01Data, + ElacComputer_P.uDLookupTable1_tableData, 3U), rtb_BusAssignment_p_logic_high_speed_prot_hi_thresh_kn * + look1_binlxpw(rtb_handleIndex_f, ElacComputer_P.uDLookupTable2_bp01Data, + ElacComputer_P.uDLookupTable2_tableData, 3U)))) { + ElacComputer_DWork.sProtActive = (rtb_NOT_k || ElacComputer_DWork.sProtActive); + } + + rtb_OR1_hu = (ElacComputer_U.in.discrete_inputs.ap_1_disengaged && ElacComputer_U.in.discrete_inputs.ap_2_disengaged); + ElacComputer_DWork.sProtActive = ((rtb_V_ias >= rtb_Y_k) && (rtb_OR1_hu && rtb_NOT_k && + ElacComputer_DWork.sProtActive)); + rtb_NOT_k = ((priorityPitchPitchLawCap == pitch_efcs_law::NormalLaw) || (rtb_activeLateralLaw == lateral_efcs_law:: + NormalLaw)); + if (!ElacComputer_DWork.resetEventTime_not_empty) { + ElacComputer_DWork.resetEventTime = ElacComputer_U.in.time.simulation_time; + ElacComputer_DWork.resetEventTime_not_empty = true; + } + + if ((u0 >= -0.03125) || (rtb_Y >= rtb_Switch3_p) || (ElacComputer_DWork.resetEventTime == 0.0)) { + ElacComputer_DWork.resetEventTime = ElacComputer_U.in.time.simulation_time; + } + + ElacComputer_DWork.sProtActive_m = ((abnormalCondition_tmp_0 && rtb_NOT_k && rtb_OR1_hu && (rtb_Y > rtb_handleIndex) + && (ElacComputer_U.in.time.monotonic_time > 10.0)) || ElacComputer_DWork.sProtActive_m); + ElacComputer_DWork.sProtActive_m = ((ElacComputer_U.in.time.simulation_time - ElacComputer_DWork.resetEventTime <= + 0.5) && (u0 >= -0.5) && ((rtb_raComputationValue >= 200.0F) || (u0 >= 0.5) || (rtb_Y >= rtb_handleIndex - 2.0)) && + abnormalCondition_tmp_0 && rtb_NOT_k && ElacComputer_DWork.sProtActive_m); + rtb_NOT_k = ((abnormalCondition_tmp_0 && (((rtb_ap_special_disc != 0) && (rtb_Y > rtb_Switch3_p)) || (rtb_Y > + rtb_handleIndex + 0.25)) && ((priorityPitchPitchLawCap == pitch_efcs_law::NormalLaw) || (rtb_activeLateralLaw == + lateral_efcs_law::NormalLaw))) || (ElacComputer_U.in.time.simulation_time - ElacComputer_DWork.eventTime > 3.0) || + ElacComputer_DWork.sProtActive || ElacComputer_DWork.sProtActive_m); + rtb_ap_authorised = ((std::abs(u0) <= 0.5) && (std::abs(rtb_BusAssignment_c_logic_total_sidestick_roll_command) <= + 0.5) && ((std::abs(ElacComputer_U.in.analog_inputs.rudder_pedal_pos) <= 0.4) && ((rtb_alpha <= 25.0F) && + (rtb_alpha >= -13.0F) && (abnormalCondition_tmp <= 45.0) && ((!hasPriorityInPitch) || rtb_thsAvail_tmp) && + (rtb_ap_authorised || canEngageInRoll) && (!rtb_NOT_k)))); + abnormalCondition_tmp_0 = rtb_NOT_kl; + ElacComputer_Y.out.logic.protection_ap_disconnect = rtb_NOT_k; + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_actual_position_word, + ElacComputer_P.BitfromLabel_bit_e, &rtb_y_h); + rtb_NOT_k = (rtb_y_h == 0U); + ElacComputer_MATLABFunction(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_actual_position_word, &rtb_NOT_kl); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_2_bus.slat_flap_actual_position_word, + ElacComputer_P.BitfromLabel1_bit_d, &rtb_y_h); + ElacComputer_MATLABFunction(&ElacComputer_U.in.bus_inputs.sfcc_2_bus.slat_flap_actual_position_word, &rtb_y_a); + rtb_NOT_k = ((rtb_NOT_k && rtb_NOT_kl) || ((rtb_y_h == 0U) && rtb_y_a)); + rtb_y_l = (rtb_NOT_k && ((leftAileronAvail && rightAileronAvail) || + ((!ElacComputer_U.in.discrete_inputs.opp_left_aileron_lost) && rightAileronAvail) || (leftAileronAvail && + (!ElacComputer_U.in.discrete_inputs.opp_right_aileron_lost)))); + rtb_aileronAntidroopActive = (rtb_aileronAntidroopActive && rtb_NOT_k && (rtb_alpha < 2.5F) && rtb_OR1_hu && + (rtb_activeLateralLaw == lateral_efcs_law::NormalLaw)); + abnormalCondition_tmp = rtb_Y_k; + rtb_BusAssignment_p_logic_high_speed_prot_hi_thresh_kn = rtb_DataTypeConversion3_m; + if (rtb_y_l) { + rtb_Y_c = ElacComputer_P.Constant2_Value; + } else { + rtb_Y_c = ElacComputer_P.Constant1_Value; + } + + ElacComputer_RateLimiter(rtb_Y_c, ElacComputer_P.RateLimiterVariableTs2_up, ElacComputer_P.RateLimiterVariableTs2_lo, + ElacComputer_U.in.time.dt, ElacComputer_P.RateLimiterVariableTs2_InitialCondition, &rtb_Y_b, + &ElacComputer_DWork.sf_RateLimiter); + if (rtb_aileronAntidroopActive) { + rtb_DataTypeConversion7 = ElacComputer_P.Constant4_Value_a; + } else { + rtb_DataTypeConversion7 = ElacComputer_P.Constant3_Value; + } + + ElacComputer_RateLimiter(rtb_DataTypeConversion7, ElacComputer_P.RateLimiterVariableTs3_up, + ElacComputer_P.RateLimiterVariableTs3_lo, ElacComputer_U.in.time.dt, + ElacComputer_P.RateLimiterVariableTs3_InitialCondition, &rtb_Y_c, &ElacComputer_DWork.sf_RateLimiter_b); + rtb_Y_b += rtb_Y_c; + rtb_NOT_k = (rtb_AND2 || (static_cast(rtb_activeLateralLaw) != ElacComputer_P.CompareToConstant_const_m4)); + rtb_Y_c = ElacComputer_U.in.bus_inputs.fmgc_1_bus.delta_p_ail_cmd_deg.Data; + rtb_DataTypeConversion7 = ElacComputer_U.in.bus_inputs.fmgc_1_bus.delta_r_cmd_deg.Data; + rtb_OR1_hu = ((!ElacComputer_U.in.discrete_inputs.ap_1_disengaged) || + (!ElacComputer_U.in.discrete_inputs.ap_2_disengaged)); + LawMDLOBJ2.step(&ElacComputer_U.in.time.dt, &rtb_Y_i_tmp_tmp, &rtb_DataTypeConversion5, &rtb_DataTypeConversion8, + &rtb_xi_deg_m, &rtb_eta_trim_limit_lo_d, + &rtb_BusConversion_InsertedFor_BusAssignment_at_inport_8_BusCreator1_V_tas_kn, &rtb_handleIndex_i, + &rtb_BusAssignment_c_logic_total_sidestick_roll_command, + &ElacComputer_U.in.analog_inputs.rudder_pedal_pos, &rtb_OR6, &rtb_NOT_k, + &ElacComputer_DWork.sProtActive_m, &ElacComputer_DWork.sProtActive, &rtb_Y_c, + &rtb_DataTypeConversion7, &rtb_OR1_hu, &rtb_xi_deg, &rtb_zeta_deg); + LawMDLOBJ1.step(&ElacComputer_U.in.time.dt, &rtb_BusAssignment_c_logic_total_sidestick_roll_command, &rtb_xi_deg_m, + &rtb_zeta_deg_f); + switch (static_cast(rtb_activeLateralLaw)) { + case 0: + rtb_xi_deg_m = rtb_xi_deg; + break; + + case 1: + break; + + default: + rtb_xi_deg_m = ElacComputer_P.Constant_Value_c; + break; + } + + if (rtb_rightAileronCrossCommandActive) { + rtb_handleIndex_f = ElacComputer_U.in.bus_inputs.elac_opp_bus.aileron_command_deg.Data; + } else { + rtb_handleIndex_f = rtb_xi_deg_m + rtb_Y_b; + } + + if (rtb_handleIndex_f > ElacComputer_P.Saturation2_UpperSat) { + rtb_handleIndex_f = ElacComputer_P.Saturation2_UpperSat; + } else if (rtb_handleIndex_f < ElacComputer_P.Saturation2_LowerSat) { + rtb_handleIndex_f = ElacComputer_P.Saturation2_LowerSat; + } + + ElacComputer_RateLimiter_a(rtb_handleIndex_f, ElacComputer_P.RateLimiterGenericVariableTs_up_b, + ElacComputer_P.RateLimiterGenericVariableTs_lo_k, ElacComputer_U.in.time.dt, + ElacComputer_U.in.analog_inputs.right_aileron_pos_deg, (!rtb_rightAileronCrossCommandActive) && + (!rtb_isEngagedInRoll), &rtb_DataTypeConversion3_m, &ElacComputer_DWork.sf_RateLimiter_a); + if (rtb_leftAileronCrossCommandActive) { + rtb_handleIndex_f = ElacComputer_U.in.bus_inputs.elac_opp_bus.aileron_command_deg.Data; + } else { + rtb_handleIndex_f = ElacComputer_P.Gain_Gain * rtb_xi_deg_m + rtb_Y_b; + } + + if (rtb_handleIndex_f > ElacComputer_P.Saturation1_UpperSat_g) { + rtb_handleIndex_f = ElacComputer_P.Saturation1_UpperSat_g; + } else if (rtb_handleIndex_f < ElacComputer_P.Saturation1_LowerSat_n) { + rtb_handleIndex_f = ElacComputer_P.Saturation1_LowerSat_n; + } + + ElacComputer_RateLimiter_a(rtb_handleIndex_f, ElacComputer_P.RateLimiterGenericVariableTs1_up_g, + ElacComputer_P.RateLimiterGenericVariableTs1_lo_c, ElacComputer_U.in.time.dt, + ElacComputer_U.in.analog_inputs.left_aileron_pos_deg, (!rtb_leftAileronCrossCommandActive) && + (!rtb_isEngagedInRoll), &rtb_Y_k, &ElacComputer_DWork.sf_RateLimiter_p); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_actual_position_word, + ElacComputer_P.BitfromLabel_bit_a2, &rtb_y_e); + ElacComputer_MATLABFunction(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_actual_position_word, &rtb_NOT_kl); + rtb_NOT_k = ((rtb_y_e != 0U) && rtb_NOT_kl); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_2_bus.slat_flap_actual_position_word, + ElacComputer_P.BitfromLabel1_bit_p, &rtb_y_e); + ElacComputer_MATLABFunction(&ElacComputer_U.in.bus_inputs.sfcc_2_bus.slat_flap_actual_position_word, &rtb_y_a); + if (rtb_NOT_k || ((rtb_y_e != 0U) && rtb_y_a)) { + rtb_handleIndex_f = rtb_xi_deg_m; + } else { + rtb_Y_b = std::abs(rtb_xi_deg_m) + ElacComputer_P.Bias_Bias; + if (rtb_Y_b > ElacComputer_P.Saturation_UpperSat) { + rtb_Y_b = ElacComputer_P.Saturation_UpperSat; + } else if (rtb_Y_b < ElacComputer_P.Saturation_LowerSat) { + rtb_Y_b = ElacComputer_P.Saturation_LowerSat; + } + + if (rtb_xi_deg_m < 0.0) { + rtb_xi_deg_m = -1.0; + } else { + rtb_xi_deg_m = (rtb_xi_deg_m > 0.0); + } + + rtb_handleIndex_f = rtb_Y_b * rtb_xi_deg_m * ElacComputer_P.Gain2_Gain; + } + + rtb_xi_deg_m = ElacComputer_P.Gain1_Gain_b * rtb_handleIndex_f; + switch (static_cast(rtb_activeLateralLaw)) { + case 0: + rtb_zeta_deg_f = rtb_zeta_deg; + break; + + case 1: + break; + + default: + rtb_zeta_deg_f = ElacComputer_P.Constant_Value_c; + break; + } + + rtb_Y_b = rtb_Y_k; + rtb_Y_c = rtb_DataTypeConversion3_m; + rtb_DataTypeConversion7 = ElacComputer_P.DiscreteDerivativeVariableTs_Gain * rtb_theta_dot; + ElacComputer_LagFilter((rtb_DataTypeConversion7 - ElacComputer_DWork.Delay_DSTATE) / ElacComputer_U.in.time.dt, + ElacComputer_P.LagFilter_C1_e, ElacComputer_U.in.time.dt, &rtb_Y_k, &ElacComputer_DWork.sf_LagFilter); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel_bit_p, &rtb_y_e); + rtb_NOT_k = (rtb_y_e != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel1_bit_h, &rtb_y_e); + rtb_y_a = (rtb_y_e != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel2_bit_f, &rtb_y_e); + rtb_NOT_kl = (rtb_y_e != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel3_bit_c, &rtb_y_e); + rtb_AND1_h = (rtb_y_e != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel4_bit_n, &rtb_y_e); + rtb_AND3_b = (rtb_y_e != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel5_bit_p, &rtb_y_e); + ElacComputer_MATLABFunction_o(rtb_NOT_k, rtb_y_a, rtb_NOT_kl, rtb_AND1_h, rtb_AND3_b, rtb_y_e != 0U, + &rtb_handleIndex_f); + if ((ElacComputer_U.in.bus_inputs.sec_1_bus.thrust_lever_angle_1_deg.SSM == static_cast(SignStatusMatrix:: + NormalOperation)) && (ElacComputer_U.in.bus_inputs.sec_1_bus.thrust_lever_angle_2_deg.SSM == + static_cast(SignStatusMatrix::NormalOperation))) { + rtb_tla1 = ElacComputer_U.in.bus_inputs.sec_1_bus.thrust_lever_angle_1_deg.Data; + rtb_tla2 = ElacComputer_U.in.bus_inputs.sec_1_bus.thrust_lever_angle_2_deg.Data; + } else if ((ElacComputer_U.in.bus_inputs.sec_2_bus.thrust_lever_angle_1_deg.SSM == static_cast + (SignStatusMatrix::NormalOperation)) && + (ElacComputer_U.in.bus_inputs.sec_2_bus.thrust_lever_angle_2_deg.SSM == static_cast + (SignStatusMatrix::NormalOperation))) { + rtb_tla1 = ElacComputer_U.in.bus_inputs.sec_2_bus.thrust_lever_angle_1_deg.Data; + rtb_tla2 = ElacComputer_U.in.bus_inputs.sec_2_bus.thrust_lever_angle_2_deg.Data; + } else { + rtb_tla1 = 0.0F; + rtb_tla2 = 0.0F; + } + + rtb_DataTypeConversion3_m = rtb_tla1; + rtb_DataTypeConversion8 = rtb_tla2; + rtb_DataTypeConversion_o = (ElacComputer_B.in_flight != 0.0); + rtb_NOT_k = (rtb_AND2 || (static_cast(priorityPitchPitchLawCap) != ElacComputer_P.CompareToConstant_const_f)); + rtb_DataTypeConversion6_g = ElacComputer_U.in.bus_inputs.fmgc_1_bus.delta_q_cmd_deg.Data; + LawMDLOBJ5.step(&ElacComputer_U.in.time.dt, &rtb_BusAssignment_f_logic_ir_computation_data_n_z_g, &rtb_Y_i_tmp_tmp, + &rtb_DataTypeConversion5, &rtb_BusAssignment_f_logic_ir_computation_data_theta_dot_deg_s, &rtb_Y_k, + (const_cast(&ElacComputer_RGND)), &ElacComputer_U.in.analog_inputs.ths_pos_deg, &rtb_Y, + &rtb_eta_trim_limit_lo_d, + &rtb_BusConversion_InsertedFor_BusAssignment_at_inport_8_BusCreator1_V_tas_kn, &rtb_handleIndex_i, + &rtb_handleIndex_f, (const_cast(&ElacComputer_RGND)), (const_cast + (&ElacComputer_RGND)), &rtb_DataTypeConversion3_m, &rtb_DataTypeConversion8, + &ElacComputer_U.in.sim_data.tailstrike_protection_on, (const_cast(&ElacComputer_RGND)), &u0, + &rtb_OR6, &rtb_DataTypeConversion_o, &rtb_NOT_k, &ElacComputer_DWork.sProtActive_m, + &ElacComputer_DWork.sProtActive, &rtb_handleIndex, &rtb_Switch3_p, + &rtb_BusAssignment_p_logic_high_speed_prot_hi_thresh_kn, &abnormalCondition_tmp, + &rtb_DataTypeConversion6_g, &rtb_OR1_hu, &rtb_eta_deg, &rtb_eta_trim_dot_deg_s, + &rtb_eta_trim_limit_lo, &rtb_eta_trim_limit_up); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel_bit_n, &rtb_y_e); + rtb_NOT_k = (rtb_y_e != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel1_bit_h1, &rtb_y_e); + rtb_y_a = (rtb_y_e != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel2_bit_g, &rtb_y_e); + rtb_NOT_kl = (rtb_y_e != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel3_bit_b, &rtb_y_e); + rtb_AND1_h = (rtb_y_e != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel4_bit_i, &rtb_y_e); + rtb_AND3_b = (rtb_y_e != 0U); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + ElacComputer_P.BitfromLabel5_bit_l, &rtb_y_e); + ElacComputer_MATLABFunction_o(rtb_NOT_k, rtb_y_a, rtb_NOT_kl, rtb_AND1_h, rtb_AND3_b, rtb_y_e != 0U, + &rtb_handleIndex_i); + rtb_Y_k = (ElacComputer_B.in_flight != 0.0); + rtb_NOT_k = (rtb_AND2 || ((static_cast(priorityPitchPitchLawCap) != ElacComputer_P.CompareToConstant2_const) + && (static_cast(priorityPitchPitchLawCap) != ElacComputer_P.CompareToConstant3_const))); + rtb_y_a = (priorityPitchPitchLawCap != ElacComputer_P.EnumeratedConstant_Value_b); + LawMDLOBJ3.step(&ElacComputer_U.in.time.dt, &rtb_BusAssignment_f_logic_ir_computation_data_n_z_g, &rtb_Y_i_tmp_tmp, + &rtb_DataTypeConversion5, &rtb_BusAssignment_f_logic_ir_computation_data_theta_dot_deg_s, ( + const_cast(&ElacComputer_RGND)), &ElacComputer_U.in.analog_inputs.ths_pos_deg, &rtb_eta_trim_limit_lo_d, + &rtb_BusConversion_InsertedFor_BusAssignment_at_inport_8_BusCreator1_mach, + &rtb_BusConversion_InsertedFor_BusAssignment_at_inport_8_BusCreator1_V_tas_kn, &rtb_handleIndex_i, ( + const_cast(&ElacComputer_RGND)), (const_cast(&ElacComputer_RGND)), &u0, &rtb_Y_k, &rtb_NOT_k, + &rtb_y_a, &rtb_eta_deg_o, &rtb_eta_trim_dot_deg_s_a, &rtb_eta_trim_limit_lo_h, + &rtb_eta_trim_limit_up_d); + LawMDLOBJ4.step(&ElacComputer_U.in.time.dt, &u0, &rtb_handleIndex_f, &rtb_DataTypeConversion3_m, + &rtb_eta_trim_limit_lo_d, &rtb_Y_k); + switch (static_cast(priorityPitchPitchLawCap)) { + case 0: + rtb_handleIndex_f = rtb_eta_deg; + break; + + case 1: + case 2: + rtb_handleIndex_f = rtb_eta_deg_o; + break; + + case 3: + break; + + default: + rtb_handleIndex_f = ElacComputer_P.Constant_Value_a; + break; + } + + switch (static_cast(priorityPitchPitchLawCap)) { + case 0: + rtb_Y_k = rtb_eta_trim_limit_up; + break; + + case 1: + case 2: + rtb_Y_k = rtb_eta_trim_limit_up_d; + break; + + case 3: + break; + + default: + rtb_Y_k = ElacComputer_P.Constant2_Value_l; + break; + } + + if (rtb_DataTypeConversion_by) { + rtb_DataTypeConversion3_m = ElacComputer_P.Gain_Gain_l * ElacComputer_DWork.Delay_DSTATE_b; + if (rtb_DataTypeConversion3_m > ElacComputer_P.Saturation_UpperSat_g) { + rtb_DataTypeConversion3_m = ElacComputer_P.Saturation_UpperSat_g; + } else if (rtb_DataTypeConversion3_m < ElacComputer_P.Saturation_LowerSat_o) { + rtb_DataTypeConversion3_m = ElacComputer_P.Saturation_LowerSat_o; + } + } else if (ElacComputer_U.in.discrete_inputs.ths_override_active) { + rtb_DataTypeConversion3_m = ElacComputer_P.Constant_Value_n; + } else { + switch (static_cast(priorityPitchPitchLawCap)) { + case 0: + rtb_DataTypeConversion3_m = rtb_eta_trim_dot_deg_s; + break; + + case 1: + case 2: + rtb_DataTypeConversion3_m = rtb_eta_trim_dot_deg_s_a; + break; + + case 3: + break; + + default: + rtb_DataTypeConversion3_m = ElacComputer_P.Constant_Value_a; + break; + } + } + + rtb_DataTypeConversion3_m = ElacComputer_P.DiscreteTimeIntegratorVariableTsLimit_Gain * rtb_DataTypeConversion3_m * + ElacComputer_U.in.time.dt; + ElacComputer_DWork.icLoad = ((!abnormalCondition_tmp_0) || ElacComputer_DWork.icLoad); + if (ElacComputer_DWork.icLoad) { + ElacComputer_DWork.Delay_DSTATE_c = ElacComputer_U.in.analog_inputs.ths_pos_deg - rtb_DataTypeConversion3_m; + } + + ElacComputer_DWork.Delay_DSTATE_b = rtb_DataTypeConversion3_m + ElacComputer_DWork.Delay_DSTATE_c; + if (ElacComputer_DWork.Delay_DSTATE_b > rtb_Y_k) { + ElacComputer_DWork.Delay_DSTATE_b = rtb_Y_k; + } else { + switch (static_cast(priorityPitchPitchLawCap)) { + case 0: + rtb_eta_trim_limit_lo_d = rtb_eta_trim_limit_lo; + break; + + case 1: + case 2: + rtb_eta_trim_limit_lo_d = rtb_eta_trim_limit_lo_h; + break; + + case 3: + break; + + default: + rtb_eta_trim_limit_lo_d = ElacComputer_P.Constant3_Value_h; + break; + } + + if (ElacComputer_DWork.Delay_DSTATE_b < rtb_eta_trim_limit_lo_d) { + ElacComputer_DWork.Delay_DSTATE_b = rtb_eta_trim_limit_lo_d; + } + } + + rtb_NOT_kl = ((look1_binlxpw(static_cast(rtb_V_ias), ElacComputer_P.uDLookupTable_bp01Data_h, + ElacComputer_P.uDLookupTable_tableData_j, 6U) < std::abs(rtb_handleIndex_f)) && rtb_isEngagedInPitch); + rtb_eta_trim_limit_lo_d = rtb_handleIndex_f; + ElacComputer_MATLABFunction(&ElacComputer_U.in.bus_inputs.elac_opp_bus.elevator_double_pressurization_command_deg, + &rtb_y_a); + rtb_AND1_h = !rtb_isEngagedInPitch; + rtb_NOT_k = (rtb_AND1_h && rtb_y_a); + if (rtb_NOT_k) { + rtb_handleIndex_f = ElacComputer_U.in.bus_inputs.elac_opp_bus.elevator_double_pressurization_command_deg.Data; + } + + if ((rtb_NOT_k || rtb_isEngagedInPitch) && rtb_OR3) { + rtb_Y_k = rtb_handleIndex_f; + } else { + rtb_Y_k = ElacComputer_P.Constant_Value_b; + } + + if ((rtb_AND1_h && (!rtb_NOT_k)) || rtb_AND3_no_tmp) { + rtb_handleIndex_f = ElacComputer_P.Constant_Value_b; + } + + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.elac_opp_bus.discrete_status_word_1, + ElacComputer_P.BitfromLabel_bit_p3, &rtb_y_h); + rtb_AND3_b = (rtb_y_h != 0U); + if (ElacComputer_U.in.discrete_inputs.is_unit_2) { + rtb_Switch1_g_0 = &ElacComputer_U.in.bus_inputs.sec_1_bus.discrete_status_word_1; + } else { + rtb_Switch1_g_0 = &ElacComputer_U.in.bus_inputs.sec_2_bus.discrete_status_word_1; + } + + ElacComputer_MATLABFunction_j(rtb_Switch1_g_0, ElacComputer_P.BitfromLabel2_bit_j, &rtb_y_h); + rtb_AND3_b = (rtb_NOT_kl && (rtb_AND3_b || (rtb_y_h != 0U))); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.elac_opp_bus.discrete_status_word_1, + ElacComputer_P.BitfromLabel1_bit_i, &rtb_y_h); + rtb_AND1_h = (rtb_y_h != 0U); + ElacComputer_MATLABFunction_j(rtb_Switch1_g_0, ElacComputer_P.BitfromLabel3_bit_mo, &rtb_y_h); + rtb_AND1_h = ((rtb_AND1_h || (rtb_y_h != 0U)) && rtb_NOT_kl); + ElacComputer_MATLABFunction_c(rtb_BusAssignment_n_logic_is_yellow_hydraulic_power_avail || rtb_OR7 || rtb_OR, + ElacComputer_U.in.time.dt, ElacComputer_P.ConfirmNode_isRisingEdge_f, ElacComputer_P.ConfirmNode_timeDelay_p, + &rtb_y_a, &ElacComputer_DWork.sf_MATLABFunction_fb); + rtb_Switch18 = static_cast(ElacComputer_P.EnumeratedConstant_Value); + if (ElacComputer_P.EnumeratedConstant2_Value == rtb_activeLateralLaw) { + rtb_y_h = static_cast(ElacComputer_P.EnumeratedConstant1_Value); + rtb_tla1 = static_cast(rtb_zeta_deg_f); + } else { + rtb_y_h = static_cast(ElacComputer_P.EnumeratedConstant_Value); + rtb_tla1 = static_cast(ElacComputer_P.Constant7_Value); + } + + rtb_ap_special_disc = static_cast(rtb_y_h); + if (rtb_NOT_kl) { + rtb_Switch18 = static_cast(ElacComputer_P.EnumeratedConstant1_Value); + rtb_tla2 = static_cast(rtb_eta_trim_limit_lo_d); + } else { + rtb_tla2 = static_cast(ElacComputer_P.Constant8_Value); + } + + rtb_elevator_double_pressurization_command_deg_Data = rtb_tla2; + rtb_VectorConcatenate[0] = ElacComputer_U.in.discrete_inputs.l_ail_servo_failed; + rtb_VectorConcatenate[1] = ElacComputer_U.in.discrete_inputs.r_ail_servo_failed; + rtb_VectorConcatenate[2] = ElacComputer_U.in.discrete_inputs.l_elev_servo_failed; + rtb_VectorConcatenate[3] = ElacComputer_U.in.discrete_inputs.r_elev_servo_failed; + rtb_VectorConcatenate[4] = leftAileronAvail; + rtb_VectorConcatenate[5] = rightAileronAvail; + rtb_VectorConcatenate[6] = rtb_OR1; + rtb_VectorConcatenate[7] = rtb_OR3; + rtb_VectorConcatenate[8] = rtb_isEngagedInPitch; + rtb_VectorConcatenate[9] = rtb_isEngagedInRoll; + rtb_VectorConcatenate[10] = !rtb_thsAvail_tmp; + rtb_VectorConcatenate[11] = !canEngageInRoll; + rtb_VectorConcatenate[12] = ((priorityPitchPitchLawCap == pitch_efcs_law::NormalLaw) || (priorityPitchPitchLawCap == + pitch_efcs_law::AlternateLaw2)); + rtb_VectorConcatenate[13] = ((priorityPitchPitchLawCap == pitch_efcs_law::AlternateLaw1) || + (priorityPitchPitchLawCap == pitch_efcs_law::AlternateLaw2)); + rtb_VectorConcatenate[14] = (priorityPitchPitchLawCap == pitch_efcs_law::DirectLaw); + ElacComputer_LateralLawCaptoBits(rtb_activeLateralLaw, &rtb_VectorConcatenate[15], &rtb_VectorConcatenate[16]); + ElacComputer_MATLABFunction_j(&ElacComputer_U.in.bus_inputs.sec_1_bus.discrete_status_word_1, + ElacComputer_P.BitfromLabel_bit_es, &rtb_y_h); + rtb_VectorConcatenate[17] = ((rtb_activeLateralLaw == lateral_efcs_law::NormalLaw) || rtb_AND4 || ((rtb_y_h == 0U) && + (ElacComputer_U.in.bus_inputs.sec_1_bus.discrete_status_word_1.SSM == static_cast(SignStatusMatrix:: + NormalOperation)))); + rtb_VectorConcatenate[18] = ((rtb_activeLateralLaw == lateral_efcs_law::NormalLaw) || rtb_AND4); + ElacComputer_MATLABFunction_cw(rtb_VectorConcatenate, &rtb_y_m); + rtb_VectorConcatenate_a[0] = ((rtb_pitchLawCapability == pitch_efcs_law::NormalLaw) || (rtb_pitchLawCapability == + pitch_efcs_law::DirectLaw)); + rtb_VectorConcatenate_a[1] = ((rtb_pitchLawCapability == pitch_efcs_law::AlternateLaw1) || (rtb_pitchLawCapability == + pitch_efcs_law::AlternateLaw2) || (rtb_pitchLawCapability == pitch_efcs_law::DirectLaw)); + ElacComputer_LateralLawCaptoBits(rtb_lateralLawCapability, &rtb_VectorConcatenate_a[2], &rtb_VectorConcatenate_a[3]); + rtb_VectorConcatenate_a[4] = ElacComputer_P.Constant9_Value; + rtb_VectorConcatenate_a[5] = ElacComputer_P.Constant9_Value; + rtb_VectorConcatenate_a[6] = ElacComputer_DWork.pLeftStickDisabled; + rtb_VectorConcatenate_a[7] = ElacComputer_DWork.pRightStickDisabled; + rtb_VectorConcatenate_a[8] = ElacComputer_DWork.Delay_DSTATE_cc; + rtb_VectorConcatenate_a[9] = ElacComputer_DWork.Delay1_DSTATE; + rtb_VectorConcatenate_a[10] = rtb_y_l; + rtb_VectorConcatenate_a[11] = rtb_OR1_hu; + rtb_VectorConcatenate_a[12] = ElacComputer_DWork.sProtActive_m; + rtb_VectorConcatenate_a[13] = ElacComputer_P.Constant10_Value; + rtb_VectorConcatenate_a[14] = ElacComputer_P.Constant10_Value; + rtb_VectorConcatenate_a[15] = ElacComputer_P.Constant10_Value; + rtb_VectorConcatenate_a[16] = ElacComputer_P.Constant10_Value; + rtb_VectorConcatenate_a[17] = ElacComputer_P.Constant10_Value; + rtb_VectorConcatenate_a[18] = ElacComputer_P.Constant10_Value; + ElacComputer_MATLABFunction_cw(rtb_VectorConcatenate_a, &rtb_tla2); + ElacComputer_Y.out.data = ElacComputer_U.in; + ElacComputer_Y.out.laws.lateral_law_outputs.left_aileron_command_deg = rtb_Y_b; + ElacComputer_Y.out.laws.lateral_law_outputs.right_aileron_command_deg = rtb_Y_c; + ElacComputer_Y.out.laws.lateral_law_outputs.roll_spoiler_command_deg = rtb_xi_deg_m; + ElacComputer_Y.out.laws.lateral_law_outputs.yaw_damper_command_deg = rtb_zeta_deg_f; + ElacComputer_Y.out.laws.pitch_law_outputs.elevator_command_deg = rtb_eta_trim_limit_lo_d; + ElacComputer_Y.out.laws.pitch_law_outputs.ths_command_deg = ElacComputer_DWork.Delay_DSTATE_b; + ElacComputer_Y.out.laws.pitch_law_outputs.elevator_double_pressurization_active = rtb_NOT_kl; + ElacComputer_Y.out.logic.on_ground = rtb_OR6; + ElacComputer_Y.out.logic.pitch_law_in_flight = (ElacComputer_B.in_flight != 0.0); + ElacComputer_Y.out.logic.tracking_mode_on = rtb_AND2; + ElacComputer_Y.out.logic.lateral_law_capability = rtb_lateralLawCapability; + ElacComputer_Y.out.logic.active_lateral_law = rtb_activeLateralLaw; + ElacComputer_Y.out.logic.pitch_law_capability = rtb_pitchLawCapability; + ElacComputer_Y.out.logic.active_pitch_law = priorityPitchPitchLawCap; + ElacComputer_Y.out.logic.abnormal_condition_law_active = rtb_AND4; + ElacComputer_Y.out.logic.is_engaged_in_pitch = rtb_isEngagedInPitch; + ElacComputer_Y.out.logic.can_engage_in_pitch = rtb_thsAvail_tmp; + ElacComputer_Y.out.logic.has_priority_in_pitch = hasPriorityInPitch; + ElacComputer_Y.out.logic.left_elevator_avail = rtb_OR1; + ElacComputer_Y.out.logic.right_elevator_avail = rtb_OR3; + ElacComputer_Y.out.logic.ths_avail = rtb_OR4; + ElacComputer_Y.out.logic.ths_active_commanded = abnormalCondition_tmp_0; + ElacComputer_Y.out.logic.ths_ground_setting_active = rtb_DataTypeConversion_by; + ElacComputer_Y.out.logic.is_engaged_in_roll = rtb_isEngagedInRoll; + ElacComputer_Y.out.logic.can_engage_in_roll = canEngageInRoll; + ElacComputer_Y.out.logic.has_priority_in_roll = hasPriorityInRoll; + ElacComputer_Y.out.logic.left_aileron_crosscommand_active = rtb_leftAileronCrossCommandActive; + ElacComputer_Y.out.logic.right_aileron_crosscommand_active = rtb_rightAileronCrossCommandActive; + ElacComputer_Y.out.logic.left_aileron_avail = leftAileronAvail; + ElacComputer_Y.out.logic.right_aileron_avail = rightAileronAvail; + ElacComputer_Y.out.logic.aileron_droop_active = rtb_y_l; + ElacComputer_Y.out.logic.aileron_antidroop_active = rtb_aileronAntidroopActive; + ElacComputer_Y.out.logic.is_yellow_hydraulic_power_avail = rtb_BusAssignment_n_logic_is_yellow_hydraulic_power_avail; + ElacComputer_Y.out.logic.is_blue_hydraulic_power_avail = rtb_OR7; + ElacComputer_Y.out.logic.is_green_hydraulic_power_avail = rtb_OR; + ElacComputer_Y.out.logic.left_sidestick_disabled = ElacComputer_DWork.pLeftStickDisabled; + ElacComputer_Y.out.logic.right_sidestick_disabled = ElacComputer_DWork.pRightStickDisabled; + ElacComputer_Y.out.logic.left_sidestick_priority_locked = ElacComputer_DWork.Delay_DSTATE_cc; + ElacComputer_Y.out.logic.right_sidestick_priority_locked = ElacComputer_DWork.Delay1_DSTATE; + ElacComputer_Y.out.logic.total_sidestick_pitch_command = u0; + ElacComputer_Y.out.logic.total_sidestick_roll_command = rtb_BusAssignment_c_logic_total_sidestick_roll_command; + ElacComputer_Y.out.logic.ap_authorised = rtb_ap_authorised; + ElacComputer_Y.out.logic.high_alpha_prot_active = ElacComputer_DWork.sProtActive_m; + ElacComputer_Y.out.logic.alpha_prot_deg = rtb_handleIndex; + ElacComputer_Y.out.logic.alpha_max_deg = rtb_Switch3_p; + ElacComputer_Y.out.logic.high_speed_prot_active = ElacComputer_DWork.sProtActive; + ElacComputer_Y.out.logic.high_speed_prot_lo_thresh_kn = abnormalCondition_tmp; + ElacComputer_Y.out.logic.high_speed_prot_hi_thresh_kn = rtb_BusAssignment_p_logic_high_speed_prot_hi_thresh_kn; + ElacComputer_Y.out.logic.double_adr_failure = rtb_doubleAdrFault; + ElacComputer_Y.out.logic.triple_adr_failure = rtb_tripleAdrFault; + ElacComputer_Y.out.logic.cas_or_mach_disagree = ElacComputer_P.Constant1_Value_b; + ElacComputer_Y.out.logic.alpha_disagree = ElacComputer_P.Constant1_Value_b; + ElacComputer_Y.out.logic.double_ir_failure = rtb_doubleIrFault; + ElacComputer_Y.out.logic.triple_ir_failure = rtb_tripleIrFault; + ElacComputer_Y.out.logic.ir_failure_not_self_detected = ElacComputer_P.Constant_Value_ad; + ElacComputer_Y.out.logic.adr_computation_data.V_ias_kn = rtb_V_ias; + ElacComputer_Y.out.logic.adr_computation_data.V_tas_kn = rtb_V_tas; + ElacComputer_Y.out.logic.adr_computation_data.mach = rtb_mach; + ElacComputer_Y.out.logic.adr_computation_data.alpha_deg = rtb_Y; + ElacComputer_Y.out.logic.ir_computation_data.theta_deg = rtb_alpha; + ElacComputer_Y.out.logic.ir_computation_data.phi_deg = rtb_phi; + ElacComputer_Y.out.logic.ir_computation_data.q_deg_s = rtb_q; + ElacComputer_Y.out.logic.ir_computation_data.r_deg_s = rtb_r; + ElacComputer_Y.out.logic.ir_computation_data.n_x_g = rtb_n_x; + ElacComputer_Y.out.logic.ir_computation_data.n_y_g = rtb_n_y; + ElacComputer_Y.out.logic.ir_computation_data.n_z_g = rtb_n_z; + ElacComputer_Y.out.logic.ir_computation_data.theta_dot_deg_s = rtb_theta_dot; + ElacComputer_Y.out.logic.ir_computation_data.phi_dot_deg_s = rtb_phi_dot; + ElacComputer_Y.out.logic.ra_computation_data_ft = rtb_raComputationValue; + ElacComputer_Y.out.logic.dual_ra_failure = rtb_AND1; + ElacComputer_Y.out.discrete_outputs.pitch_axis_ok = rtb_thsAvail_tmp; + ElacComputer_Y.out.discrete_outputs.left_aileron_ok = leftAileronAvail; + ElacComputer_Y.out.discrete_outputs.right_aileron_ok = rightAileronAvail; + ElacComputer_Y.out.discrete_outputs.digital_output_validated = ElacComputer_P.Constant1_Value_e; + ElacComputer_Y.out.discrete_outputs.ap_1_authorised = rtb_ap_authorised; + ElacComputer_Y.out.discrete_outputs.ap_2_authorised = rtb_ap_authorised; + ElacComputer_Y.out.discrete_outputs.left_aileron_active_mode = ((rtb_isEngagedInRoll || + rtb_leftAileronCrossCommandActive) && leftAileronAvail); + ElacComputer_Y.out.discrete_outputs.right_aileron_active_mode = ((rtb_isEngagedInRoll || + rtb_rightAileronCrossCommandActive) && rightAileronAvail); + ElacComputer_Y.out.discrete_outputs.left_elevator_damping_mode = (rtb_isEngagedInPitch && rtb_OR1 && (!rtb_AND3_b)); + ElacComputer_Y.out.discrete_outputs.right_elevator_damping_mode = (rtb_isEngagedInPitch && rtb_OR3 && (!rtb_AND1_h)); + ElacComputer_Y.out.discrete_outputs.ths_active = (abnormalCondition_tmp_0 && rtb_OR4); + ElacComputer_Y.out.discrete_outputs.batt_power_supply = rtb_y_a; + ElacComputer_Y.out.analog_outputs.left_elev_pos_order_deg = rtb_handleIndex_f; + ElacComputer_Y.out.analog_outputs.right_elev_pos_order_deg = rtb_Y_k; + if (abnormalCondition_tmp_0 && rtb_OR4) { + ElacComputer_Y.out.analog_outputs.ths_pos_order = ElacComputer_DWork.Delay_DSTATE_b; + } else { + ElacComputer_Y.out.analog_outputs.ths_pos_order = ElacComputer_P.Constant_Value_b; + } + + if ((rtb_isEngagedInRoll || rtb_leftAileronCrossCommandActive) && leftAileronAvail) { + ElacComputer_Y.out.analog_outputs.left_aileron_pos_order = rtb_Y_b; + } else { + ElacComputer_Y.out.analog_outputs.left_aileron_pos_order = ElacComputer_P.Constant_Value_b; + } + + if ((rtb_isEngagedInRoll || rtb_rightAileronCrossCommandActive) && rightAileronAvail) { + ElacComputer_Y.out.analog_outputs.right_aileron_pos_order = rtb_Y_c; + } else { + ElacComputer_Y.out.analog_outputs.right_aileron_pos_order = ElacComputer_P.Constant_Value_b; + } + + if (ElacComputer_U.in.discrete_inputs.l_ail_servo_failed) { + ElacComputer_Y.out.bus_outputs.left_aileron_position_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant_Value); + ElacComputer_Y.out.bus_outputs.left_aileron_position_deg.Data = static_cast + (ElacComputer_P.Constant_Value_j); + } else { + ElacComputer_Y.out.bus_outputs.left_aileron_position_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant1_Value); + ElacComputer_Y.out.bus_outputs.left_aileron_position_deg.Data = static_cast + (ElacComputer_U.in.analog_inputs.left_aileron_pos_deg); + } + + if (ElacComputer_U.in.discrete_inputs.r_ail_servo_failed) { + ElacComputer_Y.out.bus_outputs.right_aileron_position_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant_Value); + ElacComputer_Y.out.bus_outputs.right_aileron_position_deg.Data = static_cast + (ElacComputer_P.Constant1_Value_d); + } else { + ElacComputer_Y.out.bus_outputs.right_aileron_position_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant1_Value); + ElacComputer_Y.out.bus_outputs.right_aileron_position_deg.Data = static_cast + (ElacComputer_U.in.analog_inputs.right_aileron_pos_deg); + } + + if (ElacComputer_U.in.discrete_inputs.l_elev_servo_failed) { + ElacComputer_Y.out.bus_outputs.left_elevator_position_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant_Value); + ElacComputer_Y.out.bus_outputs.left_elevator_position_deg.Data = static_cast + (ElacComputer_P.Constant2_Value_b); + } else { + ElacComputer_Y.out.bus_outputs.left_elevator_position_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant1_Value); + ElacComputer_Y.out.bus_outputs.left_elevator_position_deg.Data = static_cast + (ElacComputer_U.in.analog_inputs.left_elevator_pos_deg); + } + + if (ElacComputer_U.in.discrete_inputs.r_elev_servo_failed) { + ElacComputer_Y.out.bus_outputs.right_elevator_position_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant_Value); + ElacComputer_Y.out.bus_outputs.right_elevator_position_deg.Data = static_cast + (ElacComputer_P.Constant3_Value_f); + } else { + ElacComputer_Y.out.bus_outputs.right_elevator_position_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant1_Value); + ElacComputer_Y.out.bus_outputs.right_elevator_position_deg.Data = static_cast + (ElacComputer_U.in.analog_inputs.right_elevator_pos_deg); + } + + if (ElacComputer_U.in.discrete_inputs.ths_motor_fault) { + ElacComputer_Y.out.bus_outputs.ths_position_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant_Value); + ElacComputer_Y.out.bus_outputs.ths_position_deg.Data = static_cast(ElacComputer_P.Constant4_Value_i); + } else { + ElacComputer_Y.out.bus_outputs.ths_position_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant1_Value); + ElacComputer_Y.out.bus_outputs.ths_position_deg.Data = static_cast + (ElacComputer_U.in.analog_inputs.ths_pos_deg); + } + + ElacComputer_Y.out.bus_outputs.left_sidestick_pitch_command_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant1_Value); + ElacComputer_Y.out.bus_outputs.left_sidestick_pitch_command_deg.Data = ElacComputer_P.Gain_Gain_b * + static_cast(ElacComputer_U.in.analog_inputs.capt_pitch_stick_pos); + ElacComputer_Y.out.bus_outputs.right_sidestick_pitch_command_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant1_Value); + ElacComputer_Y.out.bus_outputs.right_sidestick_pitch_command_deg.Data = ElacComputer_P.Gain1_Gain_f * static_cast< + real32_T>(ElacComputer_U.in.analog_inputs.fo_pitch_stick_pos); + ElacComputer_Y.out.bus_outputs.left_sidestick_roll_command_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant1_Value); + ElacComputer_Y.out.bus_outputs.left_sidestick_roll_command_deg.Data = ElacComputer_P.Gain2_Gain_c * + static_cast(ElacComputer_U.in.analog_inputs.capt_roll_stick_pos); + ElacComputer_Y.out.bus_outputs.right_sidestick_roll_command_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant1_Value); + ElacComputer_Y.out.bus_outputs.right_sidestick_roll_command_deg.Data = ElacComputer_P.Gain3_Gain * + static_cast(ElacComputer_U.in.analog_inputs.fo_roll_stick_pos); + ElacComputer_Y.out.bus_outputs.rudder_pedal_position_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant1_Value); + ElacComputer_Y.out.bus_outputs.rudder_pedal_position_deg.Data = ElacComputer_P.Gain4_Gain * static_cast + (ElacComputer_U.in.analog_inputs.rudder_pedal_pos); + if ((rtb_OR_e1 || rtb_OR1_me) && rtb_isEngagedInRoll) { + ElacComputer_Y.out.bus_outputs.aileron_command_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant1_Value); + if (!leftAileronAvail) { + ElacComputer_Y.out.bus_outputs.aileron_command_deg.Data = static_cast(rtb_Y_b); + } else { + ElacComputer_Y.out.bus_outputs.aileron_command_deg.Data = static_cast(rtb_Y_c); + } + } else { + ElacComputer_Y.out.bus_outputs.aileron_command_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant_Value); + ElacComputer_Y.out.bus_outputs.aileron_command_deg.Data = static_cast(ElacComputer_P.Constant5_Value); + } + + if (static_cast(rtb_isEngagedInRoll) > ElacComputer_P.Switch13_Threshold) { + ElacComputer_Y.out.bus_outputs.roll_spoiler_command_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant1_Value); + } else { + ElacComputer_Y.out.bus_outputs.roll_spoiler_command_deg.SSM = static_cast + (ElacComputer_P.EnumeratedConstant_Value); + } + + if (static_cast(rtb_isEngagedInRoll) > ElacComputer_P.Switch12_Threshold) { + ElacComputer_Y.out.bus_outputs.roll_spoiler_command_deg.Data = static_cast(rtb_xi_deg_m); + } else { + ElacComputer_Y.out.bus_outputs.roll_spoiler_command_deg.Data = static_cast + (ElacComputer_P.Constant6_Value); + } + + ElacComputer_Y.out.bus_outputs.yaw_damper_command_deg.SSM = static_cast(rtb_ap_special_disc); + ElacComputer_Y.out.bus_outputs.yaw_damper_command_deg.Data = rtb_tla1; + ElacComputer_Y.out.bus_outputs.elevator_double_pressurization_command_deg.SSM = rtb_Switch18; + ElacComputer_Y.out.bus_outputs.elevator_double_pressurization_command_deg.Data = + rtb_elevator_double_pressurization_command_deg_Data; + ElacComputer_Y.out.bus_outputs.discrete_status_word_1.SSM = static_cast + (ElacComputer_P.EnumeratedConstant1_Value); + ElacComputer_Y.out.bus_outputs.discrete_status_word_1.Data = rtb_y_m; + ElacComputer_Y.out.bus_outputs.discrete_status_word_2.SSM = static_cast + (ElacComputer_P.EnumeratedConstant1_Value); + ElacComputer_Y.out.bus_outputs.discrete_status_word_2.Data = rtb_tla2; + ElacComputer_DWork.Delay_DSTATE = rtb_DataTypeConversion7; + ElacComputer_DWork.icLoad = false; + ElacComputer_DWork.Delay_DSTATE_c = ElacComputer_DWork.Delay_DSTATE_b; + } else { + ElacComputer_DWork.Runtime_MODE = false; + } +} + +void ElacComputer::initialize() +{ + ElacComputer_DWork.Delay_DSTATE_cc = ElacComputer_P.Delay_InitialCondition_c; + ElacComputer_DWork.Delay1_DSTATE = ElacComputer_P.Delay1_InitialCondition; + ElacComputer_DWork.Memory_PreviousInput = ElacComputer_P.SRFlipFlop_initial_condition; + ElacComputer_DWork.Delay_DSTATE = ElacComputer_P.DiscreteDerivativeVariableTs_InitialCondition; + ElacComputer_DWork.Delay_DSTATE_b = ElacComputer_P.Delay_InitialCondition; + ElacComputer_DWork.icLoad = true; + LawMDLOBJ2.init(); + LawMDLOBJ5.init(); + LawMDLOBJ3.init(); + ElacComputer_Y.out = ElacComputer_P.out_Y0; +} + +void ElacComputer::terminate() +{ +} + +ElacComputer::ElacComputer(): + ElacComputer_U(), + ElacComputer_Y(), + ElacComputer_B(), + ElacComputer_DWork() +{ +} + +ElacComputer::~ElacComputer() +{ +} diff --git a/src/fbw/src/model/ElacComputer.h b/src/fbw/src/model/ElacComputer.h new file mode 100644 index 000000000000..572b979399df --- /dev/null +++ b/src/fbw/src/model/ElacComputer.h @@ -0,0 +1,377 @@ +#ifndef RTW_HEADER_ElacComputer_h_ +#define RTW_HEADER_ElacComputer_h_ +#include "rtwtypes.h" +#include "ElacComputer_types.h" +#include "LateralNormalLaw.h" +#include "LateralDirectLaw.h" +#include "PitchNormalLaw.h" +#include "PitchAlternateLaw.h" +#include "PitchDirectLaw.h" + +extern const real_T ElacComputer_RGND; +extern base_elac_logic_outputs rtP_elac_logic_output_MATLABStruct; +extern base_elac_analog_outputs rtP_elac_analog_output_MATLABStruct; +extern base_elac_discrete_outputs rtP_elac_discrete_output_MATLABStruct; +class ElacComputer final +{ + public: + struct rtDW_RateLimiter_ElacComputer_T { + real_T pY; + boolean_T pY_not_empty; + }; + + struct rtDW_RateLimiter_ElacComputer_g_T { + real_T pY; + boolean_T pY_not_empty; + }; + + struct rtDW_LagFilter_ElacComputer_T { + real_T pY; + real_T pU; + boolean_T pY_not_empty; + boolean_T pU_not_empty; + }; + + struct rtDW_MATLABFunction_ElacComputer_kz_T { + real_T timeSinceCondition; + boolean_T output; + }; + + struct rtDW_MATLABFunction_ElacComputer_o_T { + boolean_T output; + }; + + struct rtDW_RateLimiter_ElacComputer_b_T { + real_T pY; + boolean_T pY_not_empty; + }; + + struct rtDW_MATLABFunction_ElacComputer_b_T { + boolean_T previousInput; + boolean_T previousInput_not_empty; + }; + + struct BlockIO_ElacComputer_T { + real_T in_flight; + }; + + struct D_Work_ElacComputer_T { + real_T Delay_DSTATE; + real_T Delay_DSTATE_b; + real_T Delay_DSTATE_c; + real_T configFullEventTime; + real_T eventTime; + real_T resetEventTime; + real_T eventTime_g; + real_T on_ground_time; + boolean_T Delay_DSTATE_cc; + boolean_T Delay1_DSTATE; + uint8_T is_active_c28_ElacComputer; + uint8_T is_c28_ElacComputer; + uint8_T is_active_c30_ElacComputer; + uint8_T is_c30_ElacComputer; + boolean_T Memory_PreviousInput; + boolean_T icLoad; + boolean_T pLeftStickDisabled; + boolean_T pRightStickDisabled; + boolean_T configFullEventTime_not_empty; + boolean_T ra1CoherenceRejected; + boolean_T ra2CoherenceRejected; + boolean_T sProtActive; + boolean_T eventTime_not_empty; + boolean_T resetEventTime_not_empty; + boolean_T sProtActive_m; + boolean_T eventTime_not_empty_a; + boolean_T abnormalConditionWasActive; + boolean_T Runtime_MODE; + rtDW_MATLABFunction_ElacComputer_kz_T sf_MATLABFunction_fb; + rtDW_MATLABFunction_ElacComputer_b_T sf_MATLABFunction_l0; + rtDW_MATLABFunction_ElacComputer_b_T sf_MATLABFunction_nu; + rtDW_MATLABFunction_ElacComputer_b_T sf_MATLABFunction_g4; + rtDW_MATLABFunction_ElacComputer_kz_T sf_MATLABFunction_j2; + rtDW_MATLABFunction_ElacComputer_kz_T sf_MATLABFunction_g24; + rtDW_MATLABFunction_ElacComputer_kz_T sf_MATLABFunction_lf; + rtDW_MATLABFunction_ElacComputer_kz_T sf_MATLABFunction_jl; + rtDW_MATLABFunction_ElacComputer_kz_T sf_MATLABFunction_jz; + rtDW_RateLimiter_ElacComputer_b_T sf_RateLimiter_m; + rtDW_RateLimiter_ElacComputer_b_T sf_RateLimiter_n; + rtDW_MATLABFunction_ElacComputer_kz_T sf_MATLABFunction_nb; + rtDW_MATLABFunction_ElacComputer_o_T sf_MATLABFunction_br; + rtDW_MATLABFunction_ElacComputer_o_T sf_MATLABFunction_jg; + rtDW_MATLABFunction_ElacComputer_o_T sf_MATLABFunction_mi; + rtDW_MATLABFunction_ElacComputer_kz_T sf_MATLABFunction_gfx; + rtDW_MATLABFunction_ElacComputer_kz_T sf_MATLABFunction_g2; + rtDW_MATLABFunction_ElacComputer_kz_T sf_MATLABFunction_cj; + rtDW_LagFilter_ElacComputer_T sf_LagFilter_a; + rtDW_LagFilter_ElacComputer_T sf_LagFilter; + rtDW_RateLimiter_ElacComputer_g_T sf_RateLimiter_p; + rtDW_RateLimiter_ElacComputer_g_T sf_RateLimiter_a; + rtDW_RateLimiter_ElacComputer_T sf_RateLimiter_b; + rtDW_RateLimiter_ElacComputer_T sf_RateLimiter; + }; + + struct ExternalInputs_ElacComputer_T { + elac_inputs in; + }; + + struct ExternalOutputs_ElacComputer_T { + elac_outputs out; + }; + + struct Parameters_ElacComputer_T { + real_T LagFilter_C1; + real_T LagFilter_C1_e; + real_T DiscreteDerivativeVariableTs_Gain; + real_T DiscreteTimeIntegratorVariableTsLimit_Gain; + real_T RateLimiterVariableTs2_InitialCondition; + real_T RateLimiterVariableTs3_InitialCondition; + real_T DiscreteDerivativeVariableTs_InitialCondition; + real_T BitfromLabel_bit; + real_T BitfromLabel1_bit; + real_T BitfromLabel_bit_c; + real_T BitfromLabel1_bit_j; + real_T BitfromLabel2_bit; + real_T BitfromLabel3_bit; + real_T BitfromLabel5_bit; + real_T BitfromLabel4_bit; + real_T BitfromLabel6_bit; + real_T BitfromLabel7_bit; + real_T BitfromLabel8_bit; + real_T BitfromLabel4_bit_d; + real_T BitfromLabel5_bit_e; + real_T BitfromLabel6_bit_k; + real_T BitfromLabel7_bit_h; + real_T BitfromLabel_bit_a; + real_T BitfromLabel1_bit_jr; + real_T BitfromLabel_bit_h; + real_T BitfromLabel1_bit_e; + real_T BitfromLabel2_bit_k; + real_T BitfromLabel3_bit_m; + real_T BitfromLabel_bit_h2; + real_T BitfromLabel1_bit_g; + real_T BitfromLabel2_bit_n; + real_T BitfromLabel3_bit_g; + real_T BitfromLabel4_bit_e; + real_T BitfromLabel5_bit_a; + real_T BitfromLabel_bit_e; + real_T BitfromLabel1_bit_d; + real_T BitfromLabel_bit_a2; + real_T BitfromLabel1_bit_p; + real_T BitfromLabel_bit_p; + real_T BitfromLabel1_bit_h; + real_T BitfromLabel2_bit_f; + real_T BitfromLabel3_bit_c; + real_T BitfromLabel4_bit_n; + real_T BitfromLabel5_bit_p; + real_T BitfromLabel_bit_n; + real_T BitfromLabel1_bit_h1; + real_T BitfromLabel2_bit_g; + real_T BitfromLabel3_bit_b; + real_T BitfromLabel4_bit_i; + real_T BitfromLabel5_bit_l; + real_T BitfromLabel_bit_p3; + real_T BitfromLabel2_bit_j; + real_T BitfromLabel1_bit_i; + real_T BitfromLabel3_bit_mo; + real_T BitfromLabel_bit_es; + real_T CompareToConstant_const; + real_T CompareToConstant_const_m; + real_T CompareToConstant_const_l; + real_T CompareToConstant_const_m4; + real_T CompareToConstant_const_f; + real_T CompareToConstant2_const; + real_T CompareToConstant3_const; + real_T CompareToConstant1_const; + real_T CompareToConstant1_const_d; + real_T HysteresisNode2_highTrigger; + real_T HysteresisNode1_highTrigger; + real_T HysteresisNode3_highTrigger; + real_T RateLimiterGenericVariableTs_lo; + real_T RateLimiterGenericVariableTs1_lo; + real_T RateLimiterVariableTs2_lo; + real_T RateLimiterVariableTs3_lo; + real_T RateLimiterGenericVariableTs_lo_k; + real_T RateLimiterGenericVariableTs1_lo_c; + real_T HysteresisNode2_lowTrigger; + real_T HysteresisNode1_lowTrigger; + real_T HysteresisNode3_lowTrigger; + real_T ConfirmNode_timeDelay; + real_T ConfirmNode2_timeDelay; + real_T ConfirmNode1_timeDelay; + real_T ConfirmNode_timeDelay_n; + real_T ConfirmNode1_timeDelay_h; + real_T ConfirmNode2_timeDelay_k; + real_T ConfirmNode1_timeDelay_a; + real_T ConfirmNode_timeDelay_a; + real_T ConfirmNode_timeDelay_d; + real_T ConfirmNode_timeDelay_p; + real_T RateLimiterGenericVariableTs_up; + real_T RateLimiterGenericVariableTs1_up; + real_T RateLimiterVariableTs2_up; + real_T RateLimiterVariableTs3_up; + real_T RateLimiterGenericVariableTs_up_b; + real_T RateLimiterGenericVariableTs1_up_g; + SignStatusMatrix EnumeratedConstant_Value; + SignStatusMatrix EnumeratedConstant1_Value; + lateral_efcs_law EnumeratedConstant2_Value; + pitch_efcs_law EnumeratedConstant_Value_i; + pitch_efcs_law EnumeratedConstant_Value_b; + real32_T CompareToConstant_const_ll; + boolean_T SRFlipFlop_initial_condition; + boolean_T ConfirmNode_isRisingEdge; + boolean_T ConfirmNode2_isRisingEdge; + boolean_T ConfirmNode1_isRisingEdge; + boolean_T ConfirmNode_isRisingEdge_k; + boolean_T ConfirmNode1_isRisingEdge_i; + boolean_T ConfirmNode2_isRisingEdge_j; + boolean_T PulseNode_isRisingEdge; + boolean_T PulseNode1_isRisingEdge; + boolean_T ConfirmNode1_isRisingEdge_k; + boolean_T ConfirmNode_isRisingEdge_j; + boolean_T ConfirmNode_isRisingEdge_o; + boolean_T PulseNode_isRisingEdge_g; + boolean_T ConfirmNode_isRisingEdge_f; + elac_outputs out_Y0; + base_elac_out_bus Constant4_Value; + base_elac_laws_outputs Constant_Value; + real_T Bias_Bias; + real_T Saturation_UpperSat; + real_T Saturation_LowerSat; + real_T Gain2_Gain; + real_T Constant2_Value; + real_T Constant1_Value; + real_T Constant4_Value_a; + real_T Constant3_Value; + real_T Gain_Gain; + real_T Constant2_Value_l; + real_T Constant3_Value_h; + real_T Gain_Gain_l; + real_T Saturation_UpperSat_g; + real_T Saturation_LowerSat_o; + real_T Constant_Value_n; + real_T Constant5_Value; + real_T Constant6_Value; + real_T Constant7_Value; + real_T Constant8_Value; + real_T Constant1_Value_d; + real_T Constant2_Value_b; + real_T Constant3_Value_f; + real_T Constant4_Value_i; + real_T Constant_Value_j; + real_T Constant_Value_p; + real_T Saturation_UpperSat_d; + real_T Saturation_LowerSat_h; + real_T Constant1_Value_p; + real_T Saturation1_UpperSat; + real_T Saturation1_LowerSat; + real_T alphamax_tableData[24]; + real_T alphamax_bp01Data[4]; + real_T alphamax_bp02Data[6]; + real_T alphaprotection_tableData[24]; + real_T alphaprotection_bp01Data[4]; + real_T alphaprotection_bp02Data[6]; + real_T Constant5_Value_k; + real_T Constant6_Value_b; + real_T Constant7_Value_g; + real_T Constant8_Value_h; + real_T Gain1_Gain; + real_T uDLookupTable_tableData[4]; + real_T uDLookupTable_bp01Data[4]; + real_T uDLookupTable1_tableData[4]; + real_T uDLookupTable1_bp01Data[4]; + real_T uDLookupTable2_tableData[4]; + real_T uDLookupTable2_bp01Data[4]; + real_T Constant_Value_c; + real_T Saturation2_UpperSat; + real_T Saturation2_LowerSat; + real_T Saturation1_UpperSat_g; + real_T Saturation1_LowerSat_n; + real_T Gain1_Gain_b; + real_T Constant_Value_a; + real_T Delay_InitialCondition; + real_T uDLookupTable_tableData_j[7]; + real_T uDLookupTable_bp01Data_h[7]; + real_T Constant_Value_b; + real_T Switch13_Threshold; + real_T Switch12_Threshold; + real32_T Gain_Gain_b; + real32_T Gain1_Gain_f; + real32_T Gain2_Gain_c; + real32_T Gain3_Gain; + real32_T Gain4_Gain; + uint32_T alphamax_maxIndex[2]; + uint32_T alphaprotection_maxIndex[2]; + boolean_T Constant1_Value_b; + boolean_T Constant_Value_ad; + boolean_T Delay_InitialCondition_c; + boolean_T Delay1_InitialCondition; + boolean_T Logic_table[16]; + boolean_T reset_Value; + boolean_T reset_Value_j; + boolean_T Constant1_Value_e; + boolean_T Constant9_Value; + boolean_T Constant10_Value; + }; + + ElacComputer(ElacComputer const&) = delete; + ElacComputer& operator= (ElacComputer const&) & = delete; + ElacComputer(ElacComputer &&) = delete; + ElacComputer& operator= (ElacComputer &&) = delete; + void setExternalInputs(const ExternalInputs_ElacComputer_T *pExternalInputs_ElacComputer_T) + { + ElacComputer_U = *pExternalInputs_ElacComputer_T; + } + + const ExternalOutputs_ElacComputer_T &getExternalOutputs() const + { + return ElacComputer_Y; + } + + void initialize(); + void step(); + static void terminate(); + ElacComputer(); + ~ElacComputer(); + private: + ExternalInputs_ElacComputer_T ElacComputer_U; + ExternalOutputs_ElacComputer_T ElacComputer_Y; + BlockIO_ElacComputer_T ElacComputer_B; + D_Work_ElacComputer_T ElacComputer_DWork; + static Parameters_ElacComputer_T ElacComputer_P; + static void ElacComputer_MATLABFunction(const base_arinc_429 *rtu_u, boolean_T *rty_y); + static void ElacComputer_MATLABFunction_j(const base_arinc_429 *rtu_u, real_T rtu_bit, uint32_T *rty_y); + static void ElacComputer_RateLimiter_Reset(rtDW_RateLimiter_ElacComputer_T *localDW); + static void ElacComputer_RateLimiter(real_T rtu_u, real_T rtu_up, real_T rtu_lo, real_T rtu_Ts, real_T rtu_init, + real_T *rty_Y, rtDW_RateLimiter_ElacComputer_T *localDW); + static void ElacComputer_RateLimiter_o_Reset(rtDW_RateLimiter_ElacComputer_g_T *localDW); + static void ElacComputer_RateLimiter_a(real_T rtu_u, real_T rtu_up, real_T rtu_lo, real_T rtu_Ts, real_T rtu_init, + boolean_T rtu_reset, real_T *rty_Y, rtDW_RateLimiter_ElacComputer_g_T *localDW); + static void ElacComputer_MATLABFunction_o(boolean_T rtu_bit1, boolean_T rtu_bit2, boolean_T rtu_bit3, boolean_T + rtu_bit4, boolean_T rtu_bit5, boolean_T rtu_bit6, real_T *rty_handleIndex); + static void ElacComputer_LagFilter_Reset(rtDW_LagFilter_ElacComputer_T *localDW); + static void ElacComputer_LagFilter(real_T rtu_U, real_T rtu_C1, real_T rtu_dt, real_T *rty_Y, + rtDW_LagFilter_ElacComputer_T *localDW); + static void ElacComputer_MATLABFunction_g5_Reset(rtDW_MATLABFunction_ElacComputer_kz_T *localDW); + static void ElacComputer_MATLABFunction_c(boolean_T rtu_u, real_T rtu_Ts, boolean_T rtu_isRisingEdge, real_T + rtu_timeDelay, boolean_T *rty_y, rtDW_MATLABFunction_ElacComputer_kz_T *localDW); + static void ElacComputer_MATLABFunction_k_Reset(rtDW_MATLABFunction_ElacComputer_o_T *localDW); + static void ElacComputer_MATLABFunction_m(real_T rtu_u, real_T rtu_highTrigger, real_T rtu_lowTrigger, boolean_T + *rty_y, rtDW_MATLABFunction_ElacComputer_o_T *localDW); + static void ElacComputer_GetIASforMach4(real_T rtu_m, real_T rtu_m_t, real_T rtu_v, real_T *rty_v_t); + static void ElacComputer_RateLimiter_d_Reset(rtDW_RateLimiter_ElacComputer_b_T *localDW); + static void ElacComputer_RateLimiter_n(real_T rtu_u, real_T rtu_up, real_T rtu_lo, real_T rtu_Ts, boolean_T rtu_reset, + real_T *rty_Y, rtDW_RateLimiter_ElacComputer_b_T *localDW); + static void ElacComputer_MATLABFunction_ax_Reset(rtDW_MATLABFunction_ElacComputer_b_T *localDW); + static void ElacComputer_MATLABFunction_g(boolean_T rtu_u, boolean_T rtu_isRisingEdge, boolean_T *rty_y, + rtDW_MATLABFunction_ElacComputer_b_T *localDW); + static void ElacComputer_MATLABFunction_cw(const boolean_T rtu_u[19], real32_T *rty_y); + static void ElacComputer_LateralLawCaptoBits(lateral_efcs_law rtu_law, boolean_T *rty_bit1, boolean_T *rty_bit2); + LateralDirectLaw LawMDLOBJ1; + LateralNormalLaw LawMDLOBJ2; + PitchAlternateLaw LawMDLOBJ3; + PitchDirectLaw LawMDLOBJ4; + PitchNormalLaw LawMDLOBJ5; +}; + +#endif + diff --git a/src/fbw/src/model/ElacComputer_data.cpp b/src/fbw/src/model/ElacComputer_data.cpp new file mode 100644 index 000000000000..6df88846a0ea --- /dev/null +++ b/src/fbw/src/model/ElacComputer_data.cpp @@ -0,0 +1,2212 @@ +#include "ElacComputer.h" + +base_elac_logic_outputs rtP_elac_logic_output_MATLABStruct{ + false, + false, + false, + lateral_efcs_law::None, + lateral_efcs_law::None, + pitch_efcs_law::None, + pitch_efcs_law::None, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + 0.0, + 0.0, + false, + false, + false, + 0.0, + 0.0, + false, + 0.0, + 0.0, + false, + false, + false, + false, + false, + false, + false, + + { + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + }, + 0.0, + false +} ; + +base_elac_analog_outputs rtP_elac_analog_output_MATLABStruct{ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 +} ; + +base_elac_discrete_outputs rtP_elac_discrete_output_MATLABStruct{ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false +} ; + +ElacComputer::Parameters_ElacComputer_T ElacComputer::ElacComputer_P{ + + 0.5, + + 2.0, + + 1.0, + + 1.0, + + 0.0, + + 0.0, + + 0.0, + + 23.0, + + 23.0, + + 15.0, + + 16.0, + + 15.0, + + 23.0, + + 23.0, + + 24.0, + + 24.0, + + 29.0, + + 29.0, + + 17.0, + + 17.0, + + 18.0, + + 18.0, + + 20.0, + + 20.0, + + 11.0, + + 12.0, + + 13.0, + + 14.0, + + 17.0, + + 18.0, + + 19.0, + + 20.0, + + 21.0, + + 26.0, + + 19.0, + + 19.0, + + 23.0, + + 23.0, + + 17.0, + + 18.0, + + 19.0, + + 20.0, + + 21.0, + + 26.0, + + 17.0, + + 18.0, + + 19.0, + + 20.0, + + 21.0, + + 26.0, + + 17.0, + + 17.0, + + 18.0, + + 18.0, + + 16.0, + + 50.0, + + 0.02, + + 5.0, + + 0.0, + + 0.0, + + 1.0, + + 2.0, + + 3.0, + + 1.0, + + 1750.0, + + 1750.0, + + 1750.0, + + -1.0, + + -1.0, + + -1.0, + + -20.0, + + -50.0, + + -50.0, + + 1450.0, + + 1450.0, + + 1450.0, + + 1.0, + + 1.0, + + 1.0, + + 0.5, + + 0.5, + + 0.5, + + 30.0, + + 30.0, + + 0.2, + + 30.0, + + 1.0, + + 1.0, + + 1.0, + + 20.0, + + 50.0, + + 50.0, + + SignStatusMatrix::NoComputedData, + + SignStatusMatrix::NormalOperation, + + lateral_efcs_law::NormalLaw, + + pitch_efcs_law::DirectLaw, + + pitch_efcs_law::AlternateLaw2, + + 50.0F, + + false, + + true, + + true, + + true, + + true, + + true, + + true, + + true, + + true, + + true, + + true, + + false, + + false, + + false, + + + { + { + { + 0.0, + 0.0, + 0.0 + }, + + { + false, + false, + false, + false, + false + }, + + { + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false + }, + + { + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + } + } + }, + + { + { + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + 0.0, + 0.0, + false + } + }, + + { + false, + false, + false, + lateral_efcs_law::NormalLaw, + lateral_efcs_law::NormalLaw, + pitch_efcs_law::NormalLaw, + pitch_efcs_law::NormalLaw, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + 0.0, + 0.0, + false, + false, + false, + 0.0, + 0.0, + false, + 0.0, + 0.0, + false, + false, + false, + false, + false, + false, + false, + + { + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + }, + 0.0, + false + }, + + { + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false + }, + + { + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + } + }, + + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + + { + { + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + 0.0, + 0.0, + false + } + }, + + -5.0, + + 20.0, + + 0.0, + + 1.25, + + 5.0, + + 0.0, + + -30.0, + + 0.0, + + -1.0, + + 3.5, + + -11.0, + + -2.0, + + 0.7, + + -0.7, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 1.0, + + -1.0, + + 0.0, + + 1.0, + + -1.0, + + + { 8.7, 8.7, 6.4, 6.4, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 14.2, 14.2, 14.2, 14.2, 13.1, 13.1, 13.1, 13.1, + 13.0, 13.0, 13.0, 13.0 }, + + + { 0.0, 0.5, 0.9, 1.0 }, + + + { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 }, + + + { 6.5, 6.5, 4.6, 4.6, 11.7, 11.7, 11.7, 11.7, 11.7, 11.7, 11.7, 11.7, 11.9, 11.9, 11.9, 11.9, 11.0, 11.0, 11.0, 11.0, + 10.6, 10.6, 10.6, 10.6 }, + + + { 0.0, 0.5, 0.9, 1.0 }, + + + { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 }, + + 350.0, + + 0.82, + + 380.0, + + 0.88, + + 0.017453292519943295, + + + { 0.82, 0.82, 0.85, 0.85 }, + + + { -4.0, -3.0, -1.0, 0.0 }, + + + { 350.0, 350.0, 356.0, 356.0 }, + + + { -4.0, -3.0, -1.0, 0.0 }, + + + { 0.82, 0.82, 0.83, 0.83 }, + + + { -4.0, -3.0, -1.0, 0.0 }, + + 0.0, + + 25.0, + + -25.0, + + 25.0, + + -25.0, + + 1.4, + + 0.0, + + 0.0, + + + { 30.0, 30.0, 20.0, 12.4, 8.0, 6.0, 6.0 }, + + + { 0.0, 180.0, 220.0, 280.0, 350.0, 400.0, 450.0 }, + + 0.0, + + 0.0, + + 0.0, + + 16.0F, + + 16.0F, + + 20.0F, + + 20.0F, + + 30.0F, + + + { 3U, 5U }, + + + { 3U, 5U }, + + false, + + false, + + false, + + false, + + + { false, true, false, false, true, true, false, false, true, false, true, true, false, false, false, false }, + + false, + + false, + + true, + + false, + + false +}; diff --git a/src/fbw/src/model/ElacComputer_private.h b/src/fbw/src/model/ElacComputer_private.h new file mode 100644 index 000000000000..99b696bec81a --- /dev/null +++ b/src/fbw/src/model/ElacComputer_private.h @@ -0,0 +1,5 @@ +#ifndef RTW_HEADER_ElacComputer_private_h_ +#define RTW_HEADER_ElacComputer_private_h_ +#include "rtwtypes.h" +#endif + diff --git a/src/fbw/src/model/ElacComputer_types.h b/src/fbw/src/model/ElacComputer_types.h new file mode 100644 index 000000000000..1cb8ac1d1720 --- /dev/null +++ b/src/fbw/src/model/ElacComputer_types.h @@ -0,0 +1,881 @@ +#ifndef RTW_HEADER_ElacComputer_types_h_ +#define RTW_HEADER_ElacComputer_types_h_ +#include "rtwtypes.h" + +#ifndef DEFINED_TYPEDEF_FOR_pitch_efcs_law_ +#define DEFINED_TYPEDEF_FOR_pitch_efcs_law_ + +enum class pitch_efcs_law + : int32_T { + NormalLaw = 0, + AlternateLaw1, + AlternateLaw2, + DirectLaw, + None +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_SignStatusMatrix_ +#define DEFINED_TYPEDEF_FOR_SignStatusMatrix_ + +enum class SignStatusMatrix + : int32_T { + FailureWarning = 0, + NoComputedData, + FunctionalTest, + NormalOperation +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_lateral_efcs_law_ +#define DEFINED_TYPEDEF_FOR_lateral_efcs_law_ + +enum class lateral_efcs_law + : int32_T { + NormalLaw = 0, + DirectLaw, + None +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_time_ +#define DEFINED_TYPEDEF_FOR_base_time_ + +struct base_time +{ + real_T dt; + real_T simulation_time; + real_T monotonic_time; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sim_data_ +#define DEFINED_TYPEDEF_FOR_base_sim_data_ + +struct base_sim_data +{ + boolean_T slew_on; + boolean_T pause_on; + boolean_T tracking_mode_on_override; + boolean_T tailstrike_protection_on; + boolean_T computer_running; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_elac_discrete_inputs_ +#define DEFINED_TYPEDEF_FOR_base_elac_discrete_inputs_ + +struct base_elac_discrete_inputs +{ + boolean_T ground_spoilers_active_1; + boolean_T ground_spoilers_active_2; + boolean_T is_unit_1; + boolean_T is_unit_2; + boolean_T opp_axis_pitch_failure; + boolean_T ap_1_disengaged; + boolean_T ap_2_disengaged; + boolean_T opp_left_aileron_lost; + boolean_T opp_right_aileron_lost; + boolean_T fac_1_yaw_control_lost; + boolean_T lgciu_1_nose_gear_pressed; + boolean_T lgciu_2_nose_gear_pressed; + boolean_T fac_2_yaw_control_lost; + boolean_T lgciu_1_right_main_gear_pressed; + boolean_T lgciu_2_right_main_gear_pressed; + boolean_T lgciu_1_left_main_gear_pressed; + boolean_T lgciu_2_left_main_gear_pressed; + boolean_T ths_motor_fault; + boolean_T sfcc_1_slats_out; + boolean_T sfcc_2_slats_out; + boolean_T l_ail_servo_failed; + boolean_T l_elev_servo_failed; + boolean_T r_ail_servo_failed; + boolean_T r_elev_servo_failed; + boolean_T ths_override_active; + boolean_T yellow_low_pressure; + boolean_T capt_priority_takeover_pressed; + boolean_T fo_priority_takeover_pressed; + boolean_T blue_low_pressure; + boolean_T green_low_pressure; + boolean_T elac_engaged_from_switch; + boolean_T normal_powersupply_lost; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_elac_analog_inputs_ +#define DEFINED_TYPEDEF_FOR_base_elac_analog_inputs_ + +struct base_elac_analog_inputs +{ + real_T capt_pitch_stick_pos; + real_T fo_pitch_stick_pos; + real_T capt_roll_stick_pos; + real_T fo_roll_stick_pos; + real_T left_elevator_pos_deg; + real_T right_elevator_pos_deg; + real_T ths_pos_deg; + real_T left_aileron_pos_deg; + real_T right_aileron_pos_deg; + real_T rudder_pedal_pos; + real_T load_factor_acc_1_g; + real_T load_factor_acc_2_g; + real_T blue_hyd_pressure_psi; + real_T green_hyd_pressure_psi; + real_T yellow_hyd_pressure_psi; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_arinc_429_ +#define DEFINED_TYPEDEF_FOR_base_arinc_429_ + +struct base_arinc_429 +{ + uint32_T SSM; + real32_T Data; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_adr_bus_ +#define DEFINED_TYPEDEF_FOR_base_adr_bus_ + +struct base_adr_bus +{ + base_arinc_429 altitude_standard_ft; + base_arinc_429 altitude_corrected_ft; + base_arinc_429 mach; + base_arinc_429 airspeed_computed_kn; + base_arinc_429 airspeed_true_kn; + base_arinc_429 vertical_speed_ft_min; + base_arinc_429 aoa_corrected_deg; + base_arinc_429 corrected_average_static_pressure; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_ir_bus_ +#define DEFINED_TYPEDEF_FOR_base_ir_bus_ + +struct base_ir_bus +{ + base_arinc_429 discrete_word_1; + base_arinc_429 latitude_deg; + base_arinc_429 longitude_deg; + base_arinc_429 ground_speed_kn; + base_arinc_429 track_angle_true_deg; + base_arinc_429 heading_true_deg; + base_arinc_429 wind_speed_kn; + base_arinc_429 wind_direction_true_deg; + base_arinc_429 track_angle_magnetic_deg; + base_arinc_429 heading_magnetic_deg; + base_arinc_429 drift_angle_deg; + base_arinc_429 flight_path_angle_deg; + base_arinc_429 flight_path_accel_g; + base_arinc_429 pitch_angle_deg; + base_arinc_429 roll_angle_deg; + base_arinc_429 body_pitch_rate_deg_s; + base_arinc_429 body_roll_rate_deg_s; + base_arinc_429 body_yaw_rate_deg_s; + base_arinc_429 body_long_accel_g; + base_arinc_429 body_lat_accel_g; + base_arinc_429 body_normal_accel_g; + base_arinc_429 track_angle_rate_deg_s; + base_arinc_429 pitch_att_rate_deg_s; + base_arinc_429 roll_att_rate_deg_s; + base_arinc_429 inertial_alt_ft; + base_arinc_429 along_track_horiz_acc_g; + base_arinc_429 cross_track_horiz_acc_g; + base_arinc_429 vertical_accel_g; + base_arinc_429 inertial_vertical_speed_ft_s; + base_arinc_429 north_south_velocity_kn; + base_arinc_429 east_west_velocity_kn; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fmgc_b_bus_ +#define DEFINED_TYPEDEF_FOR_base_fmgc_b_bus_ + +struct base_fmgc_b_bus +{ + base_arinc_429 fac_weight_lbs; + base_arinc_429 fm_weight_lbs; + base_arinc_429 fac_cg_percent; + base_arinc_429 fm_cg_percent; + base_arinc_429 fg_radio_height_ft; + base_arinc_429 discrete_word_4; + base_arinc_429 ats_discrete_word; + base_arinc_429 discrete_word_3; + base_arinc_429 discrete_word_1; + base_arinc_429 discrete_word_2; + base_arinc_429 approach_spd_target_kn; + base_arinc_429 delta_p_ail_cmd_deg; + base_arinc_429 delta_p_splr_cmd_deg; + base_arinc_429 delta_r_cmd_deg; + base_arinc_429 delta_nose_wheel_cmd_deg; + base_arinc_429 delta_q_cmd_deg; + base_arinc_429 n1_left_percent; + base_arinc_429 n1_right_percent; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_ra_bus_ +#define DEFINED_TYPEDEF_FOR_base_ra_bus_ + +struct base_ra_bus +{ + base_arinc_429 radio_height_ft; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sfcc_bus_ +#define DEFINED_TYPEDEF_FOR_base_sfcc_bus_ + +struct base_sfcc_bus +{ + base_arinc_429 slat_flap_component_status_word; + base_arinc_429 slat_flap_system_status_word; + base_arinc_429 slat_flap_actual_position_word; + base_arinc_429 slat_actual_position_deg; + base_arinc_429 flap_actual_position_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fcdc_bus_ +#define DEFINED_TYPEDEF_FOR_base_fcdc_bus_ + +struct base_fcdc_bus +{ + base_arinc_429 efcs_status_word_1; + base_arinc_429 efcs_status_word_2; + base_arinc_429 efcs_status_word_3; + base_arinc_429 efcs_status_word_4; + base_arinc_429 efcs_status_word_5; + base_arinc_429 capt_roll_command_deg; + base_arinc_429 fo_roll_command_deg; + base_arinc_429 rudder_pedal_position_deg; + base_arinc_429 capt_pitch_command_deg; + base_arinc_429 fo_pitch_command_deg; + base_arinc_429 aileron_left_pos_deg; + base_arinc_429 elevator_left_pos_deg; + base_arinc_429 aileron_right_pos_deg; + base_arinc_429 elevator_right_pos_deg; + base_arinc_429 horiz_stab_trim_pos_deg; + base_arinc_429 spoiler_1_left_pos_deg; + base_arinc_429 spoiler_2_left_pos_deg; + base_arinc_429 spoiler_3_left_pos_deg; + base_arinc_429 spoiler_4_left_pos_deg; + base_arinc_429 spoiler_5_left_pos_deg; + base_arinc_429 spoiler_1_right_pos_deg; + base_arinc_429 spoiler_2_right_pos_deg; + base_arinc_429 spoiler_3_right_pos_deg; + base_arinc_429 spoiler_4_right_pos_deg; + base_arinc_429 spoiler_5_right_pos_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sec_out_bus_ +#define DEFINED_TYPEDEF_FOR_base_sec_out_bus_ + +struct base_sec_out_bus +{ + base_arinc_429 left_spoiler_1_position_deg; + base_arinc_429 right_spoiler_1_position_deg; + base_arinc_429 left_spoiler_2_position_deg; + base_arinc_429 right_spoiler_2_position_deg; + base_arinc_429 left_elevator_position_deg; + base_arinc_429 right_elevator_position_deg; + base_arinc_429 ths_position_deg; + base_arinc_429 left_sidestick_pitch_command_deg; + base_arinc_429 right_sidestick_pitch_command_deg; + base_arinc_429 left_sidestick_roll_command_deg; + base_arinc_429 right_sidestick_roll_command_deg; + base_arinc_429 speed_brake_lever_command_deg; + base_arinc_429 thrust_lever_angle_1_deg; + base_arinc_429 thrust_lever_angle_2_deg; + base_arinc_429 discrete_status_word_1; + base_arinc_429 discrete_status_word_2; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_elac_out_bus_ +#define DEFINED_TYPEDEF_FOR_base_elac_out_bus_ + +struct base_elac_out_bus +{ + base_arinc_429 left_aileron_position_deg; + base_arinc_429 right_aileron_position_deg; + base_arinc_429 left_elevator_position_deg; + base_arinc_429 right_elevator_position_deg; + base_arinc_429 ths_position_deg; + base_arinc_429 left_sidestick_pitch_command_deg; + base_arinc_429 right_sidestick_pitch_command_deg; + base_arinc_429 left_sidestick_roll_command_deg; + base_arinc_429 right_sidestick_roll_command_deg; + base_arinc_429 rudder_pedal_position_deg; + base_arinc_429 aileron_command_deg; + base_arinc_429 roll_spoiler_command_deg; + base_arinc_429 yaw_damper_command_deg; + base_arinc_429 elevator_double_pressurization_command_deg; + base_arinc_429 discrete_status_word_1; + base_arinc_429 discrete_status_word_2; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_elac_bus_inputs_ +#define DEFINED_TYPEDEF_FOR_base_elac_bus_inputs_ + +struct base_elac_bus_inputs +{ + base_adr_bus adr_1_bus; + base_adr_bus adr_2_bus; + base_adr_bus adr_3_bus; + base_ir_bus ir_1_bus; + base_ir_bus ir_2_bus; + base_ir_bus ir_3_bus; + base_fmgc_b_bus fmgc_1_bus; + base_fmgc_b_bus fmgc_2_bus; + base_ra_bus ra_1_bus; + base_ra_bus ra_2_bus; + base_sfcc_bus sfcc_1_bus; + base_sfcc_bus sfcc_2_bus; + base_fcdc_bus fcdc_1_bus; + base_fcdc_bus fcdc_2_bus; + base_sec_out_bus sec_1_bus; + base_sec_out_bus sec_2_bus; + base_elac_out_bus elac_opp_bus; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_elac_inputs_ +#define DEFINED_TYPEDEF_FOR_elac_inputs_ + +struct elac_inputs +{ + base_time time; + base_sim_data sim_data; + base_elac_discrete_inputs discrete_inputs; + base_elac_analog_inputs analog_inputs; + base_elac_bus_inputs bus_inputs; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_elac_lateral_law_outputs_ +#define DEFINED_TYPEDEF_FOR_base_elac_lateral_law_outputs_ + +struct base_elac_lateral_law_outputs +{ + real_T left_aileron_command_deg; + real_T right_aileron_command_deg; + real_T roll_spoiler_command_deg; + real_T yaw_damper_command_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_elac_pitch_law_outputs_ +#define DEFINED_TYPEDEF_FOR_base_elac_pitch_law_outputs_ + +struct base_elac_pitch_law_outputs +{ + real_T elevator_command_deg; + real_T ths_command_deg; + boolean_T elevator_double_pressurization_active; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_elac_laws_outputs_ +#define DEFINED_TYPEDEF_FOR_base_elac_laws_outputs_ + +struct base_elac_laws_outputs +{ + base_elac_lateral_law_outputs lateral_law_outputs; + base_elac_pitch_law_outputs pitch_law_outputs; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_elac_adr_computation_data_ +#define DEFINED_TYPEDEF_FOR_base_elac_adr_computation_data_ + +struct base_elac_adr_computation_data +{ + real_T V_ias_kn; + real_T V_tas_kn; + real_T mach; + real_T alpha_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_elac_ir_computation_data_ +#define DEFINED_TYPEDEF_FOR_base_elac_ir_computation_data_ + +struct base_elac_ir_computation_data +{ + real_T theta_deg; + real_T phi_deg; + real_T q_deg_s; + real_T r_deg_s; + real_T n_x_g; + real_T n_y_g; + real_T n_z_g; + real_T theta_dot_deg_s; + real_T phi_dot_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_elac_logic_outputs_ +#define DEFINED_TYPEDEF_FOR_base_elac_logic_outputs_ + +struct base_elac_logic_outputs +{ + boolean_T on_ground; + boolean_T pitch_law_in_flight; + boolean_T tracking_mode_on; + lateral_efcs_law lateral_law_capability; + lateral_efcs_law active_lateral_law; + pitch_efcs_law pitch_law_capability; + pitch_efcs_law active_pitch_law; + boolean_T abnormal_condition_law_active; + boolean_T is_engaged_in_pitch; + boolean_T can_engage_in_pitch; + boolean_T has_priority_in_pitch; + boolean_T left_elevator_avail; + boolean_T right_elevator_avail; + boolean_T ths_avail; + boolean_T ths_active_commanded; + boolean_T ths_ground_setting_active; + boolean_T is_engaged_in_roll; + boolean_T can_engage_in_roll; + boolean_T has_priority_in_roll; + boolean_T left_aileron_crosscommand_active; + boolean_T right_aileron_crosscommand_active; + boolean_T left_aileron_avail; + boolean_T right_aileron_avail; + boolean_T aileron_droop_active; + boolean_T aileron_antidroop_active; + boolean_T is_yellow_hydraulic_power_avail; + boolean_T is_blue_hydraulic_power_avail; + boolean_T is_green_hydraulic_power_avail; + boolean_T left_sidestick_disabled; + boolean_T right_sidestick_disabled; + boolean_T left_sidestick_priority_locked; + boolean_T right_sidestick_priority_locked; + real_T total_sidestick_pitch_command; + real_T total_sidestick_roll_command; + boolean_T ap_authorised; + boolean_T protection_ap_disconnect; + boolean_T high_alpha_prot_active; + real_T alpha_prot_deg; + real_T alpha_max_deg; + boolean_T high_speed_prot_active; + real_T high_speed_prot_lo_thresh_kn; + real_T high_speed_prot_hi_thresh_kn; + boolean_T double_adr_failure; + boolean_T triple_adr_failure; + boolean_T cas_or_mach_disagree; + boolean_T alpha_disagree; + boolean_T double_ir_failure; + boolean_T triple_ir_failure; + boolean_T ir_failure_not_self_detected; + base_elac_adr_computation_data adr_computation_data; + base_elac_ir_computation_data ir_computation_data; + real_T ra_computation_data_ft; + boolean_T dual_ra_failure; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_elac_discrete_outputs_ +#define DEFINED_TYPEDEF_FOR_base_elac_discrete_outputs_ + +struct base_elac_discrete_outputs +{ + boolean_T pitch_axis_ok; + boolean_T left_aileron_ok; + boolean_T right_aileron_ok; + boolean_T digital_output_validated; + boolean_T ap_1_authorised; + boolean_T ap_2_authorised; + boolean_T left_aileron_active_mode; + boolean_T right_aileron_active_mode; + boolean_T left_elevator_damping_mode; + boolean_T right_elevator_damping_mode; + boolean_T ths_active; + boolean_T batt_power_supply; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_elac_analog_outputs_ +#define DEFINED_TYPEDEF_FOR_base_elac_analog_outputs_ + +struct base_elac_analog_outputs +{ + real_T left_elev_pos_order_deg; + real_T right_elev_pos_order_deg; + real_T ths_pos_order; + real_T left_aileron_pos_order; + real_T right_aileron_pos_order; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_elac_outputs_ +#define DEFINED_TYPEDEF_FOR_elac_outputs_ + +struct elac_outputs +{ + elac_inputs data; + base_elac_laws_outputs laws; + base_elac_logic_outputs logic; + base_elac_discrete_outputs discrete_outputs; + base_elac_analog_outputs analog_outputs; + base_elac_out_bus bus_outputs; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_roll_output_ +#define DEFINED_TYPEDEF_FOR_base_roll_output_ + +struct base_roll_output +{ + real_T xi_deg; + real_T zeta_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_lateral_direct_input_ +#define DEFINED_TYPEDEF_FOR_lateral_direct_input_ + +struct lateral_direct_input +{ + base_time time; + real_T delta_xi_pos; + boolean_T tracking_mode_on; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_lateral_normal_input_ +#define DEFINED_TYPEDEF_FOR_lateral_normal_input_ + +struct lateral_normal_input +{ + base_time time; + real_T Theta_deg; + real_T Phi_deg; + real_T r_deg_s; + real_T pk_deg_s; + real_T V_ias_kn; + real_T V_tas_kn; + real_T H_radio_ft; + real_T delta_xi_pos; + real_T delta_zeta_pos; + boolean_T on_ground; + boolean_T tracking_mode_on; + boolean_T high_aoa_prot_active; + boolean_T high_speed_prot_active; + real_T ap_phi_c_deg; + real_T ap_beta_c_deg; + boolean_T any_ap_engaged; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_roll_data_computed_ +#define DEFINED_TYPEDEF_FOR_base_roll_data_computed_ + +struct base_roll_data_computed +{ + real_T delta_xi_deg; + real_T in_flight; + real_T in_flight_gain; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_roll_normal_ +#define DEFINED_TYPEDEF_FOR_base_roll_normal_ + +struct base_roll_normal +{ + real_T pk_c_deg_s; + real_T Phi_c_deg; + real_T xi_deg; + real_T zeta_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_pitch_alternate_input_ +#define DEFINED_TYPEDEF_FOR_pitch_alternate_input_ + +struct pitch_alternate_input +{ + base_time time; + real_T nz_g; + real_T Theta_deg; + real_T Phi_deg; + real_T qk_deg_s; + real_T eta_deg; + real_T eta_trim_deg; + real_T V_ias_kn; + real_T mach; + real_T V_tas_kn; + real_T CG_percent_MAC; + real_T total_weight_kg; + real_T flaps_handle_index; + real_T spoilers_left_pos; + real_T spoilers_right_pos; + real_T delta_eta_pos; + boolean_T on_ground; + real_T in_flight; + boolean_T tracking_mode_on; + boolean_T stabilities_available; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_output_ +#define DEFINED_TYPEDEF_FOR_base_pitch_output_ + +struct base_pitch_output +{ + real_T eta_deg; + real_T eta_trim_dot_deg_s; + real_T eta_trim_limit_lo; + real_T eta_trim_limit_up; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_alternate_data_computed_ +#define DEFINED_TYPEDEF_FOR_base_pitch_alternate_data_computed_ + +struct base_pitch_alternate_data_computed +{ + real_T eta_trim_deg_limit_lo; + real_T eta_trim_deg_limit_up; + real_T delta_eta_deg; + real_T nz_limit_up_g; + real_T nz_limit_lo_g; + boolean_T eta_trim_deg_should_freeze; + boolean_T eta_trim_deg_reset; + real_T eta_trim_deg_reset_deg; + boolean_T eta_trim_deg_should_write; + real_T eta_trim_deg_rate_limit_up_deg_s; + real_T eta_trim_deg_rate_limit_lo_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_normal_ +#define DEFINED_TYPEDEF_FOR_base_pitch_normal_ + +struct base_pitch_normal +{ + real_T nz_c_g; + real_T Cstar_g; + real_T protection_alpha_c_deg; + real_T protection_V_c_kn; + real_T eta_dot_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_law_output_ +#define DEFINED_TYPEDEF_FOR_base_pitch_law_output_ + +struct base_pitch_law_output +{ + real_T eta_dot_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_integrated_ +#define DEFINED_TYPEDEF_FOR_base_pitch_integrated_ + +struct base_pitch_integrated +{ + real_T eta_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_pitch_direct_input_ +#define DEFINED_TYPEDEF_FOR_pitch_direct_input_ + +struct pitch_direct_input +{ + base_time time; + real_T eta_deg; + real_T flaps_handle_index; + real_T delta_eta_pos; + boolean_T tracking_mode_on; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_pitch_normal_input_ +#define DEFINED_TYPEDEF_FOR_pitch_normal_input_ + +struct pitch_normal_input +{ + base_time time; + real_T nz_g; + real_T Theta_deg; + real_T Phi_deg; + real_T qk_deg_s; + real_T qk_dot_deg_s2; + real_T eta_deg; + real_T eta_trim_deg; + real_T alpha_deg; + real_T V_ias_kn; + real_T V_tas_kn; + real_T H_radio_ft; + real_T CG_percent_MAC; + real_T total_weight_kg; + real_T flaps_handle_index; + real_T spoilers_left_pos; + real_T spoilers_right_pos; + real_T thrust_lever_1_pos; + real_T thrust_lever_2_pos; + boolean_T tailstrike_protection_on; + real_T VLS_kn; + real_T delta_eta_pos; + boolean_T on_ground; + real_T in_flight; + boolean_T tracking_mode_on; + boolean_T high_aoa_prot_active; + boolean_T high_speed_prot_active; + real_T alpha_prot; + real_T alpha_max; + real_T high_speed_prot_high_kn; + real_T high_speed_prot_low_kn; + real_T ap_theta_c_deg; + boolean_T any_ap_engaged; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_data_computed_ +#define DEFINED_TYPEDEF_FOR_base_pitch_data_computed_ + +struct base_pitch_data_computed +{ + real_T eta_trim_deg_limit_lo; + real_T eta_trim_deg_limit_up; + real_T delta_eta_deg; + real_T in_flight; + real_T in_rotation; + real_T in_flare; + real_T in_flight_gain; + real_T in_rotation_gain; + real_T nz_limit_up_g; + real_T nz_limit_lo_g; + boolean_T eta_trim_deg_should_freeze; + boolean_T eta_trim_deg_reset; + real_T eta_trim_deg_reset_deg; + boolean_T eta_trim_deg_should_write; + real_T eta_trim_deg_rate_limit_up_deg_s; + real_T eta_trim_deg_rate_limit_lo_deg_s; + real_T flare_Theta_deg; + real_T flare_Theta_c_deg; + real_T flare_Theta_c_rate_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_rotation_ +#define DEFINED_TYPEDEF_FOR_base_pitch_rotation_ + +struct base_pitch_rotation +{ + real_T qk_c_deg_s; + real_T eta_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_struct_AS8ihRtYtxSipjDCqtVGCE_ +#define DEFINED_TYPEDEF_FOR_struct_AS8ihRtYtxSipjDCqtVGCE_ + +struct struct_AS8ihRtYtxSipjDCqtVGCE +{ + real_T elevator_command_deg; + real_T ths_command_deg; + real_T elevator_double_pressurization_active; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_struct_DGJtjwcHjMwSjtoGHgU8YD_ +#define DEFINED_TYPEDEF_FOR_struct_DGJtjwcHjMwSjtoGHgU8YD_ + +struct struct_DGJtjwcHjMwSjtoGHgU8YD +{ + base_elac_lateral_law_outputs lateral_law_outputs; + struct_AS8ihRtYtxSipjDCqtVGCE pitch_law_outputs; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_struct_2OohiAWrazWy5wDS5iisgF_ +#define DEFINED_TYPEDEF_FOR_struct_2OohiAWrazWy5wDS5iisgF_ + +struct struct_2OohiAWrazWy5wDS5iisgF +{ + real_T SSM; + real_T Data; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_struct_XUfKEhVD4cm2MFYfzn8ySH_ +#define DEFINED_TYPEDEF_FOR_struct_XUfKEhVD4cm2MFYfzn8ySH_ + +struct struct_XUfKEhVD4cm2MFYfzn8ySH +{ + struct_2OohiAWrazWy5wDS5iisgF left_aileron_position_deg; + struct_2OohiAWrazWy5wDS5iisgF right_aileron_position_deg; + struct_2OohiAWrazWy5wDS5iisgF left_elevator_position_deg; + struct_2OohiAWrazWy5wDS5iisgF right_elevator_position_deg; + struct_2OohiAWrazWy5wDS5iisgF ths_position_deg; + struct_2OohiAWrazWy5wDS5iisgF left_sidestick_pitch_command_deg; + struct_2OohiAWrazWy5wDS5iisgF right_sidestick_pitch_command_deg; + struct_2OohiAWrazWy5wDS5iisgF left_sidestick_roll_command_deg; + struct_2OohiAWrazWy5wDS5iisgF right_sidestick_roll_command_deg; + struct_2OohiAWrazWy5wDS5iisgF rudder_pedal_position_deg; + struct_2OohiAWrazWy5wDS5iisgF aileron_command_deg; + struct_2OohiAWrazWy5wDS5iisgF roll_spoiler_command_deg; + struct_2OohiAWrazWy5wDS5iisgF yaw_damper_command_deg; + struct_2OohiAWrazWy5wDS5iisgF elevator_double_pressurization_command_deg; + struct_2OohiAWrazWy5wDS5iisgF discrete_status_word_1; + struct_2OohiAWrazWy5wDS5iisgF discrete_status_word_2; +}; + +#endif +#endif + diff --git a/src/fbw/src/model/FacComputer.cpp b/src/fbw/src/model/FacComputer.cpp new file mode 100644 index 000000000000..9d835f2782d2 --- /dev/null +++ b/src/fbw/src/model/FacComputer.cpp @@ -0,0 +1,1089 @@ +#include "FacComputer.h" +#include "FacComputer_types.h" +#include "rtwtypes.h" +#include +#include "look2_binlxpw.h" +#include "look1_binlxpw.h" + +const uint8_T FacComputer_IN_Flying{ 1U }; + +const uint8_T FacComputer_IN_Landed{ 2U }; + +const uint8_T FacComputer_IN_Landing100ft{ 3U }; + +const uint8_T FacComputer_IN_NO_ACTIVE_CHILD{ 0U }; + +const uint8_T FacComputer_IN_Takeoff100ft{ 4U }; + +void FacComputer::FacComputer_MATLABFunction(const base_arinc_429 *rtu_u, boolean_T *rty_y) +{ + *rty_y = (rtu_u->SSM != static_cast(SignStatusMatrix::FailureWarning)); +} + +void FacComputer::FacComputer_MATLABFunction_f(const base_arinc_429 *rtu_u, real_T rtu_bit, uint32_T *rty_y) +{ + real32_T tmp; + uint32_T a; + tmp = std::round(rtu_u->Data); + if (tmp < 4.2949673E+9F) { + if (tmp >= 0.0F) { + a = static_cast(tmp); + } else { + a = 0U; + } + } else { + a = MAX_uint32_T; + } + + if (-(rtu_bit - 1.0) >= 0.0) { + if (-(rtu_bit - 1.0) <= 31.0) { + a <<= static_cast(-(rtu_bit - 1.0)); + } else { + a = 0U; + } + } else if (-(rtu_bit - 1.0) >= -31.0) { + a >>= static_cast(rtu_bit - 1.0); + } else { + a = 0U; + } + + *rty_y = a & 1U; +} + +void FacComputer::FacComputer_LagFilter_Reset(rtDW_LagFilter_FacComputer_T *localDW) +{ + localDW->pY_not_empty = false; + localDW->pU_not_empty = false; +} + +void FacComputer::FacComputer_LagFilter(real_T rtu_U, real_T rtu_C1, real_T rtu_dt, real_T *rty_Y, + rtDW_LagFilter_FacComputer_T *localDW) +{ + real_T ca; + real_T denom_tmp; + if ((!localDW->pY_not_empty) || (!localDW->pU_not_empty)) { + localDW->pU = rtu_U; + localDW->pU_not_empty = true; + localDW->pY = rtu_U; + localDW->pY_not_empty = true; + } + + denom_tmp = rtu_dt * rtu_C1; + ca = denom_tmp / (denom_tmp + 2.0); + *rty_Y = (2.0 - denom_tmp) / (denom_tmp + 2.0) * localDW->pY + (rtu_U * ca + localDW->pU * ca); + localDW->pY = *rty_Y; + localDW->pU = rtu_U; +} + +void FacComputer::FacComputer_MATLABFunction_d(boolean_T rtu_bit1, boolean_T rtu_bit2, boolean_T rtu_bit3, boolean_T + rtu_bit4, boolean_T rtu_bit5, boolean_T rtu_bit6, real_T *rty_handleIndex) +{ + if (rtu_bit1) { + *rty_handleIndex = 0.0; + } else if (rtu_bit2 && rtu_bit6) { + *rty_handleIndex = 1.0; + } else if (rtu_bit2 && (!rtu_bit6)) { + *rty_handleIndex = 2.0; + } else if (rtu_bit3) { + *rty_handleIndex = 3.0; + } else if (rtu_bit4) { + *rty_handleIndex = 4.0; + } else if (rtu_bit5) { + *rty_handleIndex = 5.0; + } else { + *rty_handleIndex = 0.0; + } +} + +void FacComputer::FacComputer_RateLimiter_Reset(rtDW_RateLimiter_FacComputer_T *localDW) +{ + localDW->pY_not_empty = false; +} + +void FacComputer::FacComputer_RateLimiter(real_T rtu_u, real_T rtu_up, real_T rtu_lo, real_T rtu_Ts, boolean_T rtu_reset, + real_T *rty_Y, rtDW_RateLimiter_FacComputer_T *localDW) +{ + if ((!localDW->pY_not_empty) || rtu_reset) { + localDW->pY = rtu_u; + localDW->pY_not_empty = true; + } + + if (rtu_reset) { + *rty_Y = rtu_u; + } else { + *rty_Y = std::fmax(std::fmin(rtu_u - localDW->pY, std::abs(rtu_up) * rtu_Ts), -std::abs(rtu_lo) * rtu_Ts) + + localDW->pY; + } + + localDW->pY = *rty_Y; +} + +void FacComputer::FacComputer_CalculateV_alpha_max(real_T rtu_v_ias, real_T rtu_alpha, real_T rtu_alpha_0, real_T + rtu_alpha_target, real_T *rty_V_alpha_target) +{ + *rty_V_alpha_target = std::sqrt(std::abs(rtu_alpha - rtu_alpha_0) / (rtu_alpha_target - rtu_alpha_0)) * rtu_v_ias; +} + +void FacComputer::FacComputer_LagFilter_n_Reset(rtDW_LagFilter_FacComputer_g_T *localDW) +{ + localDW->pY_not_empty = false; + localDW->pU_not_empty = false; +} + +void FacComputer::FacComputer_LagFilter_k(real32_T rtu_U, real_T rtu_C1, real_T rtu_dt, real32_T *rty_Y, + rtDW_LagFilter_FacComputer_g_T *localDW) +{ + real_T ca; + real_T denom_tmp; + if ((!localDW->pY_not_empty) || (!localDW->pU_not_empty)) { + localDW->pU = rtu_U; + localDW->pU_not_empty = true; + localDW->pY = rtu_U; + localDW->pY_not_empty = true; + } + + denom_tmp = rtu_dt * rtu_C1; + ca = denom_tmp / (denom_tmp + 2.0); + *rty_Y = static_cast((2.0 - denom_tmp) / (denom_tmp + 2.0)) * localDW->pY + (rtu_U * static_cast + (ca) + localDW->pU * static_cast(ca)); + localDW->pY = *rty_Y; + localDW->pU = rtu_U; +} + +void FacComputer::FacComputer_MATLABFunction_i_Reset(rtDW_MATLABFunction_FacComputer_f_T *localDW) +{ + localDW->output = false; + localDW->timeSinceCondition = 0.0; +} + +void FacComputer::FacComputer_MATLABFunction_p(boolean_T rtu_u, real_T rtu_Ts, boolean_T rtu_isRisingEdge, real_T + rtu_timeDelay, boolean_T *rty_y, rtDW_MATLABFunction_FacComputer_f_T *localDW) +{ + if (rtu_u == rtu_isRisingEdge) { + localDW->timeSinceCondition += rtu_Ts; + if (localDW->timeSinceCondition >= rtu_timeDelay) { + localDW->output = rtu_u; + } + } else { + localDW->timeSinceCondition = 0.0; + localDW->output = rtu_u; + } + + *rty_y = localDW->output; +} + +void FacComputer::FacComputer_MATLABFunction_g(const boolean_T rtu_u[19], real32_T *rty_y) +{ + uint32_T out; + out = 0U; + for (int32_T i{0}; i < 19; i++) { + out |= static_cast(rtu_u[i]) << (i + 10); + } + + *rty_y = static_cast(out); +} + +void FacComputer::step() +{ + real_T rtb_Switch1_a; + real_T rtb_Switch4_f; + real_T rtb_Switch_b; + real_T rtb_V_alpha_target; + real_T rtb_V_alpha_target_l; + real32_T rtb_y_bp; + real32_T rtb_y_dr; + real32_T rtb_y_e; + real32_T rtb_y_i3; + real32_T rtb_y_iu; + uint32_T rtb_y; + uint32_T rtb_y_bm; + uint32_T rtb_y_c; + uint32_T rtb_y_k; + uint32_T rtb_y_la; + uint32_T rtb_y_p; + uint32_T rtb_y_pj; + boolean_T rtb_VectorConcatenate[19]; + boolean_T rtb_Memory; + boolean_T rtb_OR_k; + boolean_T rtb_y_b; + boolean_T rtb_y_j; + if (FacComputer_U.in.sim_data.computer_running) { + real_T rtb_BusAssignment_d_flight_envelope_alpha_filtered_deg; + real_T rtb_Gain_f; + real_T rtb_OR_d; + real_T rtb_beDot; + int32_T rtb_Switch2; + int32_T rtb_Switch4_a; + int32_T rtb_alpha_floor_inhib; + real32_T rtb_V_ias; + real32_T rtb_V_tas; + real32_T rtb_alpha; + real32_T rtb_mach; + real32_T rtb_n_x; + real32_T rtb_n_y; + real32_T rtb_n_z; + real32_T rtb_phi; + real32_T rtb_phi_dot; + real32_T rtb_q; + real32_T rtb_r; + real32_T rtb_theta; + real32_T rtb_theta_dot; + boolean_T guard1{ false }; + + boolean_T rtb_AND1; + boolean_T rtb_BusAssignment_c_logic_lgciu_own_valid; + boolean_T rtb_BusAssignment_h_logic_on_ground; + boolean_T rtb_BusAssignment_h_logic_speed_scale_visible; + boolean_T rtb_DataTypeConversion_c; + boolean_T rtb_DataTypeConversion_e; + boolean_T rtb_DataTypeConversion_hw; + boolean_T rtb_Switch_i_idx_1; + boolean_T rtb_Switch_i_idx_2; + boolean_T rtb_adr3Invalid; + boolean_T rtb_adrOppInvalid; + boolean_T rtb_adrOwnInvalid; + boolean_T rtb_ir3Invalid; + boolean_T rtb_irOppInvalid; + boolean_T rtb_irOwnInvalid; + boolean_T rtb_rudderTravelLimEngaged; + boolean_T rtb_rudderTrimEngaged; + boolean_T rtb_yawDamperEngaged; + boolean_T rudderTravelLimCanEngage; + boolean_T rudderTrimCanEngage; + boolean_T rudderTrimHasPriority; + boolean_T yawDamperCanEngage; + boolean_T yawDamperHasPriority; + boolean_T yawDamperHasPriority_tmp; + if (!FacComputer_DWork.Runtime_MODE) { + FacComputer_DWork.Delay_DSTATE = FacComputer_P.DiscreteDerivativeVariableTs_InitialCondition; + FacComputer_DWork.Delay1_DSTATE = FacComputer_P.Delay1_InitialCondition; + FacComputer_DWork.Memory_PreviousInput = FacComputer_P.SRFlipFlop_initial_condition; + FacComputer_DWork.Delay2_DSTATE = FacComputer_P.Delay2_InitialCondition; + FacComputer_DWork.Delay_DSTATE_d = FacComputer_P.DiscreteTimeIntegratorVariableTs_InitialCondition; + FacComputer_DWork.Delay_DSTATE_m = FacComputer_P.RateLimiterDynamicEqualVariableTs_InitialCondition; + FacComputer_DWork.Delay_DSTATE_k = FacComputer_P.RateLimiterDynamicEqualVariableTs_InitialCondition_a; + FacComputer_MATLABFunction_i_Reset(&FacComputer_DWork.sf_MATLABFunction_ax); + FacComputer_MATLABFunction_i_Reset(&FacComputer_DWork.sf_MATLABFunction_p); + FacComputer_LagFilter_Reset(&FacComputer_DWork.sf_LagFilter_b); + FacComputer_LagFilter_n_Reset(&FacComputer_DWork.sf_LagFilter_k); + FacComputer_LagFilter_Reset(&FacComputer_DWork.sf_LagFilter_f); + FacComputer_LagFilter_n_Reset(&FacComputer_DWork.sf_LagFilter_d); + FacComputer_LagFilter_Reset(&FacComputer_DWork.sf_LagFilter_c); + FacComputer_RateLimiter_Reset(&FacComputer_DWork.sf_RateLimiter); + FacComputer_LagFilter_Reset(&FacComputer_DWork.sf_LagFilter); + FacComputer_DWork.is_active_c15_FacComputer = 0U; + FacComputer_DWork.is_c15_FacComputer = FacComputer_IN_NO_ACTIVE_CHILD; + FacComputer_DWork.sAlphaFloor = 0.0; + FacComputer_RateLimiter_Reset(&FacComputer_DWork.sf_RateLimiter_c); + FacComputer_RateLimiter_Reset(&FacComputer_DWork.sf_RateLimiter_a); + FacComputer_RateLimiter_Reset(&FacComputer_DWork.sf_RateLimiter_n); + FacComputer_RateLimiter_Reset(&FacComputer_DWork.sf_RateLimiter_j); + FacComputer_DWork.previousInput_not_empty = false; + FacComputer_DWork.pY_not_empty = false; + FacComputer_DWork.pU_not_empty = false; + FacComputer_DWork.pY_not_empty_l = false; + FacComputer_DWork.Runtime_MODE = true; + } + + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.fac_opp_bus.discrete_word_5, + FacComputer_P.BitfromLabel1_bit, &rtb_y_c); + FacComputer_MATLABFunction(&FacComputer_U.in.bus_inputs.fac_opp_bus.discrete_word_5, &rtb_y_j); + rtb_AND1 = ((rtb_y_c != 0U) && rtb_y_j); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.fac_opp_bus.discrete_word_5, + FacComputer_P.BitfromLabel4_bit, &rtb_y_bm); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.fac_opp_bus.discrete_word_5, + FacComputer_P.BitfromLabel3_bit, &rtb_y_la); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.fac_opp_bus.discrete_word_5, + FacComputer_P.BitfromLabel2_bit, &rtb_y_c); + FacComputer_MATLABFunction(&FacComputer_U.in.bus_inputs.lgciu_own_bus.discrete_word_1, &rtb_Memory); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.lgciu_own_bus.discrete_word_2, + FacComputer_P.BitfromLabel_bit, &rtb_y_pj); + FacComputer_MATLABFunction_p(FacComputer_U.in.discrete_inputs.nose_gear_pressed == (rtb_y_pj != 0U), + FacComputer_U.in.time.dt, FacComputer_P.ConfirmNode_isRisingEdge, FacComputer_P.ConfirmNode_timeDelay, &rtb_OR_k, + &FacComputer_DWork.sf_MATLABFunction_ax); + rtb_Memory = (rtb_Memory && rtb_OR_k); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.lgciu_own_bus.discrete_word_2, + FacComputer_P.BitfromLabel5_bit, &rtb_y_pj); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.lgciu_own_bus.discrete_word_2, + FacComputer_P.BitfromLabel6_bit, &rtb_y_k); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.lgciu_own_bus.discrete_word_3, + FacComputer_P.BitfromLabel7_bit, &rtb_y_p); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.lgciu_own_bus.discrete_word_3, + FacComputer_P.BitfromLabel8_bit, &rtb_y); + if (rtb_Memory) { + rtb_y_j = (rtb_y_pj != 0U); + rtb_Switch_i_idx_1 = (rtb_y_k != 0U); + rtb_Switch_i_idx_2 = ((rtb_y_p != 0U) || (rtb_y != 0U)); + } else if (rtb_AND1) { + rtb_y_j = (rtb_y_bm != 0U); + rtb_Switch_i_idx_1 = (rtb_y_la != 0U); + rtb_Switch_i_idx_2 = (rtb_y_c != 0U); + } else { + rtb_y_j = FacComputer_P.Constant_Value_c; + rtb_Switch_i_idx_1 = FacComputer_P.Constant_Value_c; + rtb_Switch_i_idx_2 = FacComputer_P.Constant_Value_c; + } + + rtb_adrOwnInvalid = ((FacComputer_U.in.bus_inputs.adr_own_bus.airspeed_computed_kn.SSM == static_cast + (SignStatusMatrix::FailureWarning)) || + (FacComputer_U.in.bus_inputs.adr_own_bus.aoa_corrected_deg.SSM == static_cast + (SignStatusMatrix::FailureWarning))); + rtb_adrOppInvalid = ((FacComputer_U.in.bus_inputs.adr_opp_bus.airspeed_computed_kn.SSM == static_cast + (SignStatusMatrix::FailureWarning)) || + (FacComputer_U.in.bus_inputs.adr_opp_bus.aoa_corrected_deg.SSM == static_cast + (SignStatusMatrix::FailureWarning))); + rtb_adr3Invalid = ((FacComputer_U.in.bus_inputs.adr_3_bus.airspeed_computed_kn.SSM == static_cast + (SignStatusMatrix::FailureWarning)) || + (FacComputer_U.in.bus_inputs.adr_3_bus.aoa_corrected_deg.SSM == static_cast + (SignStatusMatrix::FailureWarning))); + rtb_irOwnInvalid = ((FacComputer_U.in.bus_inputs.ir_own_bus.body_yaw_rate_deg_s.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || + (FacComputer_U.in.bus_inputs.ir_own_bus.body_lat_accel_g.SSM != static_cast + (SignStatusMatrix::NormalOperation))); + rtb_irOppInvalid = ((FacComputer_U.in.bus_inputs.ir_opp_bus.body_yaw_rate_deg_s.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || + (FacComputer_U.in.bus_inputs.ir_opp_bus.body_lat_accel_g.SSM != static_cast + (SignStatusMatrix::NormalOperation))); + rtb_ir3Invalid = ((FacComputer_U.in.bus_inputs.ir_3_bus.body_yaw_rate_deg_s.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || + (FacComputer_U.in.bus_inputs.ir_3_bus.body_lat_accel_g.SSM != static_cast + (SignStatusMatrix::NormalOperation))); + if (!rtb_adrOwnInvalid) { + rtb_V_ias = FacComputer_U.in.bus_inputs.adr_own_bus.airspeed_computed_kn.Data; + rtb_V_tas = FacComputer_U.in.bus_inputs.adr_own_bus.airspeed_true_kn.Data; + rtb_mach = FacComputer_U.in.bus_inputs.adr_own_bus.mach.Data; + rtb_alpha = FacComputer_U.in.bus_inputs.adr_own_bus.aoa_corrected_deg.Data; + } else if (!rtb_adr3Invalid) { + rtb_V_ias = FacComputer_U.in.bus_inputs.adr_3_bus.airspeed_computed_kn.Data; + rtb_V_tas = FacComputer_U.in.bus_inputs.adr_3_bus.airspeed_true_kn.Data; + rtb_mach = FacComputer_U.in.bus_inputs.adr_3_bus.mach.Data; + rtb_alpha = FacComputer_U.in.bus_inputs.adr_3_bus.aoa_corrected_deg.Data; + } else { + rtb_V_ias = 0.0F; + rtb_V_tas = 0.0F; + rtb_mach = 0.0F; + rtb_alpha = 0.0F; + } + + if (!rtb_irOwnInvalid) { + rtb_theta = FacComputer_U.in.bus_inputs.ir_own_bus.pitch_angle_deg.Data; + rtb_phi = FacComputer_U.in.bus_inputs.ir_own_bus.roll_angle_deg.Data; + rtb_q = FacComputer_U.in.bus_inputs.ir_own_bus.body_pitch_rate_deg_s.Data; + rtb_r = FacComputer_U.in.bus_inputs.ir_own_bus.body_yaw_rate_deg_s.Data; + rtb_n_x = FacComputer_U.in.bus_inputs.ir_own_bus.body_long_accel_g.Data; + rtb_n_y = FacComputer_U.in.bus_inputs.ir_own_bus.body_lat_accel_g.Data; + rtb_n_z = FacComputer_U.in.bus_inputs.ir_own_bus.body_normal_accel_g.Data; + rtb_theta_dot = FacComputer_U.in.bus_inputs.ir_own_bus.pitch_att_rate_deg_s.Data; + rtb_phi_dot = FacComputer_U.in.bus_inputs.ir_own_bus.roll_att_rate_deg_s.Data; + } else if (!rtb_ir3Invalid) { + rtb_theta = FacComputer_U.in.bus_inputs.ir_3_bus.pitch_angle_deg.Data; + rtb_phi = FacComputer_U.in.bus_inputs.ir_3_bus.roll_angle_deg.Data; + rtb_q = FacComputer_U.in.bus_inputs.ir_3_bus.body_pitch_rate_deg_s.Data; + rtb_r = FacComputer_U.in.bus_inputs.ir_3_bus.body_yaw_rate_deg_s.Data; + rtb_n_x = FacComputer_U.in.bus_inputs.ir_3_bus.body_long_accel_g.Data; + rtb_n_y = FacComputer_U.in.bus_inputs.ir_3_bus.body_lat_accel_g.Data; + rtb_n_z = FacComputer_U.in.bus_inputs.ir_3_bus.body_normal_accel_g.Data; + rtb_theta_dot = FacComputer_U.in.bus_inputs.ir_3_bus.pitch_att_rate_deg_s.Data; + rtb_phi_dot = FacComputer_U.in.bus_inputs.ir_3_bus.roll_att_rate_deg_s.Data; + } else { + rtb_theta = 0.0F; + rtb_phi = 0.0F; + rtb_q = 0.0F; + rtb_r = 0.0F; + rtb_n_x = 0.0F; + rtb_n_y = 0.0F; + rtb_n_z = 0.0F; + rtb_theta_dot = 0.0F; + rtb_phi_dot = 0.0F; + } + + rtb_V_alpha_target_l = rtb_n_y; + rtb_Switch1_a = rtb_theta_dot; + rtb_BusAssignment_c_logic_lgciu_own_valid = rtb_Memory; + rtb_AND1 = ((!rtb_Memory) && (!rtb_AND1)); + rtb_Memory = (rtb_y_j || rtb_Switch_i_idx_1); + yawDamperCanEngage = (FacComputer_U.in.discrete_inputs.yaw_damper_has_hyd_press && + FacComputer_U.in.discrete_inputs.fac_engaged_from_switch); + yawDamperHasPriority_tmp = !FacComputer_U.in.discrete_inputs.is_unit_1; + yawDamperHasPriority = (FacComputer_U.in.discrete_inputs.is_unit_1 || (yawDamperHasPriority_tmp && + (!FacComputer_U.in.discrete_inputs.yaw_damper_opp_engaged))); + rtb_yawDamperEngaged = (yawDamperCanEngage && yawDamperHasPriority); + rudderTrimCanEngage = (FacComputer_U.in.discrete_inputs.rudder_trim_actuator_healthy && + FacComputer_U.in.discrete_inputs.fac_engaged_from_switch); + rudderTrimHasPriority = (FacComputer_U.in.discrete_inputs.is_unit_1 || (yawDamperHasPriority_tmp && + (!FacComputer_U.in.discrete_inputs.rudder_trim_opp_engaged))); + rtb_rudderTrimEngaged = (rudderTrimCanEngage && rudderTrimHasPriority); + rudderTravelLimCanEngage = (FacComputer_U.in.discrete_inputs.rudder_travel_lim_actuator_healthy && + FacComputer_U.in.discrete_inputs.fac_engaged_from_switch); + yawDamperHasPriority_tmp = (FacComputer_U.in.discrete_inputs.is_unit_1 || (yawDamperHasPriority_tmp && + (!FacComputer_U.in.discrete_inputs.rudder_travel_lim_opp_engaged))); + rtb_rudderTravelLimEngaged = (rudderTravelLimCanEngage && yawDamperHasPriority_tmp); + FacComputer_MATLABFunction_p(!rtb_Memory, FacComputer_U.in.time.dt, FacComputer_P.ConfirmNode_isRisingEdge_o, + FacComputer_P.ConfirmNode_timeDelay_l, &rtb_OR_k, &FacComputer_DWork.sf_MATLABFunction_p); + rtb_BusAssignment_h_logic_on_ground = rtb_Memory; + rtb_BusAssignment_h_logic_speed_scale_visible = rtb_OR_k; + FacComputer_MATLABFunction(&FacComputer_U.in.bus_inputs.elac_1_bus.rudder_pedal_position_deg, &rtb_OR_k); + FacComputer_MATLABFunction(&FacComputer_U.in.bus_inputs.elac_2_bus.rudder_pedal_position_deg, &rtb_y_b); + rtb_Switch4_f = std::fmax(rtb_V_tas * 0.5144, 60.0); + rtb_beDot = rtb_V_ias * 0.5144; + if (rtb_V_ias >= 60.0F) { + if (rtb_OR_k) { + rtb_OR_d = FacComputer_U.in.bus_inputs.elac_1_bus.rudder_pedal_position_deg.Data; + } else if (rtb_y_b) { + rtb_OR_d = FacComputer_U.in.bus_inputs.elac_2_bus.rudder_pedal_position_deg.Data; + } else { + rtb_OR_d = FacComputer_P.Constant_Value_n; + } + + rtb_beDot = (rtb_beDot * rtb_beDot * 0.6125 * 122.0 / (70000.0 * rtb_Switch4_f) * 3.172 * + (FacComputer_P.Gain_Gain_h * rtb_OR_d) * 3.1415926535897931 / 180.0 + (rtb_phi * 3.1415926535897931 / + 180.0 * (9.81 / rtb_Switch4_f) + -(rtb_r * 3.1415926535897931 / 180.0))) * 180.0 / 3.1415926535897931; + } else { + rtb_beDot = 0.0; + } + + FacComputer_LagFilter(rtb_beDot, FacComputer_P.LagFilter_C1, FacComputer_U.in.time.dt, &rtb_Switch4_f, + &FacComputer_DWork.sf_LagFilter_b); + FacComputer_LagFilter_k(FacComputer_U.in.bus_inputs.fmgc_own_bus.n1_right_percent.Data - + FacComputer_U.in.bus_inputs.fmgc_own_bus.n1_left_percent.Data, FacComputer_P.LagFilter1_C1, + FacComputer_U.in.time.dt, &rtb_y_i3, &FacComputer_DWork.sf_LagFilter_k); + if (rtb_alpha > FacComputer_P.Saturation_UpperSat_a) { + rtb_beDot = FacComputer_P.Saturation_UpperSat_a; + } else if (rtb_alpha < FacComputer_P.Saturation_LowerSat_l) { + rtb_beDot = FacComputer_P.Saturation_LowerSat_l; + } else { + rtb_beDot = rtb_alpha; + } + + FacComputer_LagFilter(rtb_beDot, FacComputer_P.LagFilter2_C1, FacComputer_U.in.time.dt, &rtb_Switch1_a, + &FacComputer_DWork.sf_LagFilter_f); + FacComputer_LagFilter_k(FacComputer_U.in.bus_inputs.fmgc_own_bus.n1_left_percent.Data - + FacComputer_U.in.bus_inputs.fmgc_own_bus.n1_right_percent.Data, FacComputer_P.LagFilter3_C1, + FacComputer_U.in.time.dt, &rtb_y_e, &FacComputer_DWork.sf_LagFilter_d); + if (rtb_V_ias > FacComputer_P.Saturation1_UpperSat_o) { + rtb_Switch_b = FacComputer_P.Saturation1_UpperSat_o; + } else if (rtb_V_ias < FacComputer_P.Saturation1_LowerSat_n) { + rtb_Switch_b = FacComputer_P.Saturation1_LowerSat_n; + } else { + rtb_Switch_b = rtb_V_ias; + } + + rtb_Switch_b = (rtb_Switch1_a * rtb_y_e * FacComputer_P.Gain5_Gain + FacComputer_P.Gain4_Gain * rtb_y_i3) / + rtb_Switch_b / rtb_Switch_b * FacComputer_P.Gain_Gain_k; + rtb_beDot = rtb_Switch_b; + FacComputer_LagFilter(static_cast(rtb_alpha), FacComputer_P.LagFilter_C1_f, FacComputer_U.in.time.dt, + &rtb_Switch_b, &FacComputer_DWork.sf_LagFilter_c); + rtb_OR_d = rtb_Switch4_f; + rtb_BusAssignment_d_flight_envelope_alpha_filtered_deg = rtb_Switch_b; + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_2, + FacComputer_P.BitfromLabel6_bit_m, &rtb_y_c); + rtb_OR_k = (rtb_y_c != 0U); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_2, + FacComputer_P.BitfromLabel7_bit_i, &rtb_y_c); + rtb_y_b = (rtb_OR_k || (rtb_y_c != 0U)); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.sfcc_own_bus.slat_flap_system_status_word, + FacComputer_P.BitfromLabel_bit_i, &rtb_y_c); + rtb_Memory = (rtb_y_c != 0U); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.sfcc_own_bus.slat_flap_system_status_word, + FacComputer_P.BitfromLabel1_bit_b, &rtb_y_c); + rtb_OR_k = (rtb_y_c != 0U); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.sfcc_own_bus.slat_flap_system_status_word, + FacComputer_P.BitfromLabel2_bit_d, &rtb_y_c); + rtb_DataTypeConversion_c = (rtb_y_c != 0U); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.sfcc_own_bus.slat_flap_system_status_word, + FacComputer_P.BitfromLabel3_bit_n, &rtb_y_c); + rtb_DataTypeConversion_hw = (rtb_y_c != 0U); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.sfcc_own_bus.slat_flap_system_status_word, + FacComputer_P.BitfromLabel4_bit_c, &rtb_y_c); + rtb_DataTypeConversion_e = (rtb_y_c != 0U); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.sfcc_own_bus.slat_flap_system_status_word, + FacComputer_P.BitfromLabel5_bit_g, &rtb_y_c); + FacComputer_MATLABFunction_d(rtb_Memory, rtb_OR_k, rtb_DataTypeConversion_c, rtb_DataTypeConversion_hw, + rtb_DataTypeConversion_e, rtb_y_c != 0U, &rtb_Switch4_f); + FacComputer_RateLimiter(look2_binlxpw(static_cast(rtb_mach), rtb_Switch4_f, + FacComputer_P.alphafloor_bp01Data, FacComputer_P.alphafloor_bp02Data, FacComputer_P.alphafloor_tableData, + FacComputer_P.alphafloor_maxIndex, 4U), FacComputer_P.RateLimiterGenericVariableTs1_up, + FacComputer_P.RateLimiterGenericVariableTs1_lo, FacComputer_U.in.time.dt, FacComputer_P.reset_Value, + &rtb_Switch1_a, &FacComputer_DWork.sf_RateLimiter); + rtb_Gain_f = FacComputer_P.DiscreteDerivativeVariableTs_Gain * rtb_V_ias; + rtb_Switch_b = rtb_Gain_f - FacComputer_DWork.Delay_DSTATE; + FacComputer_LagFilter(rtb_Switch_b / FacComputer_U.in.time.dt, FacComputer_P.LagFilter_C1_k, + FacComputer_U.in.time.dt, &rtb_Switch_b, &FacComputer_DWork.sf_LagFilter); + FacComputer_MATLABFunction(&FacComputer_U.in.bus_inputs.fmgc_own_bus.fg_radio_height_ft, &rtb_OR_k); + if (rtb_OR_k) { + rtb_V_alpha_target = FacComputer_U.in.bus_inputs.fmgc_own_bus.fg_radio_height_ft.Data; + } else { + rtb_V_alpha_target = FacComputer_P.Constant_Value; + } + + if (FacComputer_DWork.is_active_c15_FacComputer == 0U) { + FacComputer_DWork.is_active_c15_FacComputer = 1U; + FacComputer_DWork.is_c15_FacComputer = FacComputer_IN_Landed; + rtb_alpha_floor_inhib = 1; + } else { + switch (FacComputer_DWork.is_c15_FacComputer) { + case FacComputer_IN_Flying: + if (rtb_V_alpha_target < 100.0) { + FacComputer_DWork.is_c15_FacComputer = FacComputer_IN_Landing100ft; + rtb_alpha_floor_inhib = 1; + } else if (rtb_BusAssignment_h_logic_on_ground) { + FacComputer_DWork.is_c15_FacComputer = FacComputer_IN_Landed; + rtb_alpha_floor_inhib = 1; + } else { + rtb_alpha_floor_inhib = 0; + } + break; + + case FacComputer_IN_Landed: + if (!rtb_BusAssignment_h_logic_on_ground) { + FacComputer_DWork.is_c15_FacComputer = FacComputer_IN_Takeoff100ft; + rtb_alpha_floor_inhib = 0; + } else { + rtb_alpha_floor_inhib = 1; + } + break; + + case FacComputer_IN_Landing100ft: + if (rtb_V_alpha_target > 100.0) { + FacComputer_DWork.is_c15_FacComputer = FacComputer_IN_Flying; + rtb_alpha_floor_inhib = 0; + } else if (rtb_BusAssignment_h_logic_on_ground) { + FacComputer_DWork.is_c15_FacComputer = FacComputer_IN_Landed; + rtb_alpha_floor_inhib = 1; + } else { + rtb_alpha_floor_inhib = 1; + } + break; + + default: + if (rtb_BusAssignment_h_logic_on_ground) { + FacComputer_DWork.is_c15_FacComputer = FacComputer_IN_Landed; + rtb_alpha_floor_inhib = 1; + } else if (rtb_V_alpha_target > 100.0) { + FacComputer_DWork.is_c15_FacComputer = FacComputer_IN_Flying; + rtb_alpha_floor_inhib = 0; + } else { + rtb_alpha_floor_inhib = 0; + } + break; + } + } + + FacComputer_MATLABFunction(&FacComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_1, &rtb_Memory); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_1, + FacComputer_P.BitfromLabel9_bit, &rtb_y_c); + rtb_OR_k = ((rtb_y_c != 0U) && rtb_Memory); + FacComputer_MATLABFunction(&FacComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_1, &rtb_Memory); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_1, + FacComputer_P.BitfromLabel8_bit_i, &rtb_y_c); + rtb_Memory = (rtb_OR_k || ((rtb_y_c != 0U) && rtb_Memory)); + guard1 = false; + if ((rtb_alpha_floor_inhib == 0) && (rtb_mach < 0.6)) { + if (rtb_Switch4_f >= 4.0) { + rtb_Switch4_a = -3; + } else { + rtb_Switch4_a = 0; + } + + if ((rtb_BusAssignment_d_flight_envelope_alpha_filtered_deg > rtb_Switch1_a + std::fmin(std::fmax(rtb_Switch_b, + static_cast(rtb_Switch4_a)), 0.0)) && rtb_Memory) { + FacComputer_DWork.sAlphaFloor = 1.0; + } else { + guard1 = true; + } + } else { + guard1 = true; + } + + if (guard1) { + if ((rtb_alpha_floor_inhib != 0) || (!rtb_y_b) || (!rtb_Memory)) { + FacComputer_DWork.sAlphaFloor = 0.0; + } + } + + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.sfcc_own_bus.slat_flap_system_status_word, + FacComputer_P.BitfromLabel_bit_a, &rtb_y_c); + rtb_y_b = (rtb_y_c != 0U); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.sfcc_own_bus.slat_flap_system_status_word, + FacComputer_P.BitfromLabel1_bit_i, &rtb_y_c); + rtb_Memory = (rtb_y_c != 0U); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.sfcc_own_bus.slat_flap_system_status_word, + FacComputer_P.BitfromLabel2_bit_di, &rtb_y_c); + rtb_OR_k = (rtb_y_c != 0U); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.sfcc_own_bus.slat_flap_system_status_word, + FacComputer_P.BitfromLabel3_bit_g, &rtb_y_c); + rtb_DataTypeConversion_c = (rtb_y_c != 0U); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.sfcc_own_bus.slat_flap_system_status_word, + FacComputer_P.BitfromLabel4_bit_f, &rtb_y_c); + rtb_DataTypeConversion_hw = (rtb_y_c != 0U); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.sfcc_own_bus.slat_flap_system_status_word, + FacComputer_P.BitfromLabel5_bit_g3, &rtb_y_c); + FacComputer_MATLABFunction_d(rtb_y_b, rtb_Memory, rtb_OR_k, rtb_DataTypeConversion_c, rtb_DataTypeConversion_hw, + rtb_y_c != 0U, &rtb_Switch4_f); + FacComputer_RateLimiter(look1_binlxpw(rtb_Switch4_f, FacComputer_P.alpha0_bp01Data, FacComputer_P.alpha0_tableData, + 5U), FacComputer_P.RateLimiterGenericVariableTs1_up_g, FacComputer_P.RateLimiterGenericVariableTs1_lo_n, + FacComputer_U.in.time.dt, FacComputer_P.reset_Value_k, &rtb_Switch1_a, &FacComputer_DWork.sf_RateLimiter_c); + FacComputer_RateLimiter(look2_binlxpw(static_cast(rtb_mach), rtb_Switch4_f, FacComputer_P.alphamax_bp01Data, + FacComputer_P.alphamax_bp02Data, FacComputer_P.alphamax_tableData, FacComputer_P.alphamax_maxIndex, 4U), + FacComputer_P.RateLimiterGenericVariableTs4_up, FacComputer_P.RateLimiterGenericVariableTs4_lo, + FacComputer_U.in.time.dt, FacComputer_P.reset_Value_o, &rtb_Switch_b, &FacComputer_DWork.sf_RateLimiter_a); + FacComputer_CalculateV_alpha_max(static_cast(rtb_V_ias), + rtb_BusAssignment_d_flight_envelope_alpha_filtered_deg, rtb_Switch1_a, rtb_Switch_b, &rtb_V_alpha_target); + FacComputer_RateLimiter(look2_binlxpw(static_cast(rtb_mach), rtb_Switch4_f, + FacComputer_P.alphaprotection_bp01Data, FacComputer_P.alphaprotection_bp02Data, + FacComputer_P.alphaprotection_tableData, FacComputer_P.alphaprotection_maxIndex, 4U), + FacComputer_P.RateLimiterGenericVariableTs3_up, FacComputer_P.RateLimiterGenericVariableTs3_lo, + FacComputer_U.in.time.dt, FacComputer_P.reset_Value_a, &rtb_Switch_b, &FacComputer_DWork.sf_RateLimiter_n); + FacComputer_CalculateV_alpha_max(static_cast(rtb_V_ias), + rtb_BusAssignment_d_flight_envelope_alpha_filtered_deg, rtb_Switch1_a, rtb_Switch_b, &rtb_V_alpha_target_l); + FacComputer_RateLimiter(look2_binlxpw(static_cast(rtb_mach), rtb_Switch4_f, + FacComputer_P.alphastallwarn_bp01Data, FacComputer_P.alphastallwarn_bp02Data, + FacComputer_P.alphastallwarn_tableData, FacComputer_P.alphastallwarn_maxIndex, 4U), + FacComputer_P.RateLimiterGenericVariableTs2_up, FacComputer_P.RateLimiterGenericVariableTs2_lo, + FacComputer_U.in.time.dt, FacComputer_P.reset_Value_i, &rtb_Switch_b, &FacComputer_DWork.sf_RateLimiter_j); + FacComputer_CalculateV_alpha_max(static_cast(rtb_V_ias), + rtb_BusAssignment_d_flight_envelope_alpha_filtered_deg, rtb_Switch1_a, rtb_Switch_b, &rtb_Switch4_f); + rtb_y_b = (FacComputer_U.in.discrete_inputs.ap_own_engaged || FacComputer_U.in.discrete_inputs.ap_opp_engaged); + rtb_Memory = (FacComputer_U.in.discrete_inputs.rudder_trim_reset_button && (!rtb_y_b)); + if (!FacComputer_DWork.previousInput_not_empty) { + FacComputer_DWork.previousInput = FacComputer_P.PulseNode_isRisingEdge; + FacComputer_DWork.previousInput_not_empty = true; + } + + if (FacComputer_P.PulseNode_isRisingEdge) { + rtb_OR_k = (rtb_Memory && (!FacComputer_DWork.previousInput)); + } else { + rtb_OR_k = ((!rtb_Memory) && FacComputer_DWork.previousInput); + } + + FacComputer_DWork.previousInput = rtb_Memory; + rtb_Switch_b = FacComputer_P.Gain1_Gain * FacComputer_DWork.Delay1_DSTATE; + if (rtb_Switch_b > FacComputer_P.Saturation_UpperSat_e) { + rtb_Switch_b = FacComputer_P.Saturation_UpperSat_e; + } else if (rtb_Switch_b < FacComputer_P.Saturation_LowerSat_i) { + rtb_Switch_b = FacComputer_P.Saturation_LowerSat_i; + } + + FacComputer_DWork.Memory_PreviousInput = FacComputer_P.Logic_table + [(((FacComputer_U.in.discrete_inputs.rudder_trim_switch_left || + FacComputer_U.in.discrete_inputs.rudder_trim_switch_right || rtb_y_b || (std::abs(rtb_Switch_b) <= + FacComputer_P.CompareToConstant_const)) + (static_cast(rtb_OR_k) << 1)) << 1) + + FacComputer_DWork.Memory_PreviousInput]; + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_1, + FacComputer_P.BitfromLabel_bit_j, &rtb_y_c); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_1, + FacComputer_P.BitfromLabel1_bit_e, &rtb_y_bm); + if (!FacComputer_DWork.Memory_PreviousInput) { + if (rtb_rudderTrimEngaged) { + if (rtb_y_b) { + if ((rtb_y_c != 0U) && FacComputer_U.in.discrete_inputs.elac_1_healthy) { + rtb_Switch1_a = FacComputer_U.in.bus_inputs.elac_1_bus.yaw_damper_command_deg.Data; + } else if ((rtb_y_bm != 0U) && FacComputer_U.in.discrete_inputs.elac_2_healthy) { + rtb_Switch1_a = FacComputer_U.in.bus_inputs.elac_1_bus.yaw_damper_command_deg.Data; + } else { + rtb_Switch1_a = FacComputer_P.Constant1_Value; + } + + rtb_Switch_b = FacComputer_P.Gain2_Gain * rtb_Switch1_a; + } else if (FacComputer_U.in.discrete_inputs.rudder_trim_switch_left) { + rtb_Switch_b = 1.0; + } else if (FacComputer_U.in.discrete_inputs.rudder_trim_switch_right) { + rtb_Switch_b = -1.0; + } else { + rtb_Switch_b = 0.0; + } + } else { + rtb_Switch_b = (FacComputer_U.in.analog_inputs.rudder_trim_position_deg - FacComputer_DWork.Delay2_DSTATE) * + FacComputer_P.Gain_Gain; + } + } + + FacComputer_DWork.Delay_DSTATE_d += FacComputer_P.DiscreteTimeIntegratorVariableTs_Gain * rtb_Switch_b * + FacComputer_U.in.time.dt; + if (FacComputer_DWork.Delay_DSTATE_d > FacComputer_P.DiscreteTimeIntegratorVariableTs_UpperLimit) { + FacComputer_DWork.Delay_DSTATE_d = FacComputer_P.DiscreteTimeIntegratorVariableTs_UpperLimit; + } else if (FacComputer_DWork.Delay_DSTATE_d < FacComputer_P.DiscreteTimeIntegratorVariableTs_LowerLimit) { + FacComputer_DWork.Delay_DSTATE_d = FacComputer_P.DiscreteTimeIntegratorVariableTs_LowerLimit; + } + + rtb_Switch1_a = std::abs(FacComputer_P.Constant_Value_b); + FacComputer_DWork.Delay_DSTATE_m += std::fmax(std::fmin(FacComputer_DWork.Delay_DSTATE_d - + FacComputer_DWork.Delay_DSTATE_m, rtb_Switch1_a * FacComputer_U.in.time.dt), FacComputer_P.Gain_Gain_b * + rtb_Switch1_a * FacComputer_U.in.time.dt); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_1, + FacComputer_P.BitfromLabel_bit_c, &rtb_y_c); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_1, + FacComputer_P.BitfromLabel1_bit_n, &rtb_y_bm); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_1, + FacComputer_P.BitfromLabel3_bit_k, &rtb_y_la); + FacComputer_MATLABFunction_f(&FacComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_1, + FacComputer_P.BitfromLabel4_bit_k, &rtb_y_pj); + if ((!FacComputer_DWork.pY_not_empty) || (!FacComputer_DWork.pU_not_empty)) { + FacComputer_DWork.pU = rtb_r; + FacComputer_DWork.pU_not_empty = true; + FacComputer_DWork.pY = rtb_r; + FacComputer_DWork.pY_not_empty = true; + } + + rtb_Switch1_a = FacComputer_U.in.time.dt * FacComputer_P.WashoutFilter_C1; + rtb_Switch_b = 2.0 / (rtb_Switch1_a + 2.0); + FacComputer_DWork.pY = (2.0 - rtb_Switch1_a) / (rtb_Switch1_a + 2.0) * FacComputer_DWork.pY + (rtb_r * rtb_Switch_b + - FacComputer_DWork.pU * rtb_Switch_b); + FacComputer_DWork.pU = rtb_r; + if (rtb_yawDamperEngaged) { + rtb_y_b = ((rtb_y_c != 0U) && FacComputer_U.in.discrete_inputs.elac_1_healthy && (rtb_y_bm != 0U)); + if (rtb_y_b || ((rtb_y_la != 0U) && FacComputer_U.in.discrete_inputs.elac_2_healthy && (rtb_y_pj != 0U))) { + if (rtb_y_b) { + rtb_Switch_b = FacComputer_U.in.bus_inputs.elac_1_bus.yaw_damper_command_deg.Data; + } else { + rtb_Switch_b = FacComputer_U.in.bus_inputs.elac_2_bus.yaw_damper_command_deg.Data; + } + } else { + rtb_Switch_b = FacComputer_P.Gain_Gain_p * FacComputer_DWork.pY; + if (rtb_Switch_b > FacComputer_P.Saturation1_UpperSat) { + rtb_Switch_b = FacComputer_P.Saturation1_UpperSat; + } else if (rtb_Switch_b < FacComputer_P.Saturation1_LowerSat) { + rtb_Switch_b = FacComputer_P.Saturation1_LowerSat; + } + } + } else { + rtb_Switch_b = FacComputer_U.in.analog_inputs.yaw_damper_position_deg; + } + + rtb_Switch1_a = std::abs(FacComputer_P.Constant_Value_k); + if (rtb_Switch_b > FacComputer_P.Saturation_UpperSat_ew) { + rtb_Switch_b = FacComputer_P.Saturation_UpperSat_ew; + } else if (rtb_Switch_b < FacComputer_P.Saturation_LowerSat_o) { + rtb_Switch_b = FacComputer_P.Saturation_LowerSat_o; + } + + FacComputer_DWork.Delay_DSTATE_k += std::fmax(std::fmin(rtb_Switch_b - FacComputer_DWork.Delay_DSTATE_k, + rtb_Switch1_a * FacComputer_U.in.time.dt), FacComputer_P.Gain_Gain_m * rtb_Switch1_a * FacComputer_U.in.time.dt); + if (rtb_rudderTravelLimEngaged) { + rtb_Switch1_a = look1_binlxpw(static_cast(rtb_V_ias), FacComputer_P.uDLookupTable_bp01Data, + FacComputer_P.uDLookupTable_tableData, 6U); + if (rtb_Switch1_a > FacComputer_P.Saturation_UpperSat) { + rtb_Switch1_a = FacComputer_P.Saturation_UpperSat; + } else if (rtb_Switch1_a < FacComputer_P.Saturation_LowerSat) { + rtb_Switch1_a = FacComputer_P.Saturation_LowerSat; + } + } else { + rtb_Switch1_a = FacComputer_U.in.analog_inputs.rudder_travel_lim_position_deg; + } + + if (!FacComputer_DWork.pY_not_empty_l) { + FacComputer_DWork.pY_n = FacComputer_P.RateLimiterVariableTs_InitialCondition; + FacComputer_DWork.pY_not_empty_l = true; + } + + FacComputer_DWork.pY_n += std::fmax(std::fmin(rtb_Switch1_a - FacComputer_DWork.pY_n, std::abs + (FacComputer_P.RateLimiterVariableTs_up) * FacComputer_U.in.time.dt), -std::abs + (FacComputer_P.RateLimiterVariableTs_lo) * FacComputer_U.in.time.dt); + rtb_VectorConcatenate[0] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[1] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[2] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[3] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[4] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[5] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[6] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[7] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[8] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[9] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[10] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[11] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[12] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[13] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[14] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[15] = FacComputer_U.in.discrete_inputs.nose_gear_pressed; + rtb_VectorConcatenate[16] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[17] = FacComputer_P.Constant20_Value; + rtb_VectorConcatenate[18] = FacComputer_P.Constant20_Value; + FacComputer_MATLABFunction_g(rtb_VectorConcatenate, &rtb_y_dr); + rtb_alpha_floor_inhib = static_cast(FacComputer_P.EnumeratedConstant2_Value); + rtb_VectorConcatenate[0] = rtb_yawDamperEngaged; + rtb_VectorConcatenate[1] = FacComputer_U.in.discrete_inputs.yaw_damper_opp_engaged; + rtb_VectorConcatenate[2] = rtb_rudderTrimEngaged; + rtb_VectorConcatenate[3] = FacComputer_U.in.discrete_inputs.rudder_trim_opp_engaged; + rtb_VectorConcatenate[4] = rtb_rudderTravelLimEngaged; + rtb_VectorConcatenate[5] = FacComputer_U.in.discrete_inputs.rudder_travel_lim_opp_engaged; + rtb_VectorConcatenate[6] = FacComputer_P.Constant10_Value; + rtb_VectorConcatenate[7] = FacComputer_P.Constant10_Value; + rtb_VectorConcatenate[8] = FacComputer_P.Constant10_Value; + rtb_VectorConcatenate[9] = FacComputer_P.Constant10_Value; + rtb_VectorConcatenate[10] = FacComputer_P.Constant10_Value; + rtb_VectorConcatenate[11] = FacComputer_P.Constant10_Value; + rtb_VectorConcatenate[12] = FacComputer_P.Constant10_Value; + rtb_VectorConcatenate[13] = FacComputer_P.Constant10_Value; + rtb_VectorConcatenate[14] = FacComputer_P.Constant10_Value; + rtb_VectorConcatenate[15] = FacComputer_P.Constant10_Value; + rtb_VectorConcatenate[16] = FacComputer_P.Constant10_Value; + rtb_VectorConcatenate[17] = FacComputer_P.Constant10_Value; + rtb_VectorConcatenate[18] = FacComputer_P.Constant10_Value; + FacComputer_MATLABFunction_g(rtb_VectorConcatenate, &rtb_y_e); + if (FacComputer_P.Constant_Value_b5) { + rtb_Switch4_a = static_cast(FacComputer_P.EnumeratedConstant2_Value); + rtb_Switch2 = static_cast(FacComputer_P.EnumeratedConstant2_Value); + } else if (rtb_BusAssignment_h_logic_speed_scale_visible) { + rtb_Switch4_a = static_cast(FacComputer_P.EnumeratedConstant1_Value); + rtb_Switch2 = static_cast(FacComputer_P.EnumeratedConstant1_Value); + rtb_alpha_floor_inhib = static_cast(FacComputer_P.EnumeratedConstant1_Value); + } else { + rtb_Switch4_a = static_cast(FacComputer_P.EnumeratedConstant_Value); + rtb_Switch2 = static_cast(FacComputer_P.EnumeratedConstant_Value); + rtb_alpha_floor_inhib = static_cast(FacComputer_P.EnumeratedConstant_Value); + } + + rtb_VectorConcatenate[0] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[1] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[2] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[3] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[4] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[5] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[6] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[7] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[8] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[9] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[10] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[11] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[12] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[13] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[14] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[15] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[16] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[17] = FacComputer_P.Constant18_Value; + rtb_VectorConcatenate[18] = FacComputer_P.Constant18_Value; + FacComputer_MATLABFunction_g(rtb_VectorConcatenate, &rtb_y_i3); + rtb_VectorConcatenate[0] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[1] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[2] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[3] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[4] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[5] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[6] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[7] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[8] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[9] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[10] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[11] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[12] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[13] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[14] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[15] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[16] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[17] = FacComputer_P.Constant9_Value; + rtb_VectorConcatenate[18] = FacComputer_P.Constant9_Value; + FacComputer_MATLABFunction_g(rtb_VectorConcatenate, &rtb_y_iu); + rtb_VectorConcatenate[0] = FacComputer_P.Constant19_Value; + rtb_VectorConcatenate[1] = FacComputer_P.Constant19_Value; + rtb_VectorConcatenate[2] = FacComputer_P.Constant19_Value; + rtb_VectorConcatenate[3] = FacComputer_P.Constant19_Value; + rtb_VectorConcatenate[4] = FacComputer_P.Constant19_Value; + rtb_VectorConcatenate[5] = rtb_BusAssignment_c_logic_lgciu_own_valid; + rtb_VectorConcatenate[6] = rtb_AND1; + rtb_VectorConcatenate[7] = rtb_y_j; + rtb_VectorConcatenate[8] = rtb_Switch_i_idx_1; + rtb_VectorConcatenate[9] = rtb_Switch_i_idx_2; + rtb_VectorConcatenate[10] = FacComputer_P.Constant19_Value; + rtb_VectorConcatenate[11] = FacComputer_P.Constant19_Value; + rtb_VectorConcatenate[12] = FacComputer_P.Constant19_Value; + rtb_VectorConcatenate[13] = FacComputer_P.Constant19_Value; + rtb_VectorConcatenate[14] = FacComputer_P.Constant19_Value; + rtb_VectorConcatenate[15] = FacComputer_P.Constant19_Value; + rtb_VectorConcatenate[16] = FacComputer_P.Constant19_Value; + rtb_VectorConcatenate[17] = FacComputer_P.Constant19_Value; + rtb_VectorConcatenate[18] = (FacComputer_DWork.sAlphaFloor != 0.0); + FacComputer_MATLABFunction_g(rtb_VectorConcatenate, &rtb_y_bp); + FacComputer_Y.out.data = FacComputer_U.in; + FacComputer_Y.out.laws.yaw_damper_command_deg = FacComputer_DWork.Delay_DSTATE_k; + FacComputer_Y.out.laws.rudder_trim_command_deg = FacComputer_DWork.Delay_DSTATE_m; + FacComputer_Y.out.laws.rudder_travel_lim_command_deg = FacComputer_DWork.pY_n; + FacComputer_Y.out.logic.lgciu_own_valid = rtb_BusAssignment_c_logic_lgciu_own_valid; + FacComputer_Y.out.logic.all_lgciu_lost = rtb_AND1; + FacComputer_Y.out.logic.left_main_gear_pressed = rtb_y_j; + FacComputer_Y.out.logic.right_main_gear_pressed = rtb_Switch_i_idx_1; + FacComputer_Y.out.logic.main_gear_out = rtb_Switch_i_idx_2; + FacComputer_Y.out.logic.on_ground = rtb_BusAssignment_h_logic_on_ground; + FacComputer_Y.out.logic.tracking_mode_on = (FacComputer_U.in.sim_data.slew_on || FacComputer_U.in.sim_data.pause_on || + FacComputer_U.in.sim_data.tracking_mode_on_override); + FacComputer_Y.out.logic.double_self_detected_adr_failure = ((rtb_adrOwnInvalid && rtb_adrOppInvalid) || + (rtb_adrOwnInvalid && rtb_adr3Invalid) || (rtb_adrOppInvalid && rtb_adr3Invalid)); + FacComputer_Y.out.logic.double_self_detected_ir_failure = ((rtb_irOwnInvalid && rtb_irOppInvalid) || + (rtb_irOwnInvalid && rtb_ir3Invalid) || (rtb_irOppInvalid && rtb_ir3Invalid)); + FacComputer_Y.out.logic.double_not_self_detected_adr_failure = FacComputer_P.Constant_Value_h; + FacComputer_Y.out.logic.double_not_self_detected_ir_failure = FacComputer_P.Constant_Value_h; + FacComputer_Y.out.logic.adr_computation_data.V_ias_kn = rtb_V_ias; + FacComputer_Y.out.logic.adr_computation_data.V_tas_kn = rtb_V_tas; + FacComputer_Y.out.logic.adr_computation_data.mach = rtb_mach; + FacComputer_Y.out.logic.adr_computation_data.alpha_deg = rtb_alpha; + FacComputer_Y.out.logic.ir_computation_data.theta_deg = rtb_theta; + FacComputer_Y.out.logic.ir_computation_data.phi_deg = rtb_phi; + FacComputer_Y.out.logic.ir_computation_data.q_deg_s = rtb_q; + FacComputer_Y.out.logic.ir_computation_data.r_deg_s = rtb_r; + FacComputer_Y.out.logic.ir_computation_data.n_x_g = rtb_n_x; + FacComputer_Y.out.logic.ir_computation_data.n_y_g = rtb_n_y; + FacComputer_Y.out.logic.ir_computation_data.n_z_g = rtb_n_z; + FacComputer_Y.out.logic.ir_computation_data.theta_dot_deg_s = rtb_theta_dot; + FacComputer_Y.out.logic.ir_computation_data.phi_dot_deg_s = rtb_phi_dot; + FacComputer_Y.out.logic.yaw_damper_engaged = rtb_yawDamperEngaged; + FacComputer_Y.out.logic.yaw_damper_can_engage = yawDamperCanEngage; + FacComputer_Y.out.logic.yaw_damper_has_priority = yawDamperHasPriority; + FacComputer_Y.out.logic.rudder_trim_engaged = rtb_rudderTrimEngaged; + FacComputer_Y.out.logic.rudder_trim_can_engage = rudderTrimCanEngage; + FacComputer_Y.out.logic.rudder_trim_has_priority = rudderTrimHasPriority; + FacComputer_Y.out.logic.rudder_travel_lim_engaged = rtb_rudderTravelLimEngaged; + FacComputer_Y.out.logic.rudder_travel_lim_can_engage = rudderTravelLimCanEngage; + FacComputer_Y.out.logic.rudder_travel_lim_has_priority = yawDamperHasPriority_tmp; + FacComputer_Y.out.logic.speed_scale_lost = FacComputer_P.Constant_Value_b5; + FacComputer_Y.out.logic.speed_scale_visible = rtb_BusAssignment_h_logic_speed_scale_visible; + FacComputer_Y.out.flight_envelope.estimated_beta_deg = rtb_OR_d; + FacComputer_Y.out.flight_envelope.beta_target_deg = rtb_beDot; + FacComputer_Y.out.flight_envelope.beta_target_visible = false; + FacComputer_Y.out.flight_envelope.alpha_floor_condition = (FacComputer_DWork.sAlphaFloor != 0.0); + FacComputer_Y.out.flight_envelope.alpha_filtered_deg = rtb_BusAssignment_d_flight_envelope_alpha_filtered_deg; + FacComputer_Y.out.flight_envelope.computed_weight_lbs = 0.0; + FacComputer_Y.out.flight_envelope.computed_cg_percent = 0.0; + FacComputer_Y.out.flight_envelope.v_alpha_max_kn = rtb_V_alpha_target; + FacComputer_Y.out.flight_envelope.v_alpha_prot_kn = rtb_V_alpha_target_l; + FacComputer_Y.out.flight_envelope.v_stall_warn_kn = rtb_Switch4_f; + FacComputer_Y.out.flight_envelope.v_ls_kn = 0.0; + FacComputer_Y.out.flight_envelope.v_stall_kn = 0.0; + FacComputer_Y.out.flight_envelope.v_3_kn = 0.0; + FacComputer_Y.out.flight_envelope.v_4_kn = 0.0; + FacComputer_Y.out.flight_envelope.v_man_kn = 0.0; + FacComputer_Y.out.flight_envelope.v_max_kn = 0.0; + FacComputer_Y.out.flight_envelope.v_c_trend_kn = 0.0; + FacComputer_Y.out.discrete_outputs.fac_healthy = FacComputer_P.Constant2_Value_o; + FacComputer_Y.out.discrete_outputs.yaw_damper_engaged = rtb_yawDamperEngaged; + FacComputer_Y.out.discrete_outputs.rudder_trim_engaged = rtb_rudderTrimEngaged; + FacComputer_Y.out.discrete_outputs.rudder_travel_lim_engaged = rtb_rudderTravelLimEngaged; + FacComputer_Y.out.discrete_outputs.rudder_travel_lim_emergency_reset = FacComputer_P.Constant1_Value_d; + FacComputer_Y.out.discrete_outputs.yaw_damper_avail_for_norm_law = yawDamperCanEngage; + if (rtb_yawDamperEngaged) { + FacComputer_Y.out.analog_outputs.yaw_damper_order_deg = FacComputer_DWork.Delay_DSTATE_k; + } else { + FacComputer_Y.out.analog_outputs.yaw_damper_order_deg = FacComputer_P.Constant_Value_bu; + } + + if (rtb_rudderTrimEngaged) { + FacComputer_Y.out.analog_outputs.rudder_trim_order_deg = FacComputer_DWork.Delay_DSTATE_m; + } else { + FacComputer_Y.out.analog_outputs.rudder_trim_order_deg = FacComputer_P.Constant_Value_bu; + } + + if (rtb_rudderTravelLimEngaged) { + FacComputer_Y.out.analog_outputs.rudder_travel_limit_order_deg = FacComputer_DWork.pY_n; + } else { + FacComputer_Y.out.analog_outputs.rudder_travel_limit_order_deg = FacComputer_P.Constant_Value_bu; + } + + FacComputer_Y.out.bus_outputs.discrete_word_1.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.discrete_word_1.Data = rtb_y_dr; + FacComputer_Y.out.bus_outputs.gamma_a_deg.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.gamma_a_deg.Data = FacComputer_P.Constant28_Value; + FacComputer_Y.out.bus_outputs.gamma_t_deg.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.gamma_t_deg.Data = FacComputer_P.Constant22_Value; + FacComputer_Y.out.bus_outputs.total_weight_lbs.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.total_weight_lbs.Data = FacComputer_P.Constant21_Value; + FacComputer_Y.out.bus_outputs.center_of_gravity_pos_percent.SSM = static_cast + (FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.center_of_gravity_pos_percent.Data = FacComputer_P.Constant4_Value_b; + if (FacComputer_P.Switch7_Threshold < 0.0) { + FacComputer_Y.out.bus_outputs.sideslip_target_deg.SSM = static_cast + (FacComputer_P.EnumeratedConstant2_Value); + } else { + FacComputer_Y.out.bus_outputs.sideslip_target_deg.SSM = static_cast + (FacComputer_P.EnumeratedConstant_Value); + } + + FacComputer_Y.out.bus_outputs.sideslip_target_deg.Data = static_cast(rtb_beDot); + FacComputer_Y.out.bus_outputs.fac_slat_angle_deg.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.fac_slat_angle_deg.Data = FacComputer_P.Constant2_Value; + FacComputer_Y.out.bus_outputs.fac_flap_angle.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.fac_flap_angle.Data = FacComputer_P.Constant1_Value_k; + FacComputer_Y.out.bus_outputs.discrete_word_2.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.discrete_word_2.Data = rtb_y_e; + FacComputer_Y.out.bus_outputs.rudder_travel_limit_command_deg.SSM = static_cast + (FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.rudder_travel_limit_command_deg.Data = static_cast + (FacComputer_U.in.analog_inputs.rudder_travel_lim_position_deg); + FacComputer_Y.out.bus_outputs.delta_r_yaw_damper_deg.SSM = static_cast + (FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.delta_r_yaw_damper_deg.Data = FacComputer_P.Constant26_Value; + if (FacComputer_P.Switch6_Threshold < 0.0) { + FacComputer_Y.out.bus_outputs.estimated_sideslip_deg.SSM = static_cast + (FacComputer_P.EnumeratedConstant2_Value); + } else { + FacComputer_Y.out.bus_outputs.estimated_sideslip_deg.SSM = static_cast + (FacComputer_P.EnumeratedConstant1_Value); + } + + FacComputer_Y.out.bus_outputs.estimated_sideslip_deg.Data = static_cast(rtb_OR_d); + FacComputer_Y.out.bus_outputs.v_alpha_lim_kn.SSM = static_cast(rtb_Switch4_a); + FacComputer_Y.out.bus_outputs.v_alpha_lim_kn.Data = static_cast(rtb_V_alpha_target); + FacComputer_Y.out.bus_outputs.v_ls_kn.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.v_ls_kn.Data = FacComputer_P.Constant15_Value; + FacComputer_Y.out.bus_outputs.v_stall_kn.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.v_stall_kn.Data = FacComputer_P.Constant14_Value; + FacComputer_Y.out.bus_outputs.v_alpha_prot_kn.SSM = static_cast(rtb_Switch2); + FacComputer_Y.out.bus_outputs.v_alpha_prot_kn.Data = static_cast(rtb_V_alpha_target_l); + FacComputer_Y.out.bus_outputs.v_stall_warn_kn.SSM = static_cast(rtb_alpha_floor_inhib); + FacComputer_Y.out.bus_outputs.v_stall_warn_kn.Data = static_cast(rtb_Switch4_f); + FacComputer_Y.out.bus_outputs.speed_trend_kn.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.speed_trend_kn.Data = FacComputer_P.Constant11_Value; + FacComputer_Y.out.bus_outputs.v_3_kn.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.v_3_kn.Data = FacComputer_P.Constant8_Value; + FacComputer_Y.out.bus_outputs.v_4_kn.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.v_4_kn.Data = FacComputer_P.Constant7_Value; + FacComputer_Y.out.bus_outputs.v_man_kn.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.v_man_kn.Data = FacComputer_P.Constant6_Value; + FacComputer_Y.out.bus_outputs.v_max_kn.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.v_max_kn.Data = FacComputer_P.Constant5_Value; + FacComputer_Y.out.bus_outputs.v_fe_next_kn.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.v_fe_next_kn.Data = FacComputer_P.Constant17_Value; + FacComputer_Y.out.bus_outputs.discrete_word_3.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.discrete_word_3.Data = rtb_y_i3; + FacComputer_Y.out.bus_outputs.discrete_word_4.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.discrete_word_4.Data = rtb_y_iu; + FacComputer_Y.out.bus_outputs.discrete_word_5.SSM = static_cast(FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.discrete_word_5.Data = rtb_y_bp; + FacComputer_Y.out.bus_outputs.delta_r_rudder_trim_deg.SSM = static_cast + (FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.delta_r_rudder_trim_deg.Data = static_cast(FacComputer_DWork.Delay_DSTATE_m); + FacComputer_Y.out.bus_outputs.rudder_trim_pos_deg.SSM = static_cast + (FacComputer_P.EnumeratedConstant1_Value); + FacComputer_Y.out.bus_outputs.rudder_trim_pos_deg.Data = static_cast + (FacComputer_U.in.analog_inputs.rudder_trim_position_deg); + FacComputer_DWork.Delay_DSTATE = rtb_Gain_f; + FacComputer_DWork.Delay1_DSTATE = FacComputer_DWork.Delay_DSTATE_d; + FacComputer_DWork.Delay2_DSTATE = FacComputer_DWork.Delay_DSTATE_m; + } else { + FacComputer_DWork.Runtime_MODE = false; + } +} + +void FacComputer::initialize() +{ + FacComputer_DWork.Delay_DSTATE = FacComputer_P.DiscreteDerivativeVariableTs_InitialCondition; + FacComputer_DWork.Delay1_DSTATE = FacComputer_P.Delay1_InitialCondition; + FacComputer_DWork.Memory_PreviousInput = FacComputer_P.SRFlipFlop_initial_condition; + FacComputer_DWork.Delay2_DSTATE = FacComputer_P.Delay2_InitialCondition; + FacComputer_DWork.Delay_DSTATE_d = FacComputer_P.DiscreteTimeIntegratorVariableTs_InitialCondition; + FacComputer_DWork.Delay_DSTATE_m = FacComputer_P.RateLimiterDynamicEqualVariableTs_InitialCondition; + FacComputer_DWork.Delay_DSTATE_k = FacComputer_P.RateLimiterDynamicEqualVariableTs_InitialCondition_a; + FacComputer_Y.out = FacComputer_P.out_Y0; +} + +void FacComputer::terminate() +{ +} + +FacComputer::FacComputer(): + FacComputer_U(), + FacComputer_Y(), + FacComputer_DWork() +{ +} + +FacComputer::~FacComputer() +{ +} diff --git a/src/fbw/src/model/FacComputer.h b/src/fbw/src/model/FacComputer.h new file mode 100644 index 000000000000..05ad03a62a63 --- /dev/null +++ b/src/fbw/src/model/FacComputer.h @@ -0,0 +1,287 @@ +#ifndef RTW_HEADER_FacComputer_h_ +#define RTW_HEADER_FacComputer_h_ +#include "rtwtypes.h" +#include "FacComputer_types.h" + +class FacComputer final +{ + public: + struct rtDW_LagFilter_FacComputer_T { + real_T pY; + real_T pU; + boolean_T pY_not_empty; + boolean_T pU_not_empty; + }; + + struct rtDW_RateLimiter_FacComputer_T { + real_T pY; + boolean_T pY_not_empty; + }; + + struct rtDW_LagFilter_FacComputer_g_T { + real32_T pY; + real32_T pU; + boolean_T pY_not_empty; + boolean_T pU_not_empty; + }; + + struct rtDW_MATLABFunction_FacComputer_f_T { + real_T timeSinceCondition; + boolean_T output; + }; + + struct D_Work_FacComputer_T { + real_T Delay_DSTATE; + real_T Delay1_DSTATE; + real_T Delay2_DSTATE; + real_T Delay_DSTATE_d; + real_T Delay_DSTATE_m; + real_T Delay_DSTATE_k; + real_T pY; + real_T pU; + real_T pY_n; + real_T sAlphaFloor; + uint8_T is_active_c15_FacComputer; + uint8_T is_c15_FacComputer; + boolean_T Memory_PreviousInput; + boolean_T pY_not_empty; + boolean_T pU_not_empty; + boolean_T previousInput; + boolean_T previousInput_not_empty; + boolean_T pY_not_empty_l; + boolean_T Runtime_MODE; + rtDW_MATLABFunction_FacComputer_f_T sf_MATLABFunction_ax; + rtDW_MATLABFunction_FacComputer_f_T sf_MATLABFunction_p; + rtDW_LagFilter_FacComputer_T sf_LagFilter_c; + rtDW_LagFilter_FacComputer_g_T sf_LagFilter_d; + rtDW_LagFilter_FacComputer_T sf_LagFilter_f; + rtDW_LagFilter_FacComputer_g_T sf_LagFilter_k; + rtDW_LagFilter_FacComputer_T sf_LagFilter_b; + rtDW_RateLimiter_FacComputer_T sf_RateLimiter_a; + rtDW_RateLimiter_FacComputer_T sf_RateLimiter_n; + rtDW_RateLimiter_FacComputer_T sf_RateLimiter_j; + rtDW_RateLimiter_FacComputer_T sf_RateLimiter_c; + rtDW_RateLimiter_FacComputer_T sf_RateLimiter; + rtDW_LagFilter_FacComputer_T sf_LagFilter; + }; + + struct ExternalInputs_FacComputer_T { + fac_inputs in; + }; + + struct ExternalOutputs_FacComputer_T { + fac_outputs out; + }; + + struct Parameters_FacComputer_T { + base_fac_logic_outputs fac_logic_output_MATLABStruct; + base_fac_flight_envelope_outputs fac_flight_envelope_output_MATLABStruct; + base_fac_analog_outputs fac_analog_output_MATLABStruct; + base_fac_laws_outputs fac_laws_output_MATLABStruct; + base_fac_discrete_outputs fac_discrete_output_MATLABStruct; + real_T LagFilter_C1; + real_T LagFilter1_C1; + real_T LagFilter2_C1; + real_T LagFilter3_C1; + real_T LagFilter_C1_f; + real_T LagFilter_C1_k; + real_T WashoutFilter_C1; + real_T DiscreteDerivativeVariableTs_Gain; + real_T DiscreteTimeIntegratorVariableTs_Gain; + real_T DiscreteDerivativeVariableTs_InitialCondition; + real_T DiscreteTimeIntegratorVariableTs_InitialCondition; + real_T RateLimiterDynamicEqualVariableTs_InitialCondition; + real_T RateLimiterDynamicEqualVariableTs_InitialCondition_a; + real_T RateLimiterVariableTs_InitialCondition; + real_T DiscreteTimeIntegratorVariableTs_LowerLimit; + real_T DiscreteTimeIntegratorVariableTs_UpperLimit; + real_T BitfromLabel1_bit; + real_T BitfromLabel4_bit; + real_T BitfromLabel3_bit; + real_T BitfromLabel2_bit; + real_T BitfromLabel_bit; + real_T BitfromLabel5_bit; + real_T BitfromLabel6_bit; + real_T BitfromLabel7_bit; + real_T BitfromLabel8_bit; + real_T BitfromLabel6_bit_m; + real_T BitfromLabel7_bit_i; + real_T BitfromLabel_bit_i; + real_T BitfromLabel1_bit_b; + real_T BitfromLabel2_bit_d; + real_T BitfromLabel3_bit_n; + real_T BitfromLabel4_bit_c; + real_T BitfromLabel5_bit_g; + real_T BitfromLabel9_bit; + real_T BitfromLabel8_bit_i; + real_T BitfromLabel_bit_a; + real_T BitfromLabel1_bit_i; + real_T BitfromLabel2_bit_di; + real_T BitfromLabel3_bit_g; + real_T BitfromLabel4_bit_f; + real_T BitfromLabel5_bit_g3; + real_T BitfromLabel_bit_j; + real_T BitfromLabel1_bit_e; + real_T BitfromLabel_bit_c; + real_T BitfromLabel1_bit_n; + real_T BitfromLabel3_bit_k; + real_T BitfromLabel4_bit_k; + real_T CompareToConstant_const; + real_T RateLimiterGenericVariableTs1_lo; + real_T RateLimiterGenericVariableTs1_lo_n; + real_T RateLimiterGenericVariableTs4_lo; + real_T RateLimiterGenericVariableTs3_lo; + real_T RateLimiterGenericVariableTs2_lo; + real_T RateLimiterVariableTs_lo; + real_T ConfirmNode_timeDelay; + real_T ConfirmNode_timeDelay_l; + real_T RateLimiterGenericVariableTs1_up; + real_T RateLimiterGenericVariableTs1_up_g; + real_T RateLimiterGenericVariableTs4_up; + real_T RateLimiterGenericVariableTs3_up; + real_T RateLimiterGenericVariableTs2_up; + real_T RateLimiterVariableTs_up; + SignStatusMatrix EnumeratedConstant1_Value; + SignStatusMatrix EnumeratedConstant2_Value; + SignStatusMatrix EnumeratedConstant_Value; + real32_T CompareToConstant_const_f; + real32_T CompareToConstant1_const; + real32_T CompareToConstant2_const; + boolean_T SRFlipFlop_initial_condition; + boolean_T ConfirmNode_isRisingEdge; + boolean_T ConfirmNode_isRisingEdge_o; + boolean_T PulseNode_isRisingEdge; + fac_outputs out_Y0; + base_fac_bus Constant4_Value; + real_T Constant_Value; + real_T Constant_Value_n; + real_T uDLookupTable_tableData[7]; + real_T uDLookupTable_bp01Data[7]; + real_T Saturation_UpperSat; + real_T Saturation_LowerSat; + real_T Constant1_Value; + real_T Gain2_Gain; + real_T Gain_Gain; + real_T Gain_Gain_p; + real_T Saturation1_UpperSat; + real_T Saturation1_LowerSat; + real_T Gain_Gain_h; + real_T Saturation_UpperSat_a; + real_T Saturation_LowerSat_l; + real_T Gain5_Gain; + real_T Saturation1_UpperSat_o; + real_T Saturation1_LowerSat_n; + real_T Gain_Gain_k; + real_T alphafloor_tableData[24]; + real_T alphafloor_bp01Data[4]; + real_T alphafloor_bp02Data[6]; + real_T alpha0_tableData[6]; + real_T alpha0_bp01Data[6]; + real_T alphamax_tableData[24]; + real_T alphamax_bp01Data[4]; + real_T alphamax_bp02Data[6]; + real_T alphaprotection_tableData[24]; + real_T alphaprotection_bp01Data[4]; + real_T alphaprotection_bp02Data[6]; + real_T alphastallwarn_tableData[24]; + real_T alphastallwarn_bp01Data[4]; + real_T alphastallwarn_bp02Data[6]; + real_T Delay1_InitialCondition; + real_T Gain1_Gain; + real_T Saturation_UpperSat_e; + real_T Saturation_LowerSat_i; + real_T Delay2_InitialCondition; + real_T Constant_Value_b; + real_T Gain_Gain_b; + real_T Saturation_UpperSat_ew; + real_T Saturation_LowerSat_o; + real_T Constant_Value_k; + real_T Gain_Gain_m; + real_T Constant_Value_bu; + real_T Switch7_Threshold; + real_T Switch6_Threshold; + real32_T Gain4_Gain; + real32_T Constant28_Value; + real32_T Constant22_Value; + real32_T Constant21_Value; + real32_T Constant4_Value_b; + real32_T Constant2_Value; + real32_T Constant1_Value_k; + real32_T Constant26_Value; + real32_T Constant15_Value; + real32_T Constant14_Value; + real32_T Constant11_Value; + real32_T Constant8_Value; + real32_T Constant7_Value; + real32_T Constant6_Value; + real32_T Constant5_Value; + real32_T Constant17_Value; + uint32_T alphafloor_maxIndex[2]; + uint32_T alphamax_maxIndex[2]; + uint32_T alphaprotection_maxIndex[2]; + uint32_T alphastallwarn_maxIndex[2]; + boolean_T Constant_Value_c; + boolean_T Constant_Value_h; + boolean_T Constant_Value_b5; + boolean_T reset_Value; + boolean_T reset_Value_k; + boolean_T reset_Value_o; + boolean_T reset_Value_a; + boolean_T reset_Value_i; + boolean_T Logic_table[16]; + boolean_T Constant2_Value_o; + boolean_T Constant1_Value_d; + boolean_T Constant20_Value; + boolean_T Constant10_Value; + boolean_T Constant18_Value; + boolean_T Constant9_Value; + boolean_T Constant19_Value; + }; + + FacComputer(FacComputer const&) = delete; + FacComputer& operator= (FacComputer const&) & = delete; + FacComputer(FacComputer &&) = delete; + FacComputer& operator= (FacComputer &&) = delete; + void setExternalInputs(const ExternalInputs_FacComputer_T *pExternalInputs_FacComputer_T) + { + FacComputer_U = *pExternalInputs_FacComputer_T; + } + + const ExternalOutputs_FacComputer_T &getExternalOutputs() const + { + return FacComputer_Y; + } + + void initialize(); + void step(); + static void terminate(); + FacComputer(); + ~FacComputer(); + private: + ExternalInputs_FacComputer_T FacComputer_U; + ExternalOutputs_FacComputer_T FacComputer_Y; + D_Work_FacComputer_T FacComputer_DWork; + static Parameters_FacComputer_T FacComputer_P; + static void FacComputer_MATLABFunction(const base_arinc_429 *rtu_u, boolean_T *rty_y); + static void FacComputer_MATLABFunction_f(const base_arinc_429 *rtu_u, real_T rtu_bit, uint32_T *rty_y); + static void FacComputer_LagFilter_Reset(rtDW_LagFilter_FacComputer_T *localDW); + static void FacComputer_LagFilter(real_T rtu_U, real_T rtu_C1, real_T rtu_dt, real_T *rty_Y, + rtDW_LagFilter_FacComputer_T *localDW); + static void FacComputer_MATLABFunction_d(boolean_T rtu_bit1, boolean_T rtu_bit2, boolean_T rtu_bit3, boolean_T + rtu_bit4, boolean_T rtu_bit5, boolean_T rtu_bit6, real_T *rty_handleIndex); + static void FacComputer_RateLimiter_Reset(rtDW_RateLimiter_FacComputer_T *localDW); + static void FacComputer_RateLimiter(real_T rtu_u, real_T rtu_up, real_T rtu_lo, real_T rtu_Ts, boolean_T rtu_reset, + real_T *rty_Y, rtDW_RateLimiter_FacComputer_T *localDW); + static void FacComputer_CalculateV_alpha_max(real_T rtu_v_ias, real_T rtu_alpha, real_T rtu_alpha_0, real_T + rtu_alpha_target, real_T *rty_V_alpha_target); + static void FacComputer_LagFilter_n_Reset(rtDW_LagFilter_FacComputer_g_T *localDW); + static void FacComputer_LagFilter_k(real32_T rtu_U, real_T rtu_C1, real_T rtu_dt, real32_T *rty_Y, + rtDW_LagFilter_FacComputer_g_T *localDW); + static void FacComputer_MATLABFunction_i_Reset(rtDW_MATLABFunction_FacComputer_f_T *localDW); + static void FacComputer_MATLABFunction_p(boolean_T rtu_u, real_T rtu_Ts, boolean_T rtu_isRisingEdge, real_T + rtu_timeDelay, boolean_T *rty_y, rtDW_MATLABFunction_FacComputer_f_T *localDW); + static void FacComputer_MATLABFunction_g(const boolean_T rtu_u[19], real32_T *rty_y); +}; + +#endif + diff --git a/src/fbw/src/model/FacComputer_data.cpp b/src/fbw/src/model/FacComputer_data.cpp new file mode 100644 index 000000000000..b063d80bb020 --- /dev/null +++ b/src/fbw/src/model/FacComputer_data.cpp @@ -0,0 +1,1986 @@ +#include "FacComputer.h" + +FacComputer::Parameters_FacComputer_T FacComputer::FacComputer_P{ + + { + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + + { + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + }, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false + }, + + + { + 0.0, + 0.0, + false, + false, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + }, + + + { + 0.0, + 0.0, + 0.0 + }, + + + { + 0.0, + 0.0, + 0.0 + }, + + + { + false, + false, + false, + false, + false, + false + }, + + 0.2, + + 0.5, + + 0.5, + + 0.5, + + 0.5, + + 0.4, + + 20.0, + + 0.5, + + 1.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + -20.0, + + 20.0, + + 16.0, + + 18.0, + + 19.0, + + 20.0, + + 12.0, + + 13.0, + + 14.0, + + 11.0, + + 12.0, + + 23.0, + + 23.0, + + 17.0, + + 18.0, + + 19.0, + + 20.0, + + 21.0, + + 26.0, + + 26.0, + + 26.0, + + 17.0, + + 18.0, + + 19.0, + + 20.0, + + 21.0, + + 26.0, + + 26.0, + + 26.0, + + 20.0, + + 26.0, + + 20.0, + + 26.0, + + 0.05, + + -1.0, + + -1.0, + + -1.0, + + -1.0, + + -1.0, + + -1.0, + + 1.0, + + 10.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + SignStatusMatrix::NormalOperation, + + SignStatusMatrix::FailureWarning, + + SignStatusMatrix::NoComputedData, + + 35.0F, + + 80.0F, + + 80.0F, + + false, + + false, + + true, + + true, + + + { + { + { + 0.0, + 0.0, + 0.0 + }, + + { + false, + false, + false, + false, + false + }, + + { + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false + }, + + { + 0.0, + 0.0, + 0.0 + }, + + { + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + } + } + }, + + { + 0.0, + 0.0, + 0.0 + }, + + { + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + + { + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + }, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false + }, + + { + 0.0, + 0.0, + false, + false, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + false, + false, + false, + false, + false, + false + }, + + { + 0.0, + 0.0, + 0.0 + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + } + }, + + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + 200.0, + + 0.0, + + + { 25.0, 25.0, 14.5, 8.8, 4.8, 3.4, 3.4 }, + + + { 100.0, 160.0, 200.0, 240.0, 320.0, 380.0, 400.0 }, + + 30.0, + + 0.0, + + 0.0, + + 0.066666666666666666, + + 1.0, + + 1.0, + + 5.0, + + -5.0, + + 0.033333333333333333, + + 5.0, + + -5.0, + + 0.0302, + + 1000.0, + + 1.0, + + 57.295779513082323, + + + { 7.6, 7.6, 6.03, 6.03, 12.5, 12.5, 12.5, 12.5, 12.7, 12.7, 12.7, 12.7, 13.1, 13.1, 13.1, 13.1, 12.1, 12.1, 12.1, 12.1, + 11.8, 11.8, 11.8, 11.8 }, + + + { 0.0, 0.5, 0.9, 1.0 }, + + + { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 }, + + + { -1.84, -1.84, -2.18, -4.72, -4.27, -6.94 }, + + + { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 }, + + + { 8.7, 8.7, 6.4, 6.4, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 14.2, 14.2, 14.2, 14.2, 13.1, 13.1, 13.1, 13.1, + 13.0, 13.0, 13.0, 13.0 }, + + + { 0.0, 0.5, 0.9, 1.0 }, + + + { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 }, + + + { 6.5, 6.5, 4.6, 4.6, 11.7, 11.7, 11.7, 11.7, 11.7, 11.7, 11.7, 11.7, 11.9, 11.9, 11.9, 11.9, 11.0, 11.0, 11.0, 11.0, + 10.6, 10.6, 10.6, 10.6 }, + + + { 0.0, 0.5, 0.9, 1.0 }, + + + { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 }, + + + { 6.5, 6.5, 4.6, 4.6, 11.7, 11.7, 11.7, 11.7, 11.7, 11.7, 11.7, 11.7, 11.9, 11.9, 11.9, 11.9, 11.0, 11.0, 11.0, 11.0, + 10.6, 10.6, 10.6, 10.6 }, + + + { 0.0, 0.5, 0.9, 1.0 }, + + + { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 }, + + 0.0, + + -1.0, + + 1.5, + + -1.5, + + 0.0, + + 2.0, + + -1.0, + + 20.0, + + -20.0, + + 40.0, + + -1.0, + + 0.0, + + 0.0, + + 0.0, + + 0.1101F, + + 0.0F, + + 0.0F, + + 0.0F, + + 0.0F, + + 0.0F, + + 0.0F, + + 0.0F, + + 0.0F, + + 0.0F, + + 0.0F, + + 0.0F, + + 0.0F, + + 0.0F, + + 0.0F, + + 0.0F, + + + { 3U, 5U }, + + + { 3U, 5U }, + + + { 3U, 5U }, + + + { 3U, 5U }, + + false, + + false, + + false, + + false, + + false, + + false, + + false, + + false, + + + { false, true, false, false, true, true, false, false, true, false, true, true, false, false, false, false }, + + true, + + false, + + false, + + false, + + false, + + false, + + false +}; diff --git a/src/fbw/src/model/FacComputer_private.h b/src/fbw/src/model/FacComputer_private.h new file mode 100644 index 000000000000..794bda4217a9 --- /dev/null +++ b/src/fbw/src/model/FacComputer_private.h @@ -0,0 +1,5 @@ +#ifndef RTW_HEADER_FacComputer_private_h_ +#define RTW_HEADER_FacComputer_private_h_ +#include "rtwtypes.h" +#endif + diff --git a/src/fbw/src/model/FacComputer_types.h b/src/fbw/src/model/FacComputer_types.h new file mode 100644 index 000000000000..a62c80a3b7cc --- /dev/null +++ b/src/fbw/src/model/FacComputer_types.h @@ -0,0 +1,500 @@ +#ifndef RTW_HEADER_FacComputer_types_h_ +#define RTW_HEADER_FacComputer_types_h_ +#include "rtwtypes.h" + +#ifndef DEFINED_TYPEDEF_FOR_SignStatusMatrix_ +#define DEFINED_TYPEDEF_FOR_SignStatusMatrix_ + +enum class SignStatusMatrix + : int32_T { + FailureWarning = 0, + NoComputedData, + FunctionalTest, + NormalOperation +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_time_ +#define DEFINED_TYPEDEF_FOR_base_time_ + +struct base_time +{ + real_T dt; + real_T simulation_time; + real_T monotonic_time; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sim_data_ +#define DEFINED_TYPEDEF_FOR_base_sim_data_ + +struct base_sim_data +{ + boolean_T slew_on; + boolean_T pause_on; + boolean_T tracking_mode_on_override; + boolean_T tailstrike_protection_on; + boolean_T computer_running; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fac_discrete_inputs_ +#define DEFINED_TYPEDEF_FOR_base_fac_discrete_inputs_ + +struct base_fac_discrete_inputs +{ + boolean_T ap_own_engaged; + boolean_T ap_opp_engaged; + boolean_T yaw_damper_opp_engaged; + boolean_T rudder_trim_opp_engaged; + boolean_T rudder_travel_lim_opp_engaged; + boolean_T elac_1_healthy; + boolean_T elac_2_healthy; + boolean_T engine_1_stopped; + boolean_T engine_2_stopped; + boolean_T rudder_trim_switch_left; + boolean_T rudder_trim_switch_right; + boolean_T rudder_trim_reset_button; + boolean_T fac_engaged_from_switch; + boolean_T fac_opp_healthy; + boolean_T is_unit_1; + boolean_T rudder_trim_actuator_healthy; + boolean_T rudder_travel_lim_actuator_healthy; + boolean_T slats_extended; + boolean_T nose_gear_pressed; + boolean_T ir_3_switch; + boolean_T adr_3_switch; + boolean_T yaw_damper_has_hyd_press; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fac_analog_inputs_ +#define DEFINED_TYPEDEF_FOR_base_fac_analog_inputs_ + +struct base_fac_analog_inputs +{ + real_T yaw_damper_position_deg; + real_T rudder_trim_position_deg; + real_T rudder_travel_lim_position_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_arinc_429_ +#define DEFINED_TYPEDEF_FOR_base_arinc_429_ + +struct base_arinc_429 +{ + uint32_T SSM; + real32_T Data; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fac_bus_ +#define DEFINED_TYPEDEF_FOR_base_fac_bus_ + +struct base_fac_bus +{ + base_arinc_429 discrete_word_1; + base_arinc_429 gamma_a_deg; + base_arinc_429 gamma_t_deg; + base_arinc_429 total_weight_lbs; + base_arinc_429 center_of_gravity_pos_percent; + base_arinc_429 sideslip_target_deg; + base_arinc_429 fac_slat_angle_deg; + base_arinc_429 fac_flap_angle; + base_arinc_429 discrete_word_2; + base_arinc_429 rudder_travel_limit_command_deg; + base_arinc_429 delta_r_yaw_damper_deg; + base_arinc_429 estimated_sideslip_deg; + base_arinc_429 v_alpha_lim_kn; + base_arinc_429 v_ls_kn; + base_arinc_429 v_stall_kn; + base_arinc_429 v_alpha_prot_kn; + base_arinc_429 v_stall_warn_kn; + base_arinc_429 speed_trend_kn; + base_arinc_429 v_3_kn; + base_arinc_429 v_4_kn; + base_arinc_429 v_man_kn; + base_arinc_429 v_max_kn; + base_arinc_429 v_fe_next_kn; + base_arinc_429 discrete_word_3; + base_arinc_429 discrete_word_4; + base_arinc_429 discrete_word_5; + base_arinc_429 delta_r_rudder_trim_deg; + base_arinc_429 rudder_trim_pos_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_adr_bus_ +#define DEFINED_TYPEDEF_FOR_base_adr_bus_ + +struct base_adr_bus +{ + base_arinc_429 altitude_standard_ft; + base_arinc_429 altitude_corrected_ft; + base_arinc_429 mach; + base_arinc_429 airspeed_computed_kn; + base_arinc_429 airspeed_true_kn; + base_arinc_429 vertical_speed_ft_min; + base_arinc_429 aoa_corrected_deg; + base_arinc_429 corrected_average_static_pressure; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_ir_bus_ +#define DEFINED_TYPEDEF_FOR_base_ir_bus_ + +struct base_ir_bus +{ + base_arinc_429 discrete_word_1; + base_arinc_429 latitude_deg; + base_arinc_429 longitude_deg; + base_arinc_429 ground_speed_kn; + base_arinc_429 track_angle_true_deg; + base_arinc_429 heading_true_deg; + base_arinc_429 wind_speed_kn; + base_arinc_429 wind_direction_true_deg; + base_arinc_429 track_angle_magnetic_deg; + base_arinc_429 heading_magnetic_deg; + base_arinc_429 drift_angle_deg; + base_arinc_429 flight_path_angle_deg; + base_arinc_429 flight_path_accel_g; + base_arinc_429 pitch_angle_deg; + base_arinc_429 roll_angle_deg; + base_arinc_429 body_pitch_rate_deg_s; + base_arinc_429 body_roll_rate_deg_s; + base_arinc_429 body_yaw_rate_deg_s; + base_arinc_429 body_long_accel_g; + base_arinc_429 body_lat_accel_g; + base_arinc_429 body_normal_accel_g; + base_arinc_429 track_angle_rate_deg_s; + base_arinc_429 pitch_att_rate_deg_s; + base_arinc_429 roll_att_rate_deg_s; + base_arinc_429 inertial_alt_ft; + base_arinc_429 along_track_horiz_acc_g; + base_arinc_429 cross_track_horiz_acc_g; + base_arinc_429 vertical_accel_g; + base_arinc_429 inertial_vertical_speed_ft_s; + base_arinc_429 north_south_velocity_kn; + base_arinc_429 east_west_velocity_kn; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fmgc_b_bus_ +#define DEFINED_TYPEDEF_FOR_base_fmgc_b_bus_ + +struct base_fmgc_b_bus +{ + base_arinc_429 fac_weight_lbs; + base_arinc_429 fm_weight_lbs; + base_arinc_429 fac_cg_percent; + base_arinc_429 fm_cg_percent; + base_arinc_429 fg_radio_height_ft; + base_arinc_429 discrete_word_4; + base_arinc_429 ats_discrete_word; + base_arinc_429 discrete_word_3; + base_arinc_429 discrete_word_1; + base_arinc_429 discrete_word_2; + base_arinc_429 approach_spd_target_kn; + base_arinc_429 delta_p_ail_cmd_deg; + base_arinc_429 delta_p_splr_cmd_deg; + base_arinc_429 delta_r_cmd_deg; + base_arinc_429 delta_nose_wheel_cmd_deg; + base_arinc_429 delta_q_cmd_deg; + base_arinc_429 n1_left_percent; + base_arinc_429 n1_right_percent; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sfcc_bus_ +#define DEFINED_TYPEDEF_FOR_base_sfcc_bus_ + +struct base_sfcc_bus +{ + base_arinc_429 slat_flap_component_status_word; + base_arinc_429 slat_flap_system_status_word; + base_arinc_429 slat_flap_actual_position_word; + base_arinc_429 slat_actual_position_deg; + base_arinc_429 flap_actual_position_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_lgciu_bus_ +#define DEFINED_TYPEDEF_FOR_base_lgciu_bus_ + +struct base_lgciu_bus +{ + base_arinc_429 discrete_word_1; + base_arinc_429 discrete_word_2; + base_arinc_429 discrete_word_3; + base_arinc_429 discrete_word_4; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_elac_out_bus_ +#define DEFINED_TYPEDEF_FOR_base_elac_out_bus_ + +struct base_elac_out_bus +{ + base_arinc_429 left_aileron_position_deg; + base_arinc_429 right_aileron_position_deg; + base_arinc_429 left_elevator_position_deg; + base_arinc_429 right_elevator_position_deg; + base_arinc_429 ths_position_deg; + base_arinc_429 left_sidestick_pitch_command_deg; + base_arinc_429 right_sidestick_pitch_command_deg; + base_arinc_429 left_sidestick_roll_command_deg; + base_arinc_429 right_sidestick_roll_command_deg; + base_arinc_429 rudder_pedal_position_deg; + base_arinc_429 aileron_command_deg; + base_arinc_429 roll_spoiler_command_deg; + base_arinc_429 yaw_damper_command_deg; + base_arinc_429 elevator_double_pressurization_command_deg; + base_arinc_429 discrete_status_word_1; + base_arinc_429 discrete_status_word_2; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fac_bus_inputs_ +#define DEFINED_TYPEDEF_FOR_base_fac_bus_inputs_ + +struct base_fac_bus_inputs +{ + base_fac_bus fac_opp_bus; + base_adr_bus adr_own_bus; + base_adr_bus adr_opp_bus; + base_adr_bus adr_3_bus; + base_ir_bus ir_own_bus; + base_ir_bus ir_opp_bus; + base_ir_bus ir_3_bus; + base_fmgc_b_bus fmgc_own_bus; + base_fmgc_b_bus fmgc_opp_bus; + base_sfcc_bus sfcc_own_bus; + base_lgciu_bus lgciu_own_bus; + base_elac_out_bus elac_1_bus; + base_elac_out_bus elac_2_bus; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_fac_inputs_ +#define DEFINED_TYPEDEF_FOR_fac_inputs_ + +struct fac_inputs +{ + base_time time; + base_sim_data sim_data; + base_fac_discrete_inputs discrete_inputs; + base_fac_analog_inputs analog_inputs; + base_fac_bus_inputs bus_inputs; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fac_laws_outputs_ +#define DEFINED_TYPEDEF_FOR_base_fac_laws_outputs_ + +struct base_fac_laws_outputs +{ + real_T yaw_damper_command_deg; + real_T rudder_trim_command_deg; + real_T rudder_travel_lim_command_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fac_adr_computation_data_ +#define DEFINED_TYPEDEF_FOR_base_fac_adr_computation_data_ + +struct base_fac_adr_computation_data +{ + real_T V_ias_kn; + real_T V_tas_kn; + real_T mach; + real_T alpha_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fac_ir_computation_data_ +#define DEFINED_TYPEDEF_FOR_base_fac_ir_computation_data_ + +struct base_fac_ir_computation_data +{ + real_T theta_deg; + real_T phi_deg; + real_T q_deg_s; + real_T r_deg_s; + real_T n_x_g; + real_T n_y_g; + real_T n_z_g; + real_T theta_dot_deg_s; + real_T phi_dot_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fac_logic_outputs_ +#define DEFINED_TYPEDEF_FOR_base_fac_logic_outputs_ + +struct base_fac_logic_outputs +{ + boolean_T lgciu_own_valid; + boolean_T all_lgciu_lost; + boolean_T left_main_gear_pressed; + boolean_T right_main_gear_pressed; + boolean_T main_gear_out; + boolean_T on_ground; + boolean_T tracking_mode_on; + boolean_T double_self_detected_adr_failure; + boolean_T double_self_detected_ir_failure; + boolean_T double_not_self_detected_adr_failure; + boolean_T double_not_self_detected_ir_failure; + base_fac_adr_computation_data adr_computation_data; + base_fac_ir_computation_data ir_computation_data; + boolean_T yaw_damper_engaged; + boolean_T yaw_damper_can_engage; + boolean_T yaw_damper_has_priority; + boolean_T rudder_trim_engaged; + boolean_T rudder_trim_can_engage; + boolean_T rudder_trim_has_priority; + boolean_T rudder_travel_lim_engaged; + boolean_T rudder_travel_lim_can_engage; + boolean_T rudder_travel_lim_has_priority; + boolean_T speed_scale_lost; + boolean_T speed_scale_visible; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fac_flight_envelope_outputs_ +#define DEFINED_TYPEDEF_FOR_base_fac_flight_envelope_outputs_ + +struct base_fac_flight_envelope_outputs +{ + real_T estimated_beta_deg; + real_T beta_target_deg; + boolean_T beta_target_visible; + boolean_T alpha_floor_condition; + real_T alpha_filtered_deg; + real_T computed_weight_lbs; + real_T computed_cg_percent; + real_T v_alpha_max_kn; + real_T v_alpha_prot_kn; + real_T v_stall_warn_kn; + real_T v_ls_kn; + real_T v_stall_kn; + real_T v_3_kn; + real_T v_4_kn; + real_T v_man_kn; + real_T v_max_kn; + real_T v_c_trend_kn; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fac_discrete_outputs_ +#define DEFINED_TYPEDEF_FOR_base_fac_discrete_outputs_ + +struct base_fac_discrete_outputs +{ + boolean_T fac_healthy; + boolean_T yaw_damper_engaged; + boolean_T rudder_trim_engaged; + boolean_T rudder_travel_lim_engaged; + boolean_T rudder_travel_lim_emergency_reset; + boolean_T yaw_damper_avail_for_norm_law; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fac_analog_outputs_ +#define DEFINED_TYPEDEF_FOR_base_fac_analog_outputs_ + +struct base_fac_analog_outputs +{ + real_T yaw_damper_order_deg; + real_T rudder_trim_order_deg; + real_T rudder_travel_limit_order_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_fac_outputs_ +#define DEFINED_TYPEDEF_FOR_fac_outputs_ + +struct fac_outputs +{ + fac_inputs data; + base_fac_laws_outputs laws; + base_fac_logic_outputs logic; + base_fac_flight_envelope_outputs flight_envelope; + base_fac_discrete_outputs discrete_outputs; + base_fac_analog_outputs analog_outputs; + base_fac_bus bus_outputs; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_struct_2OohiAWrazWy5wDS5iisgF_ +#define DEFINED_TYPEDEF_FOR_struct_2OohiAWrazWy5wDS5iisgF_ + +struct struct_2OohiAWrazWy5wDS5iisgF +{ + real_T SSM; + real_T Data; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_struct_fAEsrEZhvvruiP1ICEwvRC_ +#define DEFINED_TYPEDEF_FOR_struct_fAEsrEZhvvruiP1ICEwvRC_ + +struct struct_fAEsrEZhvvruiP1ICEwvRC +{ + struct_2OohiAWrazWy5wDS5iisgF discrete_word_1; + struct_2OohiAWrazWy5wDS5iisgF gamma_a_deg; + struct_2OohiAWrazWy5wDS5iisgF gamma_t_deg; + struct_2OohiAWrazWy5wDS5iisgF total_weight_lbs; + struct_2OohiAWrazWy5wDS5iisgF center_of_gravity_pos_percent; + struct_2OohiAWrazWy5wDS5iisgF sideslip_target_deg; + struct_2OohiAWrazWy5wDS5iisgF fac_slat_angle_deg; + struct_2OohiAWrazWy5wDS5iisgF fac_flap_angle; + struct_2OohiAWrazWy5wDS5iisgF discrete_word_2; + struct_2OohiAWrazWy5wDS5iisgF rudder_travel_limit_command_deg; + struct_2OohiAWrazWy5wDS5iisgF delta_r_yaw_damper_deg; + struct_2OohiAWrazWy5wDS5iisgF estimated_sideslip_deg; + struct_2OohiAWrazWy5wDS5iisgF v_alpha_lim_kn; + struct_2OohiAWrazWy5wDS5iisgF v_ls_kn; + struct_2OohiAWrazWy5wDS5iisgF v_stall_kn; + struct_2OohiAWrazWy5wDS5iisgF v_alpha_prot_kn; + struct_2OohiAWrazWy5wDS5iisgF v_stall_warn_kn; + struct_2OohiAWrazWy5wDS5iisgF speed_trend_kn; + struct_2OohiAWrazWy5wDS5iisgF v_3_kn; + struct_2OohiAWrazWy5wDS5iisgF v_4_kn; + struct_2OohiAWrazWy5wDS5iisgF v_man_kn; + struct_2OohiAWrazWy5wDS5iisgF v_max_kn; + struct_2OohiAWrazWy5wDS5iisgF v_fe_next_kn; + struct_2OohiAWrazWy5wDS5iisgF discrete_word_3; + struct_2OohiAWrazWy5wDS5iisgF discrete_word_4; + struct_2OohiAWrazWy5wDS5iisgF discrete_word_5; + struct_2OohiAWrazWy5wDS5iisgF delta_r_rudder_trim_deg; + struct_2OohiAWrazWy5wDS5iisgF rudder_trim_pos_deg; +}; + +#endif +#endif + diff --git a/src/fbw/src/model/FlyByWire.cpp b/src/fbw/src/model/FlyByWire.cpp deleted file mode 100644 index 12af3d80a006..000000000000 --- a/src/fbw/src/model/FlyByWire.cpp +++ /dev/null @@ -1,2072 +0,0 @@ -#include "FlyByWire.h" -#include "FlyByWire_private.h" -#include "look1_binlxpw.h" -#include "look2_binlxpw.h" - -const uint8_T FlyByWire_IN_InAir{ 1U }; - -const uint8_T FlyByWire_IN_OnGround{ 2U }; - -const uint8_T FlyByWire_IN_Flying{ 1U }; - -const uint8_T FlyByWire_IN_Landed{ 2U }; - -const uint8_T FlyByWire_IN_Landing100ft{ 3U }; - -const uint8_T FlyByWire_IN_Takeoff100ft{ 4U }; - -const uint8_T FlyByWire_IN_Flare_Reduce_Theta_c{ 1U }; - -const uint8_T FlyByWire_IN_Flare_Set_Rate{ 2U }; - -const uint8_T FlyByWire_IN_Flare_Store_Theta_c_deg{ 3U }; - -const uint8_T FlyByWire_IN_Flight_High{ 4U }; - -const uint8_T FlyByWire_IN_Flight_Low{ 5U }; - -const uint8_T FlyByWire_IN_Ground{ 6U }; - -const uint8_T FlyByWire_IN_frozen{ 1U }; - -const uint8_T FlyByWire_IN_running{ 2U }; - -const uint8_T FlyByWire_IN_Flight{ 1U }; - -const uint8_T FlyByWire_IN_FlightToGroundTransition{ 2U }; - -const uint8_T FlyByWire_IN_Ground_a{ 3U }; - -const uint8_T FlyByWire_IN_automatic{ 1U }; - -const uint8_T FlyByWire_IN_manual{ 2U }; - -const uint8_T FlyByWire_IN_reset{ 3U }; - -const uint8_T FlyByWire_IN_tracking{ 4U }; - -const uint8_T FlyByWire_IN_flight_clean{ 1U }; - -const uint8_T FlyByWire_IN_flight_flaps{ 2U }; - -const uint8_T FlyByWire_IN_ground{ 3U }; - -const uint8_T FlyByWire_IN_OFF{ 1U }; - -const uint8_T FlyByWire_IN_ON{ 2U }; - -const uint8_T FlyByWire_IN_FlightMode{ 1U }; - -const uint8_T FlyByWire_IN_GroundMode{ 2U }; - -void FlyByWireModelClass::FlyByWire_GetIASforMach4(real_T rtu_m, real_T rtu_m_t, real_T rtu_v, real_T *rty_v_t) -{ - *rty_v_t = rtu_v * rtu_m_t / rtu_m; -} - -void FlyByWireModelClass::FlyByWire_LagFilter(real_T rtu_U, real_T rtu_C1, real_T rtu_dt, real_T *rty_Y, - rtDW_LagFilter_FlyByWire_T *localDW) -{ - real_T ca; - real_T denom_tmp; - if ((!localDW->pY_not_empty) || (!localDW->pU_not_empty)) { - localDW->pU = rtu_U; - localDW->pU_not_empty = true; - localDW->pY = rtu_U; - localDW->pY_not_empty = true; - } - - denom_tmp = rtu_dt * rtu_C1; - ca = denom_tmp / (denom_tmp + 2.0); - *rty_Y = (2.0 - denom_tmp) / (denom_tmp + 2.0) * localDW->pY + (rtu_U * ca + localDW->pU * ca); - localDW->pY = *rty_Y; - localDW->pU = rtu_U; -} - -void FlyByWireModelClass::FlyByWire_WashoutFilter(real_T rtu_U, real_T rtu_C1, real_T rtu_dt, real_T *rty_Y, - rtDW_WashoutFilter_FlyByWire_T *localDW) -{ - real_T ca; - real_T denom_tmp; - if ((!localDW->pY_not_empty) || (!localDW->pU_not_empty)) { - localDW->pU = rtu_U; - localDW->pU_not_empty = true; - localDW->pY = rtu_U; - localDW->pY_not_empty = true; - } - - denom_tmp = rtu_dt * rtu_C1; - ca = 2.0 / (denom_tmp + 2.0); - *rty_Y = (2.0 - denom_tmp) / (denom_tmp + 2.0) * localDW->pY + (rtu_U * ca - localDW->pU * ca); - localDW->pY = *rty_Y; - localDW->pU = rtu_U; -} - -void FlyByWireModelClass::FlyByWire_RateLimiter(real_T rtu_u, real_T rtu_up, real_T rtu_lo, real_T rtu_Ts, real_T - rtu_init, real_T *rty_Y, rtDW_RateLimiter_FlyByWire_T *localDW) -{ - if (!localDW->pY_not_empty) { - localDW->pY = rtu_init; - localDW->pY_not_empty = true; - } - - localDW->pY += std::fmax(std::fmin(rtu_u - localDW->pY, std::abs(rtu_up) * rtu_Ts), -std::abs(rtu_lo) * rtu_Ts); - *rty_Y = localDW->pY; -} - -void FlyByWireModelClass::FlyByWire_VoterAttitudeProtection(real_T rtu_input, real_T rtu_input_o, real_T rtu_input_c, - real_T *rty_vote) -{ - real_T rtb_TmpSignalConversionAtSFunctionInport1[3]; - int32_T rtu_input_0; - rtb_TmpSignalConversionAtSFunctionInport1[0] = rtu_input; - rtb_TmpSignalConversionAtSFunctionInport1[1] = rtu_input_o; - rtb_TmpSignalConversionAtSFunctionInport1[2] = rtu_input_c; - if (rtu_input < rtu_input_o) { - if (rtu_input_o < rtu_input_c) { - rtu_input_0 = 1; - } else if (rtu_input < rtu_input_c) { - rtu_input_0 = 2; - } else { - rtu_input_0 = 0; - } - } else if (rtu_input < rtu_input_c) { - rtu_input_0 = 0; - } else if (rtu_input_o < rtu_input_c) { - rtu_input_0 = 2; - } else { - rtu_input_0 = 1; - } - - *rty_vote = rtb_TmpSignalConversionAtSFunctionInport1[rtu_input_0]; -} - -void FlyByWireModelClass::FlyByWire_eta_trim_limit_lofreeze(real_T rtu_eta_trim, real_T rtu_trigger, real_T *rty_y, - rtDW_eta_trim_limit_lofreeze_FlyByWire_T *localDW) -{ - if ((rtu_trigger == 0.0) || (!localDW->frozen_eta_trim_not_empty)) { - localDW->frozen_eta_trim = rtu_eta_trim; - localDW->frozen_eta_trim_not_empty = true; - } - - *rty_y = localDW->frozen_eta_trim; -} - -void FlyByWireModelClass::FlyByWire_ConvertToEuler(real_T rtu_Theta, real_T rtu_Phi, real_T rtu_q, real_T rtu_r, real_T - rtu_p, real_T *rty_qk, real_T *rty_rk, real_T *rty_pk) -{ - real_T tmp[9]; - real_T result[3]; - real_T Phi; - real_T Theta; - real_T result_tmp; - real_T result_tmp_0; - Theta = 0.017453292519943295 * rtu_Theta; - Phi = 0.017453292519943295 * rtu_Phi; - result_tmp = std::tan(Theta); - result_tmp_0 = std::sin(Phi); - Phi = std::cos(Phi); - tmp[0] = 1.0; - tmp[3] = result_tmp_0 * result_tmp; - tmp[6] = Phi * result_tmp; - tmp[1] = 0.0; - tmp[4] = Phi; - tmp[7] = -result_tmp_0; - tmp[2] = 0.0; - Theta = 1.0 / std::cos(Theta); - tmp[5] = Theta * result_tmp_0; - tmp[8] = Theta * Phi; - for (int32_T i{0}; i < 3; i++) { - result[i] = (tmp[i + 3] * rtu_q + tmp[i] * rtu_p) + tmp[i + 6] * rtu_r; - } - - *rty_qk = result[1]; - *rty_rk = result[2]; - *rty_pk = result[0]; -} - -void FlyByWireModelClass::FlyByWire_CalculateV_alpha_max(real_T rtu_v_ias, real_T rtu_alpha, real_T rtu_alpha_0, real_T - rtu_alpha_target, real_T *rty_V_alpha_target) -{ - *rty_V_alpha_target = std::sqrt(std::abs(rtu_alpha - rtu_alpha_0) / (rtu_alpha_target - rtu_alpha_0)) * rtu_v_ias; -} - -void FlyByWireModelClass::step() -{ - static const int16_T b[4]{ 0, 120, 320, 400 }; - - static const int16_T b_0[4]{ 0, 120, 150, 380 }; - - static const int8_T c[4]{ 1, 2, 3, 3 }; - - static const int8_T c_0[4]{ -15, -15, -15, -2 }; - - real_T Vtas; - real_T omega_0; - real_T rtb_BusAssignment_a_sim_data_zeta_trim_deg; - real_T rtb_BusAssignment_a_sim_input_delta_zeta_pos; - real_T rtb_BusAssignment_cs_pitch_data_computed_delta_eta_deg; - real_T rtb_BusAssignment_cs_pitch_data_computed_eta_trim_deg_limit_lo; - real_T rtb_BusAssignment_cs_pitch_data_computed_eta_trim_deg_limit_up; - real_T rtb_BusAssignment_sim_data_qk_deg_s; - real_T rtb_BusAssignment_sim_input_delta_eta_pos; - real_T rtb_BusAssignment_sim_input_delta_xi_pos; - real_T rtb_Delay_jj; - real_T rtb_Divide_k; - real_T rtb_Divide_m1; - real_T rtb_Divide_ni; - real_T rtb_Divide_o; - real_T rtb_Gain; - real_T rtb_GainPhi; - real_T rtb_GainTheta; - real_T rtb_Gain_ce; - real_T rtb_Gain_ei; - real_T rtb_Gain_f2y; - real_T rtb_Gain_gh; - real_T rtb_Gain_gt; - real_T rtb_Gain_i0; - real_T rtb_Gain_ju; - real_T rtb_Gain_ne; - real_T rtb_Gain_ok; - real_T rtb_Gainpk; - real_T rtb_Gainqk; - real_T rtb_Limitereta; - real_T rtb_LimiteriH; - real_T rtb_LimiteriH_n; - real_T rtb_Limiterxi; - real_T rtb_Limiterxi1; - real_T rtb_Limiterxi2; - real_T rtb_Loaddemand2; - real_T rtb_ManualSwitch; - real_T rtb_Min3; - real_T rtb_Min5; - real_T rtb_Minup; - real_T rtb_Saturation3; - real_T rtb_Saturation_kd; - real_T rtb_Sum1_h; - real_T rtb_Sum1_jv; - real_T rtb_Y; - real_T rtb_Y_c; - real_T rtb_Y_cu; - real_T rtb_Y_d; - real_T rtb_Y_f; - real_T rtb_Y_fp; - real_T rtb_Y_g; - real_T rtb_Y_h; - real_T rtb_Y_jv; - real_T rtb_Y_jz; - real_T rtb_Y_k1; - real_T rtb_Y_lc; - real_T rtb_Y_mc5; - real_T rtb_Y_n0; - real_T rtb_Y_nl; - real_T rtb_Y_p; - real_T rtb_Y_p2; - real_T rtb_alpha_err_gain; - real_T rtb_eta_trim_deg_rate_limit_lo_deg_s; - real_T rtb_eta_trim_deg_rate_limit_up_deg_s; - real_T rtb_eta_trim_deg_reset_deg; - real_T rtb_nz_limit_up_g; - real_T rtb_pk; - real_T rtb_uDLookupTable_g; - real_T rtb_v_target; - real_T rtb_y_l; - real_T u0; - real_T u0_0; - real_T y; - int32_T high_i; - int32_T low_i; - int32_T low_ip1; - int32_T mid_i; - int32_T rtb_alpha_floor_inhib; - int32_T rtb_ap_special_disc; - int32_T rtb_in_flare; - int32_T rtb_nz_limit_lo_g; - int32_T rtb_on_ground; - boolean_T guard1{ false }; - - boolean_T rtb_eta_trim_deg_reset; - boolean_T rtb_eta_trim_deg_should_freeze; - boolean_T rtb_eta_trim_deg_should_write; - FlyByWire_DWork.Delay_DSTATE += FlyByWire_U.in.time.dt; - rtb_GainTheta = FlyByWire_P.GainTheta_Gain * FlyByWire_U.in.data.Theta_deg; - rtb_GainPhi = FlyByWire_P.GainPhi_Gain * FlyByWire_U.in.data.Phi_deg; - rtb_Gainqk = FlyByWire_P.Gain_Gain_n * FlyByWire_U.in.data.q_rad_s * FlyByWire_P.Gainqk_Gain; - rtb_Gain = FlyByWire_P.Gain_Gain_l * FlyByWire_U.in.data.r_rad_s; - rtb_Gainpk = FlyByWire_P.Gain_Gain_a * FlyByWire_U.in.data.p_rad_s * FlyByWire_P.Gainpk_Gain; - FlyByWire_ConvertToEuler(rtb_GainTheta, rtb_GainPhi, rtb_Gainqk, rtb_Gain, rtb_Gainpk, &rtb_Y_jv, - &FlyByWire_Y.out.sim.data.rk_deg_s, &rtb_pk); - FlyByWire_ConvertToEuler(rtb_GainTheta, rtb_GainPhi, FlyByWire_P.Gainqk1_Gain * (FlyByWire_P.Gain_Gain_e * - FlyByWire_U.in.data.q_dot_rad_s2), FlyByWire_P.Gain_Gain_aw * FlyByWire_U.in.data.r_dot_rad_s2, - FlyByWire_P.Gainpk1_Gain * (FlyByWire_P.Gain_Gain_nm * FlyByWire_U.in.data.p_dot_rad_s2), &rtb_Y_fp, &rtb_Y_nl, - &rtb_Y_p); - rtb_Minup = FlyByWire_P.Gainpk4_Gain * FlyByWire_U.in.data.eta_pos; - rtb_Delay_jj = FlyByWire_P.Gainpk2_Gain * FlyByWire_U.in.data.eta_trim_deg; - u0 = FlyByWire_P.Gain1_Gain * FlyByWire_U.in.data.gear_animation_pos_1 - FlyByWire_P.Constant_Value_g; - if (u0 > FlyByWire_P.Saturation1_UpperSat) { - u0 = FlyByWire_P.Saturation1_UpperSat; - } else if (u0 < FlyByWire_P.Saturation1_LowerSat) { - u0 = FlyByWire_P.Saturation1_LowerSat; - } - - u0_0 = FlyByWire_P.Gain2_Gain_a * FlyByWire_U.in.data.gear_animation_pos_2 - FlyByWire_P.Constant_Value_g; - if (u0_0 > FlyByWire_P.Saturation2_UpperSat) { - u0_0 = FlyByWire_P.Saturation2_UpperSat; - } else if (u0_0 < FlyByWire_P.Saturation2_LowerSat) { - u0_0 = FlyByWire_P.Saturation2_LowerSat; - } - - rtb_uDLookupTable_g = FlyByWire_P.Gaineta_Gain * FlyByWire_U.in.input.delta_eta_pos; - rtb_Limiterxi = FlyByWire_P.Gainxi_Gain * FlyByWire_U.in.input.delta_xi_pos; - rtb_BusAssignment_sim_data_qk_deg_s = rtb_Y_jv; - rtb_BusAssignment_sim_input_delta_eta_pos = rtb_uDLookupTable_g; - rtb_BusAssignment_sim_input_delta_xi_pos = rtb_Limiterxi; - FlyByWire_LagFilter(FlyByWire_U.in.data.alpha_deg, FlyByWire_P.LagFilter_C1, FlyByWire_U.in.time.dt, &rtb_Y_jv, - &FlyByWire_DWork.sf_LagFilter_n); - FlyByWire_RateLimiter(look2_binlxpw(FlyByWire_U.in.data.V_mach, FlyByWire_U.in.data.flaps_handle_index, - FlyByWire_P.alphamax_bp01Data, FlyByWire_P.alphamax_bp02Data, FlyByWire_P.alphamax_tableData, - FlyByWire_P.alphamax_maxIndex, 4U), FlyByWire_P.RateLimiterVariableTs2_up, FlyByWire_P.RateLimiterVariableTs2_lo, - FlyByWire_U.in.time.dt, FlyByWire_P.RateLimiterVariableTs2_InitialCondition, &rtb_Y_k1, - &FlyByWire_DWork.sf_RateLimiter_pr); - FlyByWire_RateLimiter(look1_binlxpw(FlyByWire_U.in.data.flaps_handle_index, FlyByWire_P.alpha0_bp01Data, - FlyByWire_P.alpha0_tableData, 5U), FlyByWire_P.RateLimiterVariableTs3_up, FlyByWire_P.RateLimiterVariableTs3_lo, - FlyByWire_U.in.time.dt, FlyByWire_P.RateLimiterVariableTs3_InitialCondition, &rtb_Y, - &FlyByWire_DWork.sf_RateLimiter_b5); - FlyByWire_CalculateV_alpha_max(FlyByWire_U.in.data.V_ias_kn, rtb_Y_jv, rtb_Y, rtb_Y_k1, &rtb_Limiterxi); - if (!FlyByWire_DWork.eventTime_not_empty) { - FlyByWire_DWork.eventTime = FlyByWire_U.in.time.simulation_time; - FlyByWire_DWork.eventTime_not_empty = true; - } - - if ((FlyByWire_P.fbw_output_MATLABStruct.sim.data_computed.on_ground != 0.0) || (FlyByWire_DWork.eventTime == 0.0)) { - FlyByWire_DWork.eventTime = FlyByWire_U.in.time.simulation_time; - } - - FlyByWire_RateLimiter(look2_binlxpw(FlyByWire_U.in.data.V_mach, FlyByWire_U.in.data.flaps_handle_index, - FlyByWire_P.alphaprotection_bp01Data, FlyByWire_P.alphaprotection_bp02Data, FlyByWire_P.alphaprotection_tableData, - FlyByWire_P.alphaprotection_maxIndex, 4U), FlyByWire_P.RateLimiterVariableTs_up, - FlyByWire_P.RateLimiterVariableTs_lo, FlyByWire_U.in.time.dt, - FlyByWire_P.RateLimiterVariableTs_InitialCondition, &rtb_Y_c, &FlyByWire_DWork.sf_RateLimiter_e); - if (FlyByWire_U.in.time.simulation_time - FlyByWire_DWork.eventTime <= FlyByWire_P.CompareToConstant_const) { - rtb_Y_c = rtb_Y_k1; - } - - FlyByWire_CalculateV_alpha_max(FlyByWire_U.in.data.V_ias_kn, rtb_Y_jv, rtb_Y, rtb_Y_c, &rtb_uDLookupTable_g); - FlyByWire_RateLimiter(look2_binlxpw(FlyByWire_U.in.data.V_mach, FlyByWire_U.in.data.flaps_handle_index, - FlyByWire_P.alphafloor_bp01Data, FlyByWire_P.alphafloor_bp02Data, FlyByWire_P.alphafloor_tableData, - FlyByWire_P.alphafloor_maxIndex, 4U), FlyByWire_P.RateLimiterVariableTs1_up, FlyByWire_P.RateLimiterVariableTs1_lo, - FlyByWire_U.in.time.dt, FlyByWire_P.RateLimiterVariableTs1_InitialCondition, &rtb_Y_h, - &FlyByWire_DWork.sf_RateLimiter_bu); - FlyByWire_Y.out.sim.data.rk_dot_deg_s2 = rtb_Y_nl; - FlyByWire_Y.out.sim.data.pk_dot_deg_s2 = rtb_Y_p; - FlyByWire_Y.out.sim.data_speeds_aoa.v_alpha_max_kn = rtb_Limiterxi; - FlyByWire_Y.out.sim.data_speeds_aoa.v_alpha_prot_kn = rtb_uDLookupTable_g; - if (FlyByWire_DWork.is_active_c1_FlyByWire == 0U) { - FlyByWire_DWork.is_active_c1_FlyByWire = 1U; - FlyByWire_DWork.is_c1_FlyByWire = FlyByWire_IN_OnGround; - rtb_on_ground = 1; - } else if (FlyByWire_DWork.is_c1_FlyByWire == 1) { - if ((u0 > 0.1) || (u0_0 > 0.1)) { - FlyByWire_DWork.is_c1_FlyByWire = FlyByWire_IN_OnGround; - rtb_on_ground = 1; - } else { - rtb_on_ground = 0; - } - } else if ((u0 == 0.0) && (u0_0 == 0.0)) { - FlyByWire_DWork.is_c1_FlyByWire = FlyByWire_IN_InAir; - rtb_on_ground = 0; - } else { - rtb_on_ground = 1; - } - - if (!FlyByWire_DWork.resetEventTime_not_empty) { - FlyByWire_DWork.resetEventTime = FlyByWire_U.in.time.simulation_time; - FlyByWire_DWork.resetEventTime_not_empty = true; - } - - if ((rtb_BusAssignment_sim_input_delta_eta_pos >= -0.03125) || (rtb_Y_jv >= rtb_Y_k1) || - (FlyByWire_DWork.resetEventTime == 0.0)) { - FlyByWire_DWork.resetEventTime = FlyByWire_U.in.time.simulation_time; - } - - if ((rtb_on_ground == 0) && (FlyByWire_U.in.data.autopilot_custom_on == 0.0) && (rtb_Y_jv > rtb_Y_c) && - (FlyByWire_DWork.Delay_DSTATE > 10.0)) { - FlyByWire_DWork.sProtActive_c = 1.0; - } - - if ((FlyByWire_U.in.time.simulation_time - FlyByWire_DWork.resetEventTime > 0.5) || - (rtb_BusAssignment_sim_input_delta_eta_pos < -0.5) || ((FlyByWire_U.in.data.H_radio_ft < 200.0) && - (rtb_BusAssignment_sim_input_delta_eta_pos < 0.5) && (rtb_Y_jv < rtb_Y_c - 2.0)) || (rtb_on_ground != 0)) { - FlyByWire_DWork.sProtActive_c = 0.0; - } - - rtb_Y = FlyByWire_P.DiscreteDerivativeVariableTs_Gain * FlyByWire_U.in.data.V_ias_kn; - FlyByWire_LagFilter((rtb_Y - FlyByWire_DWork.Delay_DSTATE_d) / FlyByWire_U.in.time.dt, FlyByWire_P.LagFilter_C1_a, - FlyByWire_U.in.time.dt, &rtb_Y_p, &FlyByWire_DWork.sf_LagFilter); - if (FlyByWire_DWork.is_active_c15_FlyByWire == 0U) { - FlyByWire_DWork.is_active_c15_FlyByWire = 1U; - FlyByWire_DWork.is_c15_FlyByWire = FlyByWire_IN_Landed; - rtb_alpha_floor_inhib = 1; - rtb_ap_special_disc = 0; - } else { - switch (FlyByWire_DWork.is_c15_FlyByWire) { - case FlyByWire_IN_Flying: - if (FlyByWire_U.in.data.H_radio_ft < 100.0) { - FlyByWire_DWork.is_c15_FlyByWire = FlyByWire_IN_Landing100ft; - rtb_alpha_floor_inhib = 1; - rtb_ap_special_disc = 1; - } else if (rtb_on_ground != 0) { - FlyByWire_DWork.is_c15_FlyByWire = FlyByWire_IN_Landed; - rtb_alpha_floor_inhib = 1; - rtb_ap_special_disc = 0; - } else { - rtb_alpha_floor_inhib = 0; - rtb_ap_special_disc = 0; - } - break; - - case FlyByWire_IN_Landed: - if (rtb_on_ground == 0) { - FlyByWire_DWork.is_c15_FlyByWire = FlyByWire_IN_Takeoff100ft; - rtb_alpha_floor_inhib = 0; - rtb_ap_special_disc = 0; - } else { - rtb_alpha_floor_inhib = 1; - rtb_ap_special_disc = 0; - } - break; - - case FlyByWire_IN_Landing100ft: - if (FlyByWire_U.in.data.H_radio_ft > 100.0) { - FlyByWire_DWork.is_c15_FlyByWire = FlyByWire_IN_Flying; - rtb_alpha_floor_inhib = 0; - rtb_ap_special_disc = 0; - } else if (rtb_on_ground != 0) { - FlyByWire_DWork.is_c15_FlyByWire = FlyByWire_IN_Landed; - rtb_alpha_floor_inhib = 1; - rtb_ap_special_disc = 0; - } else { - rtb_alpha_floor_inhib = 1; - rtb_ap_special_disc = 1; - } - break; - - default: - if (rtb_on_ground != 0) { - FlyByWire_DWork.is_c15_FlyByWire = FlyByWire_IN_Landed; - rtb_alpha_floor_inhib = 1; - rtb_ap_special_disc = 0; - } else if (FlyByWire_U.in.data.H_radio_ft > 100.0) { - FlyByWire_DWork.is_c15_FlyByWire = FlyByWire_IN_Flying; - rtb_alpha_floor_inhib = 0; - rtb_ap_special_disc = 0; - } else { - rtb_alpha_floor_inhib = 0; - rtb_ap_special_disc = 0; - } - break; - } - } - - guard1 = false; - if ((rtb_alpha_floor_inhib == 0) && (FlyByWire_U.in.data.V_mach < 0.6)) { - if (FlyByWire_U.in.data.flaps_handle_index >= 4.0) { - rtb_nz_limit_lo_g = -3; - } else { - rtb_nz_limit_lo_g = 0; - } - - if ((rtb_Y_jv > rtb_Y_h + std::fmin(std::fmax(rtb_Y_p, static_cast(rtb_nz_limit_lo_g)), 0.0)) && - (FlyByWire_DWork.Delay_DSTATE > 10.0)) { - FlyByWire_DWork.sAlphaFloor = 1.0; - } else { - guard1 = true; - } - } else { - guard1 = true; - } - - if (guard1) { - if ((rtb_alpha_floor_inhib != 0) || (FlyByWire_DWork.sProtActive_c == 0.0)) { - FlyByWire_DWork.sAlphaFloor = 0.0; - } - } - - FlyByWire_GetIASforMach4(FlyByWire_U.in.data.V_mach, FlyByWire_P.Constant6_Value, FlyByWire_U.in.data.V_ias_kn, - &rtb_Y_nl); - rtb_Min3 = std::fmin(FlyByWire_P.Constant5_Value, rtb_Y_nl); - rtb_Limiterxi = rtb_GainTheta - std::cos(FlyByWire_P.Gain1_Gain_c * rtb_GainPhi) * FlyByWire_U.in.data.alpha_deg; - if ((FlyByWire_U.in.data.autopilot_custom_on == 0.0) && (FlyByWire_U.in.data.V_ias_kn > std::fmin(look1_binlxpw - (rtb_Limiterxi, FlyByWire_P.uDLookupTable1_bp01Data, FlyByWire_P.uDLookupTable1_tableData, 3U), - FlyByWire_U.in.data.V_ias_kn / FlyByWire_U.in.data.V_mach * look1_binlxpw(rtb_Limiterxi, - FlyByWire_P.uDLookupTable2_bp01Data, FlyByWire_P.uDLookupTable2_tableData, 3U)))) { - FlyByWire_DWork.sProtActive = 1.0; - } - - if ((FlyByWire_U.in.data.V_ias_kn < rtb_Min3) || (FlyByWire_U.in.data.autopilot_custom_on != 0.0)) { - FlyByWire_DWork.sProtActive = 0.0; - } - - if (!FlyByWire_DWork.eventTime_not_empty_c) { - FlyByWire_DWork.eventTime_b = FlyByWire_U.in.time.simulation_time; - FlyByWire_DWork.eventTime_not_empty_c = true; - } - - if (FlyByWire_U.in.data.V_ias_kn <= std::fmin(365.0, FlyByWire_U.in.data.V_ias_kn / FlyByWire_U.in.data.V_mach * - (look1_binlxpw(rtb_Limiterxi, FlyByWire_P.uDLookupTable_bp01Data, FlyByWire_P.uDLookupTable_tableData, 3U) + 0.01))) - { - FlyByWire_DWork.eventTime_b = FlyByWire_U.in.time.simulation_time; - } else if (FlyByWire_DWork.eventTime_b == 0.0) { - FlyByWire_DWork.eventTime_b = FlyByWire_U.in.time.simulation_time; - } - - FlyByWire_GetIASforMach4(FlyByWire_U.in.data.V_mach, FlyByWire_P.Constant8_Value, FlyByWire_U.in.data.V_ias_kn, - &rtb_Y_p); - rtb_Min5 = std::fmin(FlyByWire_P.Constant7_Value, rtb_Y_p); - FlyByWire_Y.out.sim.data.qk_dot_deg_s2 = rtb_Y_fp; - FlyByWire_Y.out.sim.data.eta_deg = rtb_Minup; - FlyByWire_Y.out.sim.data.eta_trim_deg = rtb_Delay_jj; - rtb_BusAssignment_a_sim_data_zeta_trim_deg = FlyByWire_P.Gainpk3_Gain * FlyByWire_U.in.data.zeta_trim_pos; - FlyByWire_Y.out.sim.data_speeds_aoa.alpha_filtered_deg = rtb_Y_jv; - rtb_BusAssignment_a_sim_input_delta_zeta_pos = FlyByWire_P.Gainxi1_Gain * FlyByWire_U.in.input.delta_zeta_pos; - rtb_alpha_floor_inhib = ((FlyByWire_U.in.data.autopilot_master_on != 0.0) || (FlyByWire_U.in.data.slew_on != 0.0) || - (FlyByWire_U.in.data.pause_on != 0.0) || (FlyByWire_U.in.data.tracking_mode_on_override != 0.0)); - FlyByWire_Y.out.sim.data_computed.protection_ap_disc = (((rtb_on_ground == 0) && (((rtb_ap_special_disc != 0) && - (rtb_Y_jv > rtb_Y_k1)) || (rtb_Y_jv > rtb_Y_c + 0.25))) || (FlyByWire_U.in.time.simulation_time - - FlyByWire_DWork.eventTime_b > 3.0) || (FlyByWire_DWork.sProtActive != 0.0) || (FlyByWire_DWork.sProtActive_c != 0.0)); - FlyByWire_eta_trim_limit_lofreeze(rtb_Delay_jj, FlyByWire_DWork.sProtActive_c, &rtb_Y_p, - &FlyByWire_DWork.sf_eta_trim_limit_lofreeze); - if (FlyByWire_DWork.sProtActive_c > FlyByWire_P.Switch_Threshold) { - rtb_BusAssignment_cs_pitch_data_computed_eta_trim_deg_limit_lo = rtb_Y_p; - } else { - rtb_BusAssignment_cs_pitch_data_computed_eta_trim_deg_limit_lo = FlyByWire_P.Constant3_Value; - } - - FlyByWire_eta_trim_limit_lofreeze(rtb_Delay_jj, FlyByWire_DWork.sProtActive, &rtb_Y_p, - &FlyByWire_DWork.sf_eta_trim_limit_upfreeze); - if (FlyByWire_DWork.sProtActive > FlyByWire_P.Switch1_Threshold_k) { - rtb_BusAssignment_cs_pitch_data_computed_eta_trim_deg_limit_up = rtb_Y_p; - } else { - rtb_BusAssignment_cs_pitch_data_computed_eta_trim_deg_limit_up = FlyByWire_P.Constant2_Value; - } - - if (FlyByWire_DWork.is_active_c3_FlyByWire == 0U) { - FlyByWire_DWork.is_active_c3_FlyByWire = 1U; - FlyByWire_DWork.is_c3_FlyByWire = FlyByWire_IN_Ground_a; - FlyByWire_B.in_flight = 0.0; - } else { - switch (FlyByWire_DWork.is_c3_FlyByWire) { - case FlyByWire_IN_Flight: - if ((rtb_on_ground == 1) && (rtb_GainTheta < 2.5)) { - FlyByWire_DWork.on_ground_time = FlyByWire_U.in.time.simulation_time; - FlyByWire_DWork.is_c3_FlyByWire = FlyByWire_IN_FlightToGroundTransition; - } else { - FlyByWire_B.in_flight = 1.0; - } - break; - - case FlyByWire_IN_FlightToGroundTransition: - if (FlyByWire_U.in.time.simulation_time - FlyByWire_DWork.on_ground_time >= 5.0) { - FlyByWire_DWork.is_c3_FlyByWire = FlyByWire_IN_Ground_a; - FlyByWire_B.in_flight = 0.0; - } else if ((rtb_on_ground == 0) || (rtb_GainTheta >= 2.5)) { - FlyByWire_DWork.on_ground_time = 0.0; - FlyByWire_DWork.is_c3_FlyByWire = FlyByWire_IN_Flight; - FlyByWire_B.in_flight = 1.0; - } - break; - - default: - if (((rtb_on_ground == 0) && (rtb_GainTheta > 8.0)) || (FlyByWire_U.in.data.H_radio_ft > 400.0)) { - FlyByWire_DWork.on_ground_time = 0.0; - FlyByWire_DWork.is_c3_FlyByWire = FlyByWire_IN_Flight; - FlyByWire_B.in_flight = 1.0; - } else { - FlyByWire_B.in_flight = 0.0; - } - break; - } - } - - if (FlyByWire_B.in_flight > FlyByWire_P.Saturation_UpperSat_er) { - rtb_Y_jv = FlyByWire_P.Saturation_UpperSat_er; - } else if (FlyByWire_B.in_flight < FlyByWire_P.Saturation_LowerSat_a) { - rtb_Y_jv = FlyByWire_P.Saturation_LowerSat_a; - } else { - rtb_Y_jv = FlyByWire_B.in_flight; - } - - FlyByWire_RateLimiter(rtb_Y_jv, FlyByWire_P.RateLimiterVariableTs_up_d, FlyByWire_P.RateLimiterVariableTs_lo_c, - FlyByWire_U.in.time.dt, FlyByWire_P.RateLimiterVariableTs_InitialCondition_d, &rtb_Y_f, - &FlyByWire_DWork.sf_RateLimiter_b); - if (FlyByWire_DWork.is_active_c6_FlyByWire == 0U) { - FlyByWire_DWork.is_active_c6_FlyByWire = 1U; - FlyByWire_DWork.is_c6_FlyByWire = FlyByWire_IN_OFF; - rtb_ap_special_disc = 0; - } else if (FlyByWire_DWork.is_c6_FlyByWire == 1) { - if ((rtb_Y_f < 1.0) && (FlyByWire_U.in.data.V_tas_kn > 70.0) && ((FlyByWire_U.in.data.thrust_lever_1_pos >= 35.0) || - (FlyByWire_U.in.data.thrust_lever_2_pos >= 35.0))) { - FlyByWire_DWork.is_c6_FlyByWire = FlyByWire_IN_ON; - rtb_ap_special_disc = 1; - } else { - rtb_ap_special_disc = 0; - } - } else if ((rtb_Y_f == 1.0) || (FlyByWire_U.in.data.H_radio_ft > 400.0) || ((FlyByWire_U.in.data.V_tas_kn < 70.0) && - ((FlyByWire_U.in.data.thrust_lever_1_pos < 35.0) || (FlyByWire_U.in.data.thrust_lever_2_pos < 35.0)))) { - FlyByWire_DWork.is_c6_FlyByWire = FlyByWire_IN_OFF; - rtb_ap_special_disc = 0; - } else { - rtb_ap_special_disc = 1; - } - - FlyByWire_LagFilter(rtb_GainTheta, FlyByWire_P.LagFilter_C1_n, FlyByWire_U.in.time.dt, &rtb_Y_p, - &FlyByWire_DWork.sf_LagFilter_lo); - if (FlyByWire_P.ManualSwitch_CurrentSetting == 1) { - rtb_ManualSwitch = FlyByWire_P.Constant1_Value_f; - } else { - rtb_ManualSwitch = FlyByWire_P.Constant_Value_jz; - } - - if (FlyByWire_DWork.is_active_c2_FlyByWire == 0U) { - FlyByWire_DWork.is_active_c2_FlyByWire = 1U; - FlyByWire_DWork.is_c2_FlyByWire = FlyByWire_IN_Ground; - rtb_in_flare = 0; - FlyByWire_B.flare_Theta_c_deg = rtb_Y_p; - FlyByWire_B.flare_Theta_c_rate_deg_s = -3.0; - } else { - switch (FlyByWire_DWork.is_c2_FlyByWire) { - case FlyByWire_IN_Flare_Reduce_Theta_c: - if (FlyByWire_B.in_flight == 0.0) { - FlyByWire_DWork.is_c2_FlyByWire = FlyByWire_IN_Ground; - rtb_in_flare = 0; - FlyByWire_B.flare_Theta_c_deg = rtb_Y_p; - FlyByWire_B.flare_Theta_c_rate_deg_s = -3.0; - } else if ((FlyByWire_U.in.data.H_radio_ft > 50.0) && (rtb_ManualSwitch == 0.0)) { - FlyByWire_DWork.is_c2_FlyByWire = FlyByWire_IN_Flight_Low; - rtb_in_flare = 0; - FlyByWire_B.flare_Theta_c_deg = rtb_Y_p; - FlyByWire_B.flare_Theta_c_rate_deg_s = -3.0; - } else { - rtb_in_flare = 1; - FlyByWire_B.flare_Theta_c_deg = -2.0; - } - break; - - case FlyByWire_IN_Flare_Set_Rate: - if (FlyByWire_P.ManualSwitch1_CurrentSetting == 1) { - rtb_Y_jv = FlyByWire_P.Constant1_Value_f; - } else { - rtb_Y_jv = FlyByWire_P.Constant_Value_jz; - } - - if ((FlyByWire_U.in.data.H_radio_ft <= 30.0) || (rtb_Y_jv == 1.0)) { - FlyByWire_DWork.is_c2_FlyByWire = FlyByWire_IN_Flare_Reduce_Theta_c; - rtb_in_flare = 1; - FlyByWire_B.flare_Theta_c_deg = -2.0; - } else if ((FlyByWire_U.in.data.H_radio_ft > 50.0) && (rtb_ManualSwitch == 0.0)) { - FlyByWire_DWork.is_c2_FlyByWire = FlyByWire_IN_Flight_Low; - rtb_in_flare = 0; - FlyByWire_B.flare_Theta_c_deg = rtb_Y_p; - FlyByWire_B.flare_Theta_c_rate_deg_s = -3.0; - } else { - rtb_in_flare = 1; - } - break; - - case FlyByWire_IN_Flare_Store_Theta_c_deg: - if ((FlyByWire_U.in.data.H_radio_ft > 50.0) && (rtb_ManualSwitch == 0.0)) { - FlyByWire_DWork.is_c2_FlyByWire = FlyByWire_IN_Flight_Low; - rtb_in_flare = 0; - FlyByWire_B.flare_Theta_c_deg = rtb_Y_p; - FlyByWire_B.flare_Theta_c_rate_deg_s = -3.0; - } else { - FlyByWire_B.flare_Theta_c_rate_deg_s = -(rtb_Y_p + 2.0) / 8.0; - FlyByWire_DWork.is_c2_FlyByWire = FlyByWire_IN_Flare_Set_Rate; - rtb_in_flare = 1; - } - break; - - case FlyByWire_IN_Flight_High: - if ((FlyByWire_U.in.data.H_radio_ft <= 50.0) || (rtb_ManualSwitch == 1.0)) { - FlyByWire_B.flare_Theta_c_deg = rtb_Y_p; - FlyByWire_DWork.is_c2_FlyByWire = FlyByWire_IN_Flare_Store_Theta_c_deg; - rtb_in_flare = 1; - } else { - rtb_in_flare = 0; - FlyByWire_B.flare_Theta_c_deg = rtb_Y_p; - FlyByWire_B.flare_Theta_c_rate_deg_s = -3.0; - } - break; - - case FlyByWire_IN_Flight_Low: - if (FlyByWire_U.in.data.H_radio_ft > 50.0) { - FlyByWire_DWork.is_c2_FlyByWire = FlyByWire_IN_Flight_High; - rtb_in_flare = 0; - FlyByWire_B.flare_Theta_c_deg = rtb_Y_p; - FlyByWire_B.flare_Theta_c_rate_deg_s = -3.0; - } else { - rtb_in_flare = 0; - FlyByWire_B.flare_Theta_c_deg = rtb_Y_p; - FlyByWire_B.flare_Theta_c_rate_deg_s = -3.0; - } - break; - - default: - if (FlyByWire_B.in_flight == 1.0) { - FlyByWire_DWork.is_c2_FlyByWire = FlyByWire_IN_Flight_Low; - rtb_in_flare = 0; - FlyByWire_B.flare_Theta_c_deg = rtb_Y_p; - FlyByWire_B.flare_Theta_c_rate_deg_s = -3.0; - } else { - rtb_in_flare = 0; - FlyByWire_B.flare_Theta_c_deg = rtb_Y_p; - FlyByWire_B.flare_Theta_c_rate_deg_s = -3.0; - } - break; - } - } - - if (rtb_ap_special_disc > FlyByWire_P.Saturation1_UpperSat_f) { - rtb_nz_limit_up_g = FlyByWire_P.Saturation1_UpperSat_f; - } else if (rtb_ap_special_disc < FlyByWire_P.Saturation1_LowerSat_p) { - rtb_nz_limit_up_g = FlyByWire_P.Saturation1_LowerSat_p; - } else { - rtb_nz_limit_up_g = rtb_ap_special_disc; - } - - FlyByWire_RateLimiter(rtb_nz_limit_up_g, FlyByWire_P.RateLimiterVariableTs1_up_n, - FlyByWire_P.RateLimiterVariableTs1_lo_c, FlyByWire_U.in.time.dt, - FlyByWire_P.RateLimiterVariableTs1_InitialCondition_h, &rtb_ManualSwitch, - &FlyByWire_DWork.sf_RateLimiter_g); - if (FlyByWire_DWork.is_active_c7_FlyByWire == 0U) { - FlyByWire_DWork.is_active_c7_FlyByWire = 1U; - FlyByWire_DWork.is_c7_FlyByWire = FlyByWire_IN_ground; - rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; - rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; - rtb_nz_limit_up_g = 2.0; - rtb_nz_limit_lo_g = 0; - } else { - switch (FlyByWire_DWork.is_c7_FlyByWire) { - case FlyByWire_IN_flight_clean: - if (FlyByWire_U.in.data.flaps_handle_index != 0.0) { - FlyByWire_DWork.is_c7_FlyByWire = FlyByWire_IN_flight_flaps; - rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; - rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; - rtb_nz_limit_up_g = 2.0; - rtb_nz_limit_lo_g = 0; - } else if ((FlyByWire_B.in_flight == 0.0) && (FlyByWire_U.in.data.flaps_handle_index == 0.0)) { - FlyByWire_DWork.is_c7_FlyByWire = FlyByWire_IN_ground; - rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; - rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; - rtb_nz_limit_up_g = 2.0; - rtb_nz_limit_lo_g = 0; - } else { - rtb_eta_trim_deg_rate_limit_up_deg_s = 0.3; - rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.3; - rtb_nz_limit_up_g = 2.5; - rtb_nz_limit_lo_g = -1; - } - break; - - case FlyByWire_IN_flight_flaps: - if (FlyByWire_U.in.data.flaps_handle_index == 0.0) { - FlyByWire_DWork.is_c7_FlyByWire = FlyByWire_IN_flight_clean; - rtb_eta_trim_deg_rate_limit_up_deg_s = 0.3; - rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.3; - rtb_nz_limit_up_g = 2.5; - rtb_nz_limit_lo_g = -1; - } else if (FlyByWire_B.in_flight == 0.0) { - FlyByWire_DWork.is_c7_FlyByWire = FlyByWire_IN_ground; - rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; - rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; - rtb_nz_limit_up_g = 2.0; - rtb_nz_limit_lo_g = 0; - } else { - rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; - rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; - rtb_nz_limit_up_g = 2.0; - rtb_nz_limit_lo_g = 0; - } - break; - - default: - if ((FlyByWire_B.in_flight != 0.0) && (FlyByWire_U.in.data.flaps_handle_index == 0.0)) { - FlyByWire_DWork.is_c7_FlyByWire = FlyByWire_IN_flight_clean; - rtb_eta_trim_deg_rate_limit_up_deg_s = 0.3; - rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.3; - rtb_nz_limit_up_g = 2.5; - rtb_nz_limit_lo_g = -1; - } else if ((FlyByWire_B.in_flight != 0.0) && (FlyByWire_U.in.data.flaps_handle_index != 0.0)) { - FlyByWire_DWork.is_c7_FlyByWire = FlyByWire_IN_flight_flaps; - rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; - rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; - rtb_nz_limit_up_g = 2.0; - rtb_nz_limit_lo_g = 0; - } else { - rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; - rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; - rtb_nz_limit_up_g = 2.0; - rtb_nz_limit_lo_g = 0; - } - break; - } - } - - FlyByWire_RateLimiter(rtb_nz_limit_up_g, FlyByWire_P.RateLimiterVariableTs2_up_f, - FlyByWire_P.RateLimiterVariableTs2_lo_m, FlyByWire_U.in.time.dt, - FlyByWire_P.RateLimiterVariableTs2_InitialCondition_b, &rtb_Y_jz, - &FlyByWire_DWork.sf_RateLimiter_mr); - FlyByWire_RateLimiter(static_cast(rtb_nz_limit_lo_g), FlyByWire_P.RateLimiterVariableTs3_up_c, - FlyByWire_P.RateLimiterVariableTs3_lo_l, FlyByWire_U.in.time.dt, - FlyByWire_P.RateLimiterVariableTs3_InitialCondition_b, &rtb_Y_lc, - &FlyByWire_DWork.sf_RateLimiter_a); - if (FlyByWire_DWork.is_active_c9_FlyByWire == 0U) { - FlyByWire_DWork.is_active_c9_FlyByWire = 1U; - FlyByWire_DWork.is_c9_FlyByWire = FlyByWire_IN_running; - rtb_eta_trim_deg_should_freeze = false; - } else if (FlyByWire_DWork.is_c9_FlyByWire == 1) { - if ((rtb_in_flare == 0) && (FlyByWire_U.in.data.nz_g < 1.25) && (FlyByWire_U.in.data.nz_g > 0.5) && (std::abs - (rtb_GainPhi) <= 30.0)) { - FlyByWire_DWork.is_c9_FlyByWire = FlyByWire_IN_running; - rtb_eta_trim_deg_should_freeze = false; - } else { - rtb_eta_trim_deg_should_freeze = true; - } - } else if ((rtb_in_flare != 0) || (FlyByWire_U.in.data.nz_g >= 1.25) || (FlyByWire_U.in.data.nz_g <= 0.5) || (std::abs - (rtb_GainPhi) > 30.0)) { - FlyByWire_DWork.is_c9_FlyByWire = FlyByWire_IN_frozen; - rtb_eta_trim_deg_should_freeze = true; - } else { - rtb_eta_trim_deg_should_freeze = false; - } - - if (FlyByWire_DWork.is_active_c8_FlyByWire == 0U) { - FlyByWire_DWork.is_active_c8_FlyByWire = 1U; - FlyByWire_DWork.is_c8_FlyByWire = FlyByWire_IN_manual; - rtb_eta_trim_deg_reset = true; - rtb_eta_trim_deg_reset_deg = rtb_Delay_jj; - rtb_eta_trim_deg_should_write = false; - } else { - switch (FlyByWire_DWork.is_c8_FlyByWire) { - case FlyByWire_IN_automatic: - if (FlyByWire_B.in_flight == 0.0) { - FlyByWire_DWork.is_c8_FlyByWire = FlyByWire_IN_reset; - rtb_eta_trim_deg_reset = true; - rtb_eta_trim_deg_reset_deg = 0.0; - rtb_eta_trim_deg_should_write = true; - } else if (rtb_alpha_floor_inhib != 0) { - FlyByWire_DWork.is_c8_FlyByWire = FlyByWire_IN_tracking; - rtb_eta_trim_deg_reset = true; - rtb_eta_trim_deg_reset_deg = rtb_Delay_jj; - rtb_eta_trim_deg_should_write = false; - } else { - rtb_eta_trim_deg_reset = false; - rtb_eta_trim_deg_reset_deg = rtb_Delay_jj; - rtb_eta_trim_deg_should_write = true; - } - break; - - case FlyByWire_IN_manual: - if (FlyByWire_B.in_flight != 0.0) { - FlyByWire_DWork.is_c8_FlyByWire = FlyByWire_IN_automatic; - rtb_eta_trim_deg_reset = false; - rtb_eta_trim_deg_reset_deg = rtb_Delay_jj; - rtb_eta_trim_deg_should_write = true; - } else { - rtb_eta_trim_deg_reset = true; - rtb_eta_trim_deg_reset_deg = rtb_Delay_jj; - rtb_eta_trim_deg_should_write = false; - } - break; - - case FlyByWire_IN_reset: - if ((FlyByWire_B.in_flight == 0.0) && (rtb_Delay_jj == 0.0)) { - FlyByWire_DWork.is_c8_FlyByWire = FlyByWire_IN_manual; - rtb_eta_trim_deg_reset = true; - rtb_eta_trim_deg_reset_deg = rtb_Delay_jj; - rtb_eta_trim_deg_should_write = false; - } else { - rtb_eta_trim_deg_reset = true; - rtb_eta_trim_deg_reset_deg = 0.0; - rtb_eta_trim_deg_should_write = true; - } - break; - - default: - if (rtb_alpha_floor_inhib == 0) { - FlyByWire_DWork.is_c8_FlyByWire = FlyByWire_IN_automatic; - rtb_eta_trim_deg_reset = false; - rtb_eta_trim_deg_reset_deg = rtb_Delay_jj; - rtb_eta_trim_deg_should_write = true; - } else { - rtb_eta_trim_deg_reset = true; - rtb_eta_trim_deg_reset_deg = rtb_Delay_jj; - rtb_eta_trim_deg_should_write = false; - } - break; - } - } - - FlyByWire_DWork.Delay_DSTATE_dq += std::fmax(std::fmin(FlyByWire_B.flare_Theta_c_deg - FlyByWire_DWork.Delay_DSTATE_dq, - std::abs(FlyByWire_B.flare_Theta_c_rate_deg_s) * FlyByWire_U.in.time.dt), FlyByWire_U.in.time.dt * - FlyByWire_B.flare_Theta_c_rate_deg_s); - rtb_LimiteriH_n = rtb_Minup; - rtb_BusAssignment_cs_pitch_data_computed_delta_eta_deg = FlyByWire_P.Gain_Gain_d * - rtb_BusAssignment_sim_input_delta_eta_pos; - FlyByWire_Y.out.pitch.data_computed.flare_Theta_deg = rtb_Y_p; - FlyByWire_RateLimiter(rtb_BusAssignment_sim_input_delta_eta_pos, FlyByWire_P.RateLimiterVariableTs_up_dl, - FlyByWire_P.RateLimiterVariableTs_lo_d, FlyByWire_U.in.time.dt, - FlyByWire_P.RateLimiterVariableTs_InitialCondition_n, &rtb_nz_limit_up_g, - &FlyByWire_DWork.sf_RateLimiter_l); - if (rtb_nz_limit_up_g > FlyByWire_P.Saturation3_UpperSat) { - rtb_Gain_ne = FlyByWire_P.Saturation3_UpperSat; - } else if (rtb_nz_limit_up_g < FlyByWire_P.Saturation3_LowerSat) { - rtb_Gain_ne = FlyByWire_P.Saturation3_LowerSat; - } else { - rtb_Gain_ne = rtb_nz_limit_up_g; - } - - rtb_Limiterxi = look1_binlxpw(static_cast(FlyByWire_U.in.data.tailstrike_protection_on) * look2_binlxpw - (rtb_GainTheta, FlyByWire_U.in.data.H_radio_ft, FlyByWire_P.uDLookupTable_bp01Data_l, - FlyByWire_P.uDLookupTable_bp02Data, FlyByWire_P.uDLookupTable_tableData_d, FlyByWire_P.uDLookupTable_maxIndex, 5U) * - rtb_Gain_ne + rtb_nz_limit_up_g, FlyByWire_P.PitchRateDemand_bp01Data, FlyByWire_P.PitchRateDemand_tableData, 2U); - rtb_nz_limit_up_g = FlyByWire_P.DiscreteDerivativeVariableTs_Gain_c * rtb_Limiterxi; - rtb_Limiterxi1 = rtb_BusAssignment_sim_data_qk_deg_s - rtb_Limiterxi; - rtb_Gain_ne = FlyByWire_P.Gain1_Gain_i * rtb_Limiterxi1 * FlyByWire_P.DiscreteDerivativeVariableTs_Gain_b; - FlyByWire_LagFilter(rtb_BusAssignment_sim_data_qk_deg_s + FlyByWire_P.Gain5_Gain * rtb_Y_fp, - FlyByWire_P.LagFilter_C1_i, FlyByWire_U.in.time.dt, &rtb_Y_p, &FlyByWire_DWork.sf_LagFilter_p); - rtb_uDLookupTable_g = (((((rtb_Gain_ne - FlyByWire_DWork.Delay_DSTATE_dd) / FlyByWire_U.in.time.dt + - FlyByWire_P.Gain_Gain_h * rtb_Limiterxi1) * FlyByWire_P.Gain1_Gain_a + (rtb_nz_limit_up_g - - FlyByWire_DWork.Delay_DSTATE_f) / FlyByWire_U.in.time.dt * FlyByWire_P.Gain3_Gain) + (rtb_Y_p - rtb_Limiterxi) * - FlyByWire_P.Gain4_Gain) + FlyByWire_P.Gain6_Gain_f * rtb_Y_fp) * (FlyByWire_P.Constant2_Value_l - rtb_Y_f) * - FlyByWire_P.DiscreteTimeIntegratorVariableTs_Gain * FlyByWire_U.in.time.dt; - FlyByWire_DWork.icLoad = (((rtb_BusAssignment_sim_input_delta_eta_pos <= FlyByWire_P.Constant_Value_j) && - (rtb_on_ground != 0)) || (rtb_ManualSwitch == 0.0) || (rtb_alpha_floor_inhib != 0) || FlyByWire_DWork.icLoad); - if (FlyByWire_DWork.icLoad) { - FlyByWire_DWork.Delay_DSTATE_e = FlyByWire_P.Constant_Value_h - rtb_uDLookupTable_g; - } - - FlyByWire_DWork.Delay_DSTATE_e += rtb_uDLookupTable_g; - if (FlyByWire_DWork.Delay_DSTATE_e > FlyByWire_P.DiscreteTimeIntegratorVariableTs_UpperLimit) { - FlyByWire_DWork.Delay_DSTATE_e = FlyByWire_P.DiscreteTimeIntegratorVariableTs_UpperLimit; - } else if (FlyByWire_DWork.Delay_DSTATE_e < FlyByWire_P.DiscreteTimeIntegratorVariableTs_LowerLimit) { - FlyByWire_DWork.Delay_DSTATE_e = FlyByWire_P.DiscreteTimeIntegratorVariableTs_LowerLimit; - } - - FlyByWire_Y.out.pitch.law_rotation.qk_c_deg_s = rtb_Limiterxi; - if (rtb_on_ground > FlyByWire_P.Switch_Threshold_h) { - if (rtb_BusAssignment_cs_pitch_data_computed_delta_eta_deg > FlyByWire_P.Saturation_UpperSat_g) { - rtb_uDLookupTable_g = FlyByWire_P.Saturation_UpperSat_g; - } else if (rtb_BusAssignment_cs_pitch_data_computed_delta_eta_deg < FlyByWire_P.Saturation_LowerSat_p) { - rtb_uDLookupTable_g = FlyByWire_P.Saturation_LowerSat_p; - } else { - rtb_uDLookupTable_g = rtb_BusAssignment_cs_pitch_data_computed_delta_eta_deg; - } - } else { - rtb_uDLookupTable_g = FlyByWire_P.Constant1_Value_i; - } - - rtb_LimiteriH = FlyByWire_DWork.Delay_DSTATE_e + rtb_uDLookupTable_g; - rtb_uDLookupTable_g = std::cos(FlyByWire_P.Gain1_Gain_p * rtb_GainTheta); - rtb_Limiterxi = rtb_uDLookupTable_g / std::cos(FlyByWire_P.Gain1_Gain_pa * rtb_GainPhi); - rtb_Gain_i0 = FlyByWire_U.in.data.nz_g - rtb_Limiterxi; - rtb_Limiterxi1 = FlyByWire_P.Gain1_Gain_j * rtb_BusAssignment_sim_data_qk_deg_s * (FlyByWire_P.Gain_Gain_dc * - FlyByWire_P.Vm_currentms_Value) + rtb_Gain_i0; - FlyByWire_DWork.Delay_DSTATE_i += std::fmax(std::fmin(rtb_BusAssignment_sim_input_delta_eta_pos - - FlyByWire_DWork.Delay_DSTATE_i, FlyByWire_P.RateLimiterVariableTs3_up_m * FlyByWire_U.in.time.dt), - FlyByWire_U.in.time.dt * FlyByWire_P.RateLimiterVariableTs3_lo_e); - rtb_v_target = std::fmax((rtb_Min3 - rtb_Min5) * FlyByWire_DWork.Delay_DSTATE_i, 0.0) + rtb_Min3; - FlyByWire_RateLimiter(FlyByWire_U.in.data.autopilot_custom_Theta_c_deg, FlyByWire_P.RateLimiterVariableTs1_up_k, - FlyByWire_P.RateLimiterVariableTs1_lo_h, FlyByWire_U.in.time.dt, - FlyByWire_P.RateLimiterVariableTs1_InitialCondition_hb, &rtb_Y_mc5, - &FlyByWire_DWork.sf_RateLimiter_n); - FlyByWire_RateLimiter(rtb_BusAssignment_sim_input_delta_eta_pos, FlyByWire_P.RateLimiterVariableTs_up_f, - FlyByWire_P.RateLimiterVariableTs_lo_f, FlyByWire_U.in.time.dt, - FlyByWire_P.RateLimiterVariableTs_InitialCondition_c, &rtb_Y_g, - &FlyByWire_DWork.sf_RateLimiter_k); - rtb_Gain_ju = FlyByWire_P.Subsystem2_Gain * rtb_v_target; - rtb_Divide_ni = (rtb_Gain_ju - FlyByWire_DWork.Delay_DSTATE_j) / FlyByWire_U.in.time.dt; - rtb_Limiterxi2 = FlyByWire_U.in.time.dt * FlyByWire_P.Subsystem2_C1; - rtb_Saturation3 = rtb_Limiterxi2 + FlyByWire_P.Constant_Value_m3; - FlyByWire_DWork.Delay1_DSTATE = 1.0 / rtb_Saturation3 * (FlyByWire_P.Constant_Value_m3 - rtb_Limiterxi2) * - FlyByWire_DWork.Delay1_DSTATE + (rtb_Divide_ni + FlyByWire_DWork.Delay_DSTATE_c) * (rtb_Limiterxi2 / rtb_Saturation3); - rtb_Gain_gt = FlyByWire_P.Subsystem_Gain * FlyByWire_U.in.data.V_ias_kn; - rtb_Divide_o = (rtb_Gain_gt - FlyByWire_DWork.Delay_DSTATE_p) / FlyByWire_U.in.time.dt; - rtb_Limiterxi2 = FlyByWire_U.in.time.dt * FlyByWire_P.Subsystem_C1; - rtb_Saturation3 = rtb_Limiterxi2 + FlyByWire_P.Constant_Value_hz; - FlyByWire_DWork.Delay1_DSTATE_i = 1.0 / rtb_Saturation3 * (FlyByWire_P.Constant_Value_hz - rtb_Limiterxi2) * - FlyByWire_DWork.Delay1_DSTATE_i + (rtb_Divide_o + FlyByWire_DWork.Delay_DSTATE_m) * (rtb_Limiterxi2 / - rtb_Saturation3); - FlyByWire_DWork.Delay_DSTATE_g += std::fmax(std::fmin(FlyByWire_DWork.sProtActive - FlyByWire_DWork.Delay_DSTATE_g, - FlyByWire_P.RateLimiterVariableTs4_up * FlyByWire_U.in.time.dt), FlyByWire_U.in.time.dt * - FlyByWire_P.RateLimiterVariableTs4_lo); - FlyByWire_RateLimiter(static_cast(rtb_in_flare), FlyByWire_P.RateLimiterVariableTs6_up, - FlyByWire_P.RateLimiterVariableTs6_lo, FlyByWire_U.in.time.dt, - FlyByWire_P.RateLimiterVariableTs6_InitialCondition, &rtb_Y_p2, - &FlyByWire_DWork.sf_RateLimiter_m); - if (FlyByWire_U.in.data.autopilot_custom_on > FlyByWire_P.Switch1_Threshold_ke) { - rtb_Limiterxi2 = (rtb_Y_mc5 - rtb_GainTheta) * look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, - FlyByWire_P.ScheduledGain_BreakpointsForDimension1, FlyByWire_P.ScheduledGain_Table, 6U); - } else { - rtb_Y_mc5 = look1_binlxpw(rtb_Y_g, FlyByWire_P.Loaddemand_bp01Data, FlyByWire_P.Loaddemand_tableData, 2U); - if (rtb_Y_p2 > FlyByWire_P.Saturation_UpperSat) { - rtb_Y_p2 = FlyByWire_P.Saturation_UpperSat; - } else if (rtb_Y_p2 < FlyByWire_P.Saturation_LowerSat) { - rtb_Y_p2 = FlyByWire_P.Saturation_LowerSat; - } - - omega_0 = (FlyByWire_DWork.Delay_DSTATE_dq - rtb_GainTheta) * FlyByWire_P.Gain_Gain; - if (omega_0 > FlyByWire_P.Saturation_UpperSat_j) { - omega_0 = FlyByWire_P.Saturation_UpperSat_j; - } else if (omega_0 < FlyByWire_P.Saturation_LowerSat_b) { - omega_0 = FlyByWire_P.Saturation_LowerSat_b; - } - - rtb_Y_g = (FlyByWire_P.Constant_Value_b - rtb_Y_p2) * FlyByWire_P.Constant_Value_m + omega_0 * rtb_Y_p2; - if (FlyByWire_DWork.Delay_DSTATE_g > FlyByWire_P.Saturation_UpperSat_o) { - rtb_Saturation_kd = FlyByWire_P.Saturation_UpperSat_o; - } else if (FlyByWire_DWork.Delay_DSTATE_g < FlyByWire_P.Saturation_LowerSat_k) { - rtb_Saturation_kd = FlyByWire_P.Saturation_LowerSat_k; - } else { - rtb_Saturation_kd = FlyByWire_DWork.Delay_DSTATE_g; - } - - if (FlyByWire_DWork.sProtActive > FlyByWire_P.Switch2_Threshold) { - omega_0 = (((((rtb_v_target - FlyByWire_U.in.data.V_ias_kn) * FlyByWire_P.Gain6_Gain + - FlyByWire_P.precontrol_gain_HSP_Gain * FlyByWire_DWork.Delay1_DSTATE) + - FlyByWire_P.v_dot_gain_HSP_Gain * FlyByWire_DWork.Delay1_DSTATE_i) + FlyByWire_P.qk_gain_HSP_Gain * - rtb_BusAssignment_sim_data_qk_deg_s) + FlyByWire_P.qk_dot_gain1_Gain * rtb_Y_fp) * - FlyByWire_P.HSP_gain_Gain; - if (rtb_Y_mc5 > FlyByWire_P.Saturation8_UpperSat) { - rtb_Y_p2 = FlyByWire_P.Saturation8_UpperSat; - } else if (rtb_Y_mc5 < FlyByWire_P.Saturation8_LowerSat) { - rtb_Y_p2 = FlyByWire_P.Saturation8_LowerSat; - } else { - rtb_Y_p2 = rtb_Y_mc5; - } - - if (omega_0 > FlyByWire_P.Saturation4_UpperSat) { - omega_0 = FlyByWire_P.Saturation4_UpperSat; - } else if (omega_0 < FlyByWire_P.Saturation4_LowerSat) { - omega_0 = FlyByWire_P.Saturation4_LowerSat; - } - - rtb_Y_p2 += omega_0; - } else { - rtb_Y_p2 = FlyByWire_P.Constant1_Value; - } - - rtb_Limiterxi2 = ((FlyByWire_P.Constant_Value_k - rtb_Saturation_kd) * rtb_Y_mc5 + rtb_Y_p2 * rtb_Saturation_kd) + - rtb_Y_g; - } - - rtb_Y_mc5 = FlyByWire_P.DiscreteDerivativeVariableTs1_Gain * rtb_BusAssignment_sim_data_qk_deg_s; - if (FlyByWire_U.in.data.flaps_handle_index == 5.0) { - rtb_nz_limit_lo_g = 25; - } else { - rtb_nz_limit_lo_g = 30; - } - - FlyByWire_RateLimiter(static_cast(rtb_nz_limit_lo_g) - std::fmin(5.0, std::fmax(0.0, 5.0 - - (FlyByWire_U.in.data.V_ias_kn - (FlyByWire_U.in.data.VLS_kn + 5.0)) * 0.25)), - FlyByWire_P.RateLimiterVariableTs6_up_f, FlyByWire_P.RateLimiterVariableTs6_lo_m, - FlyByWire_U.in.time.dt, FlyByWire_P.RateLimiterVariableTs6_InitialCondition_b, &rtb_Y_g, - &FlyByWire_DWork.sf_RateLimiter); - rtb_Loaddemand2 = FlyByWire_P.Gain1_Gain_d * rtb_GainTheta; - omega_0 = FlyByWire_P.Gain2_Gain_g * rtb_Y_g - rtb_Loaddemand2; - if (FlyByWire_U.in.data.V_tas_kn > FlyByWire_P.Saturation3_UpperSat_k) { - rtb_Y_jv = FlyByWire_P.Saturation3_UpperSat_k; - } else if (FlyByWire_U.in.data.V_tas_kn < FlyByWire_P.Saturation3_LowerSat_l) { - rtb_Y_jv = FlyByWire_P.Saturation3_LowerSat_l; - } else { - rtb_Y_jv = FlyByWire_U.in.data.V_tas_kn; - } - - if (omega_0 > FlyByWire_P.Saturation1_UpperSat_h) { - omega_0 = FlyByWire_P.Saturation1_UpperSat_h; - } else if (omega_0 < FlyByWire_P.Saturation1_LowerSat_o) { - omega_0 = FlyByWire_P.Saturation1_LowerSat_o; - } - - rtb_Saturation3 = (FlyByWire_P.Gain1_Gain_c4 * rtb_BusAssignment_sim_data_qk_deg_s * (FlyByWire_P.Gain_Gain_i4 * - FlyByWire_P.Vm_currentms_Value_l) + rtb_Gain_i0) - (look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, - FlyByWire_P.uDLookupTable_bp01Data_f, FlyByWire_P.uDLookupTable_tableData_c, 6U) / (FlyByWire_P.Gain5_Gain_k * - rtb_Y_jv) + FlyByWire_P.Bias_Bias) * ((rtb_Limiterxi + look1_binlxpw(omega_0, FlyByWire_P.Loaddemand1_bp01Data, - FlyByWire_P.Loaddemand1_tableData, 2U)) - rtb_Limiterxi); - rtb_Y_g = rtb_Saturation3 * look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, FlyByWire_P.DLUT_bp01Data, - FlyByWire_P.DLUT_tableData, 1U) * FlyByWire_P.DiscreteDerivativeVariableTs_Gain_l; - rtb_Y_p2 = FlyByWire_P.DiscreteDerivativeVariableTs2_Gain * FlyByWire_U.in.data.V_tas_kn; - FlyByWire_LagFilter((rtb_Y_p2 - FlyByWire_DWork.Delay_DSTATE_o) / FlyByWire_U.in.time.dt, FlyByWire_P.LagFilter_C1_j, - FlyByWire_U.in.time.dt, &rtb_Y_p, &FlyByWire_DWork.sf_LagFilter_h); - if (rtb_Y_p > FlyByWire_P.SaturationV_dot_UpperSat) { - rtb_Y_p = FlyByWire_P.SaturationV_dot_UpperSat; - } else if (rtb_Y_p < FlyByWire_P.SaturationV_dot_LowerSat) { - rtb_Y_p = FlyByWire_P.SaturationV_dot_LowerSat; - } - - rtb_Sum1_jv = (((rtb_Y_mc5 - FlyByWire_DWork.Delay_DSTATE_l) / FlyByWire_U.in.time.dt * FlyByWire_P.Gain3_Gain_j + - rtb_Saturation3 * look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, FlyByWire_P.PLUT_bp01Data, - FlyByWire_P.PLUT_tableData, 1U)) + (rtb_Y_g - FlyByWire_DWork.Delay_DSTATE_b) / FlyByWire_U.in.time.dt) + - FlyByWire_P.Gain_Gain_de * rtb_Y_p; - FlyByWire_WashoutFilter(std::fmin(FlyByWire_U.in.data.spoilers_left_pos, FlyByWire_U.in.data.spoilers_right_pos), - FlyByWire_P.WashoutFilter_C1, FlyByWire_U.in.time.dt, &rtb_Y_p, &FlyByWire_DWork.sf_WashoutFilter_o); - if (rtb_Y_p > FlyByWire_P.SaturationSpoilers_UpperSat) { - rtb_Y_n0 = FlyByWire_P.SaturationSpoilers_UpperSat; - } else if (rtb_Y_p < FlyByWire_P.SaturationSpoilers_LowerSat) { - rtb_Y_n0 = FlyByWire_P.SaturationSpoilers_LowerSat; - } else { - rtb_Y_n0 = rtb_Y_p; - } - - rtb_Saturation_kd = FlyByWire_P.DiscreteDerivativeVariableTs1_Gain_k * rtb_BusAssignment_sim_data_qk_deg_s; - if (FlyByWire_U.in.data.V_tas_kn > FlyByWire_P.Saturation3_UpperSat_l) { - rtb_Y_jv = FlyByWire_P.Saturation3_UpperSat_l; - } else if (FlyByWire_U.in.data.V_tas_kn < FlyByWire_P.Saturation3_LowerSat_n) { - rtb_Y_jv = FlyByWire_P.Saturation3_LowerSat_n; - } else { - rtb_Y_jv = FlyByWire_U.in.data.V_tas_kn; - } - - rtb_Saturation3 = (FlyByWire_P.Gain1_Gain_g * rtb_BusAssignment_sim_data_qk_deg_s * (FlyByWire_P.Gain_Gain_g * - FlyByWire_P.Vm_currentms_Value_e) + rtb_Gain_i0) - (look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, - FlyByWire_P.uDLookupTable_bp01Data_d, FlyByWire_P.uDLookupTable_tableData_e, 6U) / (FlyByWire_P.Gain5_Gain_i * - rtb_Y_jv) + FlyByWire_P.Bias_Bias_b) * (rtb_Y_jz - rtb_Limiterxi); - rtb_Gain_ok = rtb_Saturation3 * look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, FlyByWire_P.DLUT_bp01Data_p, - FlyByWire_P.DLUT_tableData_p, 1U) * FlyByWire_P.DiscreteDerivativeVariableTs_Gain_f; - rtb_Gain_gh = FlyByWire_P.DiscreteDerivativeVariableTs2_Gain_g * FlyByWire_U.in.data.V_tas_kn; - FlyByWire_LagFilter((rtb_Gain_gh - FlyByWire_DWork.Delay_DSTATE_bk) / FlyByWire_U.in.time.dt, - FlyByWire_P.LagFilter_C1_d, FlyByWire_U.in.time.dt, &rtb_Y_p, &FlyByWire_DWork.sf_LagFilter_l); - if (rtb_Y_p > FlyByWire_P.SaturationV_dot_UpperSat_e) { - rtb_Y_p = FlyByWire_P.SaturationV_dot_UpperSat_e; - } else if (rtb_Y_p < FlyByWire_P.SaturationV_dot_LowerSat_c) { - rtb_Y_p = FlyByWire_P.SaturationV_dot_LowerSat_c; - } - - rtb_Sum1_h = (((rtb_Saturation_kd - FlyByWire_DWork.Delay_DSTATE_h) / FlyByWire_U.in.time.dt * - FlyByWire_P.Gain3_Gain_jk + rtb_Saturation3 * look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, - FlyByWire_P.PLUT_bp01Data_j, FlyByWire_P.PLUT_tableData_j, 1U)) + (rtb_Gain_ok - FlyByWire_DWork.Delay_DSTATE_dz) / - FlyByWire_U.in.time.dt) + FlyByWire_P.Gain_Gain_f * rtb_Y_p; - FlyByWire_WashoutFilter(std::fmin(FlyByWire_U.in.data.spoilers_left_pos, FlyByWire_U.in.data.spoilers_right_pos), - FlyByWire_P.WashoutFilter_C1_c, FlyByWire_U.in.time.dt, &rtb_Y_p, &FlyByWire_DWork.sf_WashoutFilter_l); - if (rtb_Y_p > FlyByWire_P.SaturationSpoilers_UpperSat_h) { - y = FlyByWire_P.SaturationSpoilers_UpperSat_h; - } else if (rtb_Y_p < FlyByWire_P.SaturationSpoilers_LowerSat_h) { - y = FlyByWire_P.SaturationSpoilers_LowerSat_h; - } else { - y = rtb_Y_p; - } - - FlyByWire_RateLimiter(rtb_BusAssignment_sim_input_delta_eta_pos, FlyByWire_P.RateLimiterVariableTs2_up_b, - FlyByWire_P.RateLimiterVariableTs2_lo_n, FlyByWire_U.in.time.dt, - FlyByWire_P.RateLimiterVariableTs2_InitialCondition_j, &rtb_Y_cu, - &FlyByWire_DWork.sf_RateLimiter_p); - rtb_y_l = (rtb_Y_k1 - rtb_Y_c) * rtb_Y_cu; - FlyByWire_LagFilter(FlyByWire_U.in.data.alpha_deg, FlyByWire_P.LagFilter1_C1, FlyByWire_U.in.time.dt, &rtb_Y_p, - &FlyByWire_DWork.sf_LagFilter_ht); - rtb_Saturation3 = rtb_Y_p - rtb_Y_c; - FlyByWire_WashoutFilter(std::fmax(std::fmax(0.0, rtb_GainTheta - 22.5), std::fmax(0.0, (std::abs(rtb_GainPhi) - 3.0) / - 6.0)), FlyByWire_P.WashoutFilter_C1_j, FlyByWire_U.in.time.dt, &rtb_Y_p, &FlyByWire_DWork.sf_WashoutFilter_i); - rtb_Saturation3 = (rtb_y_l - rtb_Saturation3) - rtb_Y_p; - rtb_Y_cu = FlyByWire_P.Subsystem1_Gain * rtb_Saturation3; - rtb_Divide_k = (rtb_Y_cu - FlyByWire_DWork.Delay_DSTATE_ps) / FlyByWire_U.in.time.dt; - rtb_Delay_jj = FlyByWire_U.in.time.dt * FlyByWire_P.Subsystem1_C1; - rtb_Minup = rtb_Delay_jj + FlyByWire_P.Constant_Value_kr; - FlyByWire_DWork.Delay1_DSTATE_o = 1.0 / rtb_Minup * (FlyByWire_P.Constant_Value_kr - rtb_Delay_jj) * - FlyByWire_DWork.Delay1_DSTATE_o + (rtb_Divide_k + FlyByWire_DWork.Delay_DSTATE_c1) * (rtb_Delay_jj / rtb_Minup); - rtb_alpha_err_gain = FlyByWire_P.alpha_err_gain_Gain * rtb_Saturation3; - rtb_Minup = FlyByWire_P.Subsystem3_Gain * FlyByWire_U.in.data.V_ias_kn; - rtb_Divide_m1 = (rtb_Minup - FlyByWire_DWork.Delay_DSTATE_l5) / FlyByWire_U.in.time.dt; - rtb_Saturation3 = FlyByWire_U.in.time.dt * FlyByWire_P.Subsystem3_C1; - rtb_Delay_jj = rtb_Saturation3 + FlyByWire_P.Constant_Value_c; - FlyByWire_DWork.Delay1_DSTATE_n = 1.0 / rtb_Delay_jj * (FlyByWire_P.Constant_Value_c - rtb_Saturation3) * - FlyByWire_DWork.Delay1_DSTATE_n + (rtb_Divide_m1 + FlyByWire_DWork.Delay_DSTATE_n) * (rtb_Saturation3 / rtb_Delay_jj); - FlyByWire_DWork.Delay_DSTATE_k += std::fmax(std::fmin(FlyByWire_DWork.sProtActive_c - FlyByWire_DWork.Delay_DSTATE_k, - FlyByWire_P.RateLimiterVariableTs5_up * FlyByWire_U.in.time.dt), FlyByWire_U.in.time.dt * - FlyByWire_P.RateLimiterVariableTs5_lo); - if (FlyByWire_DWork.Delay_DSTATE_k > FlyByWire_P.Saturation_UpperSat_a) { - rtb_Y_jv = FlyByWire_P.Saturation_UpperSat_a; - } else if (FlyByWire_DWork.Delay_DSTATE_k < FlyByWire_P.Saturation_LowerSat_ps) { - rtb_Y_jv = FlyByWire_P.Saturation_LowerSat_ps; - } else { - rtb_Y_jv = FlyByWire_DWork.Delay_DSTATE_k; - } - - omega_0 = (((FlyByWire_P.precontrol_gain_Gain * FlyByWire_DWork.Delay1_DSTATE_o + rtb_alpha_err_gain) + - FlyByWire_P.v_dot_gain_Gain * FlyByWire_DWork.Delay1_DSTATE_n) + FlyByWire_P.qk_gain_Gain * - rtb_BusAssignment_sim_data_qk_deg_s) + FlyByWire_P.qk_dot_gain_Gain * rtb_Y_fp; - if (omega_0 > FlyByWire_P.Saturation3_UpperSat_c) { - omega_0 = FlyByWire_P.Saturation3_UpperSat_c; - } else if (omega_0 < FlyByWire_P.Saturation3_LowerSat_h) { - omega_0 = FlyByWire_P.Saturation3_LowerSat_h; - } - - rtb_Y_fp = omega_0 * rtb_Y_jv; - rtb_Y_nl = FlyByWire_P.Constant_Value_p - rtb_Y_jv; - rtb_Delay_jj = FlyByWire_P.DiscreteDerivativeVariableTs1_Gain_b * rtb_BusAssignment_sim_data_qk_deg_s; - if (FlyByWire_U.in.data.V_tas_kn > FlyByWire_P.Saturation3_UpperSat_p) { - rtb_Y_jv = FlyByWire_P.Saturation3_UpperSat_p; - } else if (FlyByWire_U.in.data.V_tas_kn < FlyByWire_P.Saturation3_LowerSat_i) { - rtb_Y_jv = FlyByWire_P.Saturation3_LowerSat_i; - } else { - rtb_Y_jv = FlyByWire_U.in.data.V_tas_kn; - } - - if (rtb_GainPhi > FlyByWire_P.Saturation_UpperSat_d) { - rtb_Saturation3 = FlyByWire_P.Saturation_UpperSat_d; - } else if (rtb_GainPhi < FlyByWire_P.Saturation_LowerSat_pr) { - rtb_Saturation3 = FlyByWire_P.Saturation_LowerSat_pr; - } else { - rtb_Saturation3 = rtb_GainPhi; - } - - rtb_Y_jv = rtb_Limiterxi1 - ((rtb_uDLookupTable_g / std::cos(FlyByWire_P.Gain1_Gain_b * rtb_Saturation3) + - rtb_Limiterxi2) - rtb_Limiterxi) * (look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, FlyByWire_P.uDLookupTable_bp01Data_j, - FlyByWire_P.uDLookupTable_tableData_l, 6U) / (FlyByWire_P.Gain5_Gain_g * rtb_Y_jv) + FlyByWire_P.Bias_Bias_d); - rtb_Saturation3 = rtb_Y_jv * look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, FlyByWire_P.DLUT_bp01Data_a, - FlyByWire_P.DLUT_tableData_m, 1U) * FlyByWire_P.DiscreteDerivativeVariableTs_Gain_e; - rtb_alpha_err_gain = FlyByWire_P.DiscreteDerivativeVariableTs2_Gain_a * FlyByWire_U.in.data.V_tas_kn; - FlyByWire_LagFilter((rtb_alpha_err_gain - FlyByWire_DWork.Delay_DSTATE_fi) / FlyByWire_U.in.time.dt, - FlyByWire_P.LagFilter_C1_h, FlyByWire_U.in.time.dt, &rtb_Y_p, &FlyByWire_DWork.sf_LagFilter_f); - if (rtb_Y_p > FlyByWire_P.SaturationV_dot_UpperSat_ee) { - rtb_uDLookupTable_g = FlyByWire_P.SaturationV_dot_UpperSat_ee; - } else if (rtb_Y_p < FlyByWire_P.SaturationV_dot_LowerSat_m) { - rtb_uDLookupTable_g = FlyByWire_P.SaturationV_dot_LowerSat_m; - } else { - rtb_uDLookupTable_g = rtb_Y_p; - } - - FlyByWire_WashoutFilter(std::fmin(FlyByWire_U.in.data.spoilers_left_pos, FlyByWire_U.in.data.spoilers_right_pos), - FlyByWire_P.WashoutFilter_C1_e, FlyByWire_U.in.time.dt, &rtb_Y_p, &FlyByWire_DWork.sf_WashoutFilter_b); - if (rtb_Y_p > FlyByWire_P.SaturationSpoilers_UpperSat_c) { - rtb_Y_p = FlyByWire_P.SaturationSpoilers_UpperSat_c; - } else if (rtb_Y_p < FlyByWire_P.SaturationSpoilers_LowerSat_n) { - rtb_Y_p = FlyByWire_P.SaturationSpoilers_LowerSat_n; - } - - omega_0 = ((((rtb_Delay_jj - FlyByWire_DWork.Delay_DSTATE_ca) / FlyByWire_U.in.time.dt * FlyByWire_P.Gain3_Gain_l + - rtb_Y_jv * look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, FlyByWire_P.PLUT_bp01Data_h, - FlyByWire_P.PLUT_tableData_e, 1U)) + (rtb_Saturation3 - FlyByWire_DWork.Delay_DSTATE_jv) / FlyByWire_U.in.time.dt) + - FlyByWire_P.Gain_Gain_o * rtb_uDLookupTable_g) + rtb_Y_p * look1_binlxpw(FlyByWire_U.in.data.H_radio_ft, - FlyByWire_P.ScheduledGain_BreakpointsForDimension1_d, FlyByWire_P.ScheduledGain_Table_b, 3U); - rtb_Gain_ei = FlyByWire_P.DiscreteDerivativeVariableTs1_Gain_p * rtb_BusAssignment_sim_data_qk_deg_s; - if (FlyByWire_U.in.data.V_tas_kn > FlyByWire_P.Saturation3_UpperSat_b) { - rtb_Y_jv = FlyByWire_P.Saturation3_UpperSat_b; - } else if (FlyByWire_U.in.data.V_tas_kn < FlyByWire_P.Saturation3_LowerSat_c) { - rtb_Y_jv = FlyByWire_P.Saturation3_LowerSat_c; - } else { - rtb_Y_jv = FlyByWire_U.in.data.V_tas_kn; - } - - rtb_Y_jv = (FlyByWire_P.Gain1_Gain_gs * rtb_BusAssignment_sim_data_qk_deg_s * (FlyByWire_P.Gain_Gain_hy * - FlyByWire_P.Vm_currentms_Value_c) + rtb_Gain_i0) - (look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, - FlyByWire_P.uDLookupTable_bp01Data_lk, FlyByWire_P.uDLookupTable_tableData_p, 6U) / (FlyByWire_P.Gain5_Gain_e * - rtb_Y_jv) + FlyByWire_P.Bias_Bias_dw) * (rtb_Y_lc - rtb_Limiterxi); - rtb_Gain_f2y = rtb_Y_jv * look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, FlyByWire_P.DLUT_bp01Data_f, - FlyByWire_P.DLUT_tableData_a, 1U) * FlyByWire_P.DiscreteDerivativeVariableTs_Gain_bf; - rtb_Gain_ce = FlyByWire_P.DiscreteDerivativeVariableTs2_Gain_j * FlyByWire_U.in.data.V_tas_kn; - FlyByWire_LagFilter((rtb_Gain_ce - FlyByWire_DWork.Delay_DSTATE_ez) / FlyByWire_U.in.time.dt, - FlyByWire_P.LagFilter_C1_d2, FlyByWire_U.in.time.dt, &rtb_Y_p, &FlyByWire_DWork.sf_LagFilter_i); - if (rtb_Y_p > FlyByWire_P.SaturationV_dot_UpperSat_j) { - rtb_uDLookupTable_g = FlyByWire_P.SaturationV_dot_UpperSat_j; - } else if (rtb_Y_p < FlyByWire_P.SaturationV_dot_LowerSat_m3) { - rtb_uDLookupTable_g = FlyByWire_P.SaturationV_dot_LowerSat_m3; - } else { - rtb_uDLookupTable_g = rtb_Y_p; - } - - FlyByWire_WashoutFilter(std::fmin(FlyByWire_U.in.data.spoilers_left_pos, FlyByWire_U.in.data.spoilers_right_pos), - FlyByWire_P.WashoutFilter_C1_a, FlyByWire_U.in.time.dt, &rtb_Y_p, &FlyByWire_DWork.sf_WashoutFilter_c); - if (rtb_Y_p > FlyByWire_P.SaturationSpoilers_UpperSat_ci) { - rtb_Y_p = FlyByWire_P.SaturationSpoilers_UpperSat_ci; - } else if (rtb_Y_p < FlyByWire_P.SaturationSpoilers_LowerSat_j) { - rtb_Y_p = FlyByWire_P.SaturationSpoilers_LowerSat_j; - } - - rtb_Limitereta = ((((rtb_Gain_ei - FlyByWire_DWork.Delay_DSTATE_ds) / FlyByWire_U.in.time.dt * - FlyByWire_P.Gain3_Gain_c + rtb_Y_jv * look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, - FlyByWire_P.PLUT_bp01Data_i, FlyByWire_P.PLUT_tableData_l, 1U)) + (rtb_Gain_f2y - FlyByWire_DWork.Delay_DSTATE_jw) / - FlyByWire_U.in.time.dt) + FlyByWire_P.Gain_Gain_f0 * rtb_uDLookupTable_g) + rtb_Y_p * look1_binlxpw - (FlyByWire_U.in.data.H_radio_ft, FlyByWire_P.ScheduledGain_BreakpointsForDimension1_n, - FlyByWire_P.ScheduledGain_Table_g, 3U); - rtb_uDLookupTable_g = y * look1_binlxpw(FlyByWire_U.in.data.H_radio_ft, - FlyByWire_P.ScheduledGain_BreakpointsForDimension1_p, FlyByWire_P.ScheduledGain_Table_l, 3U) + rtb_Sum1_h; - if (rtb_uDLookupTable_g > FlyByWire_P.Saturation_UpperSat_h) { - rtb_uDLookupTable_g = FlyByWire_P.Saturation_UpperSat_h; - } else if (rtb_uDLookupTable_g < FlyByWire_P.Saturation_LowerSat_l) { - rtb_uDLookupTable_g = FlyByWire_P.Saturation_LowerSat_l; - } - - if (omega_0 > FlyByWire_P.Saturation_UpperSat_j1) { - omega_0 = FlyByWire_P.Saturation_UpperSat_j1; - } else if (omega_0 < FlyByWire_P.Saturation_LowerSat_c) { - omega_0 = FlyByWire_P.Saturation_LowerSat_c; - } - - if (rtb_Limitereta > FlyByWire_P.Saturation_UpperSat_f) { - rtb_Limitereta = FlyByWire_P.Saturation_UpperSat_f; - } else if (rtb_Limitereta < FlyByWire_P.Saturation_LowerSat_lf) { - rtb_Limitereta = FlyByWire_P.Saturation_LowerSat_lf; - } - - FlyByWire_VoterAttitudeProtection(rtb_uDLookupTable_g, rtb_Y_fp + rtb_Y_nl * omega_0, rtb_Limitereta, &rtb_Y_p); - rtb_Sum1_h = FlyByWire_P.DiscreteDerivativeVariableTs1_Gain_kf * rtb_BusAssignment_sim_data_qk_deg_s; - omega_0 = FlyByWire_P.Gain3_Gain_m * FlyByWire_P.Theta_max3_Value - rtb_Loaddemand2; - if (FlyByWire_U.in.data.V_tas_kn > FlyByWire_P.Saturation3_UpperSat_d) { - rtb_Y_jv = FlyByWire_P.Saturation3_UpperSat_d; - } else if (FlyByWire_U.in.data.V_tas_kn < FlyByWire_P.Saturation3_LowerSat_a) { - rtb_Y_jv = FlyByWire_P.Saturation3_LowerSat_a; - } else { - rtb_Y_jv = FlyByWire_U.in.data.V_tas_kn; - } - - if (omega_0 > FlyByWire_P.Saturation2_UpperSat_g) { - omega_0 = FlyByWire_P.Saturation2_UpperSat_g; - } else if (omega_0 < FlyByWire_P.Saturation2_LowerSat_i) { - omega_0 = FlyByWire_P.Saturation2_LowerSat_i; - } - - rtb_Y_jv = (FlyByWire_P.Gain1_Gain_gh * rtb_BusAssignment_sim_data_qk_deg_s * (FlyByWire_P.Gain_Gain_et * - FlyByWire_P.Vm_currentms_Value_h) + rtb_Gain_i0) - (look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, - FlyByWire_P.uDLookupTable_bp01Data_le, FlyByWire_P.uDLookupTable_tableData_j, 6U) / (FlyByWire_P.Gain5_Gain_p * - rtb_Y_jv) + FlyByWire_P.Bias_Bias_n) * ((rtb_Limiterxi + look1_binlxpw(omega_0, FlyByWire_P.Loaddemand2_bp01Data, - FlyByWire_P.Loaddemand2_tableData, 2U)) - rtb_Limiterxi); - rtb_Gain_i0 = rtb_Y_jv * look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, FlyByWire_P.DLUT_bp01Data_ai, - FlyByWire_P.DLUT_tableData_ah, 1U) * FlyByWire_P.DiscreteDerivativeVariableTs_Gain_ea; - rtb_Loaddemand2 = FlyByWire_P.DiscreteDerivativeVariableTs2_Gain_b * FlyByWire_U.in.data.V_tas_kn; - FlyByWire_LagFilter((rtb_Loaddemand2 - FlyByWire_DWork.Delay_DSTATE_es) / FlyByWire_U.in.time.dt, - FlyByWire_P.LagFilter_C1_e, FlyByWire_U.in.time.dt, &rtb_Y_nl, &FlyByWire_DWork.sf_LagFilter_a); - if (rtb_Y_nl > FlyByWire_P.SaturationV_dot_UpperSat_d) { - y = FlyByWire_P.SaturationV_dot_UpperSat_d; - } else if (rtb_Y_nl < FlyByWire_P.SaturationV_dot_LowerSat_d) { - y = FlyByWire_P.SaturationV_dot_LowerSat_d; - } else { - y = rtb_Y_nl; - } - - FlyByWire_WashoutFilter(std::fmin(FlyByWire_U.in.data.spoilers_left_pos, FlyByWire_U.in.data.spoilers_right_pos), - FlyByWire_P.WashoutFilter_C1_ji, FlyByWire_U.in.time.dt, &rtb_Y_nl, &FlyByWire_DWork.sf_WashoutFilter); - omega_0 = rtb_Y_n0 * look1_binlxpw(FlyByWire_U.in.data.H_radio_ft, - FlyByWire_P.ScheduledGain_BreakpointsForDimension1_m, FlyByWire_P.ScheduledGain_Table_e, 3U) + rtb_Sum1_jv; - if (rtb_Y_nl > FlyByWire_P.SaturationSpoilers_UpperSat_j) { - rtb_Y_nl = FlyByWire_P.SaturationSpoilers_UpperSat_j; - } else if (rtb_Y_nl < FlyByWire_P.SaturationSpoilers_LowerSat_f) { - rtb_Y_nl = FlyByWire_P.SaturationSpoilers_LowerSat_f; - } - - rtb_uDLookupTable_g = ((((rtb_Sum1_h - FlyByWire_DWork.Delay_DSTATE_gk) / FlyByWire_U.in.time.dt * - FlyByWire_P.Gain3_Gain_n + rtb_Y_jv * look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, FlyByWire_P.PLUT_bp01Data_b, - FlyByWire_P.PLUT_tableData_j5, 1U)) + (rtb_Gain_i0 - FlyByWire_DWork.Delay_DSTATE_py) / FlyByWire_U.in.time.dt) + - FlyByWire_P.Gain_Gain_fw * y) + rtb_Y_nl * look1_binlxpw(FlyByWire_U.in.data.H_radio_ft, - FlyByWire_P.ScheduledGain_BreakpointsForDimension1_h, FlyByWire_P.ScheduledGain_Table_ga, 3U); - if (omega_0 > FlyByWire_P.Saturation_UpperSat_i) { - omega_0 = FlyByWire_P.Saturation_UpperSat_i; - } else if (omega_0 < FlyByWire_P.Saturation_LowerSat_f) { - omega_0 = FlyByWire_P.Saturation_LowerSat_f; - } - - if (rtb_uDLookupTable_g > FlyByWire_P.Saturation_UpperSat_eo) { - rtb_uDLookupTable_g = FlyByWire_P.Saturation_UpperSat_eo; - } else if (rtb_uDLookupTable_g < FlyByWire_P.Saturation_LowerSat_ar) { - rtb_uDLookupTable_g = FlyByWire_P.Saturation_LowerSat_ar; - } - - FlyByWire_VoterAttitudeProtection(omega_0, rtb_Y_p, rtb_uDLookupTable_g, &rtb_Y_p); - rtb_Limitereta = rtb_Y_p * look1_binlxpw(FlyByWire_U.in.time.dt, FlyByWire_P.ScheduledGain_BreakpointsForDimension1_c, - FlyByWire_P.ScheduledGain_Table_p, 4U); - rtb_Y_jv = FlyByWire_P.DiscreteTimeIntegratorVariableTs_Gain_k * rtb_Limitereta * FlyByWire_U.in.time.dt; - FlyByWire_DWork.icLoad_e = ((rtb_Y_f == 0.0) || (rtb_alpha_floor_inhib != 0) || FlyByWire_DWork.icLoad_e); - if (FlyByWire_DWork.icLoad_e) { - if (FlyByWire_B.in_flight <= FlyByWire_P.Switch_Threshold_d) { - rtb_LimiteriH_n = rtb_BusAssignment_cs_pitch_data_computed_delta_eta_deg; - } - - FlyByWire_DWork.Delay_DSTATE_f1 = rtb_LimiteriH_n - rtb_Y_jv; - } - - FlyByWire_DWork.Delay_DSTATE_f1 += rtb_Y_jv; - if (FlyByWire_DWork.Delay_DSTATE_f1 > FlyByWire_P.DiscreteTimeIntegratorVariableTs_UpperLimit_c) { - FlyByWire_DWork.Delay_DSTATE_f1 = FlyByWire_P.DiscreteTimeIntegratorVariableTs_UpperLimit_c; - } else if (FlyByWire_DWork.Delay_DSTATE_f1 < FlyByWire_P.DiscreteTimeIntegratorVariableTs_LowerLimit_b) { - FlyByWire_DWork.Delay_DSTATE_f1 = FlyByWire_P.DiscreteTimeIntegratorVariableTs_LowerLimit_b; - } - - if (rtb_Y_f > FlyByWire_P.Saturation_UpperSat_g4) { - rtb_Y_jv = FlyByWire_P.Saturation_UpperSat_g4; - } else if (rtb_Y_f < FlyByWire_P.Saturation_LowerSat_la) { - rtb_Y_jv = FlyByWire_P.Saturation_LowerSat_la; - } else { - rtb_Y_jv = rtb_Y_f; - } - - rtb_uDLookupTable_g = FlyByWire_DWork.Delay_DSTATE_f1 * rtb_Y_jv; - rtb_Limiterxi = FlyByWire_P.Constant_Value_o - rtb_Y_jv; - if (rtb_ManualSwitch > FlyByWire_P.Saturation_UpperSat_c) { - rtb_Y_jv = FlyByWire_P.Saturation_UpperSat_c; - } else if (rtb_ManualSwitch < FlyByWire_P.Saturation_LowerSat_m) { - rtb_Y_jv = FlyByWire_P.Saturation_LowerSat_m; - } else { - rtb_Y_jv = rtb_ManualSwitch; - } - - rtb_LimiteriH_n = ((FlyByWire_P.Constant_Value_ju - rtb_Y_jv) * rtb_BusAssignment_cs_pitch_data_computed_delta_eta_deg - + rtb_LimiteriH * rtb_Y_jv) * rtb_Limiterxi + rtb_uDLookupTable_g; - if (rtb_eta_trim_deg_should_freeze == FlyByWire_P.CompareToConstant_const_h) { - rtb_uDLookupTable_g = FlyByWire_P.Constant_Value; - } else { - rtb_uDLookupTable_g = FlyByWire_DWork.Delay_DSTATE_f1; - } - - rtb_Y_jv = FlyByWire_P.Gain_Gain_ip * rtb_uDLookupTable_g * FlyByWire_P.DiscreteTimeIntegratorVariableTsLimit_Gain * - FlyByWire_U.in.time.dt; - FlyByWire_DWork.icLoad_i = (rtb_eta_trim_deg_reset || FlyByWire_DWork.icLoad_i); - if (FlyByWire_DWork.icLoad_i) { - FlyByWire_DWork.Delay_DSTATE_hh = rtb_eta_trim_deg_reset_deg - rtb_Y_jv; - } - - FlyByWire_DWork.Delay_DSTATE_hh += rtb_Y_jv; - if (FlyByWire_DWork.Delay_DSTATE_hh > rtb_BusAssignment_cs_pitch_data_computed_eta_trim_deg_limit_up) { - FlyByWire_DWork.Delay_DSTATE_hh = rtb_BusAssignment_cs_pitch_data_computed_eta_trim_deg_limit_up; - } else if (FlyByWire_DWork.Delay_DSTATE_hh < rtb_BusAssignment_cs_pitch_data_computed_eta_trim_deg_limit_lo) { - FlyByWire_DWork.Delay_DSTATE_hh = rtb_BusAssignment_cs_pitch_data_computed_eta_trim_deg_limit_lo; - } - - FlyByWire_DWork.Delay_DSTATE_ea += std::fmax(std::fmin(FlyByWire_DWork.Delay_DSTATE_hh - - FlyByWire_DWork.Delay_DSTATE_ea, rtb_eta_trim_deg_rate_limit_up_deg_s * FlyByWire_U.in.time.dt), - FlyByWire_U.in.time.dt * rtb_eta_trim_deg_rate_limit_lo_deg_s); - FlyByWire_Y.out.pitch.law_normal.Cstar_g = rtb_Limiterxi1; - FlyByWire_Y.out.pitch.law_normal.eta_dot_deg_s = rtb_Y_p; - rtb_uDLookupTable_g = look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, FlyByWire_P.uDLookupTable_bp01Data_fm, - FlyByWire_P.uDLookupTable_tableData_f, 3U); - rtb_Sum1_jv = FlyByWire_P.Gain1_Gain_jh * rtb_BusAssignment_a_sim_input_delta_zeta_pos; - if (rtb_Sum1_jv > rtb_uDLookupTable_g) { - rtb_Sum1_jv = rtb_uDLookupTable_g; - } else { - rtb_uDLookupTable_g *= FlyByWire_P.Gain2_Gain; - if (rtb_Sum1_jv < rtb_uDLookupTable_g) { - rtb_Sum1_jv = rtb_uDLookupTable_g; - } - } - - if (FlyByWire_DWork.is_active_c5_FlyByWire == 0U) { - FlyByWire_DWork.is_active_c5_FlyByWire = 1U; - FlyByWire_DWork.is_c5_FlyByWire = FlyByWire_IN_GroundMode; - rtb_nz_limit_lo_g = 0; - } else if (FlyByWire_DWork.is_c5_FlyByWire == 1) { - if (rtb_on_ground == 1) { - FlyByWire_DWork.is_c5_FlyByWire = FlyByWire_IN_GroundMode; - rtb_nz_limit_lo_g = 0; - } else { - rtb_nz_limit_lo_g = 1; - } - } else if (((rtb_on_ground == 0) && (rtb_GainTheta > 8.0)) || (FlyByWire_U.in.data.H_radio_ft > 400.0)) { - FlyByWire_DWork.is_c5_FlyByWire = FlyByWire_IN_FlightMode; - rtb_nz_limit_lo_g = 1; - } else { - rtb_nz_limit_lo_g = 0; - } - - if (rtb_nz_limit_lo_g > FlyByWire_P.Saturation_UpperSat_p) { - rtb_uDLookupTable_g = FlyByWire_P.Saturation_UpperSat_p; - } else if (rtb_nz_limit_lo_g < FlyByWire_P.Saturation_LowerSat_h) { - rtb_uDLookupTable_g = FlyByWire_P.Saturation_LowerSat_h; - } else { - rtb_uDLookupTable_g = rtb_nz_limit_lo_g; - } - - FlyByWire_RateLimiter(rtb_uDLookupTable_g, FlyByWire_P.RateLimiterVariableTs_up_k, - FlyByWire_P.RateLimiterVariableTs_lo_fs, FlyByWire_U.in.time.dt, - FlyByWire_P.RateLimiterVariableTs_InitialCondition_f, &rtb_Y_n0, - &FlyByWire_DWork.sf_RateLimiter_gp); - FlyByWire_LagFilter(FlyByWire_U.in.data.engine_2_thrust_lbf - FlyByWire_U.in.data.engine_1_thrust_lbf, - FlyByWire_P.LagFilter1_C1_j, FlyByWire_U.in.time.dt, &rtb_Y_fp, &FlyByWire_DWork.sf_LagFilter_fr); - if (FlyByWire_U.in.data.alpha_deg > FlyByWire_P.Saturation_UpperSat_l) { - rtb_Y_jv = FlyByWire_P.Saturation_UpperSat_l; - } else if (FlyByWire_U.in.data.alpha_deg < FlyByWire_P.Saturation_LowerSat_cj) { - rtb_Y_jv = FlyByWire_P.Saturation_LowerSat_cj; - } else { - rtb_Y_jv = FlyByWire_U.in.data.alpha_deg; - } - - FlyByWire_LagFilter(rtb_Y_jv, FlyByWire_P.LagFilter2_C1, FlyByWire_U.in.time.dt, &rtb_Y_nl, - &FlyByWire_DWork.sf_LagFilter_pc); - FlyByWire_LagFilter(FlyByWire_U.in.data.engine_1_thrust_lbf - FlyByWire_U.in.data.engine_2_thrust_lbf, - FlyByWire_P.LagFilter3_C1, FlyByWire_U.in.time.dt, &rtb_Y_p, &FlyByWire_DWork.sf_LagFilter_at); - if (FlyByWire_U.in.data.V_ias_kn > FlyByWire_P.Saturation1_UpperSat_fa) { - rtb_Limiterxi = FlyByWire_P.Saturation1_UpperSat_fa; - } else if (FlyByWire_U.in.data.V_ias_kn < FlyByWire_P.Saturation1_LowerSat_om) { - rtb_Limiterxi = FlyByWire_P.Saturation1_LowerSat_om; - } else { - rtb_Limiterxi = FlyByWire_U.in.data.V_ias_kn; - } - - rtb_Y_fp = (rtb_Y_nl * rtb_Y_p * FlyByWire_P.Gain5_Gain_l + FlyByWire_P.Gain4_Gain_f * rtb_Y_fp) / rtb_Limiterxi / - rtb_Limiterxi * FlyByWire_P.Gain_Gain_oq; - rtb_Y_nl = FlyByWire_P.Gain_Gain_c * rtb_BusAssignment_sim_input_delta_xi_pos; - if (FlyByWire_DWork.sProtActive > FlyByWire_P.Switch3_Threshold) { - rtb_uDLookupTable_g = look1_binlxpw(rtb_GainPhi, FlyByWire_P.BankAngleProtection2_bp01Data, - FlyByWire_P.BankAngleProtection2_tableData, 4U); - } else if (FlyByWire_DWork.sProtActive_c > FlyByWire_P.Switch2_Threshold_i) { - rtb_uDLookupTable_g = look1_binlxpw(rtb_GainPhi, FlyByWire_P.BankAngleProtection_bp01Data, - FlyByWire_P.BankAngleProtection_tableData, 8U); - } else { - rtb_uDLookupTable_g = look1_binlxpw(rtb_GainPhi, FlyByWire_P.BankAngleProtection1_bp01Data, - FlyByWire_P.BankAngleProtection1_tableData, 8U); - } - - rtb_Y_jv = FlyByWire_P.Gain1_Gain_bq * rtb_BusAssignment_sim_input_delta_xi_pos + rtb_uDLookupTable_g; - if (rtb_Y_jv > FlyByWire_P.Saturation_UpperSat_as) { - rtb_Y_jv = FlyByWire_P.Saturation_UpperSat_as; - } else if (rtb_Y_jv < FlyByWire_P.Saturation_LowerSat_o) { - rtb_Y_jv = FlyByWire_P.Saturation_LowerSat_o; - } - - rtb_uDLookupTable_g = 15.0; - rtb_Limiterxi = -15.0; - if (FlyByWire_DWork.Delay_DSTATE_eu >= 25.0) { - rtb_Limiterxi = rtb_pk; - } else if (FlyByWire_DWork.Delay_DSTATE_eu <= -25.0) { - rtb_uDLookupTable_g = rtb_pk; - } - - rtb_uDLookupTable_g = std::fmin(rtb_uDLookupTable_g, std::fmax(rtb_Limiterxi, rtb_Y_jv * rtb_Y_n0)) * - FlyByWire_P.DiscreteTimeIntegratorVariableTs_Gain_m * FlyByWire_U.in.time.dt; - FlyByWire_DWork.icLoad_l = ((rtb_Y_n0 == 0.0) || (rtb_alpha_floor_inhib != 0) || - (FlyByWire_U.in.data.autopilot_custom_on != 0.0) || FlyByWire_DWork.icLoad_l); - if (FlyByWire_DWork.icLoad_l) { - FlyByWire_DWork.Delay_DSTATE_dj = rtb_GainPhi - rtb_uDLookupTable_g; - } - - FlyByWire_DWork.Delay_DSTATE_dj += rtb_uDLookupTable_g; - if (FlyByWire_DWork.Delay_DSTATE_dj > FlyByWire_P.DiscreteTimeIntegratorVariableTs_UpperLimit_n) { - FlyByWire_DWork.Delay_DSTATE_dj = FlyByWire_P.DiscreteTimeIntegratorVariableTs_UpperLimit_n; - } else if (FlyByWire_DWork.Delay_DSTATE_dj < FlyByWire_P.DiscreteTimeIntegratorVariableTs_LowerLimit_c) { - FlyByWire_DWork.Delay_DSTATE_dj = FlyByWire_P.DiscreteTimeIntegratorVariableTs_LowerLimit_c; - } - - if (FlyByWire_DWork.Delay_DSTATE_dj > FlyByWire_P.Saturation_UpperSat_gn) { - rtb_Limiterxi = FlyByWire_P.Saturation_UpperSat_gn; - } else if (FlyByWire_DWork.Delay_DSTATE_dj < FlyByWire_P.Saturation_LowerSat_en) { - rtb_Limiterxi = FlyByWire_P.Saturation_LowerSat_en; - } else { - rtb_Limiterxi = FlyByWire_DWork.Delay_DSTATE_dj; - } - - FlyByWire_RateLimiter(rtb_Limiterxi, FlyByWire_P.RateLimiterVariableTs_up_m, FlyByWire_P.RateLimiterVariableTs_lo_k, - FlyByWire_U.in.time.dt, FlyByWire_P.RateLimiterVariableTs_InitialCondition_m, - &rtb_uDLookupTable_g, &FlyByWire_DWork.sf_RateLimiter_ny); - if (FlyByWire_U.in.data.autopilot_custom_on > FlyByWire_P.Switch_Threshold_j) { - if (rtb_alpha_floor_inhib > FlyByWire_P.Switch1_Threshold) { - rtb_uDLookupTable_g = rtb_GainPhi; - } else { - rtb_uDLookupTable_g = FlyByWire_U.in.data.autopilot_custom_Phi_c_deg; - } - } - - rtb_Limiterxi = std::fmax(FlyByWire_U.in.data.V_ias_kn, 80.0) * 0.5144; - rtb_Limiterxi1 = rtb_Limiterxi * rtb_Limiterxi * 0.6125; - rtb_Y_p = rtb_Limiterxi1 * 122.0 * 17.9 * -0.090320788790706555 / 1.0E+6; - omega_0 = 0.0; - if ((FlyByWire_U.in.data.V_ias_kn <= 400.0) && (FlyByWire_U.in.data.V_ias_kn >= 0.0)) { - high_i = 4; - low_i = 0; - low_ip1 = 2; - while (high_i > low_ip1) { - mid_i = ((low_i + high_i) + 1) >> 1; - if (FlyByWire_U.in.data.V_ias_kn >= b[mid_i - 1]) { - low_i = mid_i - 1; - low_ip1 = mid_i + 1; - } else { - high_i = mid_i; - } - } - - omega_0 = (FlyByWire_U.in.data.V_ias_kn - static_cast(b[low_i])) / static_cast(b[low_i + 1] - - b[low_i]); - if (omega_0 == 0.0) { - omega_0 = c[low_i]; - } else if (omega_0 == 1.0) { - omega_0 = c[low_i + 1]; - } else if (c[low_i + 1] == c[low_i]) { - omega_0 = c[low_i]; - } else { - omega_0 = (1.0 - omega_0) * static_cast(c[low_i]) + static_cast(c[low_i + 1]) * omega_0; - } - } - - y = -(omega_0 * omega_0) / rtb_Y_p; - FlyByWire_DWork.Delay_DSTATE_eu = ((-(rtb_Limiterxi1 / rtb_Limiterxi * 122.0 * 320.40999999999997 * -0.487 / 1.0E+6 + - 1.414 * omega_0) / rtb_Y_p * (FlyByWire_P.Gain1_Gain_cb * rtb_pk) + FlyByWire_P.Gain1_Gain_bqd * rtb_GainPhi * y) + - FlyByWire_P.Gain1_Gain_n * rtb_uDLookupTable_g * -y) * look1_binlxpw(FlyByWire_U.in.time.dt, - FlyByWire_P.ScheduledGain_BreakpointsForDimension1_j, FlyByWire_P.ScheduledGain_Table_i, 4U) * - FlyByWire_P.Gain_Gain_p; - FlyByWire_RateLimiter(rtb_Sum1_jv, FlyByWire_P.RateLimiterVariableTs_up_i, FlyByWire_P.RateLimiterVariableTs_lo_g, - FlyByWire_U.in.time.dt, FlyByWire_P.RateLimiterVariableTs_InitialCondition_j, &rtb_Limiterxi, - &FlyByWire_DWork.sf_RateLimiter_np); - if (!FlyByWire_DWork.pY_not_empty) { - FlyByWire_DWork.pY = FlyByWire_P.RateLimiterVariableTs1_InitialCondition_m; - FlyByWire_DWork.pY_not_empty = true; - } - - FlyByWire_DWork.pY += std::fmax(std::fmin(static_cast(rtb_on_ground == 0) - FlyByWire_DWork.pY, std::abs - (FlyByWire_P.RateLimiterVariableTs1_up_j) * FlyByWire_U.in.time.dt), -std::abs - (FlyByWire_P.RateLimiterVariableTs1_lo_n) * FlyByWire_U.in.time.dt); - if (FlyByWire_DWork.pY > FlyByWire_P.Saturation_UpperSat_n) { - rtb_Limiterxi1 = FlyByWire_P.Saturation_UpperSat_n; - } else if (FlyByWire_DWork.pY < FlyByWire_P.Saturation_LowerSat_bc) { - rtb_Limiterxi1 = FlyByWire_P.Saturation_LowerSat_bc; - } else { - rtb_Limiterxi1 = FlyByWire_DWork.pY; - } - - FlyByWire_Y.out.roll.law_normal.pk_c_deg_s = rtb_Y_jv; - y = rtb_uDLookupTable_g; - if (FlyByWire_U.in.data.V_tas_kn > FlyByWire_P.Saturation_UpperSat_ek) { - rtb_Y_jv = FlyByWire_P.Saturation_UpperSat_ek; - } else if (FlyByWire_U.in.data.V_tas_kn < FlyByWire_P.Saturation_LowerSat_j) { - rtb_Y_jv = FlyByWire_P.Saturation_LowerSat_j; - } else { - rtb_Y_jv = FlyByWire_U.in.data.V_tas_kn; - } - - omega_0 = (rtb_Gain - std::sin(FlyByWire_P.Gain1_Gain_f * rtb_uDLookupTable_g) * FlyByWire_P.Constant2_Value_p * std:: - cos(FlyByWire_P.Gain1_Gain_l * rtb_GainTheta) / (FlyByWire_P.Gain6_Gain_k * rtb_Y_jv) * - FlyByWire_P.Gain_Gain_i3) * look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, - FlyByWire_P.ScheduledGain_BreakpointsForDimension1_a, FlyByWire_P.ScheduledGain_Table_eg, 6U); - rtb_uDLookupTable_g = rtb_Gain * look1_binlxpw(FlyByWire_U.in.data.V_tas_kn, - FlyByWire_P.ScheduledGain1_BreakpointsForDimension1, FlyByWire_P.ScheduledGain1_Table, 6U); - if (omega_0 > FlyByWire_P.Saturation1_UpperSat_j) { - omega_0 = FlyByWire_P.Saturation1_UpperSat_j; - } else if (omega_0 < FlyByWire_P.Saturation1_LowerSat_a) { - omega_0 = FlyByWire_P.Saturation1_LowerSat_a; - } - - if (rtb_uDLookupTable_g > FlyByWire_P.Saturation2_UpperSat_n) { - rtb_uDLookupTable_g = FlyByWire_P.Saturation2_UpperSat_n; - } else if (rtb_uDLookupTable_g < FlyByWire_P.Saturation2_LowerSat_a) { - rtb_uDLookupTable_g = FlyByWire_P.Saturation2_LowerSat_a; - } - - rtb_Limiterxi1 = (FlyByWire_P.Constant_Value_ku - rtb_Limiterxi1) * rtb_uDLookupTable_g + omega_0 * rtb_Limiterxi1; - FlyByWire_RateLimiter(static_cast(rtb_on_ground), FlyByWire_P.RateLimiterVariableTs_up_f1, - FlyByWire_P.RateLimiterVariableTs_lo_e, FlyByWire_U.in.time.dt, - FlyByWire_P.RateLimiterVariableTs_InitialCondition_fa, &rtb_Y_jv, - &FlyByWire_DWork.sf_RateLimiter_f); - if (rtb_Y_jv > FlyByWire_P.Saturation_UpperSat_cr) { - rtb_Y_jv = FlyByWire_P.Saturation_UpperSat_cr; - } else if (rtb_Y_jv < FlyByWire_P.Saturation_LowerSat_o4) { - rtb_Y_jv = FlyByWire_P.Saturation_LowerSat_o4; - } - - rtb_uDLookupTable_g = FlyByWire_U.in.data.autopilot_custom_Beta_c_deg * rtb_Y_jv; - rtb_Limiterxi = FlyByWire_P.Constant_Value_i - rtb_Y_jv; - if (FlyByWire_U.in.data.autopilot_custom_on > FlyByWire_P.Switch2_Threshold_n) { - rtb_Y_jv = FlyByWire_U.in.data.autopilot_custom_Beta_c_deg + rtb_Y_fp; - } else { - rtb_Y_jv = rtb_BusAssignment_a_sim_input_delta_zeta_pos * look1_binlxpw(FlyByWire_U.in.data.V_ias_kn, - FlyByWire_P.ScheduledGain_BreakpointsForDimension1_jh, FlyByWire_P.ScheduledGain_Table_c, 3U); - } - - Vtas = std::fmax(1.0, FlyByWire_U.in.data.V_tas_kn * 0.5144); - rtb_Y_p = FlyByWire_U.in.data.V_ias_kn * 0.5144; - if (FlyByWire_U.in.data.V_ias_kn >= 60.0) { - omega_0 = FlyByWire_U.in.data.beta_deg; - rtb_Y_p = rtb_Y_p * rtb_Y_p * 0.6125 * 122.0 / (70000.0 * Vtas); - Vtas = (((rtb_Y_p * 0.814 * FlyByWire_U.in.data.beta_deg * 3.1415926535897931 / 180.0 + -(rtb_Gain * - 3.1415926535897931 / 180.0)) + y * 3.1415926535897931 / 180.0 * (9.81 / Vtas)) + rtb_Y_p * 3.172 * - (FlyByWire_P.fbw_output_MATLABStruct.roll.output.zeta_deg / 25.0) * 3.1415926535897931 / 180.0) * 180.0 / - 3.1415926535897931; - } else { - omega_0 = 0.0; - Vtas = 0.0; - } - - FlyByWire_LagFilter((rtb_Y_jv - omega_0) * look1_binlxpw(FlyByWire_U.in.data.V_ias_kn, - FlyByWire_P.ScheduledGain1_BreakpointsForDimension1_a, FlyByWire_P.ScheduledGain1_Table_o, 4U) - Vtas, - FlyByWire_P.LagFilter_C1_em, FlyByWire_U.in.time.dt, &rtb_Y_p, &FlyByWire_DWork.sf_LagFilter_e); - omega_0 = rtb_Y_jv * look1_binlxpw(FlyByWire_U.in.data.V_ias_kn, FlyByWire_P.ScheduledGain_BreakpointsForDimension1_cf, - FlyByWire_P.ScheduledGain_Table_d, 8U) + rtb_Y_p; - if (omega_0 > FlyByWire_P.Saturation_UpperSat_p4) { - omega_0 = FlyByWire_P.Saturation_UpperSat_p4; - } else if (omega_0 < FlyByWire_P.Saturation_LowerSat_he) { - omega_0 = FlyByWire_P.Saturation_LowerSat_he; - } - - rtb_Y_p = (rtb_Limiterxi * omega_0 + rtb_uDLookupTable_g) + rtb_Limiterxi1; - FlyByWire_RateLimiter(static_cast(rtb_nz_limit_lo_g), FlyByWire_P.RateLimiterVariableTs_up_l, - FlyByWire_P.RateLimiterVariableTs_lo_m, FlyByWire_U.in.time.dt, - FlyByWire_P.RateLimiterVariableTs_InitialCondition_n3, &rtb_Y_jv, - &FlyByWire_DWork.sf_RateLimiter_a5); - if (FlyByWire_U.in.data.autopilot_custom_on <= FlyByWire_P.Switch_Threshold_p) { - rtb_Y_jv = FlyByWire_P.Constant_Value_l; - } - - if (rtb_Y_jv > FlyByWire_P.Saturation_UpperSat_lt) { - rtb_Y_jv = FlyByWire_P.Saturation_UpperSat_lt; - } else if (rtb_Y_jv < FlyByWire_P.Saturation_LowerSat_ol) { - rtb_Y_jv = FlyByWire_P.Saturation_LowerSat_ol; - } - - rtb_Limiterxi = (FlyByWire_P.Constant_Value_d - rtb_Y_jv) * FlyByWire_U.in.data.autopilot_custom_Phi_c_deg + - FlyByWire_DWork.Delay_DSTATE_eu * rtb_Y_jv; - rtb_uDLookupTable_g = rtb_Y_n0 + FlyByWire_U.in.data.autopilot_custom_on; - if (rtb_uDLookupTable_g > FlyByWire_P.Saturation1_UpperSat_e) { - rtb_Y_jv = FlyByWire_P.Saturation1_UpperSat_e; - } else if (rtb_uDLookupTable_g < FlyByWire_P.Saturation1_LowerSat_l) { - rtb_Y_jv = FlyByWire_P.Saturation1_LowerSat_l; - } else { - rtb_Y_jv = rtb_uDLookupTable_g; - } - - if (rtb_Y_jv > FlyByWire_P.Saturation_UpperSat_ll) { - rtb_Y_jv = FlyByWire_P.Saturation_UpperSat_ll; - } else if (rtb_Y_jv < FlyByWire_P.Saturation_LowerSat_og) { - rtb_Y_jv = FlyByWire_P.Saturation_LowerSat_og; - } - - Vtas = (FlyByWire_P.Constant_Value_l1 - rtb_Y_jv) * rtb_Y_nl + rtb_Limiterxi * rtb_Y_jv; - if (rtb_uDLookupTable_g > FlyByWire_P.Saturation_UpperSat_eq) { - rtb_uDLookupTable_g = FlyByWire_P.Saturation_UpperSat_eq; - } else if (rtb_uDLookupTable_g < FlyByWire_P.Saturation_LowerSat_n) { - rtb_uDLookupTable_g = FlyByWire_P.Saturation_LowerSat_n; - } - - if (rtb_uDLookupTable_g > FlyByWire_P.Saturation_UpperSat_il) { - rtb_uDLookupTable_g = FlyByWire_P.Saturation_UpperSat_il; - } else if (rtb_uDLookupTable_g < FlyByWire_P.Saturation_LowerSat_fr) { - rtb_uDLookupTable_g = FlyByWire_P.Saturation_LowerSat_fr; - } - - rtb_Limiterxi = (FlyByWire_P.Constant_Value_f - rtb_uDLookupTable_g) * rtb_Sum1_jv + rtb_Y_p * rtb_uDLookupTable_g; - if (FlyByWire_U.in.data.H_radio_ft <= FlyByWire_P.CompareToConstant_const_o) { - rtb_Y_jv = FlyByWire_P.Constant2_Value_d; - } else { - rtb_Y_jv = rtb_Y_p - rtb_Limiterxi1; - } - - rtb_Y_jv = FlyByWire_P.Gain4_Gain_e * rtb_Y_jv * FlyByWire_P.DiscreteTimeIntegratorVariableTs1_Gain * - FlyByWire_U.in.time.dt; - FlyByWire_DWork.icLoad_d = ((FlyByWire_U.in.data.autopilot_custom_on == 0.0) || (rtb_alpha_floor_inhib != 0) || - FlyByWire_DWork.icLoad_d); - if (FlyByWire_DWork.icLoad_d) { - FlyByWire_DWork.Delay_DSTATE_f3 = rtb_BusAssignment_a_sim_data_zeta_trim_deg - rtb_Y_jv; - } - - FlyByWire_DWork.Delay_DSTATE_f3 += rtb_Y_jv; - if (FlyByWire_DWork.Delay_DSTATE_f3 > FlyByWire_P.DiscreteTimeIntegratorVariableTs1_UpperLimit) { - FlyByWire_DWork.Delay_DSTATE_f3 = FlyByWire_P.DiscreteTimeIntegratorVariableTs1_UpperLimit; - } else if (FlyByWire_DWork.Delay_DSTATE_f3 < FlyByWire_P.DiscreteTimeIntegratorVariableTs1_LowerLimit) { - FlyByWire_DWork.Delay_DSTATE_f3 = FlyByWire_P.DiscreteTimeIntegratorVariableTs1_LowerLimit; - } - - FlyByWire_DWork.Delay_DSTATE_mp += std::fmax(std::fmin(FlyByWire_DWork.Delay_DSTATE_f3 - - FlyByWire_DWork.Delay_DSTATE_mp, FlyByWire_P.Constant_Value_li * FlyByWire_U.in.time.dt), FlyByWire_U.in.time.dt * - FlyByWire_P.Constant1_Value_h); - FlyByWire_RateLimiter(rtb_LimiteriH_n, FlyByWire_P.RateLimitereta_up, FlyByWire_P.RateLimitereta_lo, - FlyByWire_U.in.time.dt, FlyByWire_P.RateLimitereta_InitialCondition, &rtb_uDLookupTable_g, - &FlyByWire_DWork.sf_RateLimiter_mi); - FlyByWire_RateLimiter(Vtas, FlyByWire_P.RateLimiterxi_up, FlyByWire_P.RateLimiterxi_lo, FlyByWire_U.in.time.dt, - FlyByWire_P.RateLimiterxi_InitialCondition, &rtb_Y_jv, &FlyByWire_DWork.sf_RateLimiter_h); - FlyByWire_RateLimiter(rtb_Limiterxi, FlyByWire_P.RateLimiterzeta_up, FlyByWire_P.RateLimiterzeta_lo, - FlyByWire_U.in.time.dt, FlyByWire_P.RateLimiterzeta_InitialCondition, &rtb_Y_d, - &FlyByWire_DWork.sf_RateLimiter_d0); - FlyByWire_Y.out.sim.time.dt = FlyByWire_U.in.time.dt; - FlyByWire_Y.out.sim.time.simulation_time = FlyByWire_U.in.time.simulation_time; - FlyByWire_Y.out.sim.time.monotonic_time = FlyByWire_DWork.Delay_DSTATE; - FlyByWire_Y.out.sim.data.nz_g = FlyByWire_U.in.data.nz_g; - FlyByWire_Y.out.sim.data.Theta_deg = rtb_GainTheta; - FlyByWire_Y.out.sim.data.Phi_deg = rtb_GainPhi; - FlyByWire_Y.out.sim.data.q_deg_s = rtb_Gainqk; - FlyByWire_Y.out.sim.data.r_deg_s = rtb_Gain; - FlyByWire_Y.out.sim.data.p_deg_s = rtb_Gainpk; - FlyByWire_Y.out.sim.data.qk_deg_s = rtb_BusAssignment_sim_data_qk_deg_s; - FlyByWire_Y.out.sim.data.pk_deg_s = rtb_pk; - FlyByWire_Y.out.sim.data.psi_magnetic_deg = FlyByWire_U.in.data.psi_magnetic_deg; - FlyByWire_Y.out.sim.data.psi_true_deg = FlyByWire_U.in.data.psi_true_deg; - FlyByWire_Y.out.sim.data.xi_deg = FlyByWire_P.Gainpk5_Gain * FlyByWire_U.in.data.xi_pos; - FlyByWire_Y.out.sim.data.zeta_deg = FlyByWire_P.Gainpk6_Gain * FlyByWire_U.in.data.zeta_pos; - FlyByWire_Y.out.sim.data.zeta_trim_deg = rtb_BusAssignment_a_sim_data_zeta_trim_deg; - FlyByWire_Y.out.sim.data.alpha_deg = FlyByWire_U.in.data.alpha_deg; - FlyByWire_Y.out.sim.data.beta_deg = FlyByWire_U.in.data.beta_deg; - FlyByWire_Y.out.sim.data.beta_dot_deg_s = FlyByWire_U.in.data.beta_dot_deg_s; - FlyByWire_Y.out.sim.data.V_ias_kn = FlyByWire_U.in.data.V_ias_kn; - FlyByWire_Y.out.sim.data.V_tas_kn = FlyByWire_U.in.data.V_tas_kn; - FlyByWire_Y.out.sim.data.V_mach = FlyByWire_U.in.data.V_mach; - FlyByWire_Y.out.sim.data.H_ft = FlyByWire_U.in.data.H_ft; - FlyByWire_Y.out.sim.data.H_ind_ft = FlyByWire_U.in.data.H_ind_ft; - FlyByWire_Y.out.sim.data.H_radio_ft = FlyByWire_U.in.data.H_radio_ft; - FlyByWire_Y.out.sim.data.CG_percent_MAC = FlyByWire_U.in.data.CG_percent_MAC; - FlyByWire_Y.out.sim.data.total_weight_kg = FlyByWire_U.in.data.total_weight_kg; - omega_0 = FlyByWire_P.Gain_Gain_i * FlyByWire_U.in.data.gear_animation_pos_0 - FlyByWire_P.Constant_Value_g; - if (omega_0 > FlyByWire_P.Saturation_UpperSat_e) { - FlyByWire_Y.out.sim.data.gear_strut_compression_0 = FlyByWire_P.Saturation_UpperSat_e; - } else if (omega_0 < FlyByWire_P.Saturation_LowerSat_e) { - FlyByWire_Y.out.sim.data.gear_strut_compression_0 = FlyByWire_P.Saturation_LowerSat_e; - } else { - FlyByWire_Y.out.sim.data.gear_strut_compression_0 = omega_0; - } - - FlyByWire_Y.out.sim.data.gear_strut_compression_1 = u0; - FlyByWire_Y.out.sim.data.gear_strut_compression_2 = u0_0; - FlyByWire_Y.out.sim.data.flaps_handle_index = FlyByWire_U.in.data.flaps_handle_index; - FlyByWire_Y.out.sim.data.spoilers_left_pos = FlyByWire_U.in.data.spoilers_left_pos; - FlyByWire_Y.out.sim.data.spoilers_right_pos = FlyByWire_U.in.data.spoilers_right_pos; - FlyByWire_Y.out.sim.data.autopilot_master_on = FlyByWire_U.in.data.autopilot_master_on; - FlyByWire_Y.out.sim.data.slew_on = FlyByWire_U.in.data.slew_on; - FlyByWire_Y.out.sim.data.pause_on = FlyByWire_U.in.data.pause_on; - FlyByWire_Y.out.sim.data.tracking_mode_on_override = FlyByWire_U.in.data.tracking_mode_on_override; - FlyByWire_Y.out.sim.data.autopilot_custom_on = FlyByWire_U.in.data.autopilot_custom_on; - FlyByWire_Y.out.sim.data.autopilot_custom_Theta_c_deg = FlyByWire_U.in.data.autopilot_custom_Theta_c_deg; - FlyByWire_Y.out.sim.data.autopilot_custom_Phi_c_deg = FlyByWire_U.in.data.autopilot_custom_Phi_c_deg; - FlyByWire_Y.out.sim.data.autopilot_custom_Beta_c_deg = FlyByWire_U.in.data.autopilot_custom_Beta_c_deg; - FlyByWire_Y.out.sim.data.simulation_rate = FlyByWire_U.in.data.simulation_rate; - FlyByWire_Y.out.sim.data.ice_structure_percent = FlyByWire_U.in.data.ice_structure_percent; - FlyByWire_Y.out.sim.data.linear_cl_alpha_per_deg = FlyByWire_U.in.data.linear_cl_alpha_per_deg; - FlyByWire_Y.out.sim.data.alpha_stall_deg = FlyByWire_U.in.data.alpha_stall_deg; - FlyByWire_Y.out.sim.data.alpha_zero_lift_deg = FlyByWire_U.in.data.alpha_zero_lift_deg; - FlyByWire_Y.out.sim.data.ambient_density_kg_per_m3 = FlyByWire_U.in.data.ambient_density_kg_per_m3; - FlyByWire_Y.out.sim.data.ambient_pressure_mbar = FlyByWire_U.in.data.ambient_pressure_mbar; - FlyByWire_Y.out.sim.data.ambient_temperature_celsius = FlyByWire_U.in.data.ambient_temperature_celsius; - FlyByWire_Y.out.sim.data.ambient_wind_x_kn = FlyByWire_U.in.data.ambient_wind_x_kn; - FlyByWire_Y.out.sim.data.ambient_wind_y_kn = FlyByWire_U.in.data.ambient_wind_y_kn; - FlyByWire_Y.out.sim.data.ambient_wind_z_kn = FlyByWire_U.in.data.ambient_wind_z_kn; - FlyByWire_Y.out.sim.data.ambient_wind_velocity_kn = FlyByWire_U.in.data.ambient_wind_velocity_kn; - FlyByWire_Y.out.sim.data.ambient_wind_direction_deg = FlyByWire_U.in.data.ambient_wind_direction_deg; - FlyByWire_Y.out.sim.data.total_air_temperature_celsius = FlyByWire_U.in.data.total_air_temperature_celsius; - FlyByWire_Y.out.sim.data.latitude_deg = FlyByWire_U.in.data.latitude_deg; - FlyByWire_Y.out.sim.data.longitude_deg = FlyByWire_U.in.data.longitude_deg; - FlyByWire_Y.out.sim.data.engine_1_thrust_lbf = FlyByWire_U.in.data.engine_1_thrust_lbf; - FlyByWire_Y.out.sim.data.engine_2_thrust_lbf = FlyByWire_U.in.data.engine_2_thrust_lbf; - FlyByWire_Y.out.sim.data.thrust_lever_1_pos = FlyByWire_U.in.data.thrust_lever_1_pos; - FlyByWire_Y.out.sim.data.thrust_lever_2_pos = FlyByWire_U.in.data.thrust_lever_2_pos; - FlyByWire_Y.out.sim.data.tailstrike_protection_on = FlyByWire_U.in.data.tailstrike_protection_on; - FlyByWire_Y.out.sim.data.VLS_kn = FlyByWire_U.in.data.VLS_kn; - FlyByWire_Y.out.sim.data_computed.on_ground = rtb_on_ground; - FlyByWire_Y.out.sim.data_computed.tracking_mode_on = rtb_alpha_floor_inhib; - FlyByWire_Y.out.sim.data_computed.high_aoa_prot_active = FlyByWire_DWork.sProtActive_c; - FlyByWire_Y.out.sim.data_computed.alpha_floor_command = FlyByWire_DWork.sAlphaFloor; - FlyByWire_Y.out.sim.data_computed.high_speed_prot_active = FlyByWire_DWork.sProtActive; - FlyByWire_Y.out.sim.data_computed.high_speed_prot_low_kn = rtb_Min3; - FlyByWire_Y.out.sim.data_computed.high_speed_prot_high_kn = rtb_Min5; - FlyByWire_Y.out.sim.data_speeds_aoa.alpha_max_deg = rtb_Y_k1; - FlyByWire_Y.out.sim.data_speeds_aoa.alpha_prot_deg = rtb_Y_c; - FlyByWire_Y.out.sim.data_speeds_aoa.alpha_floor_deg = rtb_Y_h; - FlyByWire_Y.out.sim.input.delta_eta_pos = rtb_BusAssignment_sim_input_delta_eta_pos; - FlyByWire_Y.out.sim.input.delta_xi_pos = rtb_BusAssignment_sim_input_delta_xi_pos; - FlyByWire_Y.out.sim.input.delta_zeta_pos = rtb_BusAssignment_a_sim_input_delta_zeta_pos; - FlyByWire_Y.out.pitch.data_computed.eta_trim_deg_limit_lo = - rtb_BusAssignment_cs_pitch_data_computed_eta_trim_deg_limit_lo; - FlyByWire_Y.out.pitch.data_computed.eta_trim_deg_limit_up = - rtb_BusAssignment_cs_pitch_data_computed_eta_trim_deg_limit_up; - FlyByWire_Y.out.pitch.data_computed.delta_eta_deg = rtb_BusAssignment_cs_pitch_data_computed_delta_eta_deg; - FlyByWire_Y.out.pitch.data_computed.in_flight = FlyByWire_B.in_flight; - FlyByWire_Y.out.pitch.data_computed.in_rotation = rtb_ap_special_disc; - FlyByWire_Y.out.pitch.data_computed.in_flare = rtb_in_flare; - FlyByWire_Y.out.pitch.data_computed.in_flight_gain = rtb_Y_f; - FlyByWire_Y.out.pitch.data_computed.in_rotation_gain = rtb_ManualSwitch; - FlyByWire_Y.out.pitch.data_computed.nz_limit_up_g = rtb_Y_jz; - FlyByWire_Y.out.pitch.data_computed.nz_limit_lo_g = rtb_Y_lc; - FlyByWire_Y.out.pitch.data_computed.eta_trim_deg_should_freeze = rtb_eta_trim_deg_should_freeze; - FlyByWire_Y.out.pitch.data_computed.eta_trim_deg_reset = rtb_eta_trim_deg_reset; - FlyByWire_Y.out.pitch.data_computed.eta_trim_deg_reset_deg = rtb_eta_trim_deg_reset_deg; - FlyByWire_Y.out.pitch.data_computed.eta_trim_deg_should_write = rtb_eta_trim_deg_should_write; - FlyByWire_Y.out.pitch.data_computed.eta_trim_deg_rate_limit_up_deg_s = rtb_eta_trim_deg_rate_limit_up_deg_s; - FlyByWire_Y.out.pitch.data_computed.eta_trim_deg_rate_limit_lo_deg_s = rtb_eta_trim_deg_rate_limit_lo_deg_s; - FlyByWire_Y.out.pitch.data_computed.flare_Theta_c_deg = FlyByWire_DWork.Delay_DSTATE_dq; - FlyByWire_Y.out.pitch.data_computed.flare_Theta_c_rate_deg_s = FlyByWire_B.flare_Theta_c_rate_deg_s; - FlyByWire_Y.out.pitch.law_rotation.eta_deg = rtb_LimiteriH; - FlyByWire_Y.out.pitch.law_normal.nz_c_g = rtb_Limiterxi2; - FlyByWire_Y.out.pitch.law_normal.protection_alpha_c_deg = rtb_Y_c + rtb_y_l; - FlyByWire_Y.out.pitch.law_normal.protection_V_c_kn = rtb_v_target; - FlyByWire_Y.out.pitch.vote.eta_dot_deg_s = rtb_Limitereta; - FlyByWire_Y.out.pitch.integrated.eta_deg = FlyByWire_DWork.Delay_DSTATE_f1; - FlyByWire_Y.out.pitch.output.eta_deg = rtb_LimiteriH_n; - FlyByWire_Y.out.pitch.output.eta_trim_deg = FlyByWire_DWork.Delay_DSTATE_ea; - FlyByWire_Y.out.roll.data_computed.delta_xi_deg = rtb_Y_nl; - FlyByWire_Y.out.roll.data_computed.delta_zeta_deg = rtb_Sum1_jv; - FlyByWire_Y.out.roll.data_computed.in_flight = rtb_nz_limit_lo_g; - FlyByWire_Y.out.roll.data_computed.in_flight_gain = rtb_Y_n0; - FlyByWire_Y.out.roll.data_computed.zeta_trim_deg_should_write = (FlyByWire_U.in.data.autopilot_custom_on != 0.0); - FlyByWire_Y.out.roll.data_computed.beta_target_deg = rtb_Y_fp; - FlyByWire_Y.out.roll.law_normal.Phi_c_deg = y; - FlyByWire_Y.out.roll.law_normal.xi_deg = FlyByWire_DWork.Delay_DSTATE_eu; - FlyByWire_Y.out.roll.law_normal.zeta_deg = rtb_Y_p; - FlyByWire_Y.out.roll.law_normal.zeta_tc_yd_deg = rtb_Limiterxi1; - FlyByWire_Y.out.roll.output.xi_deg = Vtas; - FlyByWire_Y.out.roll.output.zeta_deg = rtb_Limiterxi; - FlyByWire_Y.out.roll.output.zeta_trim_deg = FlyByWire_DWork.Delay_DSTATE_mp; - u0 = FlyByWire_P.Gaineta_Gain_d * rtb_uDLookupTable_g; - if (u0 > FlyByWire_P.Limitereta_UpperSat) { - FlyByWire_Y.out.output.eta_pos = FlyByWire_P.Limitereta_UpperSat; - } else if (u0 < FlyByWire_P.Limitereta_LowerSat) { - FlyByWire_Y.out.output.eta_pos = FlyByWire_P.Limitereta_LowerSat; - } else { - FlyByWire_Y.out.output.eta_pos = u0; - } - - u0 = FlyByWire_P.GainiH_Gain * FlyByWire_DWork.Delay_DSTATE_ea; - if (u0 > FlyByWire_P.LimiteriH_UpperSat) { - FlyByWire_Y.out.output.eta_trim_deg = FlyByWire_P.LimiteriH_UpperSat; - } else if (u0 < FlyByWire_P.LimiteriH_LowerSat) { - FlyByWire_Y.out.output.eta_trim_deg = FlyByWire_P.LimiteriH_LowerSat; - } else { - FlyByWire_Y.out.output.eta_trim_deg = u0; - } - - FlyByWire_Y.out.output.eta_trim_deg_should_write = rtb_eta_trim_deg_should_write; - u0 = FlyByWire_P.Gainxi_Gain_n * rtb_Y_jv; - if (u0 > FlyByWire_P.Limiterxi_UpperSat) { - FlyByWire_Y.out.output.xi_pos = FlyByWire_P.Limiterxi_UpperSat; - } else if (u0 < FlyByWire_P.Limiterxi_LowerSat) { - FlyByWire_Y.out.output.xi_pos = FlyByWire_P.Limiterxi_LowerSat; - } else { - FlyByWire_Y.out.output.xi_pos = u0; - } - - u0 = FlyByWire_P.Gainxi1_Gain_e * rtb_Y_d; - if (u0 > FlyByWire_P.Limiterxi1_UpperSat) { - FlyByWire_Y.out.output.zeta_pos = FlyByWire_P.Limiterxi1_UpperSat; - } else if (u0 < FlyByWire_P.Limiterxi1_LowerSat) { - FlyByWire_Y.out.output.zeta_pos = FlyByWire_P.Limiterxi1_LowerSat; - } else { - FlyByWire_Y.out.output.zeta_pos = u0; - } - - u0 = FlyByWire_P.Gainxi2_Gain * FlyByWire_DWork.Delay_DSTATE_mp; - if (u0 > FlyByWire_P.Limiterxi2_UpperSat) { - FlyByWire_Y.out.output.zeta_trim_pos = FlyByWire_P.Limiterxi2_UpperSat; - } else if (u0 < FlyByWire_P.Limiterxi2_LowerSat) { - FlyByWire_Y.out.output.zeta_trim_pos = FlyByWire_P.Limiterxi2_LowerSat; - } else { - FlyByWire_Y.out.output.zeta_trim_pos = u0; - } - - FlyByWire_Y.out.output.zeta_trim_pos_should_write = (FlyByWire_U.in.data.autopilot_custom_on != 0.0); - rtb_GainPhi = std::fmax(FlyByWire_U.in.data.V_ias_kn, 60.0); - rtb_GainTheta = 0.0; - if (rtb_GainPhi <= 380.0) { - high_i = 4; - low_i = 1; - low_ip1 = 2; - while (high_i > low_ip1) { - mid_i = (low_i + high_i) >> 1; - if (rtb_GainPhi >= b_0[mid_i - 1]) { - low_i = mid_i; - low_ip1 = mid_i + 1; - } else { - high_i = mid_i; - } - } - - omega_0 = (rtb_GainPhi - static_cast(b_0[low_i - 1])) / static_cast(b_0[low_i] - b_0[low_i - 1]); - if (omega_0 == 0.0) { - rtb_GainTheta = -15.0; - } else if (omega_0 == 1.0) { - rtb_GainTheta = c_0[low_i]; - } else if (-15 == c_0[low_i]) { - rtb_GainTheta = -15.0; - } else { - rtb_GainTheta = (1.0 - omega_0) * -15.0 + omega_0 * static_cast(c_0[low_i]); - } - } - - rtb_Y_p = rtb_GainPhi * 0.5144; - FlyByWire_RateLimiter(0.814 / std::sqrt(1.3734E+6 / (149.45000000000002 * (rtb_Y_p * rtb_Y_p))) * (rtb_GainTheta * - rtb_BusAssignment_a_sim_input_delta_zeta_pos), FlyByWire_P.RateLimiterVariableTs1_up_p, - FlyByWire_P.RateLimiterVariableTs1_lo_cu, FlyByWire_U.in.time.dt, - FlyByWire_P.RateLimiterVariableTs1_InitialCondition_o, &rtb_GainPhi, - &FlyByWire_DWork.sf_RateLimiter_d); - FlyByWire_DWork.Delay_DSTATE_d = rtb_Y; - FlyByWire_DWork.Delay_DSTATE_f = rtb_nz_limit_up_g; - FlyByWire_DWork.Delay_DSTATE_dd = rtb_Gain_ne; - FlyByWire_DWork.icLoad = false; - FlyByWire_DWork.Delay_DSTATE_j = rtb_Gain_ju; - FlyByWire_DWork.Delay_DSTATE_c = rtb_Divide_ni; - FlyByWire_DWork.Delay_DSTATE_p = rtb_Gain_gt; - FlyByWire_DWork.Delay_DSTATE_m = rtb_Divide_o; - FlyByWire_DWork.Delay_DSTATE_l = rtb_Y_mc5; - FlyByWire_DWork.Delay_DSTATE_b = rtb_Y_g; - FlyByWire_DWork.Delay_DSTATE_o = rtb_Y_p2; - FlyByWire_DWork.Delay_DSTATE_h = rtb_Saturation_kd; - FlyByWire_DWork.Delay_DSTATE_dz = rtb_Gain_ok; - FlyByWire_DWork.Delay_DSTATE_bk = rtb_Gain_gh; - FlyByWire_DWork.Delay_DSTATE_ps = rtb_Y_cu; - FlyByWire_DWork.Delay_DSTATE_c1 = rtb_Divide_k; - FlyByWire_DWork.Delay_DSTATE_l5 = rtb_Minup; - FlyByWire_DWork.Delay_DSTATE_n = rtb_Divide_m1; - FlyByWire_DWork.Delay_DSTATE_ca = rtb_Delay_jj; - FlyByWire_DWork.Delay_DSTATE_jv = rtb_Saturation3; - FlyByWire_DWork.Delay_DSTATE_fi = rtb_alpha_err_gain; - FlyByWire_DWork.Delay_DSTATE_ds = rtb_Gain_ei; - FlyByWire_DWork.Delay_DSTATE_jw = rtb_Gain_f2y; - FlyByWire_DWork.Delay_DSTATE_ez = rtb_Gain_ce; - FlyByWire_DWork.Delay_DSTATE_gk = rtb_Sum1_h; - FlyByWire_DWork.Delay_DSTATE_py = rtb_Gain_i0; - FlyByWire_DWork.Delay_DSTATE_es = rtb_Loaddemand2; - FlyByWire_DWork.icLoad_e = false; - FlyByWire_DWork.icLoad_i = false; - FlyByWire_DWork.icLoad_l = false; - FlyByWire_DWork.icLoad_d = false; -} - -void FlyByWireModelClass::initialize() -{ - FlyByWire_DWork.Delay_DSTATE = FlyByWire_P.Delay_InitialCondition; - FlyByWire_DWork.Delay_DSTATE_d = FlyByWire_P.DiscreteDerivativeVariableTs_InitialCondition; - FlyByWire_DWork.Delay_DSTATE_dq = FlyByWire_P.RateLimiterDynamicVariableTs_InitialCondition; - FlyByWire_DWork.Delay_DSTATE_f = FlyByWire_P.DiscreteDerivativeVariableTs_InitialCondition_d; - FlyByWire_DWork.Delay_DSTATE_dd = FlyByWire_P.DiscreteDerivativeVariableTs_InitialCondition_k; - FlyByWire_DWork.icLoad = true; - FlyByWire_DWork.Delay_DSTATE_i = FlyByWire_P.RateLimiterVariableTs3_InitialCondition_e; - FlyByWire_DWork.Delay_DSTATE_j = FlyByWire_P.DiscreteDerivativeVariableTs2_InitialCondition; - FlyByWire_DWork.Delay_DSTATE_c = FlyByWire_P.Delay_InitialCondition_j; - FlyByWire_DWork.Delay1_DSTATE = FlyByWire_P.Delay1_InitialCondition; - FlyByWire_DWork.Delay_DSTATE_p = FlyByWire_P.DiscreteDerivativeVariableTs2_InitialCondition_c; - FlyByWire_DWork.Delay_DSTATE_m = FlyByWire_P.Delay_InitialCondition_l; - FlyByWire_DWork.Delay1_DSTATE_i = FlyByWire_P.Delay1_InitialCondition_a; - FlyByWire_DWork.Delay_DSTATE_g = FlyByWire_P.RateLimiterVariableTs4_InitialCondition; - FlyByWire_DWork.Delay_DSTATE_l = FlyByWire_P.DiscreteDerivativeVariableTs1_InitialCondition; - FlyByWire_DWork.Delay_DSTATE_b = FlyByWire_P.DiscreteDerivativeVariableTs_InitialCondition_kb; - FlyByWire_DWork.Delay_DSTATE_o = FlyByWire_P.DiscreteDerivativeVariableTs2_InitialCondition_i; - FlyByWire_DWork.Delay_DSTATE_h = FlyByWire_P.DiscreteDerivativeVariableTs1_InitialCondition_f; - FlyByWire_DWork.Delay_DSTATE_dz = FlyByWire_P.DiscreteDerivativeVariableTs_InitialCondition_m; - FlyByWire_DWork.Delay_DSTATE_bk = FlyByWire_P.DiscreteDerivativeVariableTs2_InitialCondition_iw; - FlyByWire_DWork.Delay_DSTATE_ps = FlyByWire_P.DiscreteDerivativeVariableTs2_InitialCondition_cx; - FlyByWire_DWork.Delay_DSTATE_c1 = FlyByWire_P.Delay_InitialCondition_k; - FlyByWire_DWork.Delay1_DSTATE_o = FlyByWire_P.Delay1_InitialCondition_i; - FlyByWire_DWork.Delay_DSTATE_l5 = FlyByWire_P.DiscreteDerivativeVariableTs2_InitialCondition_b; - FlyByWire_DWork.Delay_DSTATE_n = FlyByWire_P.Delay_InitialCondition_p; - FlyByWire_DWork.Delay1_DSTATE_n = FlyByWire_P.Delay1_InitialCondition_k; - FlyByWire_DWork.Delay_DSTATE_k = FlyByWire_P.RateLimiterVariableTs5_InitialCondition; - FlyByWire_DWork.Delay_DSTATE_ca = FlyByWire_P.DiscreteDerivativeVariableTs1_InitialCondition_fh; - FlyByWire_DWork.Delay_DSTATE_jv = FlyByWire_P.DiscreteDerivativeVariableTs_InitialCondition_c; - FlyByWire_DWork.Delay_DSTATE_fi = FlyByWire_P.DiscreteDerivativeVariableTs2_InitialCondition_h; - FlyByWire_DWork.Delay_DSTATE_ds = FlyByWire_P.DiscreteDerivativeVariableTs1_InitialCondition_h; - FlyByWire_DWork.Delay_DSTATE_jw = FlyByWire_P.DiscreteDerivativeVariableTs_InitialCondition_j; - FlyByWire_DWork.Delay_DSTATE_ez = FlyByWire_P.DiscreteDerivativeVariableTs2_InitialCondition_o; - FlyByWire_DWork.Delay_DSTATE_gk = FlyByWire_P.DiscreteDerivativeVariableTs1_InitialCondition_i; - FlyByWire_DWork.Delay_DSTATE_py = FlyByWire_P.DiscreteDerivativeVariableTs_InitialCondition_p; - FlyByWire_DWork.Delay_DSTATE_es = FlyByWire_P.DiscreteDerivativeVariableTs2_InitialCondition_d; - FlyByWire_DWork.icLoad_e = true; - FlyByWire_DWork.icLoad_i = true; - FlyByWire_DWork.Delay_DSTATE_ea = FlyByWire_P.RateLimiterDynamicVariableTs_InitialCondition_i; - FlyByWire_DWork.Delay_DSTATE_eu = FlyByWire_P.Delay_InitialCondition_d; - FlyByWire_DWork.icLoad_l = true; - FlyByWire_DWork.Delay_DSTATE_mp = FlyByWire_P.RateLimiterDynamicVariableTs_InitialCondition_b; - FlyByWire_DWork.icLoad_d = true; -} - -void FlyByWireModelClass::terminate() -{ -} - -FlyByWireModelClass::FlyByWireModelClass(): - FlyByWire_U(), - FlyByWire_Y(), - FlyByWire_B(), - FlyByWire_DWork() -{ -} - -FlyByWireModelClass::~FlyByWireModelClass() -{ -} diff --git a/src/fbw/src/model/FlyByWire.h b/src/fbw/src/model/FlyByWire.h deleted file mode 100644 index 076e4b79fd47..000000000000 --- a/src/fbw/src/model/FlyByWire.h +++ /dev/null @@ -1,770 +0,0 @@ -#ifndef RTW_HEADER_FlyByWire_h_ -#define RTW_HEADER_FlyByWire_h_ -#include -#include "rtwtypes.h" -#include "FlyByWire_types.h" - -class FlyByWireModelClass -{ - public: - struct rtDW_LagFilter_FlyByWire_T { - real_T pY; - real_T pU; - boolean_T pY_not_empty; - boolean_T pU_not_empty; - }; - - struct rtDW_WashoutFilter_FlyByWire_T { - real_T pY; - real_T pU; - boolean_T pY_not_empty; - boolean_T pU_not_empty; - }; - - struct rtDW_RateLimiter_FlyByWire_T { - real_T pY; - boolean_T pY_not_empty; - }; - - struct rtDW_eta_trim_limit_lofreeze_FlyByWire_T { - real_T frozen_eta_trim; - boolean_T frozen_eta_trim_not_empty; - }; - - struct BlockIO_FlyByWire_T { - real_T in_flight; - real_T flare_Theta_c_deg; - real_T flare_Theta_c_rate_deg_s; - }; - - struct D_Work_FlyByWire_T { - real_T Delay_DSTATE; - real_T Delay_DSTATE_d; - real_T Delay_DSTATE_dq; - real_T Delay_DSTATE_f; - real_T Delay_DSTATE_dd; - real_T Delay_DSTATE_e; - real_T Delay_DSTATE_i; - real_T Delay_DSTATE_j; - real_T Delay_DSTATE_c; - real_T Delay1_DSTATE; - real_T Delay_DSTATE_p; - real_T Delay_DSTATE_m; - real_T Delay1_DSTATE_i; - real_T Delay_DSTATE_g; - real_T Delay_DSTATE_l; - real_T Delay_DSTATE_b; - real_T Delay_DSTATE_o; - real_T Delay_DSTATE_h; - real_T Delay_DSTATE_dz; - real_T Delay_DSTATE_bk; - real_T Delay_DSTATE_ps; - real_T Delay_DSTATE_c1; - real_T Delay1_DSTATE_o; - real_T Delay_DSTATE_l5; - real_T Delay_DSTATE_n; - real_T Delay1_DSTATE_n; - real_T Delay_DSTATE_k; - real_T Delay_DSTATE_ca; - real_T Delay_DSTATE_jv; - real_T Delay_DSTATE_fi; - real_T Delay_DSTATE_ds; - real_T Delay_DSTATE_jw; - real_T Delay_DSTATE_ez; - real_T Delay_DSTATE_gk; - real_T Delay_DSTATE_py; - real_T Delay_DSTATE_es; - real_T Delay_DSTATE_f1; - real_T Delay_DSTATE_hh; - real_T Delay_DSTATE_ea; - real_T Delay_DSTATE_eu; - real_T Delay_DSTATE_dj; - real_T Delay_DSTATE_mp; - real_T Delay_DSTATE_f3; - real_T eventTime; - real_T pY; - real_T on_ground_time; - real_T sProtActive; - real_T eventTime_b; - real_T resetEventTime; - real_T sProtActive_c; - real_T sAlphaFloor; - uint8_T is_active_c5_FlyByWire; - uint8_T is_c5_FlyByWire; - uint8_T is_active_c6_FlyByWire; - uint8_T is_c6_FlyByWire; - uint8_T is_active_c7_FlyByWire; - uint8_T is_c7_FlyByWire; - uint8_T is_active_c8_FlyByWire; - uint8_T is_c8_FlyByWire; - uint8_T is_active_c3_FlyByWire; - uint8_T is_c3_FlyByWire; - uint8_T is_active_c9_FlyByWire; - uint8_T is_c9_FlyByWire; - uint8_T is_active_c2_FlyByWire; - uint8_T is_c2_FlyByWire; - uint8_T is_active_c15_FlyByWire; - uint8_T is_c15_FlyByWire; - uint8_T is_active_c1_FlyByWire; - uint8_T is_c1_FlyByWire; - boolean_T icLoad; - boolean_T icLoad_e; - boolean_T icLoad_i; - boolean_T icLoad_l; - boolean_T icLoad_d; - boolean_T eventTime_not_empty; - boolean_T pY_not_empty; - boolean_T eventTime_not_empty_c; - boolean_T resetEventTime_not_empty; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_b5; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_pr; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_bu; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_e; - rtDW_LagFilter_FlyByWire_T sf_LagFilter_n; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_d0; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_h; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_mi; - rtDW_LagFilter_FlyByWire_T sf_LagFilter_e; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_f; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_a5; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_d; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_ny; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_np; - rtDW_LagFilter_FlyByWire_T sf_LagFilter_at; - rtDW_LagFilter_FlyByWire_T sf_LagFilter_pc; - rtDW_LagFilter_FlyByWire_T sf_LagFilter_fr; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_gp; - rtDW_eta_trim_limit_lofreeze_FlyByWire_T sf_eta_trim_limit_upfreeze; - rtDW_eta_trim_limit_lofreeze_FlyByWire_T sf_eta_trim_limit_lofreeze; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_a; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_mr; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_g; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_b; - rtDW_LagFilter_FlyByWire_T sf_LagFilter_lo; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_l; - rtDW_LagFilter_FlyByWire_T sf_LagFilter_p; - rtDW_WashoutFilter_FlyByWire_T sf_WashoutFilter_i; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_m; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_p; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_n; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter_k; - rtDW_RateLimiter_FlyByWire_T sf_RateLimiter; - rtDW_LagFilter_FlyByWire_T sf_LagFilter_ht; - rtDW_WashoutFilter_FlyByWire_T sf_WashoutFilter_l; - rtDW_LagFilter_FlyByWire_T sf_LagFilter_l; - rtDW_WashoutFilter_FlyByWire_T sf_WashoutFilter_c; - rtDW_LagFilter_FlyByWire_T sf_LagFilter_i; - rtDW_WashoutFilter_FlyByWire_T sf_WashoutFilter_b; - rtDW_LagFilter_FlyByWire_T sf_LagFilter_f; - rtDW_WashoutFilter_FlyByWire_T sf_WashoutFilter_o; - rtDW_LagFilter_FlyByWire_T sf_LagFilter_h; - rtDW_WashoutFilter_FlyByWire_T sf_WashoutFilter; - rtDW_LagFilter_FlyByWire_T sf_LagFilter_a; - rtDW_LagFilter_FlyByWire_T sf_LagFilter; - }; - - struct ExternalInputs_FlyByWire_T { - fbw_input in; - }; - - struct ExternalOutputs_FlyByWire_T { - fbw_output out; - }; - - struct Parameters_FlyByWire_T { - fbw_output fbw_output_MATLABStruct; - real_T ScheduledGain_BreakpointsForDimension1[7]; - real_T ScheduledGain_BreakpointsForDimension1_m[4]; - real_T ScheduledGain_BreakpointsForDimension1_p[4]; - real_T ScheduledGain_BreakpointsForDimension1_d[4]; - real_T ScheduledGain_BreakpointsForDimension1_n[4]; - real_T ScheduledGain_BreakpointsForDimension1_h[4]; - real_T ScheduledGain_BreakpointsForDimension1_c[5]; - real_T ScheduledGain_BreakpointsForDimension1_j[5]; - real_T ScheduledGain_BreakpointsForDimension1_a[7]; - real_T ScheduledGain1_BreakpointsForDimension1[7]; - real_T ScheduledGain_BreakpointsForDimension1_jh[4]; - real_T ScheduledGain_BreakpointsForDimension1_cf[9]; - real_T ScheduledGain1_BreakpointsForDimension1_a[5]; - real_T LagFilter_C1; - real_T LagFilter_C1_a; - real_T LagFilter_C1_n; - real_T LagFilter_C1_i; - real_T Subsystem2_C1; - real_T Subsystem_C1; - real_T LagFilter_C1_j; - real_T WashoutFilter_C1; - real_T LagFilter_C1_d; - real_T WashoutFilter_C1_c; - real_T LagFilter1_C1; - real_T WashoutFilter_C1_j; - real_T Subsystem1_C1; - real_T Subsystem3_C1; - real_T LagFilter_C1_h; - real_T WashoutFilter_C1_e; - real_T LagFilter_C1_d2; - real_T WashoutFilter_C1_a; - real_T LagFilter_C1_e; - real_T WashoutFilter_C1_ji; - real_T LagFilter1_C1_j; - real_T LagFilter2_C1; - real_T LagFilter3_C1; - real_T LagFilter_C1_em; - real_T DiscreteDerivativeVariableTs_Gain; - real_T DiscreteDerivativeVariableTs_Gain_c; - real_T DiscreteDerivativeVariableTs_Gain_b; - real_T DiscreteTimeIntegratorVariableTs_Gain; - real_T Subsystem2_Gain; - real_T Subsystem_Gain; - real_T DiscreteDerivativeVariableTs1_Gain; - real_T DiscreteDerivativeVariableTs_Gain_l; - real_T DiscreteDerivativeVariableTs2_Gain; - real_T DiscreteDerivativeVariableTs1_Gain_k; - real_T DiscreteDerivativeVariableTs_Gain_f; - real_T DiscreteDerivativeVariableTs2_Gain_g; - real_T Subsystem1_Gain; - real_T Subsystem3_Gain; - real_T DiscreteDerivativeVariableTs1_Gain_b; - real_T DiscreteDerivativeVariableTs_Gain_e; - real_T DiscreteDerivativeVariableTs2_Gain_a; - real_T DiscreteDerivativeVariableTs1_Gain_p; - real_T DiscreteDerivativeVariableTs_Gain_bf; - real_T DiscreteDerivativeVariableTs2_Gain_j; - real_T DiscreteDerivativeVariableTs1_Gain_kf; - real_T DiscreteDerivativeVariableTs_Gain_ea; - real_T DiscreteDerivativeVariableTs2_Gain_b; - real_T DiscreteTimeIntegratorVariableTs_Gain_k; - real_T DiscreteTimeIntegratorVariableTsLimit_Gain; - real_T DiscreteTimeIntegratorVariableTs_Gain_m; - real_T DiscreteTimeIntegratorVariableTs1_Gain; - real_T RateLimiterVariableTs2_InitialCondition; - real_T RateLimiterVariableTs3_InitialCondition; - real_T RateLimiterVariableTs_InitialCondition; - real_T RateLimiterVariableTs1_InitialCondition; - real_T DiscreteDerivativeVariableTs_InitialCondition; - real_T RateLimiterVariableTs_InitialCondition_d; - real_T RateLimiterVariableTs1_InitialCondition_h; - real_T RateLimiterVariableTs2_InitialCondition_b; - real_T RateLimiterVariableTs3_InitialCondition_b; - real_T RateLimiterDynamicVariableTs_InitialCondition; - real_T RateLimiterVariableTs_InitialCondition_n; - real_T DiscreteDerivativeVariableTs_InitialCondition_d; - real_T DiscreteDerivativeVariableTs_InitialCondition_k; - real_T RateLimiterVariableTs3_InitialCondition_e; - real_T RateLimiterVariableTs1_InitialCondition_hb; - real_T RateLimiterVariableTs_InitialCondition_c; - real_T DiscreteDerivativeVariableTs2_InitialCondition; - real_T DiscreteDerivativeVariableTs2_InitialCondition_c; - real_T RateLimiterVariableTs4_InitialCondition; - real_T RateLimiterVariableTs6_InitialCondition; - real_T DiscreteDerivativeVariableTs1_InitialCondition; - real_T RateLimiterVariableTs6_InitialCondition_b; - real_T DiscreteDerivativeVariableTs_InitialCondition_kb; - real_T DiscreteDerivativeVariableTs2_InitialCondition_i; - real_T DiscreteDerivativeVariableTs1_InitialCondition_f; - real_T DiscreteDerivativeVariableTs_InitialCondition_m; - real_T DiscreteDerivativeVariableTs2_InitialCondition_iw; - real_T RateLimiterVariableTs2_InitialCondition_j; - real_T DiscreteDerivativeVariableTs2_InitialCondition_cx; - real_T DiscreteDerivativeVariableTs2_InitialCondition_b; - real_T RateLimiterVariableTs5_InitialCondition; - real_T DiscreteDerivativeVariableTs1_InitialCondition_fh; - real_T DiscreteDerivativeVariableTs_InitialCondition_c; - real_T DiscreteDerivativeVariableTs2_InitialCondition_h; - real_T DiscreteDerivativeVariableTs1_InitialCondition_h; - real_T DiscreteDerivativeVariableTs_InitialCondition_j; - real_T DiscreteDerivativeVariableTs2_InitialCondition_o; - real_T DiscreteDerivativeVariableTs1_InitialCondition_i; - real_T DiscreteDerivativeVariableTs_InitialCondition_p; - real_T DiscreteDerivativeVariableTs2_InitialCondition_d; - real_T RateLimiterDynamicVariableTs_InitialCondition_i; - real_T RateLimiterVariableTs_InitialCondition_f; - real_T RateLimiterVariableTs_InitialCondition_m; - real_T RateLimiterVariableTs_InitialCondition_j; - real_T RateLimiterVariableTs1_InitialCondition_m; - real_T RateLimiterVariableTs_InitialCondition_fa; - real_T RateLimiterVariableTs_InitialCondition_n3; - real_T RateLimiterDynamicVariableTs_InitialCondition_b; - real_T RateLimitereta_InitialCondition; - real_T RateLimiterxi_InitialCondition; - real_T RateLimiterzeta_InitialCondition; - real_T RateLimiterVariableTs1_InitialCondition_o; - real_T DiscreteTimeIntegratorVariableTs_LowerLimit; - real_T DiscreteTimeIntegratorVariableTs_LowerLimit_b; - real_T DiscreteTimeIntegratorVariableTs_LowerLimit_c; - real_T DiscreteTimeIntegratorVariableTs1_LowerLimit; - real_T ScheduledGain_Table[7]; - real_T ScheduledGain_Table_e[4]; - real_T ScheduledGain_Table_l[4]; - real_T ScheduledGain_Table_b[4]; - real_T ScheduledGain_Table_g[4]; - real_T ScheduledGain_Table_ga[4]; - real_T ScheduledGain_Table_p[5]; - real_T ScheduledGain_Table_i[5]; - real_T ScheduledGain_Table_eg[7]; - real_T ScheduledGain1_Table[7]; - real_T ScheduledGain_Table_c[4]; - real_T ScheduledGain_Table_d[9]; - real_T ScheduledGain1_Table_o[5]; - real_T DiscreteTimeIntegratorVariableTs_UpperLimit; - real_T DiscreteTimeIntegratorVariableTs_UpperLimit_c; - real_T DiscreteTimeIntegratorVariableTs_UpperLimit_n; - real_T DiscreteTimeIntegratorVariableTs1_UpperLimit; - real_T CompareToConstant_const; - real_T CompareToConstant_const_o; - real_T RateLimiterVariableTs2_lo; - real_T RateLimiterVariableTs3_lo; - real_T RateLimiterVariableTs_lo; - real_T RateLimiterVariableTs1_lo; - real_T RateLimiterVariableTs_lo_c; - real_T RateLimiterVariableTs1_lo_c; - real_T RateLimiterVariableTs2_lo_m; - real_T RateLimiterVariableTs3_lo_l; - real_T RateLimiterVariableTs_lo_d; - real_T RateLimiterVariableTs3_lo_e; - real_T RateLimiterVariableTs1_lo_h; - real_T RateLimiterVariableTs_lo_f; - real_T RateLimiterVariableTs4_lo; - real_T RateLimiterVariableTs6_lo; - real_T RateLimiterVariableTs6_lo_m; - real_T RateLimiterVariableTs2_lo_n; - real_T RateLimiterVariableTs5_lo; - real_T RateLimiterVariableTs_lo_fs; - real_T RateLimiterVariableTs_lo_k; - real_T RateLimiterVariableTs_lo_g; - real_T RateLimiterVariableTs1_lo_n; - real_T RateLimiterVariableTs_lo_e; - real_T RateLimiterVariableTs_lo_m; - real_T RateLimitereta_lo; - real_T RateLimiterxi_lo; - real_T RateLimiterzeta_lo; - real_T RateLimiterVariableTs1_lo_cu; - real_T RateLimiterVariableTs2_up; - real_T RateLimiterVariableTs3_up; - real_T RateLimiterVariableTs_up; - real_T RateLimiterVariableTs1_up; - real_T RateLimiterVariableTs_up_d; - real_T RateLimiterVariableTs1_up_n; - real_T RateLimiterVariableTs2_up_f; - real_T RateLimiterVariableTs3_up_c; - real_T RateLimiterVariableTs_up_dl; - real_T RateLimiterVariableTs3_up_m; - real_T RateLimiterVariableTs1_up_k; - real_T RateLimiterVariableTs_up_f; - real_T RateLimiterVariableTs4_up; - real_T RateLimiterVariableTs6_up; - real_T RateLimiterVariableTs6_up_f; - real_T RateLimiterVariableTs2_up_b; - real_T RateLimiterVariableTs5_up; - real_T RateLimiterVariableTs_up_k; - real_T RateLimiterVariableTs_up_m; - real_T RateLimiterVariableTs_up_i; - real_T RateLimiterVariableTs1_up_j; - real_T RateLimiterVariableTs_up_f1; - real_T RateLimiterVariableTs_up_l; - real_T RateLimitereta_up; - real_T RateLimiterxi_up; - real_T RateLimiterzeta_up; - real_T RateLimiterVariableTs1_up_p; - boolean_T CompareToConstant_const_h; - real_T Constant_Value; - real_T qk_dot_gain1_Gain; - real_T qk_gain_HSP_Gain; - real_T v_dot_gain_HSP_Gain; - real_T Gain6_Gain; - real_T precontrol_gain_HSP_Gain; - real_T HSP_gain_Gain; - real_T Saturation4_UpperSat; - real_T Saturation4_LowerSat; - real_T Saturation8_UpperSat; - real_T Saturation8_LowerSat; - real_T Constant1_Value; - real_T Loaddemand_tableData[3]; - real_T Loaddemand_bp01Data[3]; - real_T Constant_Value_m; - real_T Constant_Value_b; - real_T Saturation_UpperSat; - real_T Saturation_LowerSat; - real_T Gain_Gain; - real_T Saturation_UpperSat_j; - real_T Saturation_LowerSat_b; - real_T Constant_Value_k; - real_T Saturation_UpperSat_o; - real_T Saturation_LowerSat_k; - real_T Switch2_Threshold; - real_T Saturation_UpperSat_g; - real_T Saturation_LowerSat_p; - real_T Constant1_Value_i; - real_T Constant2_Value; - real_T Constant3_Value; - real_T Gain2_Gain; - real_T BankAngleProtection2_tableData[5]; - real_T BankAngleProtection2_bp01Data[5]; - real_T BankAngleProtection_tableData[9]; - real_T BankAngleProtection_bp01Data[9]; - real_T BankAngleProtection1_tableData[9]; - real_T BankAngleProtection1_bp01Data[9]; - real_T Switch2_Threshold_i; - real_T Switch1_Threshold; - real_T Constant_Value_l; - real_T Constant2_Value_d; - real_T Constant_Value_j; - real_T Delay_InitialCondition; - real_T GainTheta_Gain; - real_T GainPhi_Gain; - real_T Gain_Gain_n; - real_T Gainqk_Gain; - real_T Gain_Gain_l; - real_T Gain_Gain_a; - real_T Gainpk_Gain; - real_T Gain_Gain_e; - real_T Gainqk1_Gain; - real_T Gain_Gain_aw; - real_T Gain_Gain_nm; - real_T Gainpk1_Gain; - real_T Gainpk4_Gain; - real_T Gainpk2_Gain; - real_T Gainpk5_Gain; - real_T Gainpk6_Gain; - real_T Gainpk3_Gain; - real_T Gain_Gain_i; - real_T Constant_Value_g; - real_T Saturation_UpperSat_e; - real_T Saturation_LowerSat_e; - real_T Gain1_Gain; - real_T Saturation1_UpperSat; - real_T Saturation1_LowerSat; - real_T Gain2_Gain_a; - real_T Saturation2_UpperSat; - real_T Saturation2_LowerSat; - real_T Gaineta_Gain; - real_T Gainxi_Gain; - real_T Gainxi1_Gain; - real_T alphamax_tableData[24]; - real_T alphamax_bp01Data[4]; - real_T alphamax_bp02Data[6]; - real_T alpha0_tableData[6]; - real_T alpha0_bp01Data[6]; - real_T alphaprotection_tableData[24]; - real_T alphaprotection_bp01Data[4]; - real_T alphaprotection_bp02Data[6]; - real_T alphafloor_tableData[24]; - real_T alphafloor_bp01Data[4]; - real_T alphafloor_bp02Data[6]; - real_T Constant5_Value; - real_T Constant6_Value; - real_T Gain1_Gain_c; - real_T uDLookupTable1_tableData[4]; - real_T uDLookupTable1_bp01Data[4]; - real_T uDLookupTable2_tableData[4]; - real_T uDLookupTable2_bp01Data[4]; - real_T uDLookupTable_tableData[4]; - real_T uDLookupTable_bp01Data[4]; - real_T Constant7_Value; - real_T Constant8_Value; - real_T Switch_Threshold; - real_T Switch1_Threshold_k; - real_T Gain_Gain_d; - real_T Saturation_UpperSat_er; - real_T Saturation_LowerSat_a; - real_T Constant1_Value_f; - real_T Constant_Value_jz; - real_T Saturation1_UpperSat_f; - real_T Saturation1_LowerSat_p; - real_T Constant2_Value_l; - real_T uDLookupTable_tableData_d[25]; - real_T uDLookupTable_bp01Data_l[5]; - real_T uDLookupTable_bp02Data[5]; - real_T Saturation3_UpperSat; - real_T Saturation3_LowerSat; - real_T PitchRateDemand_tableData[3]; - real_T PitchRateDemand_bp01Data[3]; - real_T Gain3_Gain; - real_T Gain_Gain_h; - real_T Gain1_Gain_i; - real_T Gain1_Gain_a; - real_T Gain5_Gain; - real_T Gain4_Gain; - real_T Gain6_Gain_f; - real_T Constant_Value_h; - real_T Switch_Threshold_h; - real_T Gain1_Gain_p; - real_T Gain1_Gain_pa; - real_T Gain1_Gain_j; - real_T Vm_currentms_Value; - real_T Gain_Gain_dc; - real_T Delay_InitialCondition_j; - real_T Constant_Value_m3; - real_T Delay1_InitialCondition; - real_T Delay_InitialCondition_l; - real_T Constant_Value_hz; - real_T Delay1_InitialCondition_a; - real_T Switch1_Threshold_ke; - real_T Gain3_Gain_j; - real_T Gain1_Gain_c4; - real_T Vm_currentms_Value_l; - real_T Gain_Gain_i4; - real_T uDLookupTable_tableData_c[7]; - real_T uDLookupTable_bp01Data_f[7]; - real_T Saturation3_UpperSat_k; - real_T Saturation3_LowerSat_l; - real_T Gain5_Gain_k; - real_T Bias_Bias; - real_T Gain2_Gain_g; - real_T Gain1_Gain_d; - real_T Saturation1_UpperSat_h; - real_T Saturation1_LowerSat_o; - real_T Loaddemand1_tableData[3]; - real_T Loaddemand1_bp01Data[3]; - real_T PLUT_tableData[2]; - real_T PLUT_bp01Data[2]; - real_T DLUT_tableData[2]; - real_T DLUT_bp01Data[2]; - real_T SaturationV_dot_UpperSat; - real_T SaturationV_dot_LowerSat; - real_T Gain_Gain_de; - real_T SaturationSpoilers_UpperSat; - real_T SaturationSpoilers_LowerSat; - real_T Saturation_UpperSat_i; - real_T Saturation_LowerSat_f; - real_T Gain3_Gain_jk; - real_T Gain1_Gain_g; - real_T Vm_currentms_Value_e; - real_T Gain_Gain_g; - real_T uDLookupTable_tableData_e[7]; - real_T uDLookupTable_bp01Data_d[7]; - real_T Saturation3_UpperSat_l; - real_T Saturation3_LowerSat_n; - real_T Gain5_Gain_i; - real_T Bias_Bias_b; - real_T PLUT_tableData_j[2]; - real_T PLUT_bp01Data_j[2]; - real_T DLUT_tableData_p[2]; - real_T DLUT_bp01Data_p[2]; - real_T SaturationV_dot_UpperSat_e; - real_T SaturationV_dot_LowerSat_c; - real_T Gain_Gain_f; - real_T SaturationSpoilers_UpperSat_h; - real_T SaturationSpoilers_LowerSat_h; - real_T Saturation_UpperSat_h; - real_T Saturation_LowerSat_l; - real_T Delay_InitialCondition_k; - real_T Constant_Value_kr; - real_T Delay1_InitialCondition_i; - real_T precontrol_gain_Gain; - real_T alpha_err_gain_Gain; - real_T Delay_InitialCondition_p; - real_T Constant_Value_c; - real_T Delay1_InitialCondition_k; - real_T v_dot_gain_Gain; - real_T qk_gain_Gain; - real_T qk_dot_gain_Gain; - real_T Saturation3_UpperSat_c; - real_T Saturation3_LowerSat_h; - real_T Saturation_UpperSat_a; - real_T Saturation_LowerSat_ps; - real_T Constant_Value_p; - real_T Gain3_Gain_l; - real_T uDLookupTable_tableData_l[7]; - real_T uDLookupTable_bp01Data_j[7]; - real_T Saturation3_UpperSat_p; - real_T Saturation3_LowerSat_i; - real_T Gain5_Gain_g; - real_T Bias_Bias_d; - real_T Saturation_UpperSat_d; - real_T Saturation_LowerSat_pr; - real_T Gain1_Gain_b; - real_T PLUT_tableData_e[2]; - real_T PLUT_bp01Data_h[2]; - real_T DLUT_tableData_m[2]; - real_T DLUT_bp01Data_a[2]; - real_T SaturationV_dot_UpperSat_ee; - real_T SaturationV_dot_LowerSat_m; - real_T Gain_Gain_o; - real_T SaturationSpoilers_UpperSat_c; - real_T SaturationSpoilers_LowerSat_n; - real_T Saturation_UpperSat_j1; - real_T Saturation_LowerSat_c; - real_T Gain3_Gain_c; - real_T Gain1_Gain_gs; - real_T Vm_currentms_Value_c; - real_T Gain_Gain_hy; - real_T uDLookupTable_tableData_p[7]; - real_T uDLookupTable_bp01Data_lk[7]; - real_T Saturation3_UpperSat_b; - real_T Saturation3_LowerSat_c; - real_T Gain5_Gain_e; - real_T Bias_Bias_dw; - real_T PLUT_tableData_l[2]; - real_T PLUT_bp01Data_i[2]; - real_T DLUT_tableData_a[2]; - real_T DLUT_bp01Data_f[2]; - real_T SaturationV_dot_UpperSat_j; - real_T SaturationV_dot_LowerSat_m3; - real_T Gain_Gain_f0; - real_T SaturationSpoilers_UpperSat_ci; - real_T SaturationSpoilers_LowerSat_j; - real_T Saturation_UpperSat_f; - real_T Saturation_LowerSat_lf; - real_T Gain3_Gain_n; - real_T Gain1_Gain_gh; - real_T Vm_currentms_Value_h; - real_T Gain_Gain_et; - real_T uDLookupTable_tableData_j[7]; - real_T uDLookupTable_bp01Data_le[7]; - real_T Saturation3_UpperSat_d; - real_T Saturation3_LowerSat_a; - real_T Gain5_Gain_p; - real_T Bias_Bias_n; - real_T Theta_max3_Value; - real_T Gain3_Gain_m; - real_T Saturation2_UpperSat_g; - real_T Saturation2_LowerSat_i; - real_T Loaddemand2_tableData[3]; - real_T Loaddemand2_bp01Data[3]; - real_T PLUT_tableData_j5[2]; - real_T PLUT_bp01Data_b[2]; - real_T DLUT_tableData_ah[2]; - real_T DLUT_bp01Data_ai[2]; - real_T SaturationV_dot_UpperSat_d; - real_T SaturationV_dot_LowerSat_d; - real_T Gain_Gain_fw; - real_T SaturationSpoilers_UpperSat_j; - real_T SaturationSpoilers_LowerSat_f; - real_T Saturation_UpperSat_eo; - real_T Saturation_LowerSat_ar; - real_T Switch_Threshold_d; - real_T Saturation_UpperSat_g4; - real_T Saturation_LowerSat_la; - real_T Constant_Value_o; - real_T Saturation_UpperSat_c; - real_T Saturation_LowerSat_m; - real_T Constant_Value_ju; - real_T Gain_Gain_ip; - real_T Gain_Gain_c; - real_T uDLookupTable_tableData_f[4]; - real_T uDLookupTable_bp01Data_fm[4]; - real_T Gain1_Gain_jh; - real_T Saturation_UpperSat_p; - real_T Saturation_LowerSat_h; - real_T Gain4_Gain_f; - real_T Saturation_UpperSat_l; - real_T Saturation_LowerSat_cj; - real_T Gain5_Gain_l; - real_T Saturation1_UpperSat_fa; - real_T Saturation1_LowerSat_om; - real_T Gain_Gain_oq; - real_T Gain1_Gain_bq; - real_T Switch3_Threshold; - real_T Saturation_UpperSat_as; - real_T Saturation_LowerSat_o; - real_T Delay_InitialCondition_d; - real_T Saturation_UpperSat_gn; - real_T Saturation_LowerSat_en; - real_T Switch_Threshold_j; - real_T Gain1_Gain_n; - real_T Gain1_Gain_bqd; - real_T Gain1_Gain_cb; - real_T Gain_Gain_p; - real_T Constant2_Value_p; - real_T Gain1_Gain_f; - real_T Gain1_Gain_l; - real_T Saturation_UpperSat_ek; - real_T Saturation_LowerSat_j; - real_T Gain6_Gain_k; - real_T Gain_Gain_i3; - real_T Saturation1_UpperSat_j; - real_T Saturation1_LowerSat_a; - real_T Saturation_UpperSat_n; - real_T Saturation_LowerSat_bc; - real_T Constant_Value_ku; - real_T Saturation2_UpperSat_n; - real_T Saturation2_LowerSat_a; - real_T Saturation_UpperSat_cr; - real_T Saturation_LowerSat_o4; - real_T Constant_Value_i; - real_T Switch2_Threshold_n; - real_T Saturation_UpperSat_p4; - real_T Saturation_LowerSat_he; - real_T Switch_Threshold_p; - real_T Saturation_UpperSat_lt; - real_T Saturation_LowerSat_ol; - real_T Constant_Value_d; - real_T Saturation1_UpperSat_e; - real_T Saturation1_LowerSat_l; - real_T Saturation_UpperSat_ll; - real_T Saturation_LowerSat_og; - real_T Constant_Value_l1; - real_T Saturation_UpperSat_eq; - real_T Saturation_LowerSat_n; - real_T Saturation_UpperSat_il; - real_T Saturation_LowerSat_fr; - real_T Constant_Value_f; - real_T Gain4_Gain_e; - real_T Constant_Value_li; - real_T Constant1_Value_h; - real_T Gaineta_Gain_d; - real_T Limitereta_UpperSat; - real_T Limitereta_LowerSat; - real_T GainiH_Gain; - real_T LimiteriH_UpperSat; - real_T LimiteriH_LowerSat; - real_T Gainxi_Gain_n; - real_T Limiterxi_UpperSat; - real_T Limiterxi_LowerSat; - real_T Gainxi1_Gain_e; - real_T Limiterxi1_UpperSat; - real_T Limiterxi1_LowerSat; - real_T Gainxi2_Gain; - real_T Limiterxi2_UpperSat; - real_T Limiterxi2_LowerSat; - uint32_T alphamax_maxIndex[2]; - uint32_T alphaprotection_maxIndex[2]; - uint32_T alphafloor_maxIndex[2]; - uint32_T uDLookupTable_maxIndex[2]; - uint8_T ManualSwitch_CurrentSetting; - uint8_T ManualSwitch1_CurrentSetting; - }; - - FlyByWireModelClass(FlyByWireModelClass const&) =delete; - FlyByWireModelClass& operator= (FlyByWireModelClass const&) & = delete; - void setExternalInputs(const ExternalInputs_FlyByWire_T *pExternalInputs_FlyByWire_T) - { - FlyByWire_U = *pExternalInputs_FlyByWire_T; - } - - const ExternalOutputs_FlyByWire_T &getExternalOutputs() const - { - return FlyByWire_Y; - } - - void initialize(); - void step(); - static void terminate(); - FlyByWireModelClass(); - ~FlyByWireModelClass(); - private: - ExternalInputs_FlyByWire_T FlyByWire_U; - ExternalOutputs_FlyByWire_T FlyByWire_Y; - BlockIO_FlyByWire_T FlyByWire_B; - D_Work_FlyByWire_T FlyByWire_DWork; - static Parameters_FlyByWire_T FlyByWire_P; - static void FlyByWire_GetIASforMach4(real_T rtu_m, real_T rtu_m_t, real_T rtu_v, real_T *rty_v_t); - static void FlyByWire_LagFilter(real_T rtu_U, real_T rtu_C1, real_T rtu_dt, real_T *rty_Y, rtDW_LagFilter_FlyByWire_T * - localDW); - static void FlyByWire_WashoutFilter(real_T rtu_U, real_T rtu_C1, real_T rtu_dt, real_T *rty_Y, - rtDW_WashoutFilter_FlyByWire_T *localDW); - static void FlyByWire_RateLimiter(real_T rtu_u, real_T rtu_up, real_T rtu_lo, real_T rtu_Ts, real_T rtu_init, real_T - *rty_Y, rtDW_RateLimiter_FlyByWire_T *localDW); - static void FlyByWire_VoterAttitudeProtection(real_T rtu_input, real_T rtu_input_o, real_T rtu_input_c, real_T - *rty_vote); - static void FlyByWire_eta_trim_limit_lofreeze(real_T rtu_eta_trim, real_T rtu_trigger, real_T *rty_y, - rtDW_eta_trim_limit_lofreeze_FlyByWire_T *localDW); - static void FlyByWire_ConvertToEuler(real_T rtu_Theta, real_T rtu_Phi, real_T rtu_q, real_T rtu_r, real_T rtu_p, - real_T *rty_qk, real_T *rty_rk, real_T *rty_pk); - static void FlyByWire_CalculateV_alpha_max(real_T rtu_v_ias, real_T rtu_alpha, real_T rtu_alpha_0, real_T - rtu_alpha_target, real_T *rty_V_alpha_target); -}; - -#endif - diff --git a/src/fbw/src/model/FlyByWire_data.cpp b/src/fbw/src/model/FlyByWire_data.cpp deleted file mode 100644 index b45bcad5fff7..000000000000 --- a/src/fbw/src/model/FlyByWire_data.cpp +++ /dev/null @@ -1,1397 +0,0 @@ -#include "FlyByWire.h" -#include "FlyByWire_private.h" - -FlyByWireModelClass::Parameters_FlyByWire_T FlyByWireModelClass::FlyByWire_P{ - - { - { - { - 0.0, - 0.0, - 0.0 - }, - - { - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - false, - 0.0 - }, - - { - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0 - }, - - { - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0 - }, - - { - 0.0, - 0.0, - 0.0 - } - }, - - { - { - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - false, - false, - 0.0, - false, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0 - }, - - { - 0.0, - 0.0 - }, - - { - 0.0, - 0.0, - 0.0, - 0.0, - 0.0 - }, - - { - 0.0 - }, - - { - 0.0 - }, - - { - 0.0, - 0.0 - } - }, - - { - { - 0.0, - 0.0, - 0.0, - 0.0, - false, - 0.0 - }, - - { - 0.0, - 0.0, - 0.0, - 0.0, - 0.0 - }, - - { - 0.0, - 0.0, - 0.0 - } - }, - - { - 0.0, - 0.0, - false, - 0.0, - 0.0, - 0.0, - false - } - }, - - - { 0.0, 100.0, 150.0, 200.0, 250.0, 300.0, 400.0 }, - - - { 0.0, 50.0, 100.0, 200.0 }, - - - { 0.0, 50.0, 100.0, 200.0 }, - - - { 0.0, 50.0, 100.0, 200.0 }, - - - { 0.0, 50.0, 100.0, 200.0 }, - - - { 0.0, 50.0, 100.0, 200.0 }, - - - { 0.0, 0.06, 0.1, 0.2, 1.0 }, - - - { 0.0, 0.06, 0.1, 0.2, 1.0 }, - - - { 0.0, 100.0, 150.0, 200.0, 250.0, 300.0, 400.0 }, - - - { 0.0, 100.0, 150.0, 200.0, 250.0, 300.0, 400.0 }, - - - { 0.0, 120.0, 150.0, 380.0 }, - - - { 0.0, 140.0, 180.0, 220.0, 250.0, 270.0, 300.0, 320.0, 400.0 }, - - - { 0.0, 130.0, 200.0, 250.0, 300.0 }, - - 0.5, - - 0.4, - - 0.05, - - 1.0, - - 2.0, - - 2.0, - - 0.3, - - 5.0, - - 0.3, - - 5.0, - - 1.6, - - 1.0, - - 2.0, - - 2.0, - - 0.3, - - 5.0, - - 0.3, - - 5.0, - - 0.3, - - 5.0, - - 0.5, - - 0.5, - - 0.5, - - 2.0, - - 0.5, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 2.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 30.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - 0.0, - - -30.0, - - -30.0, - - -67.0, - - -20.0, - - - { 0.1, 0.1, 0.15, 0.2, 0.3, 0.5, 0.5 }, - - - { 0.0, 0.0, -30.0, -30.0 }, - - - { 0.0, 0.0, -30.0, -30.0 }, - - - { 0.0, 0.0, -30.0, -30.0 }, - - - { 0.0, 0.0, -30.0, -30.0 }, - - - { 0.0, 0.0, -30.0, -30.0 }, - - - { 1.0, 1.0, 0.5, 0.3, 0.3 }, - - - { 1.1, 1.0, 0.6, 0.3, 0.1 }, - - - { 4.5, 4.5, 4.5, 3.5, 2.0, 1.5, 1.5 }, - - - { 1.4, 1.4, 1.4, 1.2, 1.0, 0.8, 0.8 }, - - - { -15.0, -15.0, -15.0, -2.0 }, - - - { 1.1, 1.3, 1.8, 2.0, 2.2, 2.5, 2.7, 3.2, 3.8 }, - - - { 3.0, 3.0, 2.5, 1.0, 1.0 }, - - 30.0, - - 30.0, - - 67.0, - - 20.0, - - 5.0, - - 100.0, - - -1.0, - - -1.0, - - -1.0, - - -1.0, - - -0.2, - - -0.5, - - -0.5, - - -0.5, - - -0.5, - - -0.5, - - -2.0, - - -1.0, - - -0.33333333333333331, - - -4.0, - - -1.0, - - -1.0, - - -0.25, - - -2.0, - - -15.0, - - -15.0, - - -2.0, - - -1000.0, - - -0.25, - - -45.0, - - -50.0, - - -45.0, - - -5.0, - - 1.0, - - 1.0, - - 1.0, - - 1.0, - - 0.2, - - 0.5, - - 0.5, - - 0.5, - - 0.5, - - 0.5, - - 2.0, - - 1.0, - - 4.0, - - 2.0, - - 1.0, - - 1.0, - - 4.0, - - 2.0, - - 15.0, - - 15.0, - - 2.0, - - 0.33333333333333331, - - 2.0, - - 45.0, - - 50.0, - - 45.0, - - 5.0, - - true, - - 0.0, - - -0.4, - - -0.4, - - 1.4, - - -0.1, - - -0.6, - - 0.2, - - 0.75, - - -0.75, - - 1.5, - - 0.0, - - 0.0, - - - { -2.0, 0.0, 1.5 }, - - - { -1.0, 0.0, 1.0 }, - - 0.0, - - 1.0, - - 1.0, - - 0.0, - - 0.06, - - 0.0, - - -0.8, - - 1.0, - - 1.0, - - 0.0, - - 0.0, - - 30.0, - - 0.0, - - 0.0, - - 3.5, - - -11.0, - - -1.0, - - - { 20.0, 15.0, 0.0, -15.0, -20.0 }, - - - { -50.0, -40.0, 0.0, 40.0, 50.0 }, - - - { 20.0, 20.0, 15.0, 0.0, 0.0, 0.0, -15.0, -20.0, -20.0 }, - - - { -90.0, -66.0, -45.0, -33.0, 0.0, 33.0, 45.0, 66.0, 90.0 }, - - - { 20.0, 20.0, 15.0, 0.0, 0.0, 0.0, -15.0, -20.0, -20.0 }, - - - { -90.0, -71.0, -66.0, -33.0, 0.0, 33.0, 66.0, 71.0, 90.0 }, - - 0.0, - - 0.0, - - 1.0, - - 0.0, - - 0.0, - - 0.0, - - -1.0, - - -1.0, - - 57.295779513082323, - - -1.0, - - 57.295779513082323, - - 57.295779513082323, - - -1.0, - - 57.295779513082323, - - -1.0, - - 57.295779513082323, - - 57.295779513082323, - - -1.0, - - -30.0, - - -1.0, - - -25.0, - - -25.0, - - -25.0, - - 2.0, - - 1.0, - - 1.0, - - 0.0, - - 2.0, - - 1.0, - - 0.0, - - 2.0, - - 1.0, - - 0.0, - - -1.0, - - -1.0, - - -1.0, - - - { 8.7, 8.7, 6.4, 6.4, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 14.2, 14.2, 14.2, 14.2, 13.1, 13.1, 13.1, 13.1, - 13.0, 13.0, 13.0, 13.0 }, - - - { 0.0, 0.5, 0.9, 1.0 }, - - - { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 }, - - - { -1.84, -1.84, -2.18, -4.72, -4.27, -6.94 }, - - - { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 }, - - - { 6.5, 6.5, 4.6, 4.6, 11.7, 11.7, 11.7, 11.7, 11.7, 11.7, 11.7, 11.7, 11.9, 11.9, 11.9, 11.9, 11.0, 11.0, 11.0, 11.0, - 10.6, 10.6, 10.6, 10.6 }, - - - { 0.0, 0.5, 0.9, 1.0 }, - - - { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 }, - - - { 7.6, 7.6, 6.03, 6.03, 12.5, 12.5, 12.5, 12.5, 12.7, 12.7, 12.7, 12.7, 13.1, 13.1, 13.1, 13.1, 12.1, 12.1, 12.1, 12.1, - 11.8, 11.8, 11.8, 11.8 }, - - - { 0.0, 0.5, 0.9, 1.0 }, - - - { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 }, - - 350.0, - - 0.82, - - 0.017453292519943295, - - - { 350.0, 350.0, 356.0, 356.0 }, - - - { -4.0, -3.0, -1.0, 0.0 }, - - - { 0.82, 0.82, 0.83, 0.83 }, - - - { -4.0, -3.0, -1.0, 0.0 }, - - - { 0.82, 0.82, 0.85, 0.85 }, - - - { -4.0, -3.0, -1.0, 0.0 }, - - 380.0, - - 0.88, - - 0.0, - - 0.0, - - -30.0, - - 1.0, - - 0.0, - - 0.0, - - 1.0, - - 1.0, - - 0.0, - - 1.0, - - - { 0.0, 0.0, 0.0, -0.75, -0.75, 0.0, 0.0, 0.0, -0.75, -0.75, 0.0, 0.0, 0.0, 0.0, -0.75, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0 }, - - - { -1.0, 0.0, 6.0, 11.0, 15.0 }, - - - { -1.0, 8.0, 18.0, 28.0, 40.0 }, - - 1.0, - - 0.0, - - - { -9.0, 0.0, 9.0 }, - - - { -1.0, 0.0, 1.0 }, - - -2.0, - - 4.0, - - 0.2, - - 1.0, - - 2.5, - - -1.5, - - 2.0, - - 0.0, - - 0.0, - - 0.017453292519943295, - - 0.017453292519943295, - - 0.017453292519943295, - - 100.0, - - 0.1019367991845056, - - 0.0, - - 2.0, - - 0.0, - - 0.0, - - 2.0, - - 0.0, - - 0.0, - - 1.0, - - 0.017453292519943295, - - 100.0, - - 0.1019367991845056, - - - { 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0 }, - - - { 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0 }, - - 2000.0, - - 100.0, - - 0.51444444444444448, - - 1.0, - - 0.2, - - 0.2, - - 1.0, - - -0.25, - - - { -2.0, 0.0, 1.5 }, - - - { -1.0, 0.0, 1.0 }, - - - { 13.5, 13.5 }, - - - { 0.0, 350.0 }, - - - { 0.5, 0.5 }, - - - { 0.0, 350.0 }, - - 1.0, - - -1.0, - - 0.06, - - 1.0, - - -1.0, - - 30.0, - - -30.0, - - 1.0, - - 0.017453292519943295, - - 100.0, - - 0.1019367991845056, - - - { 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0 }, - - - { 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0 }, - - 2000.0, - - 100.0, - - 0.51444444444444448, - - 1.0, - - - { 13.5, 13.5 }, - - - { 0.0, 350.0 }, - - - { 0.5, 0.5 }, - - - { 0.0, 350.0 }, - - 1.0, - - -1.0, - - 0.06, - - 1.0, - - -1.0, - - 30.0, - - -30.0, - - 0.0, - - 2.0, - - 0.0, - - -1.0, - - -1.2, - - 0.0, - - 2.0, - - 0.0, - - -0.8, - - 1.0, - - 1.0, - - 4.0, - - -4.0, - - 1.0, - - 0.0, - - 1.0, - - 1.0, - - - { 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0 }, - - - { 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0 }, - - 2000.0, - - 100.0, - - 0.51444444444444448, - - 1.0, - - 33.0, - - -33.0, - - 0.017453292519943295, - - - { 13.5, 13.5 }, - - - { 0.0, 350.0 }, - - - { 0.5, 0.5 }, - - - { 0.0, 350.0 }, - - 1.0, - - -1.0, - - 0.06, - - 1.0, - - -1.0, - - 30.0, - - -30.0, - - 1.0, - - 0.017453292519943295, - - 100.0, - - 0.1019367991845056, - - - { 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0 }, - - - { 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0 }, - - 2000.0, - - 100.0, - - 0.51444444444444448, - - 1.0, - - - { 13.5, 13.5 }, - - - { 0.0, 350.0 }, - - - { 0.5, 0.5 }, - - - { 0.0, 350.0 }, - - 1.0, - - -1.0, - - 0.06, - - 1.0, - - -1.0, - - 30.0, - - -30.0, - - 1.0, - - 0.017453292519943295, - - 100.0, - - 0.1019367991845056, - - - { 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0 }, - - - { 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0 }, - - 2000.0, - - 100.0, - - 0.51444444444444448, - - 1.0, - - -15.0, - - 0.2, - - 0.25, - - -1.0, - - - { -2.0, 0.0, 1.5 }, - - - { -1.0, 0.0, 1.0 }, - - - { 13.5, 13.5 }, - - - { 0.0, 350.0 }, - - - { 0.5, 0.5 }, - - - { 0.0, 350.0 }, - - 1.0, - - -1.0, - - 0.06, - - 1.0, - - -1.0, - - 30.0, - - -30.0, - - 0.0, - - 1.0, - - 0.0, - - 1.0, - - 1.0, - - 0.0, - - 1.0, - - 0.076923076923076927, - - -25.0, - - - { 25.0, 25.0, 3.5, 3.5 }, - - - { 0.0, 160.0, 410.0, 500.0 }, - - -25.0, - - 1.0, - - 0.0, - - 0.1101, - - 5.0, - - -5.0, - - 0.0302, - - 1000.0, - - 1.0, - - 57.295779513082323, - - 15.0, - - 0.0, - - 15.0, - - -15.0, - - 0.0, - - 67.0, - - -67.0, - - 0.0, - - 0.017453292519943295, - - 0.017453292519943295, - - 0.017453292519943295, - - 57.295779513082323, - - 9.81, - - 0.017453292519943295, - - 0.017453292519943295, - - 1000.0, - - 100.0, - - 0.51444444444444448, - - 57.295779513082323, - - 25.0, - - -25.0, - - 1.0, - - 0.0, - - 1.0, - - 25.0, - - -25.0, - - 1.0, - - 0.0, - - 1.0, - - 0.0, - - 25.0, - - -25.0, - - 0.0, - - 1.0, - - 0.0, - - 1.0, - - 1.0, - - 0.0, - - 1.0, - - 0.0, - - 1.0, - - 1.0, - - 0.0, - - 1.0, - - 0.0, - - 1.0, - - 0.33333333333333331, - - 1.7, - - -1.7, - - -0.033333333333333333, - - 1.0, - - -1.0, - - -1.0, - - 13.0, - - -3.5, - - -0.04, - - 1.0, - - -1.0, - - -0.04, - - 1.0, - - -1.0, - - -0.04, - - 1.0, - - -1.0, - - - { 3U, 5U }, - - - { 3U, 5U }, - - - { 3U, 5U }, - - - { 4U, 4U }, - - 1U, - - 1U -}; diff --git a/src/fbw/src/model/FlyByWire_private.h b/src/fbw/src/model/FlyByWire_private.h deleted file mode 100644 index b611548868f1..000000000000 --- a/src/fbw/src/model/FlyByWire_private.h +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef RTW_HEADER_FlyByWire_private_h_ -#define RTW_HEADER_FlyByWire_private_h_ -#include "rtwtypes.h" -#endif - diff --git a/src/fbw/src/model/FlyByWire_types.h b/src/fbw/src/model/FlyByWire_types.h deleted file mode 100644 index 4978f3eb8562..000000000000 --- a/src/fbw/src/model/FlyByWire_types.h +++ /dev/null @@ -1,426 +0,0 @@ -#ifndef RTW_HEADER_FlyByWire_types_h_ -#define RTW_HEADER_FlyByWire_types_h_ -#include "rtwtypes.h" - -#ifndef DEFINED_TYPEDEF_FOR_base_raw_time_ -#define DEFINED_TYPEDEF_FOR_base_raw_time_ - -struct base_raw_time -{ - real_T dt; - real_T simulation_time; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_raw_data_ -#define DEFINED_TYPEDEF_FOR_base_raw_data_ - -struct base_raw_data -{ - real_T nz_g; - real_T Theta_deg; - real_T Phi_deg; - real_T q_rad_s; - real_T r_rad_s; - real_T p_rad_s; - real_T q_dot_rad_s2; - real_T r_dot_rad_s2; - real_T p_dot_rad_s2; - real_T psi_magnetic_deg; - real_T psi_true_deg; - real_T eta_pos; - real_T eta_trim_deg; - real_T xi_pos; - real_T zeta_pos; - real_T zeta_trim_pos; - real_T alpha_deg; - real_T beta_deg; - real_T beta_dot_deg_s; - real_T V_ias_kn; - real_T V_tas_kn; - real_T V_mach; - real_T H_ft; - real_T H_ind_ft; - real_T H_radio_ft; - real_T CG_percent_MAC; - real_T total_weight_kg; - real_T gear_animation_pos_0; - real_T gear_animation_pos_1; - real_T gear_animation_pos_2; - real_T flaps_handle_index; - real_T spoilers_left_pos; - real_T spoilers_right_pos; - real_T autopilot_master_on; - real_T slew_on; - real_T pause_on; - real_T tracking_mode_on_override; - real_T autopilot_custom_on; - real_T autopilot_custom_Theta_c_deg; - real_T autopilot_custom_Phi_c_deg; - real_T autopilot_custom_Beta_c_deg; - real_T simulation_rate; - real_T ice_structure_percent; - real_T linear_cl_alpha_per_deg; - real_T alpha_stall_deg; - real_T alpha_zero_lift_deg; - real_T ambient_density_kg_per_m3; - real_T ambient_pressure_mbar; - real_T ambient_temperature_celsius; - real_T ambient_wind_x_kn; - real_T ambient_wind_y_kn; - real_T ambient_wind_z_kn; - real_T ambient_wind_velocity_kn; - real_T ambient_wind_direction_deg; - real_T total_air_temperature_celsius; - real_T latitude_deg; - real_T longitude_deg; - real_T engine_1_thrust_lbf; - real_T engine_2_thrust_lbf; - real_T thrust_lever_1_pos; - real_T thrust_lever_2_pos; - boolean_T tailstrike_protection_on; - real_T VLS_kn; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_input_ -#define DEFINED_TYPEDEF_FOR_base_input_ - -struct base_input -{ - real_T delta_eta_pos; - real_T delta_xi_pos; - real_T delta_zeta_pos; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_fbw_input_ -#define DEFINED_TYPEDEF_FOR_fbw_input_ - -struct fbw_input -{ - base_raw_time time; - base_raw_data data; - base_input input; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_time_ -#define DEFINED_TYPEDEF_FOR_base_time_ - -struct base_time -{ - real_T dt; - real_T simulation_time; - real_T monotonic_time; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_data_ -#define DEFINED_TYPEDEF_FOR_base_data_ - -struct base_data -{ - real_T nz_g; - real_T Theta_deg; - real_T Phi_deg; - real_T q_deg_s; - real_T r_deg_s; - real_T p_deg_s; - real_T qk_deg_s; - real_T rk_deg_s; - real_T pk_deg_s; - real_T qk_dot_deg_s2; - real_T rk_dot_deg_s2; - real_T pk_dot_deg_s2; - real_T psi_magnetic_deg; - real_T psi_true_deg; - real_T eta_deg; - real_T eta_trim_deg; - real_T xi_deg; - real_T zeta_deg; - real_T zeta_trim_deg; - real_T alpha_deg; - real_T beta_deg; - real_T beta_dot_deg_s; - real_T V_ias_kn; - real_T V_tas_kn; - real_T V_mach; - real_T H_ft; - real_T H_ind_ft; - real_T H_radio_ft; - real_T CG_percent_MAC; - real_T total_weight_kg; - real_T gear_strut_compression_0; - real_T gear_strut_compression_1; - real_T gear_strut_compression_2; - real_T flaps_handle_index; - real_T spoilers_left_pos; - real_T spoilers_right_pos; - real_T autopilot_master_on; - real_T slew_on; - real_T pause_on; - real_T tracking_mode_on_override; - real_T autopilot_custom_on; - real_T autopilot_custom_Theta_c_deg; - real_T autopilot_custom_Phi_c_deg; - real_T autopilot_custom_Beta_c_deg; - real_T simulation_rate; - real_T ice_structure_percent; - real_T linear_cl_alpha_per_deg; - real_T alpha_stall_deg; - real_T alpha_zero_lift_deg; - real_T ambient_density_kg_per_m3; - real_T ambient_pressure_mbar; - real_T ambient_temperature_celsius; - real_T ambient_wind_x_kn; - real_T ambient_wind_y_kn; - real_T ambient_wind_z_kn; - real_T ambient_wind_velocity_kn; - real_T ambient_wind_direction_deg; - real_T total_air_temperature_celsius; - real_T latitude_deg; - real_T longitude_deg; - real_T engine_1_thrust_lbf; - real_T engine_2_thrust_lbf; - real_T thrust_lever_1_pos; - real_T thrust_lever_2_pos; - boolean_T tailstrike_protection_on; - real_T VLS_kn; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_data_computed_ -#define DEFINED_TYPEDEF_FOR_base_data_computed_ - -struct base_data_computed -{ - real_T on_ground; - real_T tracking_mode_on; - real_T high_aoa_prot_active; - real_T alpha_floor_command; - real_T protection_ap_disc; - real_T high_speed_prot_active; - real_T high_speed_prot_low_kn; - real_T high_speed_prot_high_kn; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_data_speeds_aoa_ -#define DEFINED_TYPEDEF_FOR_base_data_speeds_aoa_ - -struct base_data_speeds_aoa -{ - real_T v_alpha_max_kn; - real_T alpha_max_deg; - real_T v_alpha_prot_kn; - real_T alpha_prot_deg; - real_T alpha_floor_deg; - real_T alpha_filtered_deg; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_sim_ -#define DEFINED_TYPEDEF_FOR_base_sim_ - -struct base_sim -{ - base_time time; - base_data data; - base_data_computed data_computed; - base_data_speeds_aoa data_speeds_aoa; - base_input input; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_pitch_data_computed_ -#define DEFINED_TYPEDEF_FOR_base_pitch_data_computed_ - -struct base_pitch_data_computed -{ - real_T eta_trim_deg_limit_lo; - real_T eta_trim_deg_limit_up; - real_T delta_eta_deg; - real_T in_flight; - real_T in_rotation; - real_T in_flare; - real_T in_flight_gain; - real_T in_rotation_gain; - real_T nz_limit_up_g; - real_T nz_limit_lo_g; - boolean_T eta_trim_deg_should_freeze; - boolean_T eta_trim_deg_reset; - real_T eta_trim_deg_reset_deg; - boolean_T eta_trim_deg_should_write; - real_T eta_trim_deg_rate_limit_up_deg_s; - real_T eta_trim_deg_rate_limit_lo_deg_s; - real_T flare_Theta_deg; - real_T flare_Theta_c_deg; - real_T flare_Theta_c_rate_deg_s; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_pitch_rotation_ -#define DEFINED_TYPEDEF_FOR_base_pitch_rotation_ - -struct base_pitch_rotation -{ - real_T qk_c_deg_s; - real_T eta_deg; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_pitch_normal_ -#define DEFINED_TYPEDEF_FOR_base_pitch_normal_ - -struct base_pitch_normal -{ - real_T nz_c_g; - real_T Cstar_g; - real_T protection_alpha_c_deg; - real_T protection_V_c_kn; - real_T eta_dot_deg_s; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_pitch_law_output_ -#define DEFINED_TYPEDEF_FOR_base_pitch_law_output_ - -struct base_pitch_law_output -{ - real_T eta_dot_deg_s; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_pitch_integrated_ -#define DEFINED_TYPEDEF_FOR_base_pitch_integrated_ - -struct base_pitch_integrated -{ - real_T eta_deg; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_pitch_output_ -#define DEFINED_TYPEDEF_FOR_base_pitch_output_ - -struct base_pitch_output -{ - real_T eta_deg; - real_T eta_trim_deg; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_pitch_ -#define DEFINED_TYPEDEF_FOR_base_pitch_ - -struct base_pitch -{ - base_pitch_data_computed data_computed; - base_pitch_rotation law_rotation; - base_pitch_normal law_normal; - base_pitch_law_output vote; - base_pitch_integrated integrated; - base_pitch_output output; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_roll_data_computed_ -#define DEFINED_TYPEDEF_FOR_base_roll_data_computed_ - -struct base_roll_data_computed -{ - real_T delta_xi_deg; - real_T delta_zeta_deg; - real_T in_flight; - real_T in_flight_gain; - boolean_T zeta_trim_deg_should_write; - real_T beta_target_deg; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_roll_normal_ -#define DEFINED_TYPEDEF_FOR_base_roll_normal_ - -struct base_roll_normal -{ - real_T pk_c_deg_s; - real_T Phi_c_deg; - real_T xi_deg; - real_T zeta_deg; - real_T zeta_tc_yd_deg; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_roll_output_ -#define DEFINED_TYPEDEF_FOR_base_roll_output_ - -struct base_roll_output -{ - real_T xi_deg; - real_T zeta_deg; - real_T zeta_trim_deg; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_roll_ -#define DEFINED_TYPEDEF_FOR_base_roll_ - -struct base_roll -{ - base_roll_data_computed data_computed; - base_roll_normal law_normal; - base_roll_output output; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_base_raw_output_ -#define DEFINED_TYPEDEF_FOR_base_raw_output_ - -struct base_raw_output -{ - real_T eta_pos; - real_T eta_trim_deg; - boolean_T eta_trim_deg_should_write; - real_T xi_pos; - real_T zeta_pos; - real_T zeta_trim_pos; - boolean_T zeta_trim_pos_should_write; -}; - -#endif - -#ifndef DEFINED_TYPEDEF_FOR_fbw_output_ -#define DEFINED_TYPEDEF_FOR_fbw_output_ - -struct fbw_output -{ - base_sim sim; - base_pitch pitch; - base_roll roll; - base_raw_output output; -}; - -#endif -#endif - diff --git a/src/fbw/src/model/LateralDirectLaw.cpp b/src/fbw/src/model/LateralDirectLaw.cpp new file mode 100644 index 000000000000..d03e3949b4bd --- /dev/null +++ b/src/fbw/src/model/LateralDirectLaw.cpp @@ -0,0 +1,47 @@ +#include "LateralDirectLaw.h" +#include "rtwtypes.h" +#include + +LateralDirectLaw::Parameters_LateralDirectLaw_T LateralDirectLaw::LateralDirectLaw_rtP{ + + 0.0, + + -50.0, + + 50.0, + + 0.0, + + -25.0 +}; + +void LateralDirectLaw::reset(void) +{ + LateralDirectLaw_DWork.pY_not_empty = false; +} + +void LateralDirectLaw::step(const real_T *rtu_In_time_dt, const real_T *rtu_In_delta_xi_pos, real_T *rty_Out_xi_deg, + real_T *rty_Out_zeta_deg) +{ + real_T rtb_Gain1; + *rty_Out_zeta_deg = LateralDirectLaw_rtP.Constant1_Value; + rtb_Gain1 = LateralDirectLaw_rtP.Gain1_Gain * *rtu_In_delta_xi_pos; + if (!LateralDirectLaw_DWork.pY_not_empty) { + LateralDirectLaw_DWork.pY = LateralDirectLaw_rtP.RateLimiterVariableTs_InitialCondition; + LateralDirectLaw_DWork.pY_not_empty = true; + } + + LateralDirectLaw_DWork.pY += std::fmax(std::fmin(rtb_Gain1 - LateralDirectLaw_DWork.pY, std::abs + (LateralDirectLaw_rtP.RateLimiterVariableTs_up) * *rtu_In_time_dt), -std::abs + (LateralDirectLaw_rtP.RateLimiterVariableTs_lo) * *rtu_In_time_dt); + *rty_Out_xi_deg = LateralDirectLaw_DWork.pY; +} + +LateralDirectLaw::LateralDirectLaw(): + LateralDirectLaw_DWork() +{ +} + +LateralDirectLaw::~LateralDirectLaw() +{ +} diff --git a/src/fbw/src/model/LateralDirectLaw.h b/src/fbw/src/model/LateralDirectLaw.h new file mode 100644 index 000000000000..0743bc4caf76 --- /dev/null +++ b/src/fbw/src/model/LateralDirectLaw.h @@ -0,0 +1,40 @@ +#ifndef RTW_HEADER_LateralDirectLaw_h_ +#define RTW_HEADER_LateralDirectLaw_h_ +#include "rtwtypes.h" +#include "LateralDirectLaw_types.h" +#include + +class LateralDirectLaw final +{ + public: + struct D_Work_LateralDirectLaw_T { + real_T pY; + boolean_T pY_not_empty; + }; + + struct Parameters_LateralDirectLaw_T { + real_T RateLimiterVariableTs_InitialCondition; + real_T RateLimiterVariableTs_lo; + real_T RateLimiterVariableTs_up; + real_T Constant1_Value; + real_T Gain1_Gain; + }; + + LateralDirectLaw(LateralDirectLaw const&) = delete; + LateralDirectLaw& operator= (LateralDirectLaw const&) & = delete; + LateralDirectLaw(LateralDirectLaw &&) = delete; + LateralDirectLaw& operator= (LateralDirectLaw &&) = delete; + void step(const real_T *rtu_In_time_dt, const real_T *rtu_In_delta_xi_pos, real_T *rty_Out_xi_deg, real_T + *rty_Out_zeta_deg); + void reset(); + LateralDirectLaw(); + ~LateralDirectLaw(); + private: + D_Work_LateralDirectLaw_T LateralDirectLaw_DWork; + static Parameters_LateralDirectLaw_T LateralDirectLaw_rtP; +}; + +extern LateralDirectLaw::Parameters_LateralDirectLaw_T LateralDirectLaw_rtP; + +#endif + diff --git a/src/fbw/src/model/LateralDirectLaw_private.h b/src/fbw/src/model/LateralDirectLaw_private.h new file mode 100644 index 000000000000..ab8c76570bed --- /dev/null +++ b/src/fbw/src/model/LateralDirectLaw_private.h @@ -0,0 +1,5 @@ +#ifndef RTW_HEADER_LateralDirectLaw_private_h_ +#define RTW_HEADER_LateralDirectLaw_private_h_ +#include "rtwtypes.h" +#endif + diff --git a/src/fbw/src/model/LateralDirectLaw_types.h b/src/fbw/src/model/LateralDirectLaw_types.h new file mode 100644 index 000000000000..62bbc5a3a467 --- /dev/null +++ b/src/fbw/src/model/LateralDirectLaw_types.h @@ -0,0 +1,51 @@ +#ifndef RTW_HEADER_LateralDirectLaw_types_h_ +#define RTW_HEADER_LateralDirectLaw_types_h_ +#include "rtwtypes.h" + +#ifndef DEFINED_TYPEDEF_FOR_base_time_ +#define DEFINED_TYPEDEF_FOR_base_time_ + +struct base_time +{ + real_T dt; + real_T simulation_time; + real_T monotonic_time; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_lateral_direct_input_ +#define DEFINED_TYPEDEF_FOR_lateral_direct_input_ + +struct lateral_direct_input +{ + base_time time; + real_T delta_xi_pos; + boolean_T tracking_mode_on; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_roll_output_ +#define DEFINED_TYPEDEF_FOR_base_roll_output_ + +struct base_roll_output +{ + real_T xi_deg; + real_T zeta_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_lateral_direct_output_ +#define DEFINED_TYPEDEF_FOR_lateral_direct_output_ + +struct lateral_direct_output +{ + lateral_direct_input input; + base_roll_output output; +}; + +#endif +#endif + diff --git a/src/fbw/src/model/LateralNormalLaw.cpp b/src/fbw/src/model/LateralNormalLaw.cpp new file mode 100644 index 000000000000..590d943e7229 --- /dev/null +++ b/src/fbw/src/model/LateralNormalLaw.cpp @@ -0,0 +1,599 @@ +#include "LateralNormalLaw.h" +#include "rtwtypes.h" +#include +#include "look1_binlxpw.h" + +const uint8_T LateralNormalLaw_IN_FlightMode{ 1U }; + +const uint8_T LateralNormalLaw_IN_GroundMode{ 2U }; + +const uint8_T LateralNormalLaw_IN_NO_ACTIVE_CHILD{ 0U }; + +LateralNormalLaw::Parameters_LateralNormalLaw_T LateralNormalLaw::LateralNormalLaw_rtP{ + + { 0.0, 120.0, 150.0, 380.0 }, + + + { 0.0, 130.0, 200.0, 250.0, 300.0 }, + + + { 0.0, 140.0, 180.0, 220.0, 250.0, 270.0, 300.0, 320.0, 400.0 }, + + + { 0.0, 100.0, 150.0, 200.0, 250.0, 300.0, 400.0 }, + + + { 0.0, 100.0, 150.0, 200.0, 250.0, 300.0, 400.0 }, + + + { 0.0, 0.06, 0.1, 0.2, 1.0 }, + + 0.2, + + 2.0, + + 1.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + -67.0, + + + { -15.0, -15.0, -15.0, -2.0 }, + + + { 3.0, 3.0, 2.5, 1.0, 1.0 }, + + + { 1.1, 1.3, 1.8, 2.0, 2.2, 2.5, 2.7, 3.2, 3.8 }, + + + { 4.5, 4.5, 4.5, 3.5, 2.0, 1.5, 1.5 }, + + + { 1.4, 1.4, 1.4, 1.2, 1.0, 0.8, 0.8 }, + + + { 1.1, 1.0, 0.6, 0.3, 0.1 }, + + 67.0, + + -2.0, + + -5.0, + + -15.0, + + -1000.0, + + -2.0, + + -50.0, + + 2.0, + + 5.0, + + 15.0, + + 0.33333333333333331, + + 2.0, + + 50.0, + + + { 20.0, 15.0, 0.0, -15.0, -20.0 }, + + + { -50.0, -40.0, 0.0, 40.0, 50.0 }, + + + { 20.0, 20.0, 15.0, 0.0, 0.0, 0.0, -15.0, -20.0, -20.0 }, + + + { -90.0, -66.0, -45.0, -33.0, 0.0, 33.0, 45.0, 66.0, 90.0 }, + + + { 20.0, 20.0, 15.0, 0.0, 0.0, 0.0, -15.0, -20.0, -20.0 }, + + + { -90.0, -71.0, -66.0, -33.0, 0.0, 33.0, 66.0, 71.0, 90.0 }, + + 1.0, + + 0.0, + + 1.0, + + 25.0, + + -25.0, + + 0.0, + + 1.0, + + 0.0, + + -25.0, + + 0.0, + + 0.0, + + 15.0, + + 15.0, + + -15.0, + + 0.0, + + 67.0, + + -67.0, + + 9.81, + + 0.017453292519943295, + + 0.017453292519943295, + + 1000.0, + + 100.0, + + 0.51444444444444448, + + 57.295779513082323, + + 25.0, + + -25.0, + + 1.0, + + 0.0, + + 25.0, + + -25.0, + + 1.0, + + 0.017453292519943295, + + 0.017453292519943295, + + 0.017453292519943295, + + 57.295779513082323, + + 25.0, + + -25.0, + + 1.0, + + 0.0, + + 1.0, + + 0.0, + + 1.0 +}; + +void LateralNormalLaw::LateralNormalLaw_RateLimiter_Reset(rtDW_RateLimiter_LateralNormalLaw_T *localDW) +{ + localDW->pY_not_empty = false; +} + +void LateralNormalLaw::LateralNormalLaw_RateLimiter(real_T rtu_u, real_T rtu_up, real_T rtu_lo, const real_T *rtu_Ts, + real_T rtu_init, real_T *rty_Y, rtDW_RateLimiter_LateralNormalLaw_T *localDW) +{ + if (!localDW->pY_not_empty) { + localDW->pY = rtu_init; + localDW->pY_not_empty = true; + } + + localDW->pY += std::fmax(std::fmin(rtu_u - localDW->pY, std::abs(rtu_up) * *rtu_Ts), -std::abs(rtu_lo) * *rtu_Ts); + *rty_Y = localDW->pY; +} + +void LateralNormalLaw::LateralNormalLaw_LagFilter_Reset(rtDW_LagFilter_LateralNormalLaw_T *localDW) +{ + localDW->pY_not_empty = false; + localDW->pU_not_empty = false; +} + +void LateralNormalLaw::LateralNormalLaw_LagFilter(real_T rtu_U, real_T rtu_C1, const real_T *rtu_dt, real_T *rty_Y, + rtDW_LagFilter_LateralNormalLaw_T *localDW) +{ + real_T ca; + real_T denom_tmp; + if ((!localDW->pY_not_empty) || (!localDW->pU_not_empty)) { + localDW->pU = rtu_U; + localDW->pU_not_empty = true; + localDW->pY = rtu_U; + localDW->pY_not_empty = true; + } + + denom_tmp = *rtu_dt * rtu_C1; + ca = denom_tmp / (denom_tmp + 2.0); + *rty_Y = (2.0 - denom_tmp) / (denom_tmp + 2.0) * localDW->pY + (rtu_U * ca + localDW->pU * ca); + localDW->pY = *rty_Y; + localDW->pU = rtu_U; +} + +void LateralNormalLaw::init(void) +{ + LateralNormalLaw_DWork.Delay_DSTATE = LateralNormalLaw_rtP.Delay_InitialCondition; + LateralNormalLaw_DWork.icLoad = true; +} + +void LateralNormalLaw::reset(void) +{ + LateralNormalLaw_DWork.Delay_DSTATE = LateralNormalLaw_rtP.Delay_InitialCondition; + LateralNormalLaw_DWork.icLoad = true; + LateralNormalLaw_DWork.is_active_c5_LateralNormalLaw = 0U; + LateralNormalLaw_DWork.is_c5_LateralNormalLaw = LateralNormalLaw_IN_NO_ACTIVE_CHILD; + LateralNormalLaw_RateLimiter_Reset(&LateralNormalLaw_DWork.sf_RateLimiter); + LateralNormalLaw_RateLimiter_Reset(&LateralNormalLaw_DWork.sf_RateLimiter_d); + LateralNormalLaw_RateLimiter_Reset(&LateralNormalLaw_DWork.sf_RateLimiter_n); + LateralNormalLaw_LagFilter_Reset(&LateralNormalLaw_DWork.sf_LagFilter); + LateralNormalLaw_LagFilter_Reset(&LateralNormalLaw_DWork.sf_LagFilter_m); + LateralNormalLaw_DWork.pY_not_empty_h = false; + LateralNormalLaw_DWork.pY_not_empty = false; + LateralNormalLaw_RateLimiter_Reset(&LateralNormalLaw_DWork.sf_RateLimiter_j); +} + +void LateralNormalLaw::step(const real_T *rtu_In_time_dt, const real_T *rtu_In_Theta_deg, const real_T *rtu_In_Phi_deg, + const real_T *rtu_In_r_deg_s, const real_T *rtu_In_pk_deg_s, const real_T *rtu_In_V_ias_kn, const real_T + *rtu_In_V_tas_kn, const real_T *rtu_In_H_radio_ft, const real_T *rtu_In_delta_xi_pos, const real_T + *rtu_In_delta_zeta_pos, const boolean_T *rtu_In_on_ground, const boolean_T *rtu_In_tracking_mode_on, const boolean_T + *rtu_In_high_aoa_prot_active, const boolean_T *rtu_In_high_speed_prot_active, const real_T *rtu_In_ap_phi_c_deg, const + real_T *rtu_In_ap_beta_c_deg, const boolean_T *rtu_In_any_ap_engaged, real_T *rty_Out_xi_deg, real_T *rty_Out_zeta_deg) +{ + static const int16_T b[4]{ 0, 120, 150, 380 }; + + static const int16_T b_0[4]{ 0, 120, 320, 400 }; + + static const int8_T c[4]{ -15, -15, -15, -2 }; + + static const int8_T c_0[4]{ 1, 2, 3, 3 }; + + real_T Vias; + real_T Vtas; + real_T r; + real_T rtb_Gain1; + real_T rtb_Gain1_c; + real_T rtb_Gain1_l; + real_T rtb_Gain_b; + real_T rtb_Sum_x0; + real_T rtb_Y_i; + real_T rtb_Y_j; + real_T rtb_beDot; + int32_T low_i; + int32_T low_ip1; + int32_T mid_i; + int32_T rtb_in_flight; + boolean_T rtb_OR; + if (LateralNormalLaw_DWork.is_active_c5_LateralNormalLaw == 0U) { + LateralNormalLaw_DWork.is_active_c5_LateralNormalLaw = 1U; + LateralNormalLaw_DWork.is_c5_LateralNormalLaw = LateralNormalLaw_IN_GroundMode; + rtb_in_flight = 0; + } else if (LateralNormalLaw_DWork.is_c5_LateralNormalLaw == LateralNormalLaw_IN_FlightMode) { + if (*rtu_In_on_ground) { + LateralNormalLaw_DWork.is_c5_LateralNormalLaw = LateralNormalLaw_IN_GroundMode; + rtb_in_flight = 0; + } else { + rtb_in_flight = 1; + } + } else if (((!*rtu_In_on_ground) && (*rtu_In_Theta_deg > 8.0)) || (*rtu_In_H_radio_ft > 400.0)) { + LateralNormalLaw_DWork.is_c5_LateralNormalLaw = LateralNormalLaw_IN_FlightMode; + rtb_in_flight = 1; + } else { + rtb_in_flight = 0; + } + + if (rtb_in_flight > LateralNormalLaw_rtP.Saturation_UpperSat_p) { + rtb_Gain_b = LateralNormalLaw_rtP.Saturation_UpperSat_p; + } else if (rtb_in_flight < LateralNormalLaw_rtP.Saturation_LowerSat_h) { + rtb_Gain_b = LateralNormalLaw_rtP.Saturation_LowerSat_h; + } else { + rtb_Gain_b = rtb_in_flight; + } + + LateralNormalLaw_RateLimiter(rtb_Gain_b, LateralNormalLaw_rtP.RateLimiterVariableTs_up, + LateralNormalLaw_rtP.RateLimiterVariableTs_lo, rtu_In_time_dt, + LateralNormalLaw_rtP.RateLimiterVariableTs_InitialCondition, &rtb_Y_i, &LateralNormalLaw_DWork.sf_RateLimiter); + rtb_Gain_b = LateralNormalLaw_rtP.Gain_Gain * *rtu_In_delta_xi_pos; + Vias = *rtu_In_V_ias_kn; + rtb_Sum_x0 = *rtu_In_delta_zeta_pos; + Vias = std::fmax(Vias, 60.0); + r = 0.0; + if (Vias <= 380.0) { + rtb_in_flight = 4; + low_i = 1; + low_ip1 = 2; + while (rtb_in_flight > low_ip1) { + mid_i = (low_i + rtb_in_flight) >> 1; + if (Vias >= b[mid_i - 1]) { + low_i = mid_i; + low_ip1 = mid_i + 1; + } else { + rtb_in_flight = mid_i; + } + } + + r = (Vias - static_cast(b[low_i - 1])) / static_cast(b[low_i] - b[low_i - 1]); + if (r == 0.0) { + r = -15.0; + } else if (r == 1.0) { + r = c[low_i]; + } else if (c[low_i] == -15) { + r = -15.0; + } else { + r = (1.0 - r) * -15.0 + r * static_cast(c[low_i]); + } + } + + Vias *= 0.5144; + LateralNormalLaw_RateLimiter(0.814 / std::sqrt(1.3734E+6 / (149.45000000000002 * (Vias * Vias))) * (r * rtb_Sum_x0), + LateralNormalLaw_rtP.RateLimiterVariableTs1_up, LateralNormalLaw_rtP.RateLimiterVariableTs1_lo, rtu_In_time_dt, + LateralNormalLaw_rtP.RateLimiterVariableTs1_InitialCondition, &rtb_Y_j, &LateralNormalLaw_DWork.sf_RateLimiter_d); + r = *rtu_In_r_deg_s; + rtb_Y_j = *rtu_In_V_ias_kn; + Vtas = *rtu_In_V_tas_kn; + rtb_beDot = *rtu_In_delta_zeta_pos; + rtb_Gain1 = LateralNormalLaw_rtP.Gain1_Gain * *rtu_In_delta_xi_pos; + if (*rtu_In_high_speed_prot_active) { + Vias = look1_binlxpw(*rtu_In_Phi_deg, LateralNormalLaw_rtP.BankAngleProtection2_bp01Data, + LateralNormalLaw_rtP.BankAngleProtection2_tableData, 4U); + } else if (*rtu_In_high_aoa_prot_active) { + Vias = look1_binlxpw(*rtu_In_Phi_deg, LateralNormalLaw_rtP.BankAngleProtection_bp01Data, + LateralNormalLaw_rtP.BankAngleProtection_tableData, 8U); + } else { + Vias = look1_binlxpw(*rtu_In_Phi_deg, LateralNormalLaw_rtP.BankAngleProtection1_bp01Data, + LateralNormalLaw_rtP.BankAngleProtection1_tableData, 8U); + } + + rtb_Sum_x0 = 15.0; + rtb_Gain1_l = -15.0; + if (LateralNormalLaw_DWork.Delay_DSTATE >= 25.0) { + rtb_Gain1_l = *rtu_In_pk_deg_s; + } else if (LateralNormalLaw_DWork.Delay_DSTATE <= -25.0) { + rtb_Sum_x0 = *rtu_In_pk_deg_s; + } + + rtb_Gain1 += Vias; + if (rtb_Gain1 > LateralNormalLaw_rtP.Saturation_UpperSat_a) { + rtb_Gain1 = LateralNormalLaw_rtP.Saturation_UpperSat_a; + } else if (rtb_Gain1 < LateralNormalLaw_rtP.Saturation_LowerSat_o) { + rtb_Gain1 = LateralNormalLaw_rtP.Saturation_LowerSat_o; + } + + Vias = std::fmin(rtb_Sum_x0, std::fmax(rtb_Gain1_l, rtb_Gain1 * rtb_Y_i)) * + LateralNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_Gain * *rtu_In_time_dt; + rtb_OR = ((rtb_Y_i == 0.0) || (*rtu_In_tracking_mode_on) || (*rtu_In_any_ap_engaged)); + rtb_Sum_x0 = *rtu_In_Phi_deg - Vias; + LateralNormalLaw_DWork.icLoad = (rtb_OR || LateralNormalLaw_DWork.icLoad); + if (LateralNormalLaw_DWork.icLoad) { + LateralNormalLaw_DWork.Delay_DSTATE_d = rtb_Sum_x0; + } + + LateralNormalLaw_DWork.Delay_DSTATE_d += Vias; + if (LateralNormalLaw_DWork.Delay_DSTATE_d > LateralNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_UpperLimit) { + LateralNormalLaw_DWork.Delay_DSTATE_d = LateralNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_UpperLimit; + } else if (LateralNormalLaw_DWork.Delay_DSTATE_d < LateralNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_LowerLimit) { + LateralNormalLaw_DWork.Delay_DSTATE_d = LateralNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_LowerLimit; + } + + if (LateralNormalLaw_DWork.Delay_DSTATE_d > LateralNormalLaw_rtP.Saturation_UpperSat_g) { + Vias = LateralNormalLaw_rtP.Saturation_UpperSat_g; + } else if (LateralNormalLaw_DWork.Delay_DSTATE_d < LateralNormalLaw_rtP.Saturation_LowerSat_e) { + Vias = LateralNormalLaw_rtP.Saturation_LowerSat_e; + } else { + Vias = LateralNormalLaw_DWork.Delay_DSTATE_d; + } + + LateralNormalLaw_RateLimiter(Vias, LateralNormalLaw_rtP.RateLimiterVariableTs_up_m, + LateralNormalLaw_rtP.RateLimiterVariableTs_lo_k, rtu_In_time_dt, + LateralNormalLaw_rtP.RateLimiterVariableTs_InitialCondition_m, &rtb_Sum_x0, &LateralNormalLaw_DWork.sf_RateLimiter_n); + if (*rtu_In_any_ap_engaged) { + if (*rtu_In_tracking_mode_on) { + rtb_Sum_x0 = *rtu_In_Phi_deg; + } else { + rtb_Sum_x0 = *rtu_In_ap_phi_c_deg; + } + } + + Vtas = std::fmax(Vtas * 0.5144, 60.0); + Vias = rtb_Y_j * 0.5144; + if (rtb_Y_j >= 60.0) { + rtb_beDot = (Vias * Vias * 0.6125 * 122.0 / (70000.0 * Vtas) * 3.172 * -rtb_beDot * 3.1415926535897931 / 180.0 + + (rtb_Sum_x0 * 3.1415926535897931 / 180.0 * (9.81 / Vtas) + -(r * 3.1415926535897931 / 180.0))) * 180.0 / + 3.1415926535897931; + } else { + rtb_beDot = 0.0; + } + + LateralNormalLaw_LagFilter(rtb_beDot, LateralNormalLaw_rtP.LagFilter_C1, rtu_In_time_dt, &r, + &LateralNormalLaw_DWork.sf_LagFilter); + Vias = look1_binlxpw(*rtu_In_V_ias_kn, LateralNormalLaw_rtP.ScheduledGain2_BreakpointsForDimension1, + LateralNormalLaw_rtP.ScheduledGain2_Table, 3U); + if (*rtu_In_any_ap_engaged) { + rtb_Y_j = *rtu_In_ap_beta_c_deg; + } else { + rtb_Y_j = *rtu_In_delta_zeta_pos * Vias; + } + + Vias = look1_binlxpw(*rtu_In_V_ias_kn, LateralNormalLaw_rtP.ScheduledGain1_BreakpointsForDimension1, + LateralNormalLaw_rtP.ScheduledGain1_Table, 4U); + LateralNormalLaw_LagFilter((rtb_Y_j - r) * Vias - rtb_beDot, LateralNormalLaw_rtP.LagFilter_C1_d, rtu_In_time_dt, &r, + &LateralNormalLaw_DWork.sf_LagFilter_m); + Vtas = look1_binlxpw(*rtu_In_V_ias_kn, LateralNormalLaw_rtP.ScheduledGain_BreakpointsForDimension1, + LateralNormalLaw_rtP.ScheduledGain_Table, 8U); + if (!LateralNormalLaw_DWork.pY_not_empty_h) { + LateralNormalLaw_DWork.pY_p = LateralNormalLaw_rtP.RateLimiterVariableTs_InitialCondition_d; + LateralNormalLaw_DWork.pY_not_empty_h = true; + } + + LateralNormalLaw_DWork.pY_p += std::fmax(std::fmin(static_cast(*rtu_In_on_ground) - + LateralNormalLaw_DWork.pY_p, std::abs(LateralNormalLaw_rtP.RateLimiterVariableTs_up_o) * *rtu_In_time_dt), -std::abs + (LateralNormalLaw_rtP.RateLimiterVariableTs_lo_l) * *rtu_In_time_dt); + if (*rtu_In_any_ap_engaged) { + if (LateralNormalLaw_DWork.pY_p > LateralNormalLaw_rtP.Saturation_UpperSat) { + Vias = LateralNormalLaw_rtP.Saturation_UpperSat; + } else if (LateralNormalLaw_DWork.pY_p < LateralNormalLaw_rtP.Saturation_LowerSat) { + Vias = LateralNormalLaw_rtP.Saturation_LowerSat; + } else { + Vias = LateralNormalLaw_DWork.pY_p; + } + + rtb_beDot = *rtu_In_ap_beta_c_deg * Vias; + rtb_Gain1 = rtb_Y_j * Vtas + r; + if (rtb_Gain1 > LateralNormalLaw_rtP.Saturation_UpperSat_f) { + rtb_Gain1 = LateralNormalLaw_rtP.Saturation_UpperSat_f; + } else if (rtb_Gain1 < LateralNormalLaw_rtP.Saturation_LowerSat_j) { + rtb_Gain1 = LateralNormalLaw_rtP.Saturation_LowerSat_j; + } + + Vias = (LateralNormalLaw_rtP.Constant_Value - Vias) * rtb_Gain1 + rtb_beDot; + } else { + Vias = LateralNormalLaw_rtP.Constant_Value_b; + } + + r = LateralNormalLaw_rtP.Gain1_Gain_l * *rtu_In_Theta_deg; + rtb_Gain1 = *rtu_In_V_tas_kn; + if (rtb_Gain1 > LateralNormalLaw_rtP.Saturation_UpperSat_e) { + rtb_Gain1 = LateralNormalLaw_rtP.Saturation_UpperSat_e; + } else if (rtb_Gain1 < LateralNormalLaw_rtP.Saturation_LowerSat_jd) { + rtb_Gain1 = LateralNormalLaw_rtP.Saturation_LowerSat_jd; + } + + r = *rtu_In_r_deg_s - std::sin(LateralNormalLaw_rtP.Gain1_Gain_f * rtb_Sum_x0) * LateralNormalLaw_rtP.Constant2_Value * + std::cos(r) / (LateralNormalLaw_rtP.Gain6_Gain * rtb_Gain1) * LateralNormalLaw_rtP.Gain_Gain_i; + rtb_Y_j = look1_binlxpw(*rtu_In_V_tas_kn, LateralNormalLaw_rtP.ScheduledGain_BreakpointsForDimension1_a, + LateralNormalLaw_rtP.ScheduledGain_Table_e, 6U); + rtb_beDot = r * rtb_Y_j; + rtb_OR = !*rtu_In_on_ground; + if (!LateralNormalLaw_DWork.pY_not_empty) { + LateralNormalLaw_DWork.pY = LateralNormalLaw_rtP.RateLimiterVariableTs1_InitialCondition_m; + LateralNormalLaw_DWork.pY_not_empty = true; + } + + LateralNormalLaw_DWork.pY += std::fmax(std::fmin(static_cast(rtb_OR) - LateralNormalLaw_DWork.pY, std::abs + (LateralNormalLaw_rtP.RateLimiterVariableTs1_up_j) * *rtu_In_time_dt), -std::abs + (LateralNormalLaw_rtP.RateLimiterVariableTs1_lo_n) * *rtu_In_time_dt); + if (LateralNormalLaw_DWork.pY > LateralNormalLaw_rtP.Saturation_UpperSat_n) { + r = LateralNormalLaw_rtP.Saturation_UpperSat_n; + } else if (LateralNormalLaw_DWork.pY < LateralNormalLaw_rtP.Saturation_LowerSat_b) { + r = LateralNormalLaw_rtP.Saturation_LowerSat_b; + } else { + r = LateralNormalLaw_DWork.pY; + } + + rtb_Y_j = look1_binlxpw(*rtu_In_V_tas_kn, LateralNormalLaw_rtP.ScheduledGain1_BreakpointsForDimension1_j, + LateralNormalLaw_rtP.ScheduledGain1_Table_m, 6U); + rtb_Y_j *= *rtu_In_r_deg_s; + if (rtb_beDot > LateralNormalLaw_rtP.Saturation1_UpperSat) { + rtb_beDot = LateralNormalLaw_rtP.Saturation1_UpperSat; + } else if (rtb_beDot < LateralNormalLaw_rtP.Saturation1_LowerSat) { + rtb_beDot = LateralNormalLaw_rtP.Saturation1_LowerSat; + } + + if (rtb_Y_j > LateralNormalLaw_rtP.Saturation2_UpperSat) { + rtb_Y_j = LateralNormalLaw_rtP.Saturation2_UpperSat; + } else if (rtb_Y_j < LateralNormalLaw_rtP.Saturation2_LowerSat) { + rtb_Y_j = LateralNormalLaw_rtP.Saturation2_LowerSat; + } + + *rty_Out_zeta_deg = ((LateralNormalLaw_rtP.Constant_Value_k - r) * rtb_Y_j + rtb_beDot * r) + Vias; + Vias = std::fmax(*rtu_In_V_ias_kn, 80.0) * 0.5144; + rtb_Y_j = Vias * Vias * 0.6125; + rtb_beDot = rtb_Y_j * 122.0 * 17.9 * -0.090320788790706555 / 1.0E+6; + Vtas = 0.0; + if ((*rtu_In_V_ias_kn <= 400.0) && (*rtu_In_V_ias_kn >= 0.0)) { + rtb_in_flight = 4; + low_i = 0; + low_ip1 = 2; + while (rtb_in_flight > low_ip1) { + mid_i = ((low_i + rtb_in_flight) + 1) >> 1; + if (*rtu_In_V_ias_kn >= b_0[mid_i - 1]) { + low_i = mid_i - 1; + low_ip1 = mid_i + 1; + } else { + rtb_in_flight = mid_i; + } + } + + r = (*rtu_In_V_ias_kn - static_cast(b_0[low_i])) / static_cast(b_0[low_i + 1] - b_0[low_i]); + if (r == 0.0) { + Vtas = c_0[low_i]; + } else if (r == 1.0) { + Vtas = c_0[low_i + 1]; + } else if (c_0[low_i + 1] == c_0[low_i]) { + Vtas = c_0[low_i]; + } else { + Vtas = (1.0 - r) * static_cast(c_0[low_i]) + static_cast(c_0[low_i + 1]) * r; + } + } + + rtb_Gain1 = -(Vtas * Vtas) / rtb_beDot; + rtb_Gain1_l = LateralNormalLaw_rtP.Gain1_Gain_b * *rtu_In_Phi_deg; + rtb_Gain1_c = LateralNormalLaw_rtP.Gain1_Gain_c * *rtu_In_pk_deg_s; + r = look1_binlxpw(*rtu_In_time_dt, LateralNormalLaw_rtP.ScheduledGain_BreakpointsForDimension1_j, + LateralNormalLaw_rtP.ScheduledGain_Table_i, 4U); + LateralNormalLaw_DWork.Delay_DSTATE = ((-(rtb_Y_j / Vias * 122.0 * 320.40999999999997 * -0.487 / 1.0E+6 + 1.414 * Vtas) + / rtb_beDot * rtb_Gain1_c + rtb_Gain1 * rtb_Gain1_l) + LateralNormalLaw_rtP.Gain1_Gain_n * rtb_Sum_x0 * -rtb_Gain1) * + r * LateralNormalLaw_rtP.Gain_Gain_p; + if (rtb_Y_i > LateralNormalLaw_rtP.Saturation1_UpperSat_e) { + rtb_Y_i = LateralNormalLaw_rtP.Saturation1_UpperSat_e; + } else if (rtb_Y_i < LateralNormalLaw_rtP.Saturation1_LowerSat_l) { + rtb_Y_i = LateralNormalLaw_rtP.Saturation1_LowerSat_l; + } + + if (rtb_Y_i > LateralNormalLaw_rtP.Saturation_UpperSat_l) { + rtb_Y_j = LateralNormalLaw_rtP.Saturation_UpperSat_l; + } else if (rtb_Y_i < LateralNormalLaw_rtP.Saturation_LowerSat_og) { + rtb_Y_j = LateralNormalLaw_rtP.Saturation_LowerSat_og; + } else { + rtb_Y_j = rtb_Y_i; + } + + if (LateralNormalLaw_DWork.Delay_DSTATE > LateralNormalLaw_rtP.Limiterxi_UpperSat) { + rtb_Y_i = LateralNormalLaw_rtP.Limiterxi_UpperSat; + } else if (LateralNormalLaw_DWork.Delay_DSTATE < LateralNormalLaw_rtP.Limiterxi_LowerSat) { + rtb_Y_i = LateralNormalLaw_rtP.Limiterxi_LowerSat; + } else { + rtb_Y_i = LateralNormalLaw_DWork.Delay_DSTATE; + } + + LateralNormalLaw_RateLimiter(rtb_Y_i * rtb_Y_j + (LateralNormalLaw_rtP.Constant_Value_l1 - rtb_Y_j) * rtb_Gain_b, + LateralNormalLaw_rtP.RateLimiterVariableTs_up_d, LateralNormalLaw_rtP.RateLimiterVariableTs_lo_b, rtu_In_time_dt, + LateralNormalLaw_rtP.RateLimiterVariableTs_InitialCondition_k, rty_Out_xi_deg, + &LateralNormalLaw_DWork.sf_RateLimiter_j); + LateralNormalLaw_DWork.icLoad = false; +} + +LateralNormalLaw::LateralNormalLaw(): + LateralNormalLaw_DWork() +{ +} + +LateralNormalLaw::~LateralNormalLaw() +{ +} diff --git a/src/fbw/src/model/LateralNormalLaw.h b/src/fbw/src/model/LateralNormalLaw.h new file mode 100644 index 000000000000..a533fd859ef1 --- /dev/null +++ b/src/fbw/src/model/LateralNormalLaw.h @@ -0,0 +1,155 @@ +#ifndef RTW_HEADER_LateralNormalLaw_h_ +#define RTW_HEADER_LateralNormalLaw_h_ +#include "rtwtypes.h" +#include "LateralNormalLaw_types.h" +#include + +class LateralNormalLaw final +{ + public: + struct rtDW_RateLimiter_LateralNormalLaw_T { + real_T pY; + boolean_T pY_not_empty; + }; + + struct rtDW_LagFilter_LateralNormalLaw_T { + real_T pY; + real_T pU; + boolean_T pY_not_empty; + boolean_T pU_not_empty; + }; + + struct D_Work_LateralNormalLaw_T { + real_T Delay_DSTATE; + real_T Delay_DSTATE_d; + real_T pY; + real_T pY_p; + uint8_T is_active_c5_LateralNormalLaw; + uint8_T is_c5_LateralNormalLaw; + boolean_T icLoad; + boolean_T pY_not_empty; + boolean_T pY_not_empty_h; + rtDW_RateLimiter_LateralNormalLaw_T sf_RateLimiter_j; + rtDW_LagFilter_LateralNormalLaw_T sf_LagFilter_m; + rtDW_RateLimiter_LateralNormalLaw_T sf_RateLimiter_d; + rtDW_RateLimiter_LateralNormalLaw_T sf_RateLimiter_n; + rtDW_LagFilter_LateralNormalLaw_T sf_LagFilter; + rtDW_RateLimiter_LateralNormalLaw_T sf_RateLimiter; + }; + + struct Parameters_LateralNormalLaw_T { + real_T ScheduledGain2_BreakpointsForDimension1[4]; + real_T ScheduledGain1_BreakpointsForDimension1[5]; + real_T ScheduledGain_BreakpointsForDimension1[9]; + real_T ScheduledGain_BreakpointsForDimension1_a[7]; + real_T ScheduledGain1_BreakpointsForDimension1_j[7]; + real_T ScheduledGain_BreakpointsForDimension1_j[5]; + real_T LagFilter_C1; + real_T LagFilter_C1_d; + real_T DiscreteTimeIntegratorVariableTs_Gain; + real_T RateLimiterVariableTs_InitialCondition; + real_T RateLimiterVariableTs1_InitialCondition; + real_T RateLimiterVariableTs_InitialCondition_m; + real_T RateLimiterVariableTs_InitialCondition_d; + real_T RateLimiterVariableTs1_InitialCondition_m; + real_T RateLimiterVariableTs_InitialCondition_k; + real_T DiscreteTimeIntegratorVariableTs_LowerLimit; + real_T ScheduledGain2_Table[4]; + real_T ScheduledGain1_Table[5]; + real_T ScheduledGain_Table[9]; + real_T ScheduledGain_Table_e[7]; + real_T ScheduledGain1_Table_m[7]; + real_T ScheduledGain_Table_i[5]; + real_T DiscreteTimeIntegratorVariableTs_UpperLimit; + real_T RateLimiterVariableTs_lo; + real_T RateLimiterVariableTs1_lo; + real_T RateLimiterVariableTs_lo_k; + real_T RateLimiterVariableTs_lo_l; + real_T RateLimiterVariableTs1_lo_n; + real_T RateLimiterVariableTs_lo_b; + real_T RateLimiterVariableTs_up; + real_T RateLimiterVariableTs1_up; + real_T RateLimiterVariableTs_up_m; + real_T RateLimiterVariableTs_up_o; + real_T RateLimiterVariableTs1_up_j; + real_T RateLimiterVariableTs_up_d; + real_T BankAngleProtection2_tableData[5]; + real_T BankAngleProtection2_bp01Data[5]; + real_T BankAngleProtection_tableData[9]; + real_T BankAngleProtection_bp01Data[9]; + real_T BankAngleProtection1_tableData[9]; + real_T BankAngleProtection1_bp01Data[9]; + real_T Saturation_UpperSat; + real_T Saturation_LowerSat; + real_T Constant_Value; + real_T Saturation_UpperSat_f; + real_T Saturation_LowerSat_j; + real_T Constant_Value_b; + real_T Saturation_UpperSat_p; + real_T Saturation_LowerSat_h; + real_T Gain_Gain; + real_T Constant_Value_l; + real_T Constant_Value_m; + real_T Gain1_Gain; + real_T Saturation_UpperSat_a; + real_T Saturation_LowerSat_o; + real_T Delay_InitialCondition; + real_T Saturation_UpperSat_g; + real_T Saturation_LowerSat_e; + real_T Constant2_Value; + real_T Gain1_Gain_f; + real_T Gain1_Gain_l; + real_T Saturation_UpperSat_e; + real_T Saturation_LowerSat_jd; + real_T Gain6_Gain; + real_T Gain_Gain_i; + real_T Saturation1_UpperSat; + real_T Saturation1_LowerSat; + real_T Saturation_UpperSat_n; + real_T Saturation_LowerSat_b; + real_T Saturation2_UpperSat; + real_T Saturation2_LowerSat; + real_T Constant_Value_k; + real_T Gain1_Gain_n; + real_T Gain1_Gain_b; + real_T Gain1_Gain_c; + real_T Gain_Gain_p; + real_T Limiterxi_UpperSat; + real_T Limiterxi_LowerSat; + real_T Saturation1_UpperSat_e; + real_T Saturation1_LowerSat_l; + real_T Saturation_UpperSat_l; + real_T Saturation_LowerSat_og; + real_T Constant_Value_l1; + }; + + void init(); + LateralNormalLaw(LateralNormalLaw const&) = delete; + LateralNormalLaw& operator= (LateralNormalLaw const&) & = delete; + LateralNormalLaw(LateralNormalLaw &&) = delete; + LateralNormalLaw& operator= (LateralNormalLaw &&) = delete; + void step(const real_T *rtu_In_time_dt, const real_T *rtu_In_Theta_deg, const real_T *rtu_In_Phi_deg, const real_T + *rtu_In_r_deg_s, const real_T *rtu_In_pk_deg_s, const real_T *rtu_In_V_ias_kn, const real_T *rtu_In_V_tas_kn, + const real_T *rtu_In_H_radio_ft, const real_T *rtu_In_delta_xi_pos, const real_T *rtu_In_delta_zeta_pos, + const boolean_T *rtu_In_on_ground, const boolean_T *rtu_In_tracking_mode_on, const boolean_T + *rtu_In_high_aoa_prot_active, const boolean_T *rtu_In_high_speed_prot_active, const real_T + *rtu_In_ap_phi_c_deg, const real_T *rtu_In_ap_beta_c_deg, const boolean_T *rtu_In_any_ap_engaged, real_T + *rty_Out_xi_deg, real_T *rty_Out_zeta_deg); + void reset(); + LateralNormalLaw(); + ~LateralNormalLaw(); + private: + D_Work_LateralNormalLaw_T LateralNormalLaw_DWork; + static Parameters_LateralNormalLaw_T LateralNormalLaw_rtP; + static void LateralNormalLaw_RateLimiter_Reset(rtDW_RateLimiter_LateralNormalLaw_T *localDW); + static void LateralNormalLaw_RateLimiter(real_T rtu_u, real_T rtu_up, real_T rtu_lo, const real_T *rtu_Ts, real_T + rtu_init, real_T *rty_Y, rtDW_RateLimiter_LateralNormalLaw_T *localDW); + static void LateralNormalLaw_LagFilter_Reset(rtDW_LagFilter_LateralNormalLaw_T *localDW); + static void LateralNormalLaw_LagFilter(real_T rtu_U, real_T rtu_C1, const real_T *rtu_dt, real_T *rty_Y, + rtDW_LagFilter_LateralNormalLaw_T *localDW); +}; + +extern LateralNormalLaw::Parameters_LateralNormalLaw_T LateralNormalLaw_rtP; + +#endif + diff --git a/src/fbw/src/model/LateralNormalLaw_private.h b/src/fbw/src/model/LateralNormalLaw_private.h new file mode 100644 index 000000000000..6ca3a0aa6213 --- /dev/null +++ b/src/fbw/src/model/LateralNormalLaw_private.h @@ -0,0 +1,5 @@ +#ifndef RTW_HEADER_LateralNormalLaw_private_h_ +#define RTW_HEADER_LateralNormalLaw_private_h_ +#include "rtwtypes.h" +#endif + diff --git a/src/fbw/src/model/LateralNormalLaw_types.h b/src/fbw/src/model/LateralNormalLaw_types.h new file mode 100644 index 000000000000..268902b86b07 --- /dev/null +++ b/src/fbw/src/model/LateralNormalLaw_types.h @@ -0,0 +1,92 @@ +#ifndef RTW_HEADER_LateralNormalLaw_types_h_ +#define RTW_HEADER_LateralNormalLaw_types_h_ +#include "rtwtypes.h" + +#ifndef DEFINED_TYPEDEF_FOR_base_time_ +#define DEFINED_TYPEDEF_FOR_base_time_ + +struct base_time +{ + real_T dt; + real_T simulation_time; + real_T monotonic_time; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_lateral_normal_input_ +#define DEFINED_TYPEDEF_FOR_lateral_normal_input_ + +struct lateral_normal_input +{ + base_time time; + real_T Theta_deg; + real_T Phi_deg; + real_T r_deg_s; + real_T pk_deg_s; + real_T V_ias_kn; + real_T V_tas_kn; + real_T H_radio_ft; + real_T delta_xi_pos; + real_T delta_zeta_pos; + boolean_T on_ground; + boolean_T tracking_mode_on; + boolean_T high_aoa_prot_active; + boolean_T high_speed_prot_active; + real_T ap_phi_c_deg; + real_T ap_beta_c_deg; + boolean_T any_ap_engaged; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_roll_data_computed_ +#define DEFINED_TYPEDEF_FOR_base_roll_data_computed_ + +struct base_roll_data_computed +{ + real_T delta_xi_deg; + real_T in_flight; + real_T in_flight_gain; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_roll_normal_ +#define DEFINED_TYPEDEF_FOR_base_roll_normal_ + +struct base_roll_normal +{ + real_T pk_c_deg_s; + real_T Phi_c_deg; + real_T xi_deg; + real_T zeta_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_roll_output_ +#define DEFINED_TYPEDEF_FOR_base_roll_output_ + +struct base_roll_output +{ + real_T xi_deg; + real_T zeta_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_lateral_normal_output_ +#define DEFINED_TYPEDEF_FOR_lateral_normal_output_ + +struct lateral_normal_output +{ + lateral_normal_input input; + base_roll_data_computed data_computed; + base_roll_normal law_normal; + base_roll_output output; +}; + +#endif +#endif + diff --git a/src/fbw/src/model/PitchAlternateLaw.cpp b/src/fbw/src/model/PitchAlternateLaw.cpp new file mode 100644 index 000000000000..989a3ec48c96 --- /dev/null +++ b/src/fbw/src/model/PitchAlternateLaw.cpp @@ -0,0 +1,898 @@ +#include "PitchAlternateLaw.h" +#include "rtwtypes.h" +#include +#include "look1_binlxpw.h" + +const uint8_T PitchAlternateLaw_IN_NO_ACTIVE_CHILD{ 0U }; + +const uint8_T PitchAlternateLaw_IN_frozen{ 1U }; + +const uint8_T PitchAlternateLaw_IN_running{ 2U }; + +const uint8_T PitchAlternateLaw_IN_automatic{ 1U }; + +const uint8_T PitchAlternateLaw_IN_manual{ 2U }; + +const uint8_T PitchAlternateLaw_IN_reset{ 3U }; + +const uint8_T PitchAlternateLaw_IN_tracking{ 4U }; + +const uint8_T PitchAlternateLaw_IN_flight_clean{ 1U }; + +const uint8_T PitchAlternateLaw_IN_flight_flaps{ 2U }; + +const uint8_T PitchAlternateLaw_IN_ground{ 3U }; + +PitchAlternateLaw::Parameters_PitchAlternateLaw_T PitchAlternateLaw::PitchAlternateLaw_rtP{ + + { 0.0, 0.06, 0.1, 0.2, 1.0 }, + + 0.3, + + 5.0, + + 0.3, + + 5.0, + + 0.3, + + 5.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 2.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + -30.0, + + + { 1.0, 1.0, 0.5, 0.3, 0.3 }, + + 17.0, + + -0.5, + + -1.0, + + -0.5, + + -45.0, + + 0.5, + + 1.0, + + 0.5, + + 45.0, + + true, + + 0.0, + + 350.0, + + -0.1, + + 0.0, + + + { 180.0, 145.0, 130.0, 120.0, 120.0, 115.0 }, + + + { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 }, + + -0.04, + + 0.0, + + -30.0, + + 0.017453292519943295, + + 0.017453292519943295, + + + { 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0 }, + + + { 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0 }, + + 2000.0, + + 100.0, + + 0.51444444444444448, + + 1.0, + + 0.017453292519943295, + + 100.0, + + 0.1019367991845056, + + + { 13.5, 13.5 }, + + + { 0.0, 350.0 }, + + 1.0, + + + { 0.5, 0.5 }, + + + { 0.0, 350.0 }, + + 1.0, + + -1.0, + + 0.06, + + 1.0, + + -1.0, + + -30.0, + + 30.0, + + -30.0, + + 1.0, + + 0.017453292519943295, + + 100.0, + + 0.1019367991845056, + + + { 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0 }, + + + { 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0 }, + + 2000.0, + + 100.0, + + 0.51444444444444448, + + 1.0, + + + { -2.0, 0.0, 1.5 }, + + + { -1.0, 0.0, 1.0 }, + + 0.0, + + -0.5, + + 0.82, + + 0.5, + + 0.0, + + 33.0, + + -33.0, + + 0.017453292519943295, + + + { 13.5, 13.5 }, + + + { 0.0, 350.0 }, + + + { 0.5, 0.5 }, + + + { 0.0, 350.0 }, + + 1.0, + + -1.0, + + 0.06, + + 1.0, + + -1.0, + + -30.0, + + 30.0, + + -30.0, + + 1.0, + + 0.017453292519943295, + + 100.0, + + 0.1019367991845056, + + + { 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0 }, + + + { 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0 }, + + 2000.0, + + 100.0, + + 0.51444444444444448, + + 1.0, + + + { 13.5, 13.5 }, + + + { 0.0, 350.0 }, + + + { 0.5, 0.5 }, + + + { 0.0, 350.0 }, + + 1.0, + + -1.0, + + 0.06, + + 1.0, + + -1.0, + + -30.0, + + 30.0, + + -30.0, + + 0.0, + + 0.076923076923076927, + + 3.5, + + -11.0, + + false +}; + +void PitchAlternateLaw::PitchAlternateLaw_RateLimiter_Reset(rtDW_RateLimiter_PitchAlternateLaw_T *localDW) +{ + localDW->pY_not_empty = false; +} + +void PitchAlternateLaw::PitchAlternateLaw_RateLimiter(real_T rtu_u, real_T rtu_up, real_T rtu_lo, const real_T *rtu_Ts, + real_T rtu_init, real_T *rty_Y, rtDW_RateLimiter_PitchAlternateLaw_T *localDW) +{ + if (!localDW->pY_not_empty) { + localDW->pY = rtu_init; + localDW->pY_not_empty = true; + } + + localDW->pY += std::fmax(std::fmin(rtu_u - localDW->pY, std::abs(rtu_up) * *rtu_Ts), -std::abs(rtu_lo) * *rtu_Ts); + *rty_Y = localDW->pY; +} + +void PitchAlternateLaw::PitchAlternateLaw_LagFilter_Reset(rtDW_LagFilter_PitchAlternateLaw_T *localDW) +{ + localDW->pY_not_empty = false; + localDW->pU_not_empty = false; +} + +void PitchAlternateLaw::PitchAlternateLaw_LagFilter(real_T rtu_U, real_T rtu_C1, const real_T *rtu_dt, real_T *rty_Y, + rtDW_LagFilter_PitchAlternateLaw_T *localDW) +{ + real_T ca; + real_T denom_tmp; + if ((!localDW->pY_not_empty) || (!localDW->pU_not_empty)) { + localDW->pU = rtu_U; + localDW->pU_not_empty = true; + localDW->pY = rtu_U; + localDW->pY_not_empty = true; + } + + denom_tmp = *rtu_dt * rtu_C1; + ca = denom_tmp / (denom_tmp + 2.0); + *rty_Y = (2.0 - denom_tmp) / (denom_tmp + 2.0) * localDW->pY + (rtu_U * ca + localDW->pU * ca); + localDW->pY = *rty_Y; + localDW->pU = rtu_U; +} + +void PitchAlternateLaw::PitchAlternateLaw_WashoutFilter_Reset(rtDW_WashoutFilter_PitchAlternateLaw_T *localDW) +{ + localDW->pY_not_empty = false; + localDW->pU_not_empty = false; +} + +void PitchAlternateLaw::PitchAlternateLaw_WashoutFilter(real_T rtu_U, real_T rtu_C1, const real_T *rtu_dt, real_T *rty_Y, + rtDW_WashoutFilter_PitchAlternateLaw_T *localDW) +{ + real_T ca; + real_T denom_tmp; + if ((!localDW->pY_not_empty) || (!localDW->pU_not_empty)) { + localDW->pU = rtu_U; + localDW->pU_not_empty = true; + localDW->pY = rtu_U; + localDW->pY_not_empty = true; + } + + denom_tmp = *rtu_dt * rtu_C1; + ca = 2.0 / (denom_tmp + 2.0); + *rty_Y = (2.0 - denom_tmp) / (denom_tmp + 2.0) * localDW->pY + (rtu_U * ca - localDW->pU * ca); + localDW->pY = *rty_Y; + localDW->pU = rtu_U; +} + +void PitchAlternateLaw::init(void) +{ + PitchAlternateLaw_DWork.Delay_DSTATE = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition; + PitchAlternateLaw_DWork.Delay_DSTATE_k = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition; + PitchAlternateLaw_DWork.Delay_DSTATE_d = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition; + PitchAlternateLaw_DWork.Delay_DSTATE_kd = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition_j; + PitchAlternateLaw_DWork.Delay_DSTATE_j = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_a; + PitchAlternateLaw_DWork.Delay_DSTATE_dy = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_d; + PitchAlternateLaw_DWork.Delay_DSTATE_e = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition_f; + PitchAlternateLaw_DWork.Delay_DSTATE_g = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_g; + PitchAlternateLaw_DWork.Delay_DSTATE_l = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_c; + PitchAlternateLaw_DWork.icLoad = true; +} + +void PitchAlternateLaw::reset(void) +{ + real_T rtb_nz_limit_up_g; + real_T rtb_nz_limit_lo_g; + PitchAlternateLaw_DWork.Delay_DSTATE = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition; + PitchAlternateLaw_DWork.Delay_DSTATE_k = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition; + PitchAlternateLaw_DWork.Delay_DSTATE_d = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition; + PitchAlternateLaw_DWork.Delay_DSTATE_kd = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition_j; + PitchAlternateLaw_DWork.Delay_DSTATE_j = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_a; + PitchAlternateLaw_DWork.Delay_DSTATE_dy = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_d; + PitchAlternateLaw_DWork.Delay_DSTATE_e = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition_f; + PitchAlternateLaw_DWork.Delay_DSTATE_g = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_g; + PitchAlternateLaw_DWork.Delay_DSTATE_l = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_c; + PitchAlternateLaw_DWork.icLoad = true; + PitchAlternateLaw_DWork.is_active_c9_PitchAlternateLaw = 0U; + PitchAlternateLaw_DWork.is_c9_PitchAlternateLaw = PitchAlternateLaw_IN_NO_ACTIVE_CHILD; + PitchAlternateLaw_DWork.is_active_c8_PitchAlternateLaw = 0U; + PitchAlternateLaw_DWork.is_c8_PitchAlternateLaw = PitchAlternateLaw_IN_NO_ACTIVE_CHILD; + PitchAlternateLaw_DWork.is_active_c7_PitchAlternateLaw = 0U; + PitchAlternateLaw_DWork.is_c7_PitchAlternateLaw = PitchAlternateLaw_IN_NO_ACTIVE_CHILD; + rtb_nz_limit_up_g = 0.0; + rtb_nz_limit_lo_g = 0.0; + PitchAlternateLaw_RateLimiter_Reset(&PitchAlternateLaw_DWork.sf_RateLimiter); + PitchAlternateLaw_LagFilter_Reset(&PitchAlternateLaw_DWork.sf_LagFilter_g3); + PitchAlternateLaw_WashoutFilter_Reset(&PitchAlternateLaw_DWork.sf_WashoutFilter_c); + PitchAlternateLaw_DWork.pY_not_empty = false; + PitchAlternateLaw_LagFilter_Reset(&PitchAlternateLaw_DWork.sf_LagFilter); + PitchAlternateLaw_WashoutFilter_Reset(&PitchAlternateLaw_DWork.sf_WashoutFilter); + PitchAlternateLaw_RateLimiter_Reset(&PitchAlternateLaw_DWork.sf_RateLimiter_n); + PitchAlternateLaw_LagFilter_Reset(&PitchAlternateLaw_DWork.sf_LagFilter_g); + PitchAlternateLaw_WashoutFilter_Reset(&PitchAlternateLaw_DWork.sf_WashoutFilter_d); + PitchAlternateLaw_RateLimiter_Reset(&PitchAlternateLaw_DWork.sf_RateLimiter_b); +} + +void PitchAlternateLaw::step(const real_T *rtu_In_time_dt, const real_T *rtu_In_nz_g, const real_T *rtu_In_Theta_deg, + const real_T *rtu_In_Phi_deg, const real_T *rtu_In_qk_deg_s, const real_T *rtu_In_eta_deg, const real_T + *rtu_In_eta_trim_deg, const real_T *rtu_In_V_ias_kn, const real_T *rtu_In_mach, const real_T *rtu_In_V_tas_kn, const + real_T *rtu_In_flaps_handle_index, const real_T *rtu_In_spoilers_left_pos, const real_T *rtu_In_spoilers_right_pos, + const real_T *rtu_In_delta_eta_pos, const real_T *rtu_In_in_flight, const boolean_T *rtu_In_tracking_mode_on, const + boolean_T *rtu_In_stabilities_available, real_T *rty_Out_eta_deg, real_T *rty_Out_eta_trim_dot_deg_s, real_T + *rty_Out_eta_trim_limit_lo, real_T *rty_Out_eta_trim_limit_up) +{ + real_T rtb_nz_limit_up_g; + real_T rtb_nz_limit_lo_g; + real_T rtb_TmpSignalConversionAtSFunctionInport1[3]; + real_T rtb_Bias_o; + real_T rtb_Cos; + real_T rtb_Divide; + real_T rtb_Divide1_e; + real_T rtb_Divide_c; + real_T rtb_Divide_d; + real_T rtb_Divide_i; + real_T rtb_Divide_n; + real_T rtb_Gain1; + real_T rtb_Gain_h; + real_T rtb_Gain_j; + real_T rtb_Gain_m; + real_T rtb_Product1_b; + real_T rtb_Product1_d; + real_T rtb_Product1_f; + real_T rtb_Switch_c; + real_T rtb_Switch_i; + real_T rtb_Y_g; + real_T rtb_eta_trim_deg_rate_limit_lo_deg_s; + real_T rtb_eta_trim_deg_rate_limit_up_deg_s; + real_T y; + real_T y_0; + int32_T rtb_TmpSignalConversionAtSFunct; + boolean_T rtb_NOT; + boolean_T rtb_eta_trim_deg_should_freeze; + if (PitchAlternateLaw_DWork.is_active_c9_PitchAlternateLaw == 0U) { + PitchAlternateLaw_DWork.is_active_c9_PitchAlternateLaw = 1U; + PitchAlternateLaw_DWork.is_c9_PitchAlternateLaw = PitchAlternateLaw_IN_running; + rtb_eta_trim_deg_should_freeze = false; + } else if (PitchAlternateLaw_DWork.is_c9_PitchAlternateLaw == PitchAlternateLaw_IN_frozen) { + if ((!PitchAlternateLaw_rtP.Constant_Value_m) && (*rtu_In_nz_g < 1.25) && (*rtu_In_nz_g > 0.5) && (std::abs + (*rtu_In_Phi_deg) <= 30.0)) { + PitchAlternateLaw_DWork.is_c9_PitchAlternateLaw = PitchAlternateLaw_IN_running; + rtb_eta_trim_deg_should_freeze = false; + } else { + rtb_eta_trim_deg_should_freeze = true; + } + } else if (PitchAlternateLaw_rtP.Constant_Value_m || (*rtu_In_nz_g >= 1.25) || (*rtu_In_nz_g <= 0.5) || (std::abs + (*rtu_In_Phi_deg) > 30.0)) { + PitchAlternateLaw_DWork.is_c9_PitchAlternateLaw = PitchAlternateLaw_IN_frozen; + rtb_eta_trim_deg_should_freeze = true; + } else { + rtb_eta_trim_deg_should_freeze = false; + } + + if (PitchAlternateLaw_DWork.is_active_c8_PitchAlternateLaw == 0U) { + PitchAlternateLaw_DWork.is_active_c8_PitchAlternateLaw = 1U; + PitchAlternateLaw_DWork.is_c8_PitchAlternateLaw = PitchAlternateLaw_IN_manual; + } else { + switch (PitchAlternateLaw_DWork.is_c8_PitchAlternateLaw) { + case PitchAlternateLaw_IN_automatic: + if (*rtu_In_in_flight == 0.0) { + PitchAlternateLaw_DWork.is_c8_PitchAlternateLaw = PitchAlternateLaw_IN_reset; + } else if (*rtu_In_tracking_mode_on) { + PitchAlternateLaw_DWork.is_c8_PitchAlternateLaw = PitchAlternateLaw_IN_tracking; + } + break; + + case PitchAlternateLaw_IN_manual: + if (*rtu_In_in_flight != 0.0) { + PitchAlternateLaw_DWork.is_c8_PitchAlternateLaw = PitchAlternateLaw_IN_automatic; + } + break; + + case PitchAlternateLaw_IN_reset: + if ((*rtu_In_in_flight == 0.0) && (*rtu_In_eta_trim_deg == 0.0)) { + PitchAlternateLaw_DWork.is_c8_PitchAlternateLaw = PitchAlternateLaw_IN_manual; + } + break; + + default: + if (!*rtu_In_tracking_mode_on) { + PitchAlternateLaw_DWork.is_c8_PitchAlternateLaw = PitchAlternateLaw_IN_automatic; + } + break; + } + } + + if (PitchAlternateLaw_DWork.is_active_c7_PitchAlternateLaw == 0U) { + PitchAlternateLaw_DWork.is_active_c7_PitchAlternateLaw = 1U; + PitchAlternateLaw_DWork.is_c7_PitchAlternateLaw = PitchAlternateLaw_IN_ground; + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; + rtb_nz_limit_up_g = 2.0; + rtb_nz_limit_lo_g = 0.0; + } else { + switch (PitchAlternateLaw_DWork.is_c7_PitchAlternateLaw) { + case PitchAlternateLaw_IN_flight_clean: + if (*rtu_In_flaps_handle_index != 0.0) { + PitchAlternateLaw_DWork.is_c7_PitchAlternateLaw = PitchAlternateLaw_IN_flight_flaps; + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; + rtb_nz_limit_up_g = 2.0; + rtb_nz_limit_lo_g = 0.0; + } else if (*rtu_In_in_flight == 0.0) { + PitchAlternateLaw_DWork.is_c7_PitchAlternateLaw = PitchAlternateLaw_IN_ground; + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; + rtb_nz_limit_up_g = 2.0; + rtb_nz_limit_lo_g = 0.0; + } else { + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.3; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.3; + rtb_nz_limit_up_g = 2.5; + rtb_nz_limit_lo_g = -1.0; + } + break; + + case PitchAlternateLaw_IN_flight_flaps: + if (*rtu_In_flaps_handle_index == 0.0) { + PitchAlternateLaw_DWork.is_c7_PitchAlternateLaw = PitchAlternateLaw_IN_flight_clean; + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.3; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.3; + rtb_nz_limit_up_g = 2.5; + rtb_nz_limit_lo_g = -1.0; + } else if (*rtu_In_in_flight == 0.0) { + PitchAlternateLaw_DWork.is_c7_PitchAlternateLaw = PitchAlternateLaw_IN_ground; + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; + rtb_nz_limit_up_g = 2.0; + rtb_nz_limit_lo_g = 0.0; + } else { + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; + rtb_nz_limit_up_g = 2.0; + rtb_nz_limit_lo_g = 0.0; + } + break; + + default: + if ((*rtu_In_in_flight != 0.0) && (*rtu_In_flaps_handle_index == 0.0)) { + PitchAlternateLaw_DWork.is_c7_PitchAlternateLaw = PitchAlternateLaw_IN_flight_clean; + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.3; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.3; + rtb_nz_limit_up_g = 2.5; + rtb_nz_limit_lo_g = -1.0; + } else if ((*rtu_In_in_flight != 0.0) && (*rtu_In_flaps_handle_index != 0.0)) { + PitchAlternateLaw_DWork.is_c7_PitchAlternateLaw = PitchAlternateLaw_IN_flight_flaps; + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; + rtb_nz_limit_up_g = 2.0; + rtb_nz_limit_lo_g = 0.0; + } else { + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; + rtb_nz_limit_up_g = 2.0; + rtb_nz_limit_lo_g = 0.0; + } + break; + } + } + + PitchAlternateLaw_RateLimiter(rtb_nz_limit_up_g, PitchAlternateLaw_rtP.RateLimiterVariableTs2_up, + PitchAlternateLaw_rtP.RateLimiterVariableTs2_lo, rtu_In_time_dt, + PitchAlternateLaw_rtP.RateLimiterVariableTs2_InitialCondition, &rtb_Y_g, &PitchAlternateLaw_DWork.sf_RateLimiter); + rtb_Gain1 = PitchAlternateLaw_rtP.Gain1_Gain_c * *rtu_In_Theta_deg; + rtb_Cos = std::cos(rtb_Gain1); + rtb_Gain1 = PitchAlternateLaw_rtP.Gain1_Gain_l * *rtu_In_Phi_deg; + rtb_Divide1_e = rtb_Cos / std::cos(rtb_Gain1); + rtb_Switch_i = look1_binlxpw(*rtu_In_V_tas_kn, PitchAlternateLaw_rtP.uDLookupTable_bp01Data_o, + PitchAlternateLaw_rtP.uDLookupTable_tableData_e, 6U); + rtb_Product1_d = *rtu_In_V_tas_kn; + rtb_Gain1 = PitchAlternateLaw_rtP.Gain1_Gain_o * *rtu_In_qk_deg_s; + rtb_Gain_m = *rtu_In_nz_g - rtb_Divide1_e; + if (rtb_Product1_d > PitchAlternateLaw_rtP.Saturation3_UpperSat) { + rtb_Product1_d = PitchAlternateLaw_rtP.Saturation3_UpperSat; + } else if (rtb_Product1_d < PitchAlternateLaw_rtP.Saturation3_LowerSat) { + rtb_Product1_d = PitchAlternateLaw_rtP.Saturation3_LowerSat; + } + + rtb_Switch_c = (PitchAlternateLaw_rtP.Gain_Gain_a * PitchAlternateLaw_rtP.Vm_currentms_Value * rtb_Gain1 + rtb_Gain_m) + - (rtb_Switch_i / (PitchAlternateLaw_rtP.Gain5_Gain * rtb_Product1_d) + PitchAlternateLaw_rtP.Bias_Bias) * (rtb_Y_g + - rtb_Divide1_e); + rtb_Switch_i = look1_binlxpw(*rtu_In_V_tas_kn, PitchAlternateLaw_rtP.PLUT_bp01Data, + PitchAlternateLaw_rtP.PLUT_tableData, 1U); + rtb_Product1_f = rtb_Switch_c * rtb_Switch_i; + rtb_Y_g = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs1_Gain * *rtu_In_qk_deg_s; + rtb_Divide = (rtb_Y_g - PitchAlternateLaw_DWork.Delay_DSTATE) / *rtu_In_time_dt; + rtb_Switch_i = look1_binlxpw(*rtu_In_V_tas_kn, PitchAlternateLaw_rtP.DLUT_bp01Data, + PitchAlternateLaw_rtP.DLUT_tableData, 1U); + rtb_Gain1 = rtb_Switch_c * rtb_Switch_i * PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs_Gain; + rtb_Divide_c = (rtb_Gain1 - PitchAlternateLaw_DWork.Delay_DSTATE_k) / *rtu_In_time_dt; + rtb_Gain_j = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs2_Gain * *rtu_In_V_tas_kn; + rtb_Switch_c = PitchAlternateLaw_DWork.Delay_DSTATE_d; + rtb_Divide_d = (rtb_Gain_j - PitchAlternateLaw_DWork.Delay_DSTATE_d) / *rtu_In_time_dt; + PitchAlternateLaw_LagFilter(rtb_Divide_d, PitchAlternateLaw_rtP.LagFilter_C1, rtu_In_time_dt, &rtb_Switch_c, + &PitchAlternateLaw_DWork.sf_LagFilter_g3); + if (rtb_Switch_c > PitchAlternateLaw_rtP.SaturationV_dot_UpperSat) { + rtb_Switch_c = PitchAlternateLaw_rtP.SaturationV_dot_UpperSat; + } else if (rtb_Switch_c < PitchAlternateLaw_rtP.SaturationV_dot_LowerSat) { + rtb_Switch_c = PitchAlternateLaw_rtP.SaturationV_dot_LowerSat; + } + + rtb_Divide_c = ((PitchAlternateLaw_rtP.Gain3_Gain * rtb_Divide + rtb_Product1_f) + rtb_Divide_c) + + PitchAlternateLaw_rtP.Gain_Gain_j * rtb_Switch_c; + rtb_Divide_d = std::fmin(*rtu_In_spoilers_left_pos, *rtu_In_spoilers_right_pos); + PitchAlternateLaw_WashoutFilter(rtb_Divide_d, PitchAlternateLaw_rtP.WashoutFilter_C1, rtu_In_time_dt, &rtb_Switch_c, + &PitchAlternateLaw_DWork.sf_WashoutFilter_c); + if (rtb_Switch_c > PitchAlternateLaw_rtP.SaturationSpoilers_UpperSat) { + y = PitchAlternateLaw_rtP.SaturationSpoilers_UpperSat; + } else if (rtb_Switch_c < PitchAlternateLaw_rtP.SaturationSpoilers_LowerSat) { + y = PitchAlternateLaw_rtP.SaturationSpoilers_LowerSat; + } else { + y = rtb_Switch_c; + } + + rtb_Product1_f = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs1_Gain_m * *rtu_In_qk_deg_s; + rtb_Divide_n = (rtb_Product1_f - PitchAlternateLaw_DWork.Delay_DSTATE_kd) / *rtu_In_time_dt; + rtb_Divide = PitchAlternateLaw_rtP.Gain1_Gain_e * *rtu_In_qk_deg_s; + rtb_Switch_c = look1_binlxpw(*rtu_In_V_tas_kn, PitchAlternateLaw_rtP.uDLookupTable_bp01Data_b, + PitchAlternateLaw_rtP.uDLookupTable_tableData_h, 6U); + rtb_Product1_d = *rtu_In_V_tas_kn; + if (rtb_Product1_d > PitchAlternateLaw_rtP.Saturation3_UpperSat_b) { + rtb_Product1_d = PitchAlternateLaw_rtP.Saturation3_UpperSat_b; + } else if (rtb_Product1_d < PitchAlternateLaw_rtP.Saturation3_LowerSat_e) { + rtb_Product1_d = PitchAlternateLaw_rtP.Saturation3_LowerSat_e; + } + + rtb_Bias_o = rtb_Switch_c / (PitchAlternateLaw_rtP.Gain5_Gain_e * rtb_Product1_d) + PitchAlternateLaw_rtP.Bias_Bias_f; + if (!PitchAlternateLaw_DWork.pY_not_empty) { + PitchAlternateLaw_DWork.pY = PitchAlternateLaw_rtP.RateLimiterVariableTs_InitialCondition; + PitchAlternateLaw_DWork.pY_not_empty = true; + } + + PitchAlternateLaw_DWork.pY += std::fmax(std::fmin(*rtu_In_delta_eta_pos - PitchAlternateLaw_DWork.pY, std::abs + (PitchAlternateLaw_rtP.RateLimiterVariableTs_up) * *rtu_In_time_dt), -std::abs + (PitchAlternateLaw_rtP.RateLimiterVariableTs_lo) * *rtu_In_time_dt); + if (*rtu_In_stabilities_available) { + rtb_Switch_c = look1_binlxpw(*rtu_In_flaps_handle_index, PitchAlternateLaw_rtP.uDLookupTable_bp01Data, + PitchAlternateLaw_rtP.uDLookupTable_tableData, 5U); + rtb_Switch_c -= *rtu_In_V_ias_kn; + rtb_Switch_i = PitchAlternateLaw_rtP.Gain1_Gain * rtb_Switch_c; + } else { + rtb_Switch_i = PitchAlternateLaw_rtP.Constant_Value_n; + } + + rtb_Switch_c = *rtu_In_V_ias_kn * PitchAlternateLaw_rtP.Constant6_Value / *rtu_In_mach; + if (*rtu_In_stabilities_available) { + rtb_Switch_c = std::fmin(PitchAlternateLaw_rtP.Constant5_Value, rtb_Switch_c) - *rtu_In_V_ias_kn; + rtb_Switch_c *= PitchAlternateLaw_rtP.Gain2_Gain; + } else { + rtb_Switch_c = PitchAlternateLaw_rtP.Constant3_Value; + } + + rtb_Product1_d = *rtu_In_Phi_deg; + if (rtb_Switch_i > PitchAlternateLaw_rtP.Saturation_UpperSat_m) { + rtb_Switch_i = PitchAlternateLaw_rtP.Saturation_UpperSat_m; + } else if (rtb_Switch_i < PitchAlternateLaw_rtP.Saturation_LowerSat_e) { + rtb_Switch_i = PitchAlternateLaw_rtP.Saturation_LowerSat_e; + } + + if (rtb_Switch_c > PitchAlternateLaw_rtP.Saturation1_UpperSat) { + rtb_Switch_c = PitchAlternateLaw_rtP.Saturation1_UpperSat; + } else if (rtb_Switch_c < PitchAlternateLaw_rtP.Saturation1_LowerSat) { + rtb_Switch_c = PitchAlternateLaw_rtP.Saturation1_LowerSat; + } + + if (rtb_Product1_d > PitchAlternateLaw_rtP.Saturation_UpperSat_f) { + rtb_Product1_d = PitchAlternateLaw_rtP.Saturation_UpperSat_f; + } else if (rtb_Product1_d < PitchAlternateLaw_rtP.Saturation_LowerSat_o) { + rtb_Product1_d = PitchAlternateLaw_rtP.Saturation_LowerSat_o; + } + + rtb_Switch_c = (PitchAlternateLaw_rtP.Gain_Gain_b * PitchAlternateLaw_rtP.Vm_currentms_Value_h * rtb_Divide + + rtb_Gain_m) - ((((look1_binlxpw(PitchAlternateLaw_DWork.pY, PitchAlternateLaw_rtP.Loaddemand_bp01Data, + PitchAlternateLaw_rtP.Loaddemand_tableData, 2U) + rtb_Switch_i) + rtb_Switch_c) + rtb_Cos / std::cos + (PitchAlternateLaw_rtP.Gain1_Gain_lm * rtb_Product1_d)) - rtb_Divide1_e) * rtb_Bias_o; + rtb_Switch_i = look1_binlxpw(*rtu_In_V_tas_kn, PitchAlternateLaw_rtP.PLUT_bp01Data_f, + PitchAlternateLaw_rtP.PLUT_tableData_k, 1U); + rtb_Product1_d = rtb_Switch_c * rtb_Switch_i; + rtb_Switch_i = look1_binlxpw(*rtu_In_V_tas_kn, PitchAlternateLaw_rtP.DLUT_bp01Data_m, + PitchAlternateLaw_rtP.DLUT_tableData_a, 1U); + rtb_Cos = rtb_Switch_c * rtb_Switch_i * PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs_Gain_b; + rtb_Switch_i = (rtb_Cos - PitchAlternateLaw_DWork.Delay_DSTATE_j) / *rtu_In_time_dt; + rtb_Divide = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs2_Gain_c * *rtu_In_V_tas_kn; + rtb_Switch_c = PitchAlternateLaw_DWork.Delay_DSTATE_dy; + rtb_Bias_o = (rtb_Divide - PitchAlternateLaw_DWork.Delay_DSTATE_dy) / *rtu_In_time_dt; + PitchAlternateLaw_LagFilter(rtb_Bias_o, PitchAlternateLaw_rtP.LagFilter_C1_p, rtu_In_time_dt, &rtb_Switch_c, + &PitchAlternateLaw_DWork.sf_LagFilter); + if (rtb_Switch_c > PitchAlternateLaw_rtP.SaturationV_dot_UpperSat_b) { + rtb_Switch_c = PitchAlternateLaw_rtP.SaturationV_dot_UpperSat_b; + } else if (rtb_Switch_c < PitchAlternateLaw_rtP.SaturationV_dot_LowerSat_m) { + rtb_Switch_c = PitchAlternateLaw_rtP.SaturationV_dot_LowerSat_m; + } + + rtb_Bias_o = ((PitchAlternateLaw_rtP.Gain3_Gain_c * rtb_Divide_n + rtb_Product1_d) + rtb_Switch_i) + + PitchAlternateLaw_rtP.Gain_Gain_f * rtb_Switch_c; + PitchAlternateLaw_WashoutFilter(rtb_Divide_d, PitchAlternateLaw_rtP.WashoutFilter_C1_l, rtu_In_time_dt, &rtb_Switch_c, + &PitchAlternateLaw_DWork.sf_WashoutFilter); + if (rtb_Switch_c > PitchAlternateLaw_rtP.SaturationSpoilers_UpperSat_o) { + y_0 = PitchAlternateLaw_rtP.SaturationSpoilers_UpperSat_o; + } else if (rtb_Switch_c < PitchAlternateLaw_rtP.SaturationSpoilers_LowerSat_j) { + y_0 = PitchAlternateLaw_rtP.SaturationSpoilers_LowerSat_j; + } else { + y_0 = rtb_Switch_c; + } + + rtb_Divide_n = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs1_Gain_c * *rtu_In_qk_deg_s; + rtb_Divide_i = (rtb_Divide_n - PitchAlternateLaw_DWork.Delay_DSTATE_e) / *rtu_In_time_dt; + rtb_Switch_i = PitchAlternateLaw_rtP.Gain1_Gain_b * *rtu_In_qk_deg_s; + rtb_Switch_c = look1_binlxpw(*rtu_In_V_tas_kn, PitchAlternateLaw_rtP.uDLookupTable_bp01Data_a, + PitchAlternateLaw_rtP.uDLookupTable_tableData_p, 6U); + rtb_Product1_d = *rtu_In_V_tas_kn; + if (rtb_Product1_d > PitchAlternateLaw_rtP.Saturation3_UpperSat_n) { + rtb_Product1_d = PitchAlternateLaw_rtP.Saturation3_UpperSat_n; + } else if (rtb_Product1_d < PitchAlternateLaw_rtP.Saturation3_LowerSat_a) { + rtb_Product1_d = PitchAlternateLaw_rtP.Saturation3_LowerSat_a; + } + + rtb_Product1_d = rtb_Switch_c / (PitchAlternateLaw_rtP.Gain5_Gain_n * rtb_Product1_d) + + PitchAlternateLaw_rtP.Bias_Bias_a; + PitchAlternateLaw_RateLimiter(rtb_nz_limit_lo_g, PitchAlternateLaw_rtP.RateLimiterVariableTs3_up, + PitchAlternateLaw_rtP.RateLimiterVariableTs3_lo, rtu_In_time_dt, + PitchAlternateLaw_rtP.RateLimiterVariableTs3_InitialCondition, &rtb_Switch_c, + &PitchAlternateLaw_DWork.sf_RateLimiter_n); + rtb_Switch_c = (PitchAlternateLaw_rtP.Gain_Gain_p * PitchAlternateLaw_rtP.Vm_currentms_Value_p * rtb_Switch_i + + rtb_Gain_m) - (rtb_Switch_c - rtb_Divide1_e) * rtb_Product1_d; + rtb_Switch_i = look1_binlxpw(*rtu_In_V_tas_kn, PitchAlternateLaw_rtP.PLUT_bp01Data_a, + PitchAlternateLaw_rtP.PLUT_tableData_o, 1U); + rtb_Product1_b = rtb_Switch_c * rtb_Switch_i; + rtb_Switch_i = look1_binlxpw(*rtu_In_V_tas_kn, PitchAlternateLaw_rtP.DLUT_bp01Data_k, + PitchAlternateLaw_rtP.DLUT_tableData_e, 1U); + rtb_Divide1_e = rtb_Switch_c * rtb_Switch_i * PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs_Gain_p; + rtb_Switch_i = (rtb_Divide1_e - PitchAlternateLaw_DWork.Delay_DSTATE_g) / *rtu_In_time_dt; + rtb_Gain_m = PitchAlternateLaw_rtP.DiscreteDerivativeVariableTs2_Gain_a * *rtu_In_V_tas_kn; + rtb_Switch_c = PitchAlternateLaw_DWork.Delay_DSTATE_l; + rtb_Product1_d = (rtb_Gain_m - PitchAlternateLaw_DWork.Delay_DSTATE_l) / *rtu_In_time_dt; + PitchAlternateLaw_LagFilter(rtb_Product1_d, PitchAlternateLaw_rtP.LagFilter_C1_l, rtu_In_time_dt, &rtb_Switch_c, + &PitchAlternateLaw_DWork.sf_LagFilter_g); + if (rtb_Switch_c > PitchAlternateLaw_rtP.SaturationV_dot_UpperSat_m) { + rtb_Switch_c = PitchAlternateLaw_rtP.SaturationV_dot_UpperSat_m; + } else if (rtb_Switch_c < PitchAlternateLaw_rtP.SaturationV_dot_LowerSat_e) { + rtb_Switch_c = PitchAlternateLaw_rtP.SaturationV_dot_LowerSat_e; + } + + rtb_Gain_h = PitchAlternateLaw_rtP.Gain_Gain_k * rtb_Switch_c; + PitchAlternateLaw_WashoutFilter(rtb_Divide_d, PitchAlternateLaw_rtP.WashoutFilter_C1_h, rtu_In_time_dt, &rtb_Switch_c, + &PitchAlternateLaw_DWork.sf_WashoutFilter_d); + rtb_Product1_d = PitchAlternateLaw_rtP.Gain1_Gain_p * y + rtb_Divide_c; + if (rtb_Product1_d > PitchAlternateLaw_rtP.Saturation_UpperSat) { + rtb_TmpSignalConversionAtSFunctionInport1[0] = PitchAlternateLaw_rtP.Saturation_UpperSat; + } else if (rtb_Product1_d < PitchAlternateLaw_rtP.Saturation_LowerSat) { + rtb_TmpSignalConversionAtSFunctionInport1[0] = PitchAlternateLaw_rtP.Saturation_LowerSat; + } else { + rtb_TmpSignalConversionAtSFunctionInport1[0] = rtb_Product1_d; + } + + rtb_Product1_d = PitchAlternateLaw_rtP.Gain1_Gain_h * y_0 + rtb_Bias_o; + if (rtb_Product1_d > PitchAlternateLaw_rtP.Saturation_UpperSat_k) { + rtb_TmpSignalConversionAtSFunctionInport1[1] = PitchAlternateLaw_rtP.Saturation_UpperSat_k; + } else if (rtb_Product1_d < PitchAlternateLaw_rtP.Saturation_LowerSat_p) { + rtb_TmpSignalConversionAtSFunctionInport1[1] = PitchAlternateLaw_rtP.Saturation_LowerSat_p; + } else { + rtb_TmpSignalConversionAtSFunctionInport1[1] = rtb_Product1_d; + } + + if (rtb_Switch_c > PitchAlternateLaw_rtP.SaturationSpoilers_UpperSat_h) { + rtb_Switch_c = PitchAlternateLaw_rtP.SaturationSpoilers_UpperSat_h; + } else if (rtb_Switch_c < PitchAlternateLaw_rtP.SaturationSpoilers_LowerSat_l) { + rtb_Switch_c = PitchAlternateLaw_rtP.SaturationSpoilers_LowerSat_l; + } + + rtb_Product1_d = (((PitchAlternateLaw_rtP.Gain3_Gain_b * rtb_Divide_i + rtb_Product1_b) + rtb_Switch_i) + rtb_Gain_h) + + PitchAlternateLaw_rtP.Gain1_Gain_ov * rtb_Switch_c; + if (rtb_Product1_d > PitchAlternateLaw_rtP.Saturation_UpperSat_j) { + rtb_TmpSignalConversionAtSFunctionInport1[2] = PitchAlternateLaw_rtP.Saturation_UpperSat_j; + } else if (rtb_Product1_d < PitchAlternateLaw_rtP.Saturation_LowerSat_d) { + rtb_TmpSignalConversionAtSFunctionInport1[2] = PitchAlternateLaw_rtP.Saturation_LowerSat_d; + } else { + rtb_TmpSignalConversionAtSFunctionInport1[2] = rtb_Product1_d; + } + + rtb_Switch_c = look1_binlxpw(*rtu_In_time_dt, PitchAlternateLaw_rtP.ScheduledGain_BreakpointsForDimension1, + PitchAlternateLaw_rtP.ScheduledGain_Table, 4U); + if (rtb_TmpSignalConversionAtSFunctionInport1[0] < rtb_TmpSignalConversionAtSFunctionInport1[1]) { + if (rtb_TmpSignalConversionAtSFunctionInport1[1] < rtb_TmpSignalConversionAtSFunctionInport1[2]) { + rtb_TmpSignalConversionAtSFunct = 1; + } else if (rtb_TmpSignalConversionAtSFunctionInport1[0] < rtb_TmpSignalConversionAtSFunctionInport1[2]) { + rtb_TmpSignalConversionAtSFunct = 2; + } else { + rtb_TmpSignalConversionAtSFunct = 0; + } + } else if (rtb_TmpSignalConversionAtSFunctionInport1[0] < rtb_TmpSignalConversionAtSFunctionInport1[2]) { + rtb_TmpSignalConversionAtSFunct = 0; + } else if (rtb_TmpSignalConversionAtSFunctionInport1[1] < rtb_TmpSignalConversionAtSFunctionInport1[2]) { + rtb_TmpSignalConversionAtSFunct = 2; + } else { + rtb_TmpSignalConversionAtSFunct = 1; + } + + rtb_Switch_c = rtb_TmpSignalConversionAtSFunctionInport1[rtb_TmpSignalConversionAtSFunct] * rtb_Switch_c * + PitchAlternateLaw_rtP.DiscreteTimeIntegratorVariableTs_Gain * *rtu_In_time_dt; + if (*rtu_In_in_flight > PitchAlternateLaw_rtP.Switch_Threshold) { + rtb_Switch_i = *rtu_In_eta_deg; + } else { + rtb_Switch_i = PitchAlternateLaw_rtP.Gain_Gain * *rtu_In_delta_eta_pos; + } + + rtb_NOT = (*rtu_In_in_flight == 0.0); + rtb_NOT = (rtb_NOT || (*rtu_In_tracking_mode_on)); + PitchAlternateLaw_DWork.icLoad = (rtb_NOT || PitchAlternateLaw_DWork.icLoad); + if (PitchAlternateLaw_DWork.icLoad) { + PitchAlternateLaw_DWork.Delay_DSTATE_o = rtb_Switch_i - rtb_Switch_c; + } + + PitchAlternateLaw_DWork.Delay_DSTATE_o += rtb_Switch_c; + if (PitchAlternateLaw_DWork.Delay_DSTATE_o > PitchAlternateLaw_rtP.DiscreteTimeIntegratorVariableTs_UpperLimit) { + PitchAlternateLaw_DWork.Delay_DSTATE_o = PitchAlternateLaw_rtP.DiscreteTimeIntegratorVariableTs_UpperLimit; + } else if (PitchAlternateLaw_DWork.Delay_DSTATE_o < PitchAlternateLaw_rtP.DiscreteTimeIntegratorVariableTs_LowerLimit) + { + PitchAlternateLaw_DWork.Delay_DSTATE_o = PitchAlternateLaw_rtP.DiscreteTimeIntegratorVariableTs_LowerLimit; + } + + if (rtb_eta_trim_deg_should_freeze == PitchAlternateLaw_rtP.CompareToConstant_const) { + rtb_Switch_c = PitchAlternateLaw_rtP.Constant_Value; + } else { + rtb_Switch_c = PitchAlternateLaw_DWork.Delay_DSTATE_o; + } + + rtb_Switch_c *= PitchAlternateLaw_rtP.Gain_Gain_c; + if (rtb_Switch_c > rtb_eta_trim_deg_rate_limit_up_deg_s) { + *rty_Out_eta_trim_dot_deg_s = rtb_eta_trim_deg_rate_limit_up_deg_s; + } else if (rtb_Switch_c < rtb_eta_trim_deg_rate_limit_lo_deg_s) { + *rty_Out_eta_trim_dot_deg_s = rtb_eta_trim_deg_rate_limit_lo_deg_s; + } else { + *rty_Out_eta_trim_dot_deg_s = rtb_Switch_c; + } + + PitchAlternateLaw_RateLimiter(PitchAlternateLaw_DWork.Delay_DSTATE_o, PitchAlternateLaw_rtP.RateLimitereta_up, + PitchAlternateLaw_rtP.RateLimitereta_lo, rtu_In_time_dt, PitchAlternateLaw_rtP.RateLimitereta_InitialCondition, + rty_Out_eta_deg, &PitchAlternateLaw_DWork.sf_RateLimiter_b); + *rty_Out_eta_trim_limit_up = PitchAlternateLaw_rtP.Constant2_Value; + *rty_Out_eta_trim_limit_lo = PitchAlternateLaw_rtP.Constant3_Value_j; + PitchAlternateLaw_DWork.Delay_DSTATE = rtb_Y_g; + PitchAlternateLaw_DWork.Delay_DSTATE_k = rtb_Gain1; + PitchAlternateLaw_DWork.Delay_DSTATE_d = rtb_Gain_j; + PitchAlternateLaw_DWork.Delay_DSTATE_kd = rtb_Product1_f; + PitchAlternateLaw_DWork.Delay_DSTATE_j = rtb_Cos; + PitchAlternateLaw_DWork.Delay_DSTATE_dy = rtb_Divide; + PitchAlternateLaw_DWork.Delay_DSTATE_e = rtb_Divide_n; + PitchAlternateLaw_DWork.Delay_DSTATE_g = rtb_Divide1_e; + PitchAlternateLaw_DWork.Delay_DSTATE_l = rtb_Gain_m; + PitchAlternateLaw_DWork.icLoad = false; +} + +PitchAlternateLaw::PitchAlternateLaw(): + PitchAlternateLaw_DWork() +{ +} + +PitchAlternateLaw::~PitchAlternateLaw() +{ +} diff --git a/src/fbw/src/model/PitchAlternateLaw.h b/src/fbw/src/model/PitchAlternateLaw.h new file mode 100644 index 000000000000..494d858ba1fa --- /dev/null +++ b/src/fbw/src/model/PitchAlternateLaw.h @@ -0,0 +1,229 @@ +#ifndef RTW_HEADER_PitchAlternateLaw_h_ +#define RTW_HEADER_PitchAlternateLaw_h_ +#include "rtwtypes.h" +#include "PitchAlternateLaw_types.h" +#include + +class PitchAlternateLaw final +{ + public: + struct rtDW_RateLimiter_PitchAlternateLaw_T { + real_T pY; + boolean_T pY_not_empty; + }; + + struct rtDW_LagFilter_PitchAlternateLaw_T { + real_T pY; + real_T pU; + boolean_T pY_not_empty; + boolean_T pU_not_empty; + }; + + struct rtDW_WashoutFilter_PitchAlternateLaw_T { + real_T pY; + real_T pU; + boolean_T pY_not_empty; + boolean_T pU_not_empty; + }; + + struct D_Work_PitchAlternateLaw_T { + real_T Delay_DSTATE; + real_T Delay_DSTATE_k; + real_T Delay_DSTATE_d; + real_T Delay_DSTATE_kd; + real_T Delay_DSTATE_j; + real_T Delay_DSTATE_dy; + real_T Delay_DSTATE_e; + real_T Delay_DSTATE_g; + real_T Delay_DSTATE_l; + real_T Delay_DSTATE_o; + real_T pY; + uint8_T is_active_c7_PitchAlternateLaw; + uint8_T is_c7_PitchAlternateLaw; + uint8_T is_active_c8_PitchAlternateLaw; + uint8_T is_c8_PitchAlternateLaw; + uint8_T is_active_c9_PitchAlternateLaw; + uint8_T is_c9_PitchAlternateLaw; + boolean_T icLoad; + boolean_T pY_not_empty; + rtDW_RateLimiter_PitchAlternateLaw_T sf_RateLimiter_b; + rtDW_WashoutFilter_PitchAlternateLaw_T sf_WashoutFilter_c; + rtDW_LagFilter_PitchAlternateLaw_T sf_LagFilter_g3; + rtDW_WashoutFilter_PitchAlternateLaw_T sf_WashoutFilter_d; + rtDW_LagFilter_PitchAlternateLaw_T sf_LagFilter_g; + rtDW_WashoutFilter_PitchAlternateLaw_T sf_WashoutFilter; + rtDW_LagFilter_PitchAlternateLaw_T sf_LagFilter; + rtDW_RateLimiter_PitchAlternateLaw_T sf_RateLimiter_n; + rtDW_RateLimiter_PitchAlternateLaw_T sf_RateLimiter; + }; + + struct Parameters_PitchAlternateLaw_T { + real_T ScheduledGain_BreakpointsForDimension1[5]; + real_T LagFilter_C1; + real_T WashoutFilter_C1; + real_T LagFilter_C1_p; + real_T WashoutFilter_C1_l; + real_T LagFilter_C1_l; + real_T WashoutFilter_C1_h; + real_T DiscreteDerivativeVariableTs1_Gain; + real_T DiscreteDerivativeVariableTs_Gain; + real_T DiscreteDerivativeVariableTs2_Gain; + real_T DiscreteDerivativeVariableTs1_Gain_m; + real_T DiscreteDerivativeVariableTs_Gain_b; + real_T DiscreteDerivativeVariableTs2_Gain_c; + real_T DiscreteDerivativeVariableTs1_Gain_c; + real_T DiscreteDerivativeVariableTs_Gain_p; + real_T DiscreteDerivativeVariableTs2_Gain_a; + real_T DiscreteTimeIntegratorVariableTs_Gain; + real_T RateLimiterVariableTs2_InitialCondition; + real_T DiscreteDerivativeVariableTs1_InitialCondition; + real_T DiscreteDerivativeVariableTs_InitialCondition; + real_T DiscreteDerivativeVariableTs2_InitialCondition; + real_T DiscreteDerivativeVariableTs1_InitialCondition_j; + real_T RateLimiterVariableTs_InitialCondition; + real_T DiscreteDerivativeVariableTs_InitialCondition_a; + real_T DiscreteDerivativeVariableTs2_InitialCondition_d; + real_T DiscreteDerivativeVariableTs1_InitialCondition_f; + real_T RateLimiterVariableTs3_InitialCondition; + real_T DiscreteDerivativeVariableTs_InitialCondition_g; + real_T DiscreteDerivativeVariableTs2_InitialCondition_c; + real_T RateLimitereta_InitialCondition; + real_T DiscreteTimeIntegratorVariableTs_LowerLimit; + real_T ScheduledGain_Table[5]; + real_T DiscreteTimeIntegratorVariableTs_UpperLimit; + real_T RateLimiterVariableTs2_lo; + real_T RateLimiterVariableTs_lo; + real_T RateLimiterVariableTs3_lo; + real_T RateLimitereta_lo; + real_T RateLimiterVariableTs2_up; + real_T RateLimiterVariableTs_up; + real_T RateLimiterVariableTs3_up; + real_T RateLimitereta_up; + boolean_T CompareToConstant_const; + real_T Constant_Value; + real_T Constant5_Value; + real_T Gain2_Gain; + real_T Constant3_Value; + real_T uDLookupTable_tableData[6]; + real_T uDLookupTable_bp01Data[6]; + real_T Gain1_Gain; + real_T Constant_Value_n; + real_T Gain_Gain; + real_T Gain1_Gain_c; + real_T Gain1_Gain_l; + real_T uDLookupTable_tableData_e[7]; + real_T uDLookupTable_bp01Data_o[7]; + real_T Saturation3_UpperSat; + real_T Saturation3_LowerSat; + real_T Gain5_Gain; + real_T Bias_Bias; + real_T Gain1_Gain_o; + real_T Vm_currentms_Value; + real_T Gain_Gain_a; + real_T PLUT_tableData[2]; + real_T PLUT_bp01Data[2]; + real_T Gain3_Gain; + real_T DLUT_tableData[2]; + real_T DLUT_bp01Data[2]; + real_T SaturationV_dot_UpperSat; + real_T SaturationV_dot_LowerSat; + real_T Gain_Gain_j; + real_T SaturationSpoilers_UpperSat; + real_T SaturationSpoilers_LowerSat; + real_T Gain1_Gain_p; + real_T Saturation_UpperSat; + real_T Saturation_LowerSat; + real_T Gain3_Gain_c; + real_T Gain1_Gain_e; + real_T Vm_currentms_Value_h; + real_T Gain_Gain_b; + real_T uDLookupTable_tableData_h[7]; + real_T uDLookupTable_bp01Data_b[7]; + real_T Saturation3_UpperSat_b; + real_T Saturation3_LowerSat_e; + real_T Gain5_Gain_e; + real_T Bias_Bias_f; + real_T Loaddemand_tableData[3]; + real_T Loaddemand_bp01Data[3]; + real_T Saturation_UpperSat_m; + real_T Saturation_LowerSat_e; + real_T Constant6_Value; + real_T Saturation1_UpperSat; + real_T Saturation1_LowerSat; + real_T Saturation_UpperSat_f; + real_T Saturation_LowerSat_o; + real_T Gain1_Gain_lm; + real_T PLUT_tableData_k[2]; + real_T PLUT_bp01Data_f[2]; + real_T DLUT_tableData_a[2]; + real_T DLUT_bp01Data_m[2]; + real_T SaturationV_dot_UpperSat_b; + real_T SaturationV_dot_LowerSat_m; + real_T Gain_Gain_f; + real_T SaturationSpoilers_UpperSat_o; + real_T SaturationSpoilers_LowerSat_j; + real_T Gain1_Gain_h; + real_T Saturation_UpperSat_k; + real_T Saturation_LowerSat_p; + real_T Gain3_Gain_b; + real_T Gain1_Gain_b; + real_T Vm_currentms_Value_p; + real_T Gain_Gain_p; + real_T uDLookupTable_tableData_p[7]; + real_T uDLookupTable_bp01Data_a[7]; + real_T Saturation3_UpperSat_n; + real_T Saturation3_LowerSat_a; + real_T Gain5_Gain_n; + real_T Bias_Bias_a; + real_T PLUT_tableData_o[2]; + real_T PLUT_bp01Data_a[2]; + real_T DLUT_tableData_e[2]; + real_T DLUT_bp01Data_k[2]; + real_T SaturationV_dot_UpperSat_m; + real_T SaturationV_dot_LowerSat_e; + real_T Gain_Gain_k; + real_T SaturationSpoilers_UpperSat_h; + real_T SaturationSpoilers_LowerSat_l; + real_T Gain1_Gain_ov; + real_T Saturation_UpperSat_j; + real_T Saturation_LowerSat_d; + real_T Switch_Threshold; + real_T Gain_Gain_c; + real_T Constant2_Value; + real_T Constant3_Value_j; + boolean_T Constant_Value_m; + }; + + void init(); + PitchAlternateLaw(PitchAlternateLaw const&) = delete; + PitchAlternateLaw& operator= (PitchAlternateLaw const&) & = delete; + PitchAlternateLaw(PitchAlternateLaw &&) = delete; + PitchAlternateLaw& operator= (PitchAlternateLaw &&) = delete; + void step(const real_T *rtu_In_time_dt, const real_T *rtu_In_nz_g, const real_T *rtu_In_Theta_deg, const real_T + *rtu_In_Phi_deg, const real_T *rtu_In_qk_deg_s, const real_T *rtu_In_eta_deg, const real_T + *rtu_In_eta_trim_deg, const real_T *rtu_In_V_ias_kn, const real_T *rtu_In_mach, const real_T + *rtu_In_V_tas_kn, const real_T *rtu_In_flaps_handle_index, const real_T *rtu_In_spoilers_left_pos, const + real_T *rtu_In_spoilers_right_pos, const real_T *rtu_In_delta_eta_pos, const real_T *rtu_In_in_flight, const + boolean_T *rtu_In_tracking_mode_on, const boolean_T *rtu_In_stabilities_available, real_T *rty_Out_eta_deg, + real_T *rty_Out_eta_trim_dot_deg_s, real_T *rty_Out_eta_trim_limit_lo, real_T *rty_Out_eta_trim_limit_up); + void reset(); + PitchAlternateLaw(); + ~PitchAlternateLaw(); + private: + D_Work_PitchAlternateLaw_T PitchAlternateLaw_DWork; + static Parameters_PitchAlternateLaw_T PitchAlternateLaw_rtP; + static void PitchAlternateLaw_RateLimiter_Reset(rtDW_RateLimiter_PitchAlternateLaw_T *localDW); + static void PitchAlternateLaw_RateLimiter(real_T rtu_u, real_T rtu_up, real_T rtu_lo, const real_T *rtu_Ts, real_T + rtu_init, real_T *rty_Y, rtDW_RateLimiter_PitchAlternateLaw_T *localDW); + static void PitchAlternateLaw_LagFilter_Reset(rtDW_LagFilter_PitchAlternateLaw_T *localDW); + static void PitchAlternateLaw_LagFilter(real_T rtu_U, real_T rtu_C1, const real_T *rtu_dt, real_T *rty_Y, + rtDW_LagFilter_PitchAlternateLaw_T *localDW); + static void PitchAlternateLaw_WashoutFilter_Reset(rtDW_WashoutFilter_PitchAlternateLaw_T *localDW); + static void PitchAlternateLaw_WashoutFilter(real_T rtu_U, real_T rtu_C1, const real_T *rtu_dt, real_T *rty_Y, + rtDW_WashoutFilter_PitchAlternateLaw_T *localDW); +}; + +extern PitchAlternateLaw::Parameters_PitchAlternateLaw_T PitchAlternateLaw_rtP; + +#endif + diff --git a/src/fbw/src/model/PitchAlternateLaw_private.h b/src/fbw/src/model/PitchAlternateLaw_private.h new file mode 100644 index 000000000000..bb620249e839 --- /dev/null +++ b/src/fbw/src/model/PitchAlternateLaw_private.h @@ -0,0 +1,5 @@ +#ifndef RTW_HEADER_PitchAlternateLaw_private_h_ +#define RTW_HEADER_PitchAlternateLaw_private_h_ +#include "rtwtypes.h" +#endif + diff --git a/src/fbw/src/model/PitchAlternateLaw_types.h b/src/fbw/src/model/PitchAlternateLaw_types.h new file mode 100644 index 000000000000..03b0759bd136 --- /dev/null +++ b/src/fbw/src/model/PitchAlternateLaw_types.h @@ -0,0 +1,128 @@ +#ifndef RTW_HEADER_PitchAlternateLaw_types_h_ +#define RTW_HEADER_PitchAlternateLaw_types_h_ +#include "rtwtypes.h" + +#ifndef DEFINED_TYPEDEF_FOR_base_time_ +#define DEFINED_TYPEDEF_FOR_base_time_ + +struct base_time +{ + real_T dt; + real_T simulation_time; + real_T monotonic_time; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_pitch_alternate_input_ +#define DEFINED_TYPEDEF_FOR_pitch_alternate_input_ + +struct pitch_alternate_input +{ + base_time time; + real_T nz_g; + real_T Theta_deg; + real_T Phi_deg; + real_T qk_deg_s; + real_T eta_deg; + real_T eta_trim_deg; + real_T V_ias_kn; + real_T mach; + real_T V_tas_kn; + real_T CG_percent_MAC; + real_T total_weight_kg; + real_T flaps_handle_index; + real_T spoilers_left_pos; + real_T spoilers_right_pos; + real_T delta_eta_pos; + boolean_T on_ground; + real_T in_flight; + boolean_T tracking_mode_on; + boolean_T stabilities_available; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_alternate_data_computed_ +#define DEFINED_TYPEDEF_FOR_base_pitch_alternate_data_computed_ + +struct base_pitch_alternate_data_computed +{ + real_T eta_trim_deg_limit_lo; + real_T eta_trim_deg_limit_up; + real_T delta_eta_deg; + real_T nz_limit_up_g; + real_T nz_limit_lo_g; + boolean_T eta_trim_deg_should_freeze; + boolean_T eta_trim_deg_reset; + real_T eta_trim_deg_reset_deg; + boolean_T eta_trim_deg_should_write; + real_T eta_trim_deg_rate_limit_up_deg_s; + real_T eta_trim_deg_rate_limit_lo_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_normal_ +#define DEFINED_TYPEDEF_FOR_base_pitch_normal_ + +struct base_pitch_normal +{ + real_T nz_c_g; + real_T Cstar_g; + real_T protection_alpha_c_deg; + real_T protection_V_c_kn; + real_T eta_dot_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_law_output_ +#define DEFINED_TYPEDEF_FOR_base_pitch_law_output_ + +struct base_pitch_law_output +{ + real_T eta_dot_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_integrated_ +#define DEFINED_TYPEDEF_FOR_base_pitch_integrated_ + +struct base_pitch_integrated +{ + real_T eta_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_output_ +#define DEFINED_TYPEDEF_FOR_base_pitch_output_ + +struct base_pitch_output +{ + real_T eta_deg; + real_T eta_trim_dot_deg_s; + real_T eta_trim_limit_lo; + real_T eta_trim_limit_up; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_pitch_alternate_output_ +#define DEFINED_TYPEDEF_FOR_pitch_alternate_output_ + +struct pitch_alternate_output +{ + pitch_alternate_input input; + base_pitch_alternate_data_computed data_computed; + base_pitch_normal law_normal; + base_pitch_law_output vote; + base_pitch_integrated integrated; + base_pitch_output output; +}; + +#endif +#endif + diff --git a/src/fbw/src/model/PitchDirectLaw.cpp b/src/fbw/src/model/PitchDirectLaw.cpp new file mode 100644 index 000000000000..2691f4912b8d --- /dev/null +++ b/src/fbw/src/model/PitchDirectLaw.cpp @@ -0,0 +1,63 @@ +#include "PitchDirectLaw.h" +#include "rtwtypes.h" +#include + +PitchDirectLaw::Parameters_PitchDirectLaw_T PitchDirectLaw::PitchDirectLaw_rtP{ + + 0.0, + + -45.0, + + 45.0, + + 0.0, + + 3.5, + + -11.0, + + -30.0, + + 17.0, + + -30.0 +}; + +void PitchDirectLaw::reset(void) +{ + PitchDirectLaw_DWork.pY_not_empty = false; +} + +void PitchDirectLaw::step(const real_T *rtu_In_time_dt, const real_T *rtu_In_delta_eta_pos, real_T *rty_Out_eta_deg, + real_T *rty_Out_eta_trim_dot_deg_s, real_T *rty_Out_eta_trim_limit_lo, real_T *rty_Out_eta_trim_limit_up) +{ + real_T rtb_Gain; + *rty_Out_eta_trim_dot_deg_s = PitchDirectLaw_rtP.Constant_Value; + *rty_Out_eta_trim_limit_up = PitchDirectLaw_rtP.Constant2_Value; + *rty_Out_eta_trim_limit_lo = PitchDirectLaw_rtP.Constant3_Value; + rtb_Gain = PitchDirectLaw_rtP.Gain_Gain * *rtu_In_delta_eta_pos; + if (!PitchDirectLaw_DWork.pY_not_empty) { + PitchDirectLaw_DWork.pY = PitchDirectLaw_rtP.RateLimitereta_InitialCondition; + PitchDirectLaw_DWork.pY_not_empty = true; + } + + if (rtb_Gain > PitchDirectLaw_rtP.Saturation_UpperSat) { + rtb_Gain = PitchDirectLaw_rtP.Saturation_UpperSat; + } else if (rtb_Gain < PitchDirectLaw_rtP.Saturation_LowerSat) { + rtb_Gain = PitchDirectLaw_rtP.Saturation_LowerSat; + } + + PitchDirectLaw_DWork.pY += std::fmax(std::fmin(rtb_Gain - PitchDirectLaw_DWork.pY, std::abs + (PitchDirectLaw_rtP.RateLimitereta_up) * *rtu_In_time_dt), -std::abs(PitchDirectLaw_rtP.RateLimitereta_lo) * + *rtu_In_time_dt); + *rty_Out_eta_deg = PitchDirectLaw_DWork.pY; +} + +PitchDirectLaw::PitchDirectLaw(): + PitchDirectLaw_DWork() +{ +} + +PitchDirectLaw::~PitchDirectLaw() +{ +} diff --git a/src/fbw/src/model/PitchDirectLaw.h b/src/fbw/src/model/PitchDirectLaw.h new file mode 100644 index 000000000000..05f15f48e7c4 --- /dev/null +++ b/src/fbw/src/model/PitchDirectLaw.h @@ -0,0 +1,44 @@ +#ifndef RTW_HEADER_PitchDirectLaw_h_ +#define RTW_HEADER_PitchDirectLaw_h_ +#include "rtwtypes.h" +#include "PitchDirectLaw_types.h" +#include + +class PitchDirectLaw final +{ + public: + struct D_Work_PitchDirectLaw_T { + real_T pY; + boolean_T pY_not_empty; + }; + + struct Parameters_PitchDirectLaw_T { + real_T RateLimitereta_InitialCondition; + real_T RateLimitereta_lo; + real_T RateLimitereta_up; + real_T Constant_Value; + real_T Constant2_Value; + real_T Constant3_Value; + real_T Gain_Gain; + real_T Saturation_UpperSat; + real_T Saturation_LowerSat; + }; + + PitchDirectLaw(PitchDirectLaw const&) = delete; + PitchDirectLaw& operator= (PitchDirectLaw const&) & = delete; + PitchDirectLaw(PitchDirectLaw &&) = delete; + PitchDirectLaw& operator= (PitchDirectLaw &&) = delete; + void step(const real_T *rtu_In_time_dt, const real_T *rtu_In_delta_eta_pos, real_T *rty_Out_eta_deg, real_T + *rty_Out_eta_trim_dot_deg_s, real_T *rty_Out_eta_trim_limit_lo, real_T *rty_Out_eta_trim_limit_up); + void reset(); + PitchDirectLaw(); + ~PitchDirectLaw(); + private: + D_Work_PitchDirectLaw_T PitchDirectLaw_DWork; + static Parameters_PitchDirectLaw_T PitchDirectLaw_rtP; +}; + +extern PitchDirectLaw::Parameters_PitchDirectLaw_T PitchDirectLaw_rtP; + +#endif + diff --git a/src/fbw/src/model/PitchDirectLaw_private.h b/src/fbw/src/model/PitchDirectLaw_private.h new file mode 100644 index 000000000000..ccd2298ce191 --- /dev/null +++ b/src/fbw/src/model/PitchDirectLaw_private.h @@ -0,0 +1,5 @@ +#ifndef RTW_HEADER_PitchDirectLaw_private_h_ +#define RTW_HEADER_PitchDirectLaw_private_h_ +#include "rtwtypes.h" +#endif + diff --git a/src/fbw/src/model/PitchDirectLaw_types.h b/src/fbw/src/model/PitchDirectLaw_types.h new file mode 100644 index 000000000000..23a7d167fd59 --- /dev/null +++ b/src/fbw/src/model/PitchDirectLaw_types.h @@ -0,0 +1,55 @@ +#ifndef RTW_HEADER_PitchDirectLaw_types_h_ +#define RTW_HEADER_PitchDirectLaw_types_h_ +#include "rtwtypes.h" + +#ifndef DEFINED_TYPEDEF_FOR_base_time_ +#define DEFINED_TYPEDEF_FOR_base_time_ + +struct base_time +{ + real_T dt; + real_T simulation_time; + real_T monotonic_time; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_pitch_direct_input_ +#define DEFINED_TYPEDEF_FOR_pitch_direct_input_ + +struct pitch_direct_input +{ + base_time time; + real_T eta_deg; + real_T flaps_handle_index; + real_T delta_eta_pos; + boolean_T tracking_mode_on; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_output_ +#define DEFINED_TYPEDEF_FOR_base_pitch_output_ + +struct base_pitch_output +{ + real_T eta_deg; + real_T eta_trim_dot_deg_s; + real_T eta_trim_limit_lo; + real_T eta_trim_limit_up; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_pitch_direct_output_ +#define DEFINED_TYPEDEF_FOR_pitch_direct_output_ + +struct pitch_direct_output +{ + pitch_direct_input input; + base_pitch_output output; +}; + +#endif +#endif + diff --git a/src/fbw/src/model/PitchNormalLaw.cpp b/src/fbw/src/model/PitchNormalLaw.cpp new file mode 100644 index 000000000000..62695c1a818a --- /dev/null +++ b/src/fbw/src/model/PitchNormalLaw.cpp @@ -0,0 +1,2042 @@ +#include "PitchNormalLaw.h" +#include "rtwtypes.h" +#include +#include "look1_binlxpw.h" +#include "look2_binlxpw.h" + +const uint8_T PitchNormalLaw_IN_Flare_Reduce_Theta_c{ 1U }; + +const uint8_T PitchNormalLaw_IN_Flare_Set_Rate{ 2U }; + +const uint8_T PitchNormalLaw_IN_Flare_Store_Theta_c_deg{ 3U }; + +const uint8_T PitchNormalLaw_IN_Flight_High{ 4U }; + +const uint8_T PitchNormalLaw_IN_Flight_Low{ 5U }; + +const uint8_T PitchNormalLaw_IN_Ground{ 6U }; + +const uint8_T PitchNormalLaw_IN_NO_ACTIVE_CHILD{ 0U }; + +const uint8_T PitchNormalLaw_IN_frozen{ 1U }; + +const uint8_T PitchNormalLaw_IN_running{ 2U }; + +const uint8_T PitchNormalLaw_IN_automatic{ 1U }; + +const uint8_T PitchNormalLaw_IN_manual{ 2U }; + +const uint8_T PitchNormalLaw_IN_reset{ 3U }; + +const uint8_T PitchNormalLaw_IN_tracking{ 4U }; + +const uint8_T PitchNormalLaw_IN_flight_clean{ 1U }; + +const uint8_T PitchNormalLaw_IN_flight_flaps{ 2U }; + +const uint8_T PitchNormalLaw_IN_ground{ 3U }; + +const uint8_T PitchNormalLaw_IN_OFF{ 1U }; + +const uint8_T PitchNormalLaw_IN_ON{ 2U }; + +PitchNormalLaw::Parameters_PitchNormalLaw_T PitchNormalLaw::PitchNormalLaw_rtP{ + + { 0.0, 50.0, 100.0, 200.0 }, + + + { 0.0, 50.0, 100.0, 200.0 }, + + + { 0.0, 100.0, 150.0, 200.0, 250.0, 300.0, 400.0 }, + + + { 0.0, 50.0, 100.0, 200.0 }, + + + { 0.0, 50.0, 100.0, 200.0 }, + + + { 0.0, 50.0, 100.0, 200.0 }, + + + { 0.0, 0.06, 0.1, 0.2, 1.0 }, + + 0.05, + + 0.3, + + 5.0, + + 0.3, + + 5.0, + + 1.6, + + 1.0, + + 2.0, + + 2.0, + + 2.0, + + 2.0, + + 0.3, + + 5.0, + + 0.3, + + 5.0, + + 0.3, + + 5.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 1.0, + + 0.0, + + 0.0, + + 2.0, + + 0.0, + + 0.0, + + 30.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + -30.0, + + -30.0, + + + { 0.0, 0.0, -30.0, -30.0 }, + + + { 0.0, 0.0, -30.0, -30.0 }, + + + { 0.1, 0.1, 0.15, 0.2, 0.3, 0.5, 0.5 }, + + + { 0.0, 0.0, -30.0, -30.0 }, + + + { 0.0, 0.0, -30.0, -30.0 }, + + + { 0.0, 0.0, -30.0, -30.0 }, + + + { 1.0, 1.0, 0.5, 0.3, 0.3 }, + + 17.0, + + 30.0, + + -0.2, + + -0.5, + + -0.5, + + -0.5, + + -1.0, + + -1.0, + + -0.25, + + -2.0, + + -1.0, + + -0.5, + + -0.33333333333333331, + + -4.0, + + -0.5, + + -45.0, + + 0.2, + + 0.5, + + 0.5, + + 0.5, + + 1.0, + + 1.0, + + 4.0, + + 2.0, + + 1.0, + + 0.5, + + 4.0, + + 2.0, + + 0.5, + + 45.0, + + true, + + 3.5, + + -11.0, + + 0.0, + + -0.4, + + -0.4, + + 1.4, + + -0.1, + + -0.6, + + 0.2, + + 0.75, + + -0.75, + + 1.5, + + 0.0, + + 0.0, + + + { -2.0, 0.0, 1.5 }, + + + { -1.0, 0.0, 1.0 }, + + 0.0, + + 1.0, + + 1.0, + + 0.0, + + 0.06, + + 0.0, + + -0.8, + + 1.0, + + 1.0, + + 0.0, + + 0.0, + + 30.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 1.0, + + 1.0, + + 0.0, + + 1.0, + + 0.0, + + -30.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.2, + + 0.2, + + 1.0, + + -0.25, + + + { -2.0, 0.0, 1.5 }, + + + { -1.0, 0.0, 1.0 }, + + 0.017453292519943295, + + 0.017453292519943295, + + + { 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0 }, + + + { 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0 }, + + 2000.0, + + 100.0, + + 0.51444444444444448, + + 1.0, + + 0.017453292519943295, + + 100.0, + + 0.1019367991845056, + + + { 13.5, 13.5 }, + + + { 0.0, 350.0 }, + + 1.0, + + + { 0.5, 0.5 }, + + + { 0.0, 350.0 }, + + 1.0, + + -1.0, + + 0.06, + + 1.0, + + -1.0, + + 30.0, + + -30.0, + + 1.0, + + 0.017453292519943295, + + 100.0, + + 0.1019367991845056, + + + { 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0 }, + + + { 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0 }, + + 2000.0, + + 100.0, + + 0.51444444444444448, + + 1.0, + + + { 13.5, 13.5 }, + + + { 0.0, 350.0 }, + + + { 0.5, 0.5 }, + + + { 0.0, 350.0 }, + + 1.0, + + -1.0, + + 0.06, + + 1.0, + + -1.0, + + 30.0, + + -30.0, + + 0.0, + + 2.0, + + 0.0, + + -1.0, + + -1.2, + + 0.0, + + 2.0, + + 0.0, + + -0.8, + + 1.0, + + 1.0, + + 4.0, + + -4.0, + + 1.0, + + 0.0, + + 1.0, + + 1.0, + + 0.017453292519943295, + + 100.0, + + 0.1019367991845056, + + + { 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0 }, + + + { 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0 }, + + 2000.0, + + 100.0, + + 0.51444444444444448, + + 1.0, + + 0.0, + + 2.0, + + 0.0, + + 0.0, + + 2.0, + + 0.0, + + 33.0, + + -33.0, + + 0.017453292519943295, + + + { 13.5, 13.5 }, + + + { 0.0, 350.0 }, + + + { 0.5, 0.5 }, + + + { 0.0, 350.0 }, + + 1.0, + + -1.0, + + 0.06, + + 1.0, + + -1.0, + + 30.0, + + -30.0, + + 1.0, + + 0.017453292519943295, + + 100.0, + + 0.1019367991845056, + + + { 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0 }, + + + { 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0 }, + + 2000.0, + + 100.0, + + 0.51444444444444448, + + 1.0, + + + { 13.5, 13.5 }, + + + { 0.0, 350.0 }, + + + { 0.5, 0.5 }, + + + { 0.0, 350.0 }, + + 1.0, + + -1.0, + + 0.06, + + 1.0, + + -1.0, + + 30.0, + + -30.0, + + 1.0, + + 0.017453292519943295, + + 100.0, + + 0.1019367991845056, + + + { 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0 }, + + + { 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0 }, + + 2000.0, + + 100.0, + + 0.51444444444444448, + + 1.0, + + -15.0, + + 0.2, + + 0.25, + + -1.0, + + + { -2.0, 0.0, 1.5 }, + + + { -1.0, 0.0, 1.0 }, + + + { 13.5, 13.5 }, + + + { 0.0, 350.0 }, + + + { 0.5, 0.5 }, + + + { 0.0, 350.0 }, + + 1.0, + + -1.0, + + 0.06, + + 1.0, + + -1.0, + + 30.0, + + -30.0, + + 0.0, + + 0.076923076923076927, + + 1.0, + + 0.0, + + 1.0, + + 1.0, + + + { 0.0, 0.0, 0.0, -0.75, -0.75, 0.0, 0.0, 0.0, -0.75, -0.75, 0.0, 0.0, 0.0, 0.0, -0.75, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0 }, + + + { -1.0, 0.0, 6.0, 11.0, 15.0 }, + + + { -1.0, 8.0, 18.0, 28.0, 40.0 }, + + 1.0, + + 0.0, + + + { -9.0, 0.0, 9.0 }, + + + { -1.0, 0.0, 1.0 }, + + -2.0, + + 4.0, + + 0.2, + + 1.0, + + 2.5, + + -1.5, + + 2.0, + + 0.0, + + 1.0, + + 0.0, + + 1.0, + + 17.0, + + -30.0, + + + { 4U, 4U }, + + 1U, + + 1U +}; + +void PitchNormalLaw::PitchNormalLaw_LagFilter_Reset(rtDW_LagFilter_PitchNormalLaw_T *localDW) +{ + localDW->pY_not_empty = false; + localDW->pU_not_empty = false; +} + +void PitchNormalLaw::PitchNormalLaw_LagFilter(const real_T *rtu_U, real_T rtu_C1, const real_T *rtu_dt, real_T *rty_Y, + rtDW_LagFilter_PitchNormalLaw_T *localDW) +{ + real_T ca; + real_T denom_tmp; + if ((!localDW->pY_not_empty) || (!localDW->pU_not_empty)) { + localDW->pU = *rtu_U; + localDW->pU_not_empty = true; + localDW->pY = *rtu_U; + localDW->pY_not_empty = true; + } + + denom_tmp = *rtu_dt * rtu_C1; + ca = denom_tmp / (denom_tmp + 2.0); + *rty_Y = (2.0 - denom_tmp) / (denom_tmp + 2.0) * localDW->pY + (*rtu_U * ca + localDW->pU * ca); + localDW->pY = *rty_Y; + localDW->pU = *rtu_U; +} + +void PitchNormalLaw::PitchNormalLaw_RateLimiter_Reset(rtDW_RateLimiter_PitchNormalLaw_T *localDW) +{ + localDW->pY_not_empty = false; +} + +void PitchNormalLaw::PitchNormalLaw_RateLimiter(real_T rtu_u, real_T rtu_up, real_T rtu_lo, const real_T *rtu_Ts, real_T + rtu_init, real_T *rty_Y, rtDW_RateLimiter_PitchNormalLaw_T *localDW) +{ + if (!localDW->pY_not_empty) { + localDW->pY = rtu_init; + localDW->pY_not_empty = true; + } + + localDW->pY += std::fmax(std::fmin(rtu_u - localDW->pY, std::abs(rtu_up) * *rtu_Ts), -std::abs(rtu_lo) * *rtu_Ts); + *rty_Y = localDW->pY; +} + +void PitchNormalLaw::PitchNormalLaw_eta_trim_limit_lofreeze_Reset(rtDW_eta_trim_limit_lofreeze_PitchNormalLaw_T *localDW) +{ + localDW->frozen_eta_trim_not_empty = false; +} + +void PitchNormalLaw::PitchNormalLaw_eta_trim_limit_lofreeze(const real_T *rtu_eta_trim, const boolean_T *rtu_trigger, + real_T *rty_y, rtDW_eta_trim_limit_lofreeze_PitchNormalLaw_T *localDW) +{ + if ((!*rtu_trigger) || (!localDW->frozen_eta_trim_not_empty)) { + localDW->frozen_eta_trim = *rtu_eta_trim; + localDW->frozen_eta_trim_not_empty = true; + } + + *rty_y = localDW->frozen_eta_trim; +} + +void PitchNormalLaw::PitchNormalLaw_LagFilter_i_Reset(rtDW_LagFilter_PitchNormalLaw_d_T *localDW) +{ + localDW->pY_not_empty = false; + localDW->pU_not_empty = false; +} + +void PitchNormalLaw::PitchNormalLaw_LagFilter_n(real_T rtu_U, real_T rtu_C1, const real_T *rtu_dt, real_T *rty_Y, + rtDW_LagFilter_PitchNormalLaw_d_T *localDW) +{ + real_T ca; + real_T denom_tmp; + if ((!localDW->pY_not_empty) || (!localDW->pU_not_empty)) { + localDW->pU = rtu_U; + localDW->pU_not_empty = true; + localDW->pY = rtu_U; + localDW->pY_not_empty = true; + } + + denom_tmp = *rtu_dt * rtu_C1; + ca = denom_tmp / (denom_tmp + 2.0); + *rty_Y = (2.0 - denom_tmp) / (denom_tmp + 2.0) * localDW->pY + (rtu_U * ca + localDW->pU * ca); + localDW->pY = *rty_Y; + localDW->pU = rtu_U; +} + +void PitchNormalLaw::PitchNormalLaw_WashoutFilter_Reset(rtDW_WashoutFilter_PitchNormalLaw_T *localDW) +{ + localDW->pY_not_empty = false; + localDW->pU_not_empty = false; +} + +void PitchNormalLaw::PitchNormalLaw_WashoutFilter(real_T rtu_U, real_T rtu_C1, const real_T *rtu_dt, real_T *rty_Y, + rtDW_WashoutFilter_PitchNormalLaw_T *localDW) +{ + real_T ca; + real_T denom_tmp; + if ((!localDW->pY_not_empty) || (!localDW->pU_not_empty)) { + localDW->pU = rtu_U; + localDW->pU_not_empty = true; + localDW->pY = rtu_U; + localDW->pY_not_empty = true; + } + + denom_tmp = *rtu_dt * rtu_C1; + ca = 2.0 / (denom_tmp + 2.0); + *rty_Y = (2.0 - denom_tmp) / (denom_tmp + 2.0) * localDW->pY + (rtu_U * ca - localDW->pU * ca); + localDW->pY = *rty_Y; + localDW->pU = rtu_U; +} + +void PitchNormalLaw::PitchNormalLaw_RateLimiter_l_Reset(rtDW_RateLimiter_PitchNormalLaw_o_T *localDW) +{ + localDW->pY_not_empty = false; +} + +void PitchNormalLaw::PitchNormalLaw_RateLimiter_c(const real_T *rtu_u, real_T rtu_up, real_T rtu_lo, const real_T + *rtu_Ts, real_T rtu_init, real_T *rty_Y, rtDW_RateLimiter_PitchNormalLaw_o_T *localDW) +{ + if (!localDW->pY_not_empty) { + localDW->pY = rtu_init; + localDW->pY_not_empty = true; + } + + localDW->pY += std::fmax(std::fmin(*rtu_u - localDW->pY, std::abs(rtu_up) * *rtu_Ts), -std::abs(rtu_lo) * *rtu_Ts); + *rty_Y = localDW->pY; +} + +void PitchNormalLaw::PitchNormalLaw_VoterAttitudeProtection(real_T rtu_input, real_T rtu_input_l, real_T rtu_input_o, + real_T *rty_vote) +{ + real_T rtb_TmpSignalConversionAtSFunctionInport1[3]; + int32_T rtu_input_0; + rtb_TmpSignalConversionAtSFunctionInport1[0] = rtu_input; + rtb_TmpSignalConversionAtSFunctionInport1[1] = rtu_input_l; + rtb_TmpSignalConversionAtSFunctionInport1[2] = rtu_input_o; + if (rtu_input < rtu_input_l) { + if (rtu_input_l < rtu_input_o) { + rtu_input_0 = 1; + } else if (rtu_input < rtu_input_o) { + rtu_input_0 = 2; + } else { + rtu_input_0 = 0; + } + } else if (rtu_input < rtu_input_o) { + rtu_input_0 = 0; + } else if (rtu_input_l < rtu_input_o) { + rtu_input_0 = 2; + } else { + rtu_input_0 = 1; + } + + *rty_vote = rtb_TmpSignalConversionAtSFunctionInport1[rtu_input_0]; +} + +void PitchNormalLaw::init(void) +{ + PitchNormalLaw_DWork.Delay_DSTATE = PitchNormalLaw_rtP.RateLimiterDynamicVariableTs_InitialCondition; + PitchNormalLaw_DWork.Delay_DSTATE_h = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition; + PitchNormalLaw_DWork.Delay_DSTATE_n = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition; + PitchNormalLaw_DWork.Delay_DSTATE_c = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition; + PitchNormalLaw_DWork.Delay_DSTATE_l = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition_l; + PitchNormalLaw_DWork.Delay_DSTATE_k = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_o; + PitchNormalLaw_DWork.Delay_DSTATE_d = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_d; + PitchNormalLaw_DWork.Delay_DSTATE_f = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_h; + PitchNormalLaw_DWork.Delay_DSTATE_g = PitchNormalLaw_rtP.Delay_InitialCondition; + PitchNormalLaw_DWork.Delay1_DSTATE = PitchNormalLaw_rtP.Delay1_InitialCondition; + PitchNormalLaw_DWork.Delay_DSTATE_j = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_m; + PitchNormalLaw_DWork.Delay_DSTATE_ca = PitchNormalLaw_rtP.Delay_InitialCondition_e; + PitchNormalLaw_DWork.Delay1_DSTATE_i = PitchNormalLaw_rtP.Delay1_InitialCondition_g; + PitchNormalLaw_DWork.Delay_DSTATE_e = PitchNormalLaw_rtP.RateLimiterVariableTs5_InitialCondition; + PitchNormalLaw_DWork.Delay_DSTATE_kd = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition_j; + PitchNormalLaw_DWork.Delay_DSTATE_b = PitchNormalLaw_rtP.RateLimiterVariableTs3_InitialCondition_e; + PitchNormalLaw_DWork.Delay_DSTATE_ku = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_f; + PitchNormalLaw_DWork.Delay_DSTATE_gl = PitchNormalLaw_rtP.Delay_InitialCondition_c; + PitchNormalLaw_DWork.Delay1_DSTATE_l = PitchNormalLaw_rtP.Delay1_InitialCondition_gf; + PitchNormalLaw_DWork.Delay_DSTATE_m = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_e; + PitchNormalLaw_DWork.Delay_DSTATE_k2 = PitchNormalLaw_rtP.Delay_InitialCondition_h; + PitchNormalLaw_DWork.Delay1_DSTATE_n = PitchNormalLaw_rtP.Delay1_InitialCondition_e; + PitchNormalLaw_DWork.Delay_DSTATE_mz = PitchNormalLaw_rtP.RateLimiterVariableTs4_InitialCondition; + PitchNormalLaw_DWork.Delay_DSTATE_jh = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_a; + PitchNormalLaw_DWork.Delay_DSTATE_dy = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_di; + PitchNormalLaw_DWork.Delay_DSTATE_e5 = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition_f; + PitchNormalLaw_DWork.Delay_DSTATE_gz = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_g; + PitchNormalLaw_DWork.Delay_DSTATE_lf = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_c; + PitchNormalLaw_DWork.Delay_DSTATE_ho = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition_g; + PitchNormalLaw_DWork.Delay_DSTATE_ds = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_h; + PitchNormalLaw_DWork.Delay_DSTATE_jt = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_a; + PitchNormalLaw_DWork.icLoad = true; + PitchNormalLaw_DWork.Delay_DSTATE_ej = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_b; + PitchNormalLaw_DWork.Delay_DSTATE_e4 = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_p; + PitchNormalLaw_DWork.icLoad_p = true; +} + +void PitchNormalLaw::reset(void) +{ + real_T rtb_nz_limit_up_g; + real_T rtb_nz_limit_lo_g; + real_T rtb_in_flare; + PitchNormalLaw_DWork.Delay_DSTATE = PitchNormalLaw_rtP.RateLimiterDynamicVariableTs_InitialCondition; + PitchNormalLaw_DWork.Delay_DSTATE_h = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition; + PitchNormalLaw_DWork.Delay_DSTATE_n = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition; + PitchNormalLaw_DWork.Delay_DSTATE_c = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition; + PitchNormalLaw_DWork.Delay_DSTATE_l = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition_l; + PitchNormalLaw_DWork.Delay_DSTATE_k = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_o; + PitchNormalLaw_DWork.Delay_DSTATE_d = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_d; + PitchNormalLaw_DWork.Delay_DSTATE_f = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_h; + PitchNormalLaw_DWork.Delay_DSTATE_g = PitchNormalLaw_rtP.Delay_InitialCondition; + PitchNormalLaw_DWork.Delay1_DSTATE = PitchNormalLaw_rtP.Delay1_InitialCondition; + PitchNormalLaw_DWork.Delay_DSTATE_j = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_m; + PitchNormalLaw_DWork.Delay_DSTATE_ca = PitchNormalLaw_rtP.Delay_InitialCondition_e; + PitchNormalLaw_DWork.Delay1_DSTATE_i = PitchNormalLaw_rtP.Delay1_InitialCondition_g; + PitchNormalLaw_DWork.Delay_DSTATE_e = PitchNormalLaw_rtP.RateLimiterVariableTs5_InitialCondition; + PitchNormalLaw_DWork.Delay_DSTATE_kd = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition_j; + PitchNormalLaw_DWork.Delay_DSTATE_b = PitchNormalLaw_rtP.RateLimiterVariableTs3_InitialCondition_e; + PitchNormalLaw_DWork.Delay_DSTATE_ku = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_f; + PitchNormalLaw_DWork.Delay_DSTATE_gl = PitchNormalLaw_rtP.Delay_InitialCondition_c; + PitchNormalLaw_DWork.Delay1_DSTATE_l = PitchNormalLaw_rtP.Delay1_InitialCondition_gf; + PitchNormalLaw_DWork.Delay_DSTATE_m = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_e; + PitchNormalLaw_DWork.Delay_DSTATE_k2 = PitchNormalLaw_rtP.Delay_InitialCondition_h; + PitchNormalLaw_DWork.Delay1_DSTATE_n = PitchNormalLaw_rtP.Delay1_InitialCondition_e; + PitchNormalLaw_DWork.Delay_DSTATE_mz = PitchNormalLaw_rtP.RateLimiterVariableTs4_InitialCondition; + PitchNormalLaw_DWork.Delay_DSTATE_jh = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_a; + PitchNormalLaw_DWork.Delay_DSTATE_dy = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_di; + PitchNormalLaw_DWork.Delay_DSTATE_e5 = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition_f; + PitchNormalLaw_DWork.Delay_DSTATE_gz = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_g; + PitchNormalLaw_DWork.Delay_DSTATE_lf = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_c; + PitchNormalLaw_DWork.Delay_DSTATE_ho = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_InitialCondition_g; + PitchNormalLaw_DWork.Delay_DSTATE_ds = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_h; + PitchNormalLaw_DWork.Delay_DSTATE_jt = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_InitialCondition_a; + PitchNormalLaw_DWork.icLoad = true; + PitchNormalLaw_DWork.Delay_DSTATE_ej = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_b; + PitchNormalLaw_DWork.Delay_DSTATE_e4 = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_InitialCondition_p; + PitchNormalLaw_DWork.icLoad_p = true; + PitchNormalLaw_LagFilter_Reset(&PitchNormalLaw_DWork.sf_LagFilter); + PitchNormalLaw_DWork.is_active_c2_PitchNormalLaw = 0U; + PitchNormalLaw_DWork.is_c2_PitchNormalLaw = PitchNormalLaw_IN_NO_ACTIVE_CHILD; + rtb_in_flare = 0.0; + PitchNormalLaw_B.flare_Theta_c_deg = 0.0; + PitchNormalLaw_B.flare_Theta_c_rate_deg_s = 0.0; + PitchNormalLaw_DWork.is_active_c9_PitchNormalLaw = 0U; + PitchNormalLaw_DWork.is_c9_PitchNormalLaw = PitchNormalLaw_IN_NO_ACTIVE_CHILD; + PitchNormalLaw_DWork.is_active_c8_PitchNormalLaw = 0U; + PitchNormalLaw_DWork.is_c8_PitchNormalLaw = PitchNormalLaw_IN_NO_ACTIVE_CHILD; + PitchNormalLaw_DWork.is_active_c7_PitchNormalLaw = 0U; + PitchNormalLaw_DWork.is_c7_PitchNormalLaw = PitchNormalLaw_IN_NO_ACTIVE_CHILD; + rtb_nz_limit_up_g = 0.0; + rtb_nz_limit_lo_g = 0.0; + PitchNormalLaw_RateLimiter_Reset(&PitchNormalLaw_DWork.sf_RateLimiter); + PitchNormalLaw_DWork.is_active_c6_PitchNormalLaw = 0U; + PitchNormalLaw_DWork.is_c6_PitchNormalLaw = PitchNormalLaw_IN_NO_ACTIVE_CHILD; + PitchNormalLaw_RateLimiter_Reset(&PitchNormalLaw_DWork.sf_RateLimiter_p); + PitchNormalLaw_RateLimiter_Reset(&PitchNormalLaw_DWork.sf_RateLimiter_c); + PitchNormalLaw_RateLimiter_Reset(&PitchNormalLaw_DWork.sf_RateLimiter_n); + PitchNormalLaw_eta_trim_limit_lofreeze_Reset(&PitchNormalLaw_DWork.sf_eta_trim_limit_lofreeze); + PitchNormalLaw_eta_trim_limit_lofreeze_Reset(&PitchNormalLaw_DWork.sf_eta_trim_limit_upfreeze); + PitchNormalLaw_RateLimiter_Reset(&PitchNormalLaw_DWork.sf_RateLimiter_o); + PitchNormalLaw_LagFilter_i_Reset(&PitchNormalLaw_DWork.sf_LagFilter_k); + PitchNormalLaw_WashoutFilter_Reset(&PitchNormalLaw_DWork.sf_WashoutFilter_k); + PitchNormalLaw_LagFilter_i_Reset(&PitchNormalLaw_DWork.sf_LagFilter_g3); + PitchNormalLaw_WashoutFilter_Reset(&PitchNormalLaw_DWork.sf_WashoutFilter_c); + PitchNormalLaw_RateLimiter_l_Reset(&PitchNormalLaw_DWork.sf_RateLimiter_nx); + PitchNormalLaw_LagFilter_Reset(&PitchNormalLaw_DWork.sf_LagFilter_m); + PitchNormalLaw_WashoutFilter_Reset(&PitchNormalLaw_DWork.sf_WashoutFilter_h); + PitchNormalLaw_RateLimiter_l_Reset(&PitchNormalLaw_DWork.sf_RateLimiter_d); + PitchNormalLaw_RateLimiter_l_Reset(&PitchNormalLaw_DWork.sf_RateLimiter_c2); + PitchNormalLaw_RateLimiter_Reset(&PitchNormalLaw_DWork.sf_RateLimiter_l); + PitchNormalLaw_LagFilter_i_Reset(&PitchNormalLaw_DWork.sf_LagFilter_i); + PitchNormalLaw_WashoutFilter_Reset(&PitchNormalLaw_DWork.sf_WashoutFilter_l); + PitchNormalLaw_LagFilter_i_Reset(&PitchNormalLaw_DWork.sf_LagFilter_g); + PitchNormalLaw_WashoutFilter_Reset(&PitchNormalLaw_DWork.sf_WashoutFilter_d); + PitchNormalLaw_LagFilter_i_Reset(&PitchNormalLaw_DWork.sf_LagFilter_n); + PitchNormalLaw_WashoutFilter_Reset(&PitchNormalLaw_DWork.sf_WashoutFilter); + PitchNormalLaw_RateLimiter_l_Reset(&PitchNormalLaw_DWork.sf_RateLimiter_ct); + PitchNormalLaw_LagFilter_i_Reset(&PitchNormalLaw_DWork.sf_LagFilter_f); + PitchNormalLaw_RateLimiter_Reset(&PitchNormalLaw_DWork.sf_RateLimiter_b); +} + +void PitchNormalLaw::step(const real_T *rtu_In_time_dt, const real_T *rtu_In_nz_g, const real_T *rtu_In_Theta_deg, const + real_T *rtu_In_Phi_deg, const real_T *rtu_In_qk_deg_s, const real_T *rtu_In_qk_dot_deg_s2, const real_T + *rtu_In_eta_deg, const real_T *rtu_In_eta_trim_deg, const real_T *rtu_In_alpha_deg, const real_T *rtu_In_V_ias_kn, + const real_T *rtu_In_V_tas_kn, const real_T *rtu_In_H_radio_ft, const real_T *rtu_In_flaps_handle_index, const real_T * + rtu_In_spoilers_left_pos, const real_T *rtu_In_spoilers_right_pos, const real_T *rtu_In_thrust_lever_1_pos, const + real_T *rtu_In_thrust_lever_2_pos, const boolean_T *rtu_In_tailstrike_protection_on, const real_T *rtu_In_VLS_kn, + const real_T *rtu_In_delta_eta_pos, const boolean_T *rtu_In_on_ground, const real_T *rtu_In_in_flight, const boolean_T + *rtu_In_tracking_mode_on, const boolean_T *rtu_In_high_aoa_prot_active, const boolean_T *rtu_In_high_speed_prot_active, + const real_T *rtu_In_alpha_prot, const real_T *rtu_In_alpha_max, const real_T *rtu_In_high_speed_prot_high_kn, const + real_T *rtu_In_high_speed_prot_low_kn, const real_T *rtu_In_ap_theta_c_deg, const boolean_T *rtu_In_any_ap_engaged, + real_T *rty_Out_eta_deg, real_T *rty_Out_eta_trim_dot_deg_s, real_T *rty_Out_eta_trim_limit_lo, real_T + *rty_Out_eta_trim_limit_up) +{ + real_T rtb_nz_limit_up_g; + real_T rtb_nz_limit_lo_g; + real_T rtb_in_flare; + real_T rtb_Bias_o; + real_T rtb_Cos; + real_T rtb_Divide; + real_T rtb_Divide_a0; + real_T rtb_Divide_an; + real_T rtb_Divide_b4; + real_T rtb_Divide_cq; + real_T rtb_Divide_kq; + real_T rtb_Divide_l; + real_T rtb_Divide_m; + real_T rtb_Divide_o; + real_T rtb_Divide_o2; + real_T rtb_Gain; + real_T rtb_Gain1_e; + real_T rtb_Gain1_ft; + real_T rtb_Gain5_gq; + real_T rtb_Gain_bs; + real_T rtb_Gain_g; + real_T rtb_Gain_ll; + real_T rtb_Gain_ot; + real_T rtb_Gain_px; + real_T rtb_Loaddemand2; + real_T rtb_Loaddemand2_l; + real_T rtb_ManualSwitch; + real_T rtb_Product1_ck; + real_T rtb_Product1_dm; + real_T rtb_Product_k; + real_T rtb_Product_kz; + real_T rtb_Product_n3; + real_T rtb_Saturation3; + real_T rtb_Sum10; + real_T rtb_Sum1_d4; + real_T rtb_Sum_j4; + real_T rtb_Y_aj; + real_T rtb_Y_am; + real_T rtb_Y_b; + real_T rtb_Y_cm; + real_T rtb_Y_ll; + real_T rtb_Y_n; + real_T rtb_Y_p; + real_T rtb_alpha_err_gain; + real_T rtb_eta_trim_deg_rate_limit_lo_deg_s; + real_T rtb_eta_trim_deg_rate_limit_up_deg_s; + real_T rtb_qk_dot_gain; + real_T rtb_qk_gain; + real_T rtb_uDLookupTable; + real_T rtb_v_target; + real_T rtb_y; + real_T y; + int32_T rtb_in_rotation; + boolean_T rtb_OR; + boolean_T rtb_eta_trim_deg_should_freeze; + PitchNormalLaw_LagFilter(rtu_In_Theta_deg, PitchNormalLaw_rtP.LagFilter_C1, rtu_In_time_dt, &rtb_Sum_j4, + &PitchNormalLaw_DWork.sf_LagFilter); + if (PitchNormalLaw_rtP.ManualSwitch_CurrentSetting == 1) { + rtb_ManualSwitch = PitchNormalLaw_rtP.Constant1_Value_k; + } else { + rtb_ManualSwitch = PitchNormalLaw_rtP.Constant_Value_p; + } + + if (PitchNormalLaw_DWork.is_active_c2_PitchNormalLaw == 0U) { + PitchNormalLaw_DWork.is_active_c2_PitchNormalLaw = 1U; + PitchNormalLaw_DWork.is_c2_PitchNormalLaw = PitchNormalLaw_IN_Ground; + rtb_in_flare = 0.0; + PitchNormalLaw_B.flare_Theta_c_deg = rtb_Sum_j4; + PitchNormalLaw_B.flare_Theta_c_rate_deg_s = -3.0; + } else { + switch (PitchNormalLaw_DWork.is_c2_PitchNormalLaw) { + case PitchNormalLaw_IN_Flare_Reduce_Theta_c: + if (*rtu_In_in_flight == 0.0) { + PitchNormalLaw_DWork.is_c2_PitchNormalLaw = PitchNormalLaw_IN_Ground; + rtb_in_flare = 0.0; + PitchNormalLaw_B.flare_Theta_c_deg = rtb_Sum_j4; + PitchNormalLaw_B.flare_Theta_c_rate_deg_s = -3.0; + } else if ((*rtu_In_H_radio_ft > 50.0) && (rtb_ManualSwitch == 0.0)) { + PitchNormalLaw_DWork.is_c2_PitchNormalLaw = PitchNormalLaw_IN_Flight_Low; + rtb_in_flare = 0.0; + PitchNormalLaw_B.flare_Theta_c_deg = rtb_Sum_j4; + PitchNormalLaw_B.flare_Theta_c_rate_deg_s = -3.0; + } else { + rtb_in_flare = 1.0; + PitchNormalLaw_B.flare_Theta_c_deg = -2.0; + } + break; + + case PitchNormalLaw_IN_Flare_Set_Rate: + if (PitchNormalLaw_rtP.ManualSwitch1_CurrentSetting == 1) { + rtb_Y_b = PitchNormalLaw_rtP.Constant1_Value_k; + } else { + rtb_Y_b = PitchNormalLaw_rtP.Constant_Value_p; + } + + if ((*rtu_In_H_radio_ft <= 30.0) || (rtb_Y_b == 1.0)) { + PitchNormalLaw_DWork.is_c2_PitchNormalLaw = PitchNormalLaw_IN_Flare_Reduce_Theta_c; + rtb_in_flare = 1.0; + PitchNormalLaw_B.flare_Theta_c_deg = -2.0; + } else if ((*rtu_In_H_radio_ft > 50.0) && (rtb_ManualSwitch == 0.0)) { + PitchNormalLaw_DWork.is_c2_PitchNormalLaw = PitchNormalLaw_IN_Flight_Low; + rtb_in_flare = 0.0; + PitchNormalLaw_B.flare_Theta_c_deg = rtb_Sum_j4; + PitchNormalLaw_B.flare_Theta_c_rate_deg_s = -3.0; + } else { + rtb_in_flare = 1.0; + } + break; + + case PitchNormalLaw_IN_Flare_Store_Theta_c_deg: + if ((*rtu_In_H_radio_ft > 50.0) && (rtb_ManualSwitch == 0.0)) { + PitchNormalLaw_DWork.is_c2_PitchNormalLaw = PitchNormalLaw_IN_Flight_Low; + rtb_in_flare = 0.0; + PitchNormalLaw_B.flare_Theta_c_deg = rtb_Sum_j4; + PitchNormalLaw_B.flare_Theta_c_rate_deg_s = -3.0; + } else { + PitchNormalLaw_B.flare_Theta_c_rate_deg_s = -(rtb_Sum_j4 + 2.0) / 8.0; + PitchNormalLaw_DWork.is_c2_PitchNormalLaw = PitchNormalLaw_IN_Flare_Set_Rate; + rtb_in_flare = 1.0; + } + break; + + case PitchNormalLaw_IN_Flight_High: + if ((*rtu_In_H_radio_ft <= 50.0) || (rtb_ManualSwitch == 1.0)) { + PitchNormalLaw_B.flare_Theta_c_deg = rtb_Sum_j4; + PitchNormalLaw_DWork.is_c2_PitchNormalLaw = PitchNormalLaw_IN_Flare_Store_Theta_c_deg; + rtb_in_flare = 1.0; + } else { + rtb_in_flare = 0.0; + PitchNormalLaw_B.flare_Theta_c_deg = rtb_Sum_j4; + PitchNormalLaw_B.flare_Theta_c_rate_deg_s = -3.0; + } + break; + + case PitchNormalLaw_IN_Flight_Low: + if (*rtu_In_H_radio_ft > 50.0) { + PitchNormalLaw_DWork.is_c2_PitchNormalLaw = PitchNormalLaw_IN_Flight_High; + rtb_in_flare = 0.0; + PitchNormalLaw_B.flare_Theta_c_deg = rtb_Sum_j4; + PitchNormalLaw_B.flare_Theta_c_rate_deg_s = -3.0; + } else { + rtb_in_flare = 0.0; + PitchNormalLaw_B.flare_Theta_c_deg = rtb_Sum_j4; + PitchNormalLaw_B.flare_Theta_c_rate_deg_s = -3.0; + } + break; + + default: + if (*rtu_In_in_flight == 1.0) { + PitchNormalLaw_DWork.is_c2_PitchNormalLaw = PitchNormalLaw_IN_Flight_Low; + rtb_in_flare = 0.0; + PitchNormalLaw_B.flare_Theta_c_deg = rtb_Sum_j4; + PitchNormalLaw_B.flare_Theta_c_rate_deg_s = -3.0; + } else { + rtb_in_flare = 0.0; + PitchNormalLaw_B.flare_Theta_c_deg = rtb_Sum_j4; + PitchNormalLaw_B.flare_Theta_c_rate_deg_s = -3.0; + } + break; + } + } + + if (PitchNormalLaw_DWork.is_active_c9_PitchNormalLaw == 0U) { + PitchNormalLaw_DWork.is_active_c9_PitchNormalLaw = 1U; + PitchNormalLaw_DWork.is_c9_PitchNormalLaw = PitchNormalLaw_IN_running; + rtb_eta_trim_deg_should_freeze = false; + } else if (PitchNormalLaw_DWork.is_c9_PitchNormalLaw == PitchNormalLaw_IN_frozen) { + if ((rtb_in_flare == 0.0) && (*rtu_In_nz_g < 1.25) && (*rtu_In_nz_g > 0.5) && (std::abs(*rtu_In_Phi_deg) <= 30.0)) { + PitchNormalLaw_DWork.is_c9_PitchNormalLaw = PitchNormalLaw_IN_running; + rtb_eta_trim_deg_should_freeze = false; + } else { + rtb_eta_trim_deg_should_freeze = true; + } + } else if ((rtb_in_flare != 0.0) || (*rtu_In_nz_g >= 1.25) || (*rtu_In_nz_g <= 0.5) || (std::abs(*rtu_In_Phi_deg) > + 30.0)) { + PitchNormalLaw_DWork.is_c9_PitchNormalLaw = PitchNormalLaw_IN_frozen; + rtb_eta_trim_deg_should_freeze = true; + } else { + rtb_eta_trim_deg_should_freeze = false; + } + + if (PitchNormalLaw_DWork.is_active_c8_PitchNormalLaw == 0U) { + PitchNormalLaw_DWork.is_active_c8_PitchNormalLaw = 1U; + PitchNormalLaw_DWork.is_c8_PitchNormalLaw = PitchNormalLaw_IN_manual; + } else { + switch (PitchNormalLaw_DWork.is_c8_PitchNormalLaw) { + case PitchNormalLaw_IN_automatic: + if (*rtu_In_in_flight == 0.0) { + PitchNormalLaw_DWork.is_c8_PitchNormalLaw = PitchNormalLaw_IN_reset; + } else if (*rtu_In_tracking_mode_on) { + PitchNormalLaw_DWork.is_c8_PitchNormalLaw = PitchNormalLaw_IN_tracking; + } + break; + + case PitchNormalLaw_IN_manual: + if (*rtu_In_in_flight != 0.0) { + PitchNormalLaw_DWork.is_c8_PitchNormalLaw = PitchNormalLaw_IN_automatic; + } + break; + + case PitchNormalLaw_IN_reset: + if ((*rtu_In_in_flight == 0.0) && (*rtu_In_eta_trim_deg == 0.0)) { + PitchNormalLaw_DWork.is_c8_PitchNormalLaw = PitchNormalLaw_IN_manual; + } + break; + + default: + if (!*rtu_In_tracking_mode_on) { + PitchNormalLaw_DWork.is_c8_PitchNormalLaw = PitchNormalLaw_IN_automatic; + } + break; + } + } + + if (PitchNormalLaw_DWork.is_active_c7_PitchNormalLaw == 0U) { + PitchNormalLaw_DWork.is_active_c7_PitchNormalLaw = 1U; + PitchNormalLaw_DWork.is_c7_PitchNormalLaw = PitchNormalLaw_IN_ground; + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; + rtb_nz_limit_up_g = 2.0; + rtb_nz_limit_lo_g = 0.0; + } else { + switch (PitchNormalLaw_DWork.is_c7_PitchNormalLaw) { + case PitchNormalLaw_IN_flight_clean: + if (*rtu_In_flaps_handle_index != 0.0) { + PitchNormalLaw_DWork.is_c7_PitchNormalLaw = PitchNormalLaw_IN_flight_flaps; + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; + rtb_nz_limit_up_g = 2.0; + rtb_nz_limit_lo_g = 0.0; + } else if (*rtu_In_in_flight == 0.0) { + PitchNormalLaw_DWork.is_c7_PitchNormalLaw = PitchNormalLaw_IN_ground; + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; + rtb_nz_limit_up_g = 2.0; + rtb_nz_limit_lo_g = 0.0; + } else { + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.3; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.3; + rtb_nz_limit_up_g = 2.5; + rtb_nz_limit_lo_g = -1.0; + } + break; + + case PitchNormalLaw_IN_flight_flaps: + if (*rtu_In_flaps_handle_index == 0.0) { + PitchNormalLaw_DWork.is_c7_PitchNormalLaw = PitchNormalLaw_IN_flight_clean; + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.3; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.3; + rtb_nz_limit_up_g = 2.5; + rtb_nz_limit_lo_g = -1.0; + } else if (*rtu_In_in_flight == 0.0) { + PitchNormalLaw_DWork.is_c7_PitchNormalLaw = PitchNormalLaw_IN_ground; + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; + rtb_nz_limit_up_g = 2.0; + rtb_nz_limit_lo_g = 0.0; + } else { + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; + rtb_nz_limit_up_g = 2.0; + rtb_nz_limit_lo_g = 0.0; + } + break; + + default: + if ((*rtu_In_in_flight != 0.0) && (*rtu_In_flaps_handle_index == 0.0)) { + PitchNormalLaw_DWork.is_c7_PitchNormalLaw = PitchNormalLaw_IN_flight_clean; + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.3; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.3; + rtb_nz_limit_up_g = 2.5; + rtb_nz_limit_lo_g = -1.0; + } else if ((*rtu_In_in_flight != 0.0) && (*rtu_In_flaps_handle_index != 0.0)) { + PitchNormalLaw_DWork.is_c7_PitchNormalLaw = PitchNormalLaw_IN_flight_flaps; + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; + rtb_nz_limit_up_g = 2.0; + rtb_nz_limit_lo_g = 0.0; + } else { + rtb_eta_trim_deg_rate_limit_up_deg_s = 0.7; + rtb_eta_trim_deg_rate_limit_lo_deg_s = -0.7; + rtb_nz_limit_up_g = 2.0; + rtb_nz_limit_lo_g = 0.0; + } + break; + } + } + + rtb_uDLookupTable = *rtu_In_in_flight; + if (rtb_uDLookupTable > PitchNormalLaw_rtP.Saturation_UpperSat_c) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation_UpperSat_c; + } else if (rtb_uDLookupTable < PitchNormalLaw_rtP.Saturation_LowerSat_n) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation_LowerSat_n; + } + + PitchNormalLaw_RateLimiter(rtb_uDLookupTable, PitchNormalLaw_rtP.RateLimiterVariableTs_up, + PitchNormalLaw_rtP.RateLimiterVariableTs_lo, rtu_In_time_dt, + PitchNormalLaw_rtP.RateLimiterVariableTs_InitialCondition, &rtb_Y_b, &PitchNormalLaw_DWork.sf_RateLimiter); + if (PitchNormalLaw_DWork.is_active_c6_PitchNormalLaw == 0U) { + PitchNormalLaw_DWork.is_active_c6_PitchNormalLaw = 1U; + PitchNormalLaw_DWork.is_c6_PitchNormalLaw = PitchNormalLaw_IN_OFF; + rtb_in_rotation = 0; + } else if (PitchNormalLaw_DWork.is_c6_PitchNormalLaw == PitchNormalLaw_IN_OFF) { + if ((rtb_Y_b < 1.0) && (*rtu_In_V_tas_kn > 70.0) && ((*rtu_In_thrust_lever_1_pos >= 35.0) || + (*rtu_In_thrust_lever_2_pos >= 35.0))) { + PitchNormalLaw_DWork.is_c6_PitchNormalLaw = PitchNormalLaw_IN_ON; + rtb_in_rotation = 1; + } else { + rtb_in_rotation = 0; + } + } else if ((rtb_Y_b == 1.0) || (*rtu_In_H_radio_ft > 400.0) || ((*rtu_In_V_tas_kn < 70.0) && + ((*rtu_In_thrust_lever_1_pos < 35.0) || (*rtu_In_thrust_lever_2_pos < 35.0)))) { + PitchNormalLaw_DWork.is_c6_PitchNormalLaw = PitchNormalLaw_IN_OFF; + rtb_in_rotation = 0; + } else { + rtb_in_rotation = 1; + } + + if (rtb_in_rotation > PitchNormalLaw_rtP.Saturation1_UpperSat) { + rtb_Gain = PitchNormalLaw_rtP.Saturation1_UpperSat; + } else if (rtb_in_rotation < PitchNormalLaw_rtP.Saturation1_LowerSat) { + rtb_Gain = PitchNormalLaw_rtP.Saturation1_LowerSat; + } else { + rtb_Gain = rtb_in_rotation; + } + + PitchNormalLaw_RateLimiter(rtb_Gain, PitchNormalLaw_rtP.RateLimiterVariableTs1_up, + PitchNormalLaw_rtP.RateLimiterVariableTs1_lo, rtu_In_time_dt, + PitchNormalLaw_rtP.RateLimiterVariableTs1_InitialCondition, &rtb_ManualSwitch, + &PitchNormalLaw_DWork.sf_RateLimiter_p); + rtb_Gain = PitchNormalLaw_rtP.Gain_Gain_a * *rtu_In_delta_eta_pos; + PitchNormalLaw_RateLimiter(rtb_nz_limit_up_g, PitchNormalLaw_rtP.RateLimiterVariableTs2_up, + PitchNormalLaw_rtP.RateLimiterVariableTs2_lo, rtu_In_time_dt, + PitchNormalLaw_rtP.RateLimiterVariableTs2_InitialCondition, &rtb_Y_p, &PitchNormalLaw_DWork.sf_RateLimiter_c); + PitchNormalLaw_RateLimiter(rtb_nz_limit_lo_g, PitchNormalLaw_rtP.RateLimiterVariableTs3_up, + PitchNormalLaw_rtP.RateLimiterVariableTs3_lo, rtu_In_time_dt, + PitchNormalLaw_rtP.RateLimiterVariableTs3_InitialCondition, &rtb_Y_ll, &PitchNormalLaw_DWork.sf_RateLimiter_n); + rtb_Loaddemand2_l = std::abs(PitchNormalLaw_B.flare_Theta_c_rate_deg_s) * *rtu_In_time_dt; + rtb_Loaddemand2 = *rtu_In_time_dt * PitchNormalLaw_B.flare_Theta_c_rate_deg_s; + PitchNormalLaw_DWork.Delay_DSTATE += std::fmax(std::fmin(PitchNormalLaw_B.flare_Theta_c_deg - + PitchNormalLaw_DWork.Delay_DSTATE, rtb_Loaddemand2_l), rtb_Loaddemand2); + PitchNormalLaw_eta_trim_limit_lofreeze(rtu_In_eta_trim_deg, rtu_In_high_aoa_prot_active, &rtb_Y_am, + &PitchNormalLaw_DWork.sf_eta_trim_limit_lofreeze); + if (*rtu_In_high_aoa_prot_active) { + *rty_Out_eta_trim_limit_lo = rtb_Y_am; + } else { + *rty_Out_eta_trim_limit_lo = PitchNormalLaw_rtP.Constant3_Value; + } + + PitchNormalLaw_eta_trim_limit_lofreeze(rtu_In_eta_trim_deg, rtu_In_high_speed_prot_active, &rtb_Y_am, + &PitchNormalLaw_DWork.sf_eta_trim_limit_upfreeze); + if (*rtu_In_high_speed_prot_active) { + *rty_Out_eta_trim_limit_up = rtb_Y_am; + } else { + *rty_Out_eta_trim_limit_up = PitchNormalLaw_rtP.Constant2_Value; + } + + rtb_Loaddemand2_l = *rtu_In_V_ias_kn; + rtb_Gain_ot = *rtu_In_flaps_handle_index; + rtb_Gain_px = *rtu_In_VLS_kn; + if (rtb_Gain_ot == 5.0) { + rtb_in_rotation = 25; + } else { + rtb_in_rotation = 30; + } + + PitchNormalLaw_RateLimiter(static_cast(rtb_in_rotation) - std::fmin(5.0, std::fmax(0.0, 5.0 - + (rtb_Loaddemand2_l - (rtb_Gain_px + 5.0)) * 0.25)), PitchNormalLaw_rtP.RateLimiterVariableTs6_up, + PitchNormalLaw_rtP.RateLimiterVariableTs6_lo, rtu_In_time_dt, + PitchNormalLaw_rtP.RateLimiterVariableTs6_InitialCondition, &rtb_Y_am, &PitchNormalLaw_DWork.sf_RateLimiter_o); + rtb_Loaddemand2 = PitchNormalLaw_rtP.Gain1_Gain * *rtu_In_Theta_deg; + rtb_Loaddemand2_l = PitchNormalLaw_rtP.Gain1_Gain_c * *rtu_In_Theta_deg; + rtb_Cos = std::cos(rtb_Loaddemand2_l); + rtb_Loaddemand2_l = PitchNormalLaw_rtP.Gain1_Gain_l * *rtu_In_Phi_deg; + rtb_Y_cm = rtb_Cos / std::cos(rtb_Loaddemand2_l); + rtb_Y_n = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.uDLookupTable_bp01Data, + PitchNormalLaw_rtP.uDLookupTable_tableData, 6U); + rtb_uDLookupTable = *rtu_In_V_tas_kn; + rtb_Loaddemand2_l = PitchNormalLaw_rtP.Gain1_Gain_e * *rtu_In_qk_deg_s; + rtb_Gain_g = *rtu_In_nz_g - rtb_Y_cm; + rtb_Divide_kq = PitchNormalLaw_rtP.Gain2_Gain * rtb_Y_am - rtb_Loaddemand2; + if (rtb_uDLookupTable > PitchNormalLaw_rtP.Saturation3_UpperSat) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation3_UpperSat; + } else if (rtb_uDLookupTable < PitchNormalLaw_rtP.Saturation3_LowerSat) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation3_LowerSat; + } + + if (rtb_Divide_kq > PitchNormalLaw_rtP.Saturation1_UpperSat_i) { + rtb_Divide_kq = PitchNormalLaw_rtP.Saturation1_UpperSat_i; + } else if (rtb_Divide_kq < PitchNormalLaw_rtP.Saturation1_LowerSat_h) { + rtb_Divide_kq = PitchNormalLaw_rtP.Saturation1_LowerSat_h; + } + + rtb_Saturation3 = (PitchNormalLaw_rtP.Gain_Gain_c * PitchNormalLaw_rtP.Vm_currentms_Value * rtb_Loaddemand2_l + + rtb_Gain_g) - (rtb_Y_n / (PitchNormalLaw_rtP.Gain5_Gain * rtb_uDLookupTable) + + PitchNormalLaw_rtP.Bias_Bias) * ((rtb_Y_cm + look1_binlxpw(rtb_Divide_kq, PitchNormalLaw_rtP.Loaddemand1_bp01Data, + PitchNormalLaw_rtP.Loaddemand1_tableData, 2U)) - rtb_Y_cm); + rtb_Y_n = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.PLUT_bp01Data, PitchNormalLaw_rtP.PLUT_tableData, 1U); + rtb_Product1_dm = rtb_Saturation3 * rtb_Y_n; + rtb_Loaddemand2_l = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_Gain * *rtu_In_qk_deg_s; + rtb_Divide = (rtb_Loaddemand2_l - PitchNormalLaw_DWork.Delay_DSTATE_h) / *rtu_In_time_dt; + rtb_Y_n = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.DLUT_bp01Data, PitchNormalLaw_rtP.DLUT_tableData, 1U); + rtb_Gain_px = rtb_Saturation3 * rtb_Y_n * PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_Gain; + rtb_Divide_o = (rtb_Gain_px - PitchNormalLaw_DWork.Delay_DSTATE_n) / *rtu_In_time_dt; + rtb_Gain_ot = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_Gain * *rtu_In_V_tas_kn; + rtb_Divide_an = (rtb_Gain_ot - PitchNormalLaw_DWork.Delay_DSTATE_c) / *rtu_In_time_dt; + PitchNormalLaw_LagFilter_n(rtb_Divide_an, PitchNormalLaw_rtP.LagFilter_C1_i, rtu_In_time_dt, &rtb_Y_am, + &PitchNormalLaw_DWork.sf_LagFilter_k); + if (rtb_Y_am > PitchNormalLaw_rtP.SaturationV_dot_UpperSat) { + rtb_Divide_a0 = PitchNormalLaw_rtP.SaturationV_dot_UpperSat; + } else if (rtb_Y_am < PitchNormalLaw_rtP.SaturationV_dot_LowerSat) { + rtb_Divide_a0 = PitchNormalLaw_rtP.SaturationV_dot_LowerSat; + } else { + rtb_Divide_a0 = rtb_Y_am; + } + + rtb_Gain5_gq = std::fmin(*rtu_In_spoilers_left_pos, *rtu_In_spoilers_right_pos); + PitchNormalLaw_WashoutFilter(rtb_Gain5_gq, PitchNormalLaw_rtP.WashoutFilter_C1, rtu_In_time_dt, &rtb_Y_am, + &PitchNormalLaw_DWork.sf_WashoutFilter_k); + rtb_Y_n = look1_binlxpw(*rtu_In_H_radio_ft, PitchNormalLaw_rtP.ScheduledGain_BreakpointsForDimension1, + PitchNormalLaw_rtP.ScheduledGain_Table, 3U); + if (rtb_Y_am > PitchNormalLaw_rtP.SaturationSpoilers_UpperSat) { + rtb_Y_am = PitchNormalLaw_rtP.SaturationSpoilers_UpperSat; + } else if (rtb_Y_am < PitchNormalLaw_rtP.SaturationSpoilers_LowerSat) { + rtb_Y_am = PitchNormalLaw_rtP.SaturationSpoilers_LowerSat; + } + + rtb_Product_kz = rtb_Y_am * rtb_Y_n; + rtb_Divide_an = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_Gain_i * *rtu_In_qk_deg_s; + rtb_Divide_cq = (rtb_Divide_an - PitchNormalLaw_DWork.Delay_DSTATE_l) / *rtu_In_time_dt; + rtb_Gain1_ft = PitchNormalLaw_rtP.Gain1_Gain_o * *rtu_In_qk_deg_s; + rtb_Saturation3 = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.uDLookupTable_bp01Data_o, + PitchNormalLaw_rtP.uDLookupTable_tableData_e, 6U); + rtb_uDLookupTable = *rtu_In_V_tas_kn; + if (rtb_uDLookupTable > PitchNormalLaw_rtP.Saturation3_UpperSat_a) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation3_UpperSat_a; + } else if (rtb_uDLookupTable < PitchNormalLaw_rtP.Saturation3_LowerSat_l) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation3_LowerSat_l; + } + + rtb_Saturation3 = (PitchNormalLaw_rtP.Gain_Gain_al * PitchNormalLaw_rtP.Vm_currentms_Value_e * rtb_Gain1_ft + + rtb_Gain_g) - (rtb_Saturation3 / (PitchNormalLaw_rtP.Gain5_Gain_d * rtb_uDLookupTable) + + PitchNormalLaw_rtP.Bias_Bias_a) * (rtb_Y_p - rtb_Y_cm); + rtb_Y_n = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.PLUT_bp01Data_b, PitchNormalLaw_rtP.PLUT_tableData_b, 1U); + rtb_Product1_ck = rtb_Saturation3 * rtb_Y_n; + rtb_Y_n = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.DLUT_bp01Data_h, PitchNormalLaw_rtP.DLUT_tableData_p, 1U); + rtb_Gain1_ft = rtb_Saturation3 * rtb_Y_n * PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_Gain_j; + rtb_Divide_l = (rtb_Gain1_ft - PitchNormalLaw_DWork.Delay_DSTATE_k) / *rtu_In_time_dt; + rtb_Gain_bs = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_Gain_e * *rtu_In_V_tas_kn; + rtb_Divide_m = (rtb_Gain_bs - PitchNormalLaw_DWork.Delay_DSTATE_d) / *rtu_In_time_dt; + PitchNormalLaw_LagFilter_n(rtb_Divide_m, PitchNormalLaw_rtP.LagFilter_C1_p, rtu_In_time_dt, &rtb_Y_am, + &PitchNormalLaw_DWork.sf_LagFilter_g3); + if (rtb_Y_am > PitchNormalLaw_rtP.SaturationV_dot_UpperSat_j) { + y = PitchNormalLaw_rtP.SaturationV_dot_UpperSat_j; + } else if (rtb_Y_am < PitchNormalLaw_rtP.SaturationV_dot_LowerSat_e) { + y = PitchNormalLaw_rtP.SaturationV_dot_LowerSat_e; + } else { + y = rtb_Y_am; + } + + PitchNormalLaw_WashoutFilter(rtb_Gain5_gq, PitchNormalLaw_rtP.WashoutFilter_C1_n, rtu_In_time_dt, &rtb_Y_am, + &PitchNormalLaw_DWork.sf_WashoutFilter_c); + rtb_Y_n = look1_binlxpw(*rtu_In_H_radio_ft, PitchNormalLaw_rtP.ScheduledGain_BreakpointsForDimension1_n, + PitchNormalLaw_rtP.ScheduledGain_Table_b, 3U); + if (rtb_Y_am > PitchNormalLaw_rtP.SaturationSpoilers_UpperSat_g) { + rtb_Y_am = PitchNormalLaw_rtP.SaturationSpoilers_UpperSat_g; + } else if (rtb_Y_am < PitchNormalLaw_rtP.SaturationSpoilers_LowerSat_j) { + rtb_Y_am = PitchNormalLaw_rtP.SaturationSpoilers_LowerSat_j; + } + + rtb_Product_n3 = rtb_Y_am * rtb_Y_n; + PitchNormalLaw_RateLimiter_c(rtu_In_delta_eta_pos, PitchNormalLaw_rtP.RateLimiterVariableTs2_up_m, + PitchNormalLaw_rtP.RateLimiterVariableTs2_lo_k, rtu_In_time_dt, + PitchNormalLaw_rtP.RateLimiterVariableTs2_InitialCondition_f, &rtb_Sum_j4, &PitchNormalLaw_DWork.sf_RateLimiter_nx); + rtb_Divide_m = (*rtu_In_alpha_max - *rtu_In_alpha_prot) * rtb_Sum_j4; + PitchNormalLaw_LagFilter(rtu_In_alpha_deg, PitchNormalLaw_rtP.LagFilter1_C1, rtu_In_time_dt, &rtb_Y_am, + &PitchNormalLaw_DWork.sf_LagFilter_m); + rtb_Sum10 = rtb_Y_am - *rtu_In_alpha_prot; + rtb_y = std::fmax(std::fmax(0.0, *rtu_In_Theta_deg - 22.5), std::fmax(0.0, (std::abs(*rtu_In_Phi_deg) - 3.0) / 6.0)); + PitchNormalLaw_WashoutFilter(rtb_y, PitchNormalLaw_rtP.WashoutFilter_C1_b, rtu_In_time_dt, &rtb_Sum_j4, + &PitchNormalLaw_DWork.sf_WashoutFilter_h); + rtb_Saturation3 = (rtb_Divide_m - rtb_Sum10) - rtb_Sum_j4; + rtb_Divide_m = PitchNormalLaw_rtP.Subsystem1_Gain * rtb_Saturation3; + rtb_Sum10 = (rtb_Divide_m - PitchNormalLaw_DWork.Delay_DSTATE_f) / *rtu_In_time_dt; + rtb_Y_n = *rtu_In_time_dt * PitchNormalLaw_rtP.Subsystem1_C1; + rtb_Y_aj = rtb_Y_n + PitchNormalLaw_rtP.Constant_Value_f; + PitchNormalLaw_DWork.Delay1_DSTATE = 1.0 / rtb_Y_aj * (PitchNormalLaw_rtP.Constant_Value_f - rtb_Y_n) * + PitchNormalLaw_DWork.Delay1_DSTATE + (rtb_Sum10 + PitchNormalLaw_DWork.Delay_DSTATE_g) * (rtb_Y_n / rtb_Y_aj); + rtb_alpha_err_gain = PitchNormalLaw_rtP.alpha_err_gain_Gain * rtb_Saturation3; + rtb_y = PitchNormalLaw_rtP.Subsystem3_Gain * *rtu_In_V_ias_kn; + rtb_Divide_o2 = (rtb_y - PitchNormalLaw_DWork.Delay_DSTATE_j) / *rtu_In_time_dt; + rtb_Y_aj = *rtu_In_time_dt * PitchNormalLaw_rtP.Subsystem3_C1; + rtb_Saturation3 = rtb_Y_aj + PitchNormalLaw_rtP.Constant_Value_b; + PitchNormalLaw_DWork.Delay1_DSTATE_i = 1.0 / rtb_Saturation3 * (PitchNormalLaw_rtP.Constant_Value_b - rtb_Y_aj) * + PitchNormalLaw_DWork.Delay1_DSTATE_i + (rtb_Divide_o2 + PitchNormalLaw_DWork.Delay_DSTATE_ca) * (rtb_Y_aj / + rtb_Saturation3); + rtb_qk_gain = PitchNormalLaw_rtP.qk_gain_Gain * *rtu_In_qk_deg_s; + rtb_qk_dot_gain = PitchNormalLaw_rtP.qk_dot_gain_Gain * *rtu_In_qk_dot_deg_s2; + rtb_Y_n = *rtu_In_high_aoa_prot_active; + rtb_Sum_j4 = PitchNormalLaw_rtP.RateLimiterVariableTs5_up * *rtu_In_time_dt; + rtb_Y_n = std::fmin(rtb_Y_n - PitchNormalLaw_DWork.Delay_DSTATE_e, rtb_Sum_j4); + rtb_Sum_j4 = *rtu_In_time_dt * PitchNormalLaw_rtP.RateLimiterVariableTs5_lo; + PitchNormalLaw_DWork.Delay_DSTATE_e += std::fmax(rtb_Y_n, rtb_Sum_j4); + if (PitchNormalLaw_DWork.Delay_DSTATE_e > PitchNormalLaw_rtP.Saturation_UpperSat_eo) { + rtb_Sum_j4 = PitchNormalLaw_rtP.Saturation_UpperSat_eo; + } else if (PitchNormalLaw_DWork.Delay_DSTATE_e < PitchNormalLaw_rtP.Saturation_LowerSat_h) { + rtb_Sum_j4 = PitchNormalLaw_rtP.Saturation_LowerSat_h; + } else { + rtb_Sum_j4 = PitchNormalLaw_DWork.Delay_DSTATE_e; + } + + rtb_uDLookupTable = (((PitchNormalLaw_rtP.precontrol_gain_Gain * PitchNormalLaw_DWork.Delay1_DSTATE + + rtb_alpha_err_gain) + PitchNormalLaw_rtP.v_dot_gain_Gain * PitchNormalLaw_DWork.Delay1_DSTATE_i) + rtb_qk_gain) + + rtb_qk_dot_gain; + if (rtb_uDLookupTable > PitchNormalLaw_rtP.Saturation3_UpperSat_f) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation3_UpperSat_f; + } else if (rtb_uDLookupTable < PitchNormalLaw_rtP.Saturation3_LowerSat_c) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation3_LowerSat_c; + } + + rtb_Product_k = rtb_uDLookupTable * rtb_Sum_j4; + rtb_Sum1_d4 = PitchNormalLaw_rtP.Constant_Value_fe - rtb_Sum_j4; + rtb_alpha_err_gain = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_Gain_m * *rtu_In_qk_deg_s; + rtb_Divide_kq = (rtb_alpha_err_gain - PitchNormalLaw_DWork.Delay_DSTATE_kd) / *rtu_In_time_dt; + rtb_Gain1_e = PitchNormalLaw_rtP.Gain1_Gain_en * *rtu_In_qk_deg_s; + rtb_Sum_j4 = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.uDLookupTable_bp01Data_b, + PitchNormalLaw_rtP.uDLookupTable_tableData_h, 6U); + rtb_uDLookupTable = *rtu_In_V_tas_kn; + if (rtb_uDLookupTable > PitchNormalLaw_rtP.Saturation3_UpperSat_b) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation3_UpperSat_b; + } else if (rtb_uDLookupTable < PitchNormalLaw_rtP.Saturation3_LowerSat_e) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation3_LowerSat_e; + } + + rtb_Bias_o = rtb_Sum_j4 / (PitchNormalLaw_rtP.Gain5_Gain_e * rtb_uDLookupTable) + PitchNormalLaw_rtP.Bias_Bias_f; + PitchNormalLaw_RateLimiter_c(rtu_In_ap_theta_c_deg, PitchNormalLaw_rtP.RateLimiterVariableTs1_up_d, + PitchNormalLaw_rtP.RateLimiterVariableTs1_lo_g, rtu_In_time_dt, + PitchNormalLaw_rtP.RateLimiterVariableTs1_InitialCondition_l, &rtb_Y_p, &PitchNormalLaw_DWork.sf_RateLimiter_d); + rtb_uDLookupTable = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.ScheduledGain_BreakpointsForDimension1_h, + PitchNormalLaw_rtP.ScheduledGain_Table_j, 6U); + PitchNormalLaw_RateLimiter_c(rtu_In_delta_eta_pos, PitchNormalLaw_rtP.RateLimiterVariableTs_up_n, + PitchNormalLaw_rtP.RateLimiterVariableTs_lo_c, rtu_In_time_dt, + PitchNormalLaw_rtP.RateLimiterVariableTs_InitialCondition_o, &rtb_Y_am, &PitchNormalLaw_DWork.sf_RateLimiter_c2); + rtb_Y_aj = *rtu_In_delta_eta_pos - PitchNormalLaw_DWork.Delay_DSTATE_b; + rtb_Saturation3 = PitchNormalLaw_rtP.RateLimiterVariableTs3_up_i * *rtu_In_time_dt; + rtb_Y_aj = std::fmin(rtb_Y_aj, rtb_Saturation3); + rtb_Saturation3 = *rtu_In_time_dt * PitchNormalLaw_rtP.RateLimiterVariableTs3_lo_b; + PitchNormalLaw_DWork.Delay_DSTATE_b += std::fmax(rtb_Y_aj, rtb_Saturation3); + rtb_v_target = std::fmax((*rtu_In_high_speed_prot_low_kn - *rtu_In_high_speed_prot_high_kn) * + PitchNormalLaw_DWork.Delay_DSTATE_b, 0.0) + *rtu_In_high_speed_prot_low_kn; + rtb_qk_gain = PitchNormalLaw_rtP.Subsystem2_Gain * rtb_v_target; + rtb_qk_dot_gain = (rtb_qk_gain - PitchNormalLaw_DWork.Delay_DSTATE_ku) / *rtu_In_time_dt; + rtb_Sum_j4 = *rtu_In_time_dt * PitchNormalLaw_rtP.Subsystem2_C1; + rtb_Y_aj = rtb_Sum_j4 + PitchNormalLaw_rtP.Constant_Value_ja; + PitchNormalLaw_DWork.Delay1_DSTATE_l = 1.0 / rtb_Y_aj * (PitchNormalLaw_rtP.Constant_Value_ja - rtb_Sum_j4) * + PitchNormalLaw_DWork.Delay1_DSTATE_l + (rtb_qk_dot_gain + PitchNormalLaw_DWork.Delay_DSTATE_gl) * (rtb_Sum_j4 / + rtb_Y_aj); + rtb_Gain_ll = PitchNormalLaw_rtP.Subsystem_Gain * *rtu_In_V_ias_kn; + rtb_Divide_b4 = (rtb_Gain_ll - PitchNormalLaw_DWork.Delay_DSTATE_m) / *rtu_In_time_dt; + rtb_Sum_j4 = *rtu_In_time_dt * PitchNormalLaw_rtP.Subsystem_C1; + rtb_Y_aj = rtb_Sum_j4 + PitchNormalLaw_rtP.Constant_Value_jj; + PitchNormalLaw_DWork.Delay1_DSTATE_n = 1.0 / rtb_Y_aj * (PitchNormalLaw_rtP.Constant_Value_jj - rtb_Sum_j4) * + PitchNormalLaw_DWork.Delay1_DSTATE_n + (rtb_Divide_b4 + PitchNormalLaw_DWork.Delay_DSTATE_k2) * (rtb_Sum_j4 / + rtb_Y_aj); + rtb_in_rotation = *rtu_In_high_speed_prot_active; + rtb_Saturation3 = PitchNormalLaw_rtP.RateLimiterVariableTs4_up * *rtu_In_time_dt; + rtb_Y_aj = std::fmin(static_cast(rtb_in_rotation) - PitchNormalLaw_DWork.Delay_DSTATE_mz, rtb_Saturation3); + rtb_Saturation3 = *rtu_In_time_dt * PitchNormalLaw_rtP.RateLimiterVariableTs4_lo; + PitchNormalLaw_DWork.Delay_DSTATE_mz += std::fmax(rtb_Y_aj, rtb_Saturation3); + PitchNormalLaw_RateLimiter(rtb_in_flare, PitchNormalLaw_rtP.RateLimiterVariableTs6_up_n, + PitchNormalLaw_rtP.RateLimiterVariableTs6_lo_p, rtu_In_time_dt, + PitchNormalLaw_rtP.RateLimiterVariableTs6_InitialCondition_f, &rtb_Y_n, &PitchNormalLaw_DWork.sf_RateLimiter_l); + if (*rtu_In_any_ap_engaged) { + rtb_Sum_j4 = rtb_Y_p - *rtu_In_Theta_deg; + rtb_Sum_j4 *= rtb_uDLookupTable; + } else { + rtb_Sum_j4 = look1_binlxpw(rtb_Y_am, PitchNormalLaw_rtP.Loaddemand_bp01Data, PitchNormalLaw_rtP.Loaddemand_tableData, + 2U); + if (rtb_Y_n > PitchNormalLaw_rtP.Saturation_UpperSat) { + rtb_Y_n = PitchNormalLaw_rtP.Saturation_UpperSat; + } else if (rtb_Y_n < PitchNormalLaw_rtP.Saturation_LowerSat) { + rtb_Y_n = PitchNormalLaw_rtP.Saturation_LowerSat; + } + + rtb_Y_p = PitchNormalLaw_DWork.Delay_DSTATE - *rtu_In_Theta_deg; + rtb_uDLookupTable = PitchNormalLaw_rtP.Gain_Gain * rtb_Y_p; + if (rtb_uDLookupTable > PitchNormalLaw_rtP.Saturation_UpperSat_g) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation_UpperSat_g; + } else if (rtb_uDLookupTable < PitchNormalLaw_rtP.Saturation_LowerSat_d) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation_LowerSat_d; + } + + rtb_Y_p = (PitchNormalLaw_rtP.Constant_Value_g - rtb_Y_n) * PitchNormalLaw_rtP.Constant_Value_l + rtb_uDLookupTable * + rtb_Y_n; + if (PitchNormalLaw_DWork.Delay_DSTATE_mz > PitchNormalLaw_rtP.Saturation_UpperSat_e) { + rtb_Y_am = PitchNormalLaw_rtP.Saturation_UpperSat_e; + } else if (PitchNormalLaw_DWork.Delay_DSTATE_mz < PitchNormalLaw_rtP.Saturation_LowerSat_m) { + rtb_Y_am = PitchNormalLaw_rtP.Saturation_LowerSat_m; + } else { + rtb_Y_am = PitchNormalLaw_DWork.Delay_DSTATE_mz; + } + + if (rtb_in_rotation > PitchNormalLaw_rtP.Switch2_Threshold) { + rtb_Y_n = PitchNormalLaw_rtP.qk_dot_gain1_Gain * *rtu_In_qk_dot_deg_s2; + rtb_uDLookupTable = PitchNormalLaw_rtP.qk_gain_HSP_Gain * *rtu_In_qk_deg_s; + rtb_Y_aj = rtb_v_target - *rtu_In_V_ias_kn; + rtb_uDLookupTable = ((((PitchNormalLaw_rtP.precontrol_gain_HSP_Gain * PitchNormalLaw_DWork.Delay1_DSTATE_l + + PitchNormalLaw_rtP.Gain6_Gain * rtb_Y_aj) + PitchNormalLaw_rtP.v_dot_gain_HSP_Gain * + PitchNormalLaw_DWork.Delay1_DSTATE_n) + rtb_uDLookupTable) + rtb_Y_n) * PitchNormalLaw_rtP.HSP_gain_Gain; + if (rtb_Sum_j4 > PitchNormalLaw_rtP.Saturation8_UpperSat) { + rtb_Y_n = PitchNormalLaw_rtP.Saturation8_UpperSat; + } else if (rtb_Sum_j4 < PitchNormalLaw_rtP.Saturation8_LowerSat) { + rtb_Y_n = PitchNormalLaw_rtP.Saturation8_LowerSat; + } else { + rtb_Y_n = rtb_Sum_j4; + } + + if (rtb_uDLookupTable > PitchNormalLaw_rtP.Saturation4_UpperSat) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation4_UpperSat; + } else if (rtb_uDLookupTable < PitchNormalLaw_rtP.Saturation4_LowerSat) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation4_LowerSat; + } + + rtb_Y_n += rtb_uDLookupTable; + } else { + rtb_Y_n = PitchNormalLaw_rtP.Constant1_Value; + } + + rtb_Sum_j4 = ((PitchNormalLaw_rtP.Constant_Value_m - rtb_Y_am) * rtb_Sum_j4 + rtb_Y_n * rtb_Y_am) + rtb_Y_p; + } + + rtb_uDLookupTable = *rtu_In_Phi_deg; + if (rtb_uDLookupTable > PitchNormalLaw_rtP.Saturation_UpperSat_f1) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation_UpperSat_f1; + } else if (rtb_uDLookupTable < PitchNormalLaw_rtP.Saturation_LowerSat_o1) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation_LowerSat_o1; + } + + rtb_Sum_j4 = (PitchNormalLaw_rtP.Gain_Gain_b * PitchNormalLaw_rtP.Vm_currentms_Value_h * rtb_Gain1_e + rtb_Gain_g) - + ((rtb_Cos / std::cos(PitchNormalLaw_rtP.Gain1_Gain_lm * rtb_uDLookupTable) + rtb_Sum_j4) - rtb_Y_cm) * rtb_Bias_o; + rtb_Y_aj = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.PLUT_bp01Data_f, PitchNormalLaw_rtP.PLUT_tableData_k, 1U); + rtb_Y_n = rtb_Sum_j4 * rtb_Y_aj; + rtb_Y_aj = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.DLUT_bp01Data_m, PitchNormalLaw_rtP.DLUT_tableData_a, 1U); + rtb_Cos = rtb_Sum_j4 * rtb_Y_aj * PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_Gain_b; + rtb_Y_am = (rtb_Cos - PitchNormalLaw_DWork.Delay_DSTATE_jh) / *rtu_In_time_dt; + rtb_Y_p = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_Gain_c * *rtu_In_V_tas_kn; + rtb_Sum_j4 = PitchNormalLaw_DWork.Delay_DSTATE_dy; + rtb_uDLookupTable = (rtb_Y_p - PitchNormalLaw_DWork.Delay_DSTATE_dy) / *rtu_In_time_dt; + PitchNormalLaw_LagFilter_n(rtb_uDLookupTable, PitchNormalLaw_rtP.LagFilter_C1_pt, rtu_In_time_dt, &rtb_Sum_j4, + &PitchNormalLaw_DWork.sf_LagFilter_i); + if (rtb_Sum_j4 > PitchNormalLaw_rtP.SaturationV_dot_UpperSat_b) { + rtb_Sum_j4 = PitchNormalLaw_rtP.SaturationV_dot_UpperSat_b; + } else if (rtb_Sum_j4 < PitchNormalLaw_rtP.SaturationV_dot_LowerSat_m) { + rtb_Sum_j4 = PitchNormalLaw_rtP.SaturationV_dot_LowerSat_m; + } + + rtb_uDLookupTable = PitchNormalLaw_rtP.Gain_Gain_f * rtb_Sum_j4; + PitchNormalLaw_WashoutFilter(rtb_Gain5_gq, PitchNormalLaw_rtP.WashoutFilter_C1_l, rtu_In_time_dt, &rtb_Sum_j4, + &PitchNormalLaw_DWork.sf_WashoutFilter_l); + rtb_Y_aj = look1_binlxpw(*rtu_In_H_radio_ft, PitchNormalLaw_rtP.ScheduledGain_BreakpointsForDimension1_c, + PitchNormalLaw_rtP.ScheduledGain_Table_g, 3U); + if (rtb_Sum_j4 > PitchNormalLaw_rtP.SaturationSpoilers_UpperSat_o) { + rtb_Sum_j4 = PitchNormalLaw_rtP.SaturationSpoilers_UpperSat_o; + } else if (rtb_Sum_j4 < PitchNormalLaw_rtP.SaturationSpoilers_LowerSat_jl) { + rtb_Sum_j4 = PitchNormalLaw_rtP.SaturationSpoilers_LowerSat_jl; + } + + rtb_uDLookupTable = (((PitchNormalLaw_rtP.Gain3_Gain_c * rtb_Divide_kq + rtb_Y_n) + rtb_Y_am) + rtb_uDLookupTable) + + rtb_Sum_j4 * rtb_Y_aj; + rtb_Y_n = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_Gain_c * *rtu_In_qk_deg_s; + rtb_Saturation3 = (rtb_Y_n - PitchNormalLaw_DWork.Delay_DSTATE_e5) / *rtu_In_time_dt; + rtb_Y_am = PitchNormalLaw_rtP.Gain1_Gain_b * *rtu_In_qk_deg_s; + rtb_Sum_j4 = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.uDLookupTable_bp01Data_a, + PitchNormalLaw_rtP.uDLookupTable_tableData_p, 6U); + rtb_Divide_kq = *rtu_In_V_tas_kn; + if (rtb_Divide_kq > PitchNormalLaw_rtP.Saturation3_UpperSat_n) { + rtb_Divide_kq = PitchNormalLaw_rtP.Saturation3_UpperSat_n; + } else if (rtb_Divide_kq < PitchNormalLaw_rtP.Saturation3_LowerSat_a) { + rtb_Divide_kq = PitchNormalLaw_rtP.Saturation3_LowerSat_a; + } + + rtb_Sum_j4 = (PitchNormalLaw_rtP.Gain_Gain_p * PitchNormalLaw_rtP.Vm_currentms_Value_p * rtb_Y_am + rtb_Gain_g) - + (rtb_Sum_j4 / (PitchNormalLaw_rtP.Gain5_Gain_n * rtb_Divide_kq) + PitchNormalLaw_rtP.Bias_Bias_ai) * (rtb_Y_ll - + rtb_Y_cm); + rtb_Y_aj = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.PLUT_bp01Data_a, PitchNormalLaw_rtP.PLUT_tableData_o, 1U); + rtb_Gain1_e = rtb_Sum_j4 * rtb_Y_aj; + rtb_Y_aj = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.DLUT_bp01Data_k, PitchNormalLaw_rtP.DLUT_tableData_e, 1U); + rtb_Y_ll = rtb_Sum_j4 * rtb_Y_aj * PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_Gain_p; + rtb_Bias_o = (rtb_Y_ll - PitchNormalLaw_DWork.Delay_DSTATE_gz) / *rtu_In_time_dt; + rtb_Y_am = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_Gain_a * *rtu_In_V_tas_kn; + rtb_Sum_j4 = PitchNormalLaw_DWork.Delay_DSTATE_lf; + rtb_Y_aj = (rtb_Y_am - PitchNormalLaw_DWork.Delay_DSTATE_lf) / *rtu_In_time_dt; + PitchNormalLaw_LagFilter_n(rtb_Y_aj, PitchNormalLaw_rtP.LagFilter_C1_l, rtu_In_time_dt, &rtb_Sum_j4, + &PitchNormalLaw_DWork.sf_LagFilter_g); + if (rtb_Sum_j4 > PitchNormalLaw_rtP.SaturationV_dot_UpperSat_m) { + rtb_Sum_j4 = PitchNormalLaw_rtP.SaturationV_dot_UpperSat_m; + } else if (rtb_Sum_j4 < PitchNormalLaw_rtP.SaturationV_dot_LowerSat_ek) { + rtb_Sum_j4 = PitchNormalLaw_rtP.SaturationV_dot_LowerSat_ek; + } + + rtb_v_target = PitchNormalLaw_rtP.Gain_Gain_k * rtb_Sum_j4; + PitchNormalLaw_WashoutFilter(rtb_Gain5_gq, PitchNormalLaw_rtP.WashoutFilter_C1_h, rtu_In_time_dt, &rtb_Sum_j4, + &PitchNormalLaw_DWork.sf_WashoutFilter_d); + rtb_Y_aj = look1_binlxpw(*rtu_In_H_radio_ft, PitchNormalLaw_rtP.ScheduledGain_BreakpointsForDimension1_f, + PitchNormalLaw_rtP.ScheduledGain_Table_h, 3U); + rtb_Divide_kq = (((PitchNormalLaw_rtP.Gain3_Gain_m * rtb_Divide_cq + rtb_Product1_ck) + rtb_Divide_l) + + PitchNormalLaw_rtP.Gain_Gain_j * y) + rtb_Product_n3; + if (rtb_Sum_j4 > PitchNormalLaw_rtP.SaturationSpoilers_UpperSat_h) { + rtb_Sum_j4 = PitchNormalLaw_rtP.SaturationSpoilers_UpperSat_h; + } else if (rtb_Sum_j4 < PitchNormalLaw_rtP.SaturationSpoilers_LowerSat_l) { + rtb_Sum_j4 = PitchNormalLaw_rtP.SaturationSpoilers_LowerSat_l; + } + + rtb_Sum_j4 = (((PitchNormalLaw_rtP.Gain3_Gain_b * rtb_Saturation3 + rtb_Gain1_e) + rtb_Bias_o) + rtb_v_target) + + rtb_Sum_j4 * rtb_Y_aj; + if (rtb_Divide_kq > PitchNormalLaw_rtP.Saturation_UpperSat_hc) { + rtb_Divide_kq = PitchNormalLaw_rtP.Saturation_UpperSat_hc; + } else if (rtb_Divide_kq < PitchNormalLaw_rtP.Saturation_LowerSat_a) { + rtb_Divide_kq = PitchNormalLaw_rtP.Saturation_LowerSat_a; + } + + if (rtb_uDLookupTable > PitchNormalLaw_rtP.Saturation_UpperSat_k) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation_UpperSat_k; + } else if (rtb_uDLookupTable < PitchNormalLaw_rtP.Saturation_LowerSat_p1) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation_LowerSat_p1; + } + + if (rtb_Sum_j4 > PitchNormalLaw_rtP.Saturation_UpperSat_j) { + rtb_Sum_j4 = PitchNormalLaw_rtP.Saturation_UpperSat_j; + } else if (rtb_Sum_j4 < PitchNormalLaw_rtP.Saturation_LowerSat_dw) { + rtb_Sum_j4 = PitchNormalLaw_rtP.Saturation_LowerSat_dw; + } + + PitchNormalLaw_VoterAttitudeProtection(rtb_Divide_kq, rtb_Product_k + rtb_Sum1_d4 * rtb_uDLookupTable, rtb_Sum_j4, + &rtb_Saturation3); + rtb_Divide_cq = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs1_Gain_k * *rtu_In_qk_deg_s; + rtb_Product1_ck = (rtb_Divide_cq - PitchNormalLaw_DWork.Delay_DSTATE_ho) / *rtu_In_time_dt; + rtb_Divide_l = PitchNormalLaw_rtP.Gain1_Gain_lk * *rtu_In_qk_deg_s; + rtb_Sum_j4 = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.uDLookupTable_bp01Data_m, + PitchNormalLaw_rtP.uDLookupTable_tableData_a, 6U); + rtb_uDLookupTable = *rtu_In_V_tas_kn; + rtb_Divide_kq = PitchNormalLaw_rtP.Gain3_Gain_g * PitchNormalLaw_rtP.Theta_max3_Value - rtb_Loaddemand2; + if (rtb_uDLookupTable > PitchNormalLaw_rtP.Saturation3_UpperSat_e) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation3_UpperSat_e; + } else if (rtb_uDLookupTable < PitchNormalLaw_rtP.Saturation3_LowerSat_k) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation3_LowerSat_k; + } + + if (rtb_Divide_kq > PitchNormalLaw_rtP.Saturation2_UpperSat) { + rtb_Divide_kq = PitchNormalLaw_rtP.Saturation2_UpperSat; + } else if (rtb_Divide_kq < PitchNormalLaw_rtP.Saturation2_LowerSat) { + rtb_Divide_kq = PitchNormalLaw_rtP.Saturation2_LowerSat; + } + + rtb_Sum_j4 = (PitchNormalLaw_rtP.Gain_Gain_jq * PitchNormalLaw_rtP.Vm_currentms_Value_b * rtb_Divide_l + rtb_Gain_g) - + (rtb_Sum_j4 / (PitchNormalLaw_rtP.Gain5_Gain_m * rtb_uDLookupTable) + PitchNormalLaw_rtP.Bias_Bias_m) * ((rtb_Y_cm + + look1_binlxpw(rtb_Divide_kq, PitchNormalLaw_rtP.Loaddemand2_bp01Data, PitchNormalLaw_rtP.Loaddemand2_tableData, 2U)) + - rtb_Y_cm); + rtb_Y_aj = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.PLUT_bp01Data_e, PitchNormalLaw_rtP.PLUT_tableData_g, 1U); + rtb_Y_cm = rtb_Sum_j4 * rtb_Y_aj; + rtb_Y_aj = look1_binlxpw(*rtu_In_V_tas_kn, PitchNormalLaw_rtP.DLUT_bp01Data_hw, PitchNormalLaw_rtP.DLUT_tableData_l, + 1U); + rtb_Loaddemand2 = rtb_Sum_j4 * rtb_Y_aj * PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_Gain_c; + rtb_uDLookupTable = (rtb_Loaddemand2 - PitchNormalLaw_DWork.Delay_DSTATE_ds) / *rtu_In_time_dt; + rtb_Gain_g = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs2_Gain_p * *rtu_In_V_tas_kn; + rtb_Sum_j4 = PitchNormalLaw_DWork.Delay_DSTATE_jt; + rtb_Divide_l = (rtb_Gain_g - PitchNormalLaw_DWork.Delay_DSTATE_jt) / *rtu_In_time_dt; + PitchNormalLaw_LagFilter_n(rtb_Divide_l, PitchNormalLaw_rtP.LagFilter_C1_f, rtu_In_time_dt, &rtb_Sum_j4, + &PitchNormalLaw_DWork.sf_LagFilter_n); + if (rtb_Sum_j4 > PitchNormalLaw_rtP.SaturationV_dot_UpperSat_j2) { + rtb_Sum_j4 = PitchNormalLaw_rtP.SaturationV_dot_UpperSat_j2; + } else if (rtb_Sum_j4 < PitchNormalLaw_rtP.SaturationV_dot_LowerSat_n) { + rtb_Sum_j4 = PitchNormalLaw_rtP.SaturationV_dot_LowerSat_n; + } + + rtb_Divide_l = PitchNormalLaw_rtP.Gain_Gain_l0 * rtb_Sum_j4; + PitchNormalLaw_WashoutFilter(rtb_Gain5_gq, PitchNormalLaw_rtP.WashoutFilter_C1_j, rtu_In_time_dt, &rtb_Sum_j4, + &PitchNormalLaw_DWork.sf_WashoutFilter); + rtb_Y_aj = look1_binlxpw(*rtu_In_H_radio_ft, PitchNormalLaw_rtP.ScheduledGain_BreakpointsForDimension1_b, + PitchNormalLaw_rtP.ScheduledGain_Table_e, 3U); + if (rtb_Sum_j4 > PitchNormalLaw_rtP.SaturationSpoilers_UpperSat_m) { + rtb_Sum_j4 = PitchNormalLaw_rtP.SaturationSpoilers_UpperSat_m; + } else if (rtb_Sum_j4 < PitchNormalLaw_rtP.SaturationSpoilers_LowerSat_d) { + rtb_Sum_j4 = PitchNormalLaw_rtP.SaturationSpoilers_LowerSat_d; + } + + rtb_Sum_j4 = (((PitchNormalLaw_rtP.Gain3_Gain_n * rtb_Product1_ck + rtb_Y_cm) + rtb_uDLookupTable) + rtb_Divide_l) + + rtb_Sum_j4 * rtb_Y_aj; + rtb_uDLookupTable = (((PitchNormalLaw_rtP.Gain3_Gain * rtb_Divide + rtb_Product1_dm) + rtb_Divide_o) + + PitchNormalLaw_rtP.Gain_Gain_l * rtb_Divide_a0) + rtb_Product_kz; + if (rtb_uDLookupTable > PitchNormalLaw_rtP.Saturation_UpperSat_h) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation_UpperSat_h; + } else if (rtb_uDLookupTable < PitchNormalLaw_rtP.Saturation_LowerSat_o) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation_LowerSat_o; + } + + if (rtb_Sum_j4 > PitchNormalLaw_rtP.Saturation_UpperSat_a) { + rtb_Y_cm = PitchNormalLaw_rtP.Saturation_UpperSat_a; + } else if (rtb_Sum_j4 < PitchNormalLaw_rtP.Saturation_LowerSat_k) { + rtb_Y_cm = PitchNormalLaw_rtP.Saturation_LowerSat_k; + } else { + rtb_Y_cm = rtb_Sum_j4; + } + + PitchNormalLaw_VoterAttitudeProtection(rtb_uDLookupTable, rtb_Saturation3, rtb_Y_cm, &rtb_Sum_j4); + rtb_Y_cm = rtb_Sum_j4; + rtb_Sum_j4 = look1_binlxpw(*rtu_In_time_dt, PitchNormalLaw_rtP.ScheduledGain_BreakpointsForDimension1_d, + PitchNormalLaw_rtP.ScheduledGain_Table_hh, 4U); + rtb_Sum_j4 = rtb_Y_cm * rtb_Sum_j4 * PitchNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_Gain * *rtu_In_time_dt; + if (*rtu_In_in_flight > PitchNormalLaw_rtP.Switch_Threshold) { + rtb_Y_aj = *rtu_In_eta_deg; + } else { + rtb_Y_aj = rtb_Gain; + } + + rtb_OR = ((rtb_Y_b == 0.0) || (*rtu_In_tracking_mode_on)); + PitchNormalLaw_DWork.icLoad = (rtb_OR || PitchNormalLaw_DWork.icLoad); + if (PitchNormalLaw_DWork.icLoad) { + PitchNormalLaw_DWork.Delay_DSTATE_o = rtb_Y_aj - rtb_Sum_j4; + } + + PitchNormalLaw_DWork.Delay_DSTATE_o += rtb_Sum_j4; + if (PitchNormalLaw_DWork.Delay_DSTATE_o > PitchNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_UpperLimit) { + PitchNormalLaw_DWork.Delay_DSTATE_o = PitchNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_UpperLimit; + } else if (PitchNormalLaw_DWork.Delay_DSTATE_o < PitchNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_LowerLimit) { + PitchNormalLaw_DWork.Delay_DSTATE_o = PitchNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_LowerLimit; + } + + if (rtb_eta_trim_deg_should_freeze == PitchNormalLaw_rtP.CompareToConstant_const) { + rtb_Sum_j4 = PitchNormalLaw_rtP.Constant_Value; + } else { + rtb_Sum_j4 = PitchNormalLaw_DWork.Delay_DSTATE_o; + } + + rtb_Sum_j4 *= PitchNormalLaw_rtP.Gain_Gain_cy; + if (rtb_Sum_j4 > rtb_eta_trim_deg_rate_limit_up_deg_s) { + *rty_Out_eta_trim_dot_deg_s = rtb_eta_trim_deg_rate_limit_up_deg_s; + } else if (rtb_Sum_j4 < rtb_eta_trim_deg_rate_limit_lo_deg_s) { + *rty_Out_eta_trim_dot_deg_s = rtb_eta_trim_deg_rate_limit_lo_deg_s; + } else { + *rty_Out_eta_trim_dot_deg_s = rtb_Sum_j4; + } + + if (rtb_Y_b > PitchNormalLaw_rtP.Saturation_UpperSat_l) { + rtb_Sum_j4 = PitchNormalLaw_rtP.Saturation_UpperSat_l; + } else if (rtb_Y_b < PitchNormalLaw_rtP.Saturation_LowerSat_kp) { + rtb_Sum_j4 = PitchNormalLaw_rtP.Saturation_LowerSat_kp; + } else { + rtb_Sum_j4 = rtb_Y_b; + } + + rtb_Product1_dm = PitchNormalLaw_DWork.Delay_DSTATE_o * rtb_Sum_j4; + rtb_Divide = PitchNormalLaw_rtP.Constant_Value_o1 - rtb_Sum_j4; + PitchNormalLaw_RateLimiter_c(rtu_In_delta_eta_pos, PitchNormalLaw_rtP.RateLimiterVariableTs_up_na, + PitchNormalLaw_rtP.RateLimiterVariableTs_lo_i, rtu_In_time_dt, + PitchNormalLaw_rtP.RateLimiterVariableTs_InitialCondition_m, &rtb_Y_cm, &PitchNormalLaw_DWork.sf_RateLimiter_ct); + rtb_Sum_j4 = look2_binlxpw(*rtu_In_Theta_deg, *rtu_In_H_radio_ft, PitchNormalLaw_rtP.uDLookupTable_bp01Data_l, + PitchNormalLaw_rtP.uDLookupTable_bp02Data, PitchNormalLaw_rtP.uDLookupTable_tableData_e5, + PitchNormalLaw_rtP.uDLookupTable_maxIndex, 5U); + rtb_Y_aj = *rtu_In_tailstrike_protection_on; + if (rtb_Y_cm > PitchNormalLaw_rtP.Saturation3_UpperSat_l) { + rtb_eta_trim_deg_rate_limit_up_deg_s = PitchNormalLaw_rtP.Saturation3_UpperSat_l; + } else if (rtb_Y_cm < PitchNormalLaw_rtP.Saturation3_LowerSat_h) { + rtb_eta_trim_deg_rate_limit_up_deg_s = PitchNormalLaw_rtP.Saturation3_LowerSat_h; + } else { + rtb_eta_trim_deg_rate_limit_up_deg_s = rtb_Y_cm; + } + + rtb_Sum_j4 = look1_binlxpw(rtb_Y_aj * rtb_Sum_j4 * rtb_eta_trim_deg_rate_limit_up_deg_s + rtb_Y_cm, + PitchNormalLaw_rtP.PitchRateDemand_bp01Data, PitchNormalLaw_rtP.PitchRateDemand_tableData, 2U); + rtb_eta_trim_deg_rate_limit_up_deg_s = PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_Gain_j3 * rtb_Sum_j4; + rtb_Y_cm = (rtb_eta_trim_deg_rate_limit_up_deg_s - PitchNormalLaw_DWork.Delay_DSTATE_ej) / *rtu_In_time_dt; + rtb_Y_aj = *rtu_In_qk_deg_s - rtb_Sum_j4; + rtb_Divide_o = PitchNormalLaw_rtP.Gain_Gain_pt * rtb_Y_aj; + rtb_eta_trim_deg_rate_limit_lo_deg_s = PitchNormalLaw_rtP.Gain1_Gain_d * rtb_Y_aj * + PitchNormalLaw_rtP.DiscreteDerivativeVariableTs_Gain_g; + rtb_Y_aj = PitchNormalLaw_DWork.Delay_DSTATE_e4; + rtb_Divide_a0 = (rtb_eta_trim_deg_rate_limit_lo_deg_s - PitchNormalLaw_DWork.Delay_DSTATE_e4) / *rtu_In_time_dt; + rtb_Gain5_gq = PitchNormalLaw_rtP.Gain5_Gain_h * *rtu_In_qk_dot_deg_s2; + rtb_Gain5_gq += *rtu_In_qk_deg_s; + PitchNormalLaw_LagFilter_n(rtb_Gain5_gq, PitchNormalLaw_rtP.LagFilter_C1_k, rtu_In_time_dt, &rtb_Y_aj, + &PitchNormalLaw_DWork.sf_LagFilter_f); + rtb_Gain5_gq = PitchNormalLaw_rtP.Gain6_Gain_g * *rtu_In_qk_dot_deg_s2; + rtb_Sum_j4 = ((((rtb_Divide_o + rtb_Divide_a0) * PitchNormalLaw_rtP.Gain1_Gain_a + PitchNormalLaw_rtP.Gain3_Gain_e * + rtb_Y_cm) + (rtb_Y_aj - rtb_Sum_j4) * PitchNormalLaw_rtP.Gain4_Gain) + rtb_Gain5_gq) * + (PitchNormalLaw_rtP.Constant2_Value_k - rtb_Y_b) * PitchNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_Gain_j * + *rtu_In_time_dt; + rtb_eta_trim_deg_should_freeze = (*rtu_In_delta_eta_pos <= PitchNormalLaw_rtP.Constant_Value_o); + rtb_eta_trim_deg_should_freeze = (rtb_eta_trim_deg_should_freeze && (*rtu_In_on_ground)); + rtb_eta_trim_deg_should_freeze = (rtb_eta_trim_deg_should_freeze || (rtb_ManualSwitch == 0.0) || + (*rtu_In_tracking_mode_on)); + PitchNormalLaw_DWork.icLoad_p = (rtb_eta_trim_deg_should_freeze || PitchNormalLaw_DWork.icLoad_p); + if (PitchNormalLaw_DWork.icLoad_p) { + PitchNormalLaw_DWork.Delay_DSTATE_cl = PitchNormalLaw_rtP.Constant_Value_jk - rtb_Sum_j4; + } + + PitchNormalLaw_DWork.Delay_DSTATE_cl += rtb_Sum_j4; + if (PitchNormalLaw_DWork.Delay_DSTATE_cl > PitchNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_UpperLimit_p) { + PitchNormalLaw_DWork.Delay_DSTATE_cl = PitchNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_UpperLimit_p; + } else if (PitchNormalLaw_DWork.Delay_DSTATE_cl < PitchNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_LowerLimit_h) { + PitchNormalLaw_DWork.Delay_DSTATE_cl = PitchNormalLaw_rtP.DiscreteTimeIntegratorVariableTs_LowerLimit_h; + } + + if (*rtu_In_on_ground) { + if (rtb_Gain > PitchNormalLaw_rtP.Saturation_UpperSat_f) { + rtb_Sum_j4 = PitchNormalLaw_rtP.Saturation_UpperSat_f; + } else if (rtb_Gain < PitchNormalLaw_rtP.Saturation_LowerSat_p) { + rtb_Sum_j4 = PitchNormalLaw_rtP.Saturation_LowerSat_p; + } else { + rtb_Sum_j4 = rtb_Gain; + } + } else { + rtb_Sum_j4 = PitchNormalLaw_rtP.Constant1_Value_h; + } + + rtb_Y_b = PitchNormalLaw_DWork.Delay_DSTATE_cl + rtb_Sum_j4; + if (rtb_ManualSwitch > PitchNormalLaw_rtP.Saturation_UpperSat_m) { + rtb_Sum_j4 = PitchNormalLaw_rtP.Saturation_UpperSat_m; + } else if (rtb_ManualSwitch < PitchNormalLaw_rtP.Saturation_LowerSat_b) { + rtb_Sum_j4 = PitchNormalLaw_rtP.Saturation_LowerSat_b; + } else { + rtb_Sum_j4 = rtb_ManualSwitch; + } + + rtb_uDLookupTable = ((PitchNormalLaw_rtP.Constant_Value_h - rtb_Sum_j4) * rtb_Gain + rtb_Y_b * rtb_Sum_j4) * + rtb_Divide + rtb_Product1_dm; + if (rtb_uDLookupTable > PitchNormalLaw_rtP.Saturation_UpperSat_kp) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation_UpperSat_kp; + } else if (rtb_uDLookupTable < PitchNormalLaw_rtP.Saturation_LowerSat_a4) { + rtb_uDLookupTable = PitchNormalLaw_rtP.Saturation_LowerSat_a4; + } + + PitchNormalLaw_RateLimiter(rtb_uDLookupTable, PitchNormalLaw_rtP.RateLimitereta_up, + PitchNormalLaw_rtP.RateLimitereta_lo, rtu_In_time_dt, PitchNormalLaw_rtP.RateLimitereta_InitialCondition, + rty_Out_eta_deg, &PitchNormalLaw_DWork.sf_RateLimiter_b); + PitchNormalLaw_DWork.Delay_DSTATE_h = rtb_Loaddemand2_l; + PitchNormalLaw_DWork.Delay_DSTATE_n = rtb_Gain_px; + PitchNormalLaw_DWork.Delay_DSTATE_c = rtb_Gain_ot; + PitchNormalLaw_DWork.Delay_DSTATE_l = rtb_Divide_an; + PitchNormalLaw_DWork.Delay_DSTATE_k = rtb_Gain1_ft; + PitchNormalLaw_DWork.Delay_DSTATE_d = rtb_Gain_bs; + PitchNormalLaw_DWork.Delay_DSTATE_f = rtb_Divide_m; + PitchNormalLaw_DWork.Delay_DSTATE_g = rtb_Sum10; + PitchNormalLaw_DWork.Delay_DSTATE_j = rtb_y; + PitchNormalLaw_DWork.Delay_DSTATE_ca = rtb_Divide_o2; + PitchNormalLaw_DWork.Delay_DSTATE_kd = rtb_alpha_err_gain; + PitchNormalLaw_DWork.Delay_DSTATE_ku = rtb_qk_gain; + PitchNormalLaw_DWork.Delay_DSTATE_gl = rtb_qk_dot_gain; + PitchNormalLaw_DWork.Delay_DSTATE_m = rtb_Gain_ll; + PitchNormalLaw_DWork.Delay_DSTATE_k2 = rtb_Divide_b4; + PitchNormalLaw_DWork.Delay_DSTATE_jh = rtb_Cos; + PitchNormalLaw_DWork.Delay_DSTATE_dy = rtb_Y_p; + PitchNormalLaw_DWork.Delay_DSTATE_e5 = rtb_Y_n; + PitchNormalLaw_DWork.Delay_DSTATE_gz = rtb_Y_ll; + PitchNormalLaw_DWork.Delay_DSTATE_lf = rtb_Y_am; + PitchNormalLaw_DWork.Delay_DSTATE_ho = rtb_Divide_cq; + PitchNormalLaw_DWork.Delay_DSTATE_ds = rtb_Loaddemand2; + PitchNormalLaw_DWork.Delay_DSTATE_jt = rtb_Gain_g; + PitchNormalLaw_DWork.icLoad = false; + PitchNormalLaw_DWork.Delay_DSTATE_ej = rtb_eta_trim_deg_rate_limit_up_deg_s; + PitchNormalLaw_DWork.Delay_DSTATE_e4 = rtb_eta_trim_deg_rate_limit_lo_deg_s; + PitchNormalLaw_DWork.icLoad_p = false; +} + +PitchNormalLaw::PitchNormalLaw(): + PitchNormalLaw_B(), + PitchNormalLaw_DWork() +{ +} + +PitchNormalLaw::~PitchNormalLaw() +{ +} diff --git a/src/fbw/src/model/PitchNormalLaw.h b/src/fbw/src/model/PitchNormalLaw.h new file mode 100644 index 000000000000..b1e7aa87c9e4 --- /dev/null +++ b/src/fbw/src/model/PitchNormalLaw.h @@ -0,0 +1,521 @@ +#ifndef RTW_HEADER_PitchNormalLaw_h_ +#define RTW_HEADER_PitchNormalLaw_h_ +#include "rtwtypes.h" +#include "PitchNormalLaw_types.h" +#include + +class PitchNormalLaw final +{ + public: + struct rtDW_LagFilter_PitchNormalLaw_T { + real_T pY; + real_T pU; + boolean_T pY_not_empty; + boolean_T pU_not_empty; + }; + + struct rtDW_RateLimiter_PitchNormalLaw_T { + real_T pY; + boolean_T pY_not_empty; + }; + + struct rtDW_eta_trim_limit_lofreeze_PitchNormalLaw_T { + real_T frozen_eta_trim; + boolean_T frozen_eta_trim_not_empty; + }; + + struct rtDW_LagFilter_PitchNormalLaw_d_T { + real_T pY; + real_T pU; + boolean_T pY_not_empty; + boolean_T pU_not_empty; + }; + + struct rtDW_WashoutFilter_PitchNormalLaw_T { + real_T pY; + real_T pU; + boolean_T pY_not_empty; + boolean_T pU_not_empty; + }; + + struct rtDW_RateLimiter_PitchNormalLaw_o_T { + real_T pY; + boolean_T pY_not_empty; + }; + + struct BlockIO_PitchNormalLaw_T { + real_T flare_Theta_c_deg; + real_T flare_Theta_c_rate_deg_s; + }; + + struct D_Work_PitchNormalLaw_T { + real_T Delay_DSTATE; + real_T Delay_DSTATE_h; + real_T Delay_DSTATE_n; + real_T Delay_DSTATE_c; + real_T Delay_DSTATE_l; + real_T Delay_DSTATE_k; + real_T Delay_DSTATE_d; + real_T Delay_DSTATE_f; + real_T Delay_DSTATE_g; + real_T Delay1_DSTATE; + real_T Delay_DSTATE_j; + real_T Delay_DSTATE_ca; + real_T Delay1_DSTATE_i; + real_T Delay_DSTATE_e; + real_T Delay_DSTATE_kd; + real_T Delay_DSTATE_b; + real_T Delay_DSTATE_ku; + real_T Delay_DSTATE_gl; + real_T Delay1_DSTATE_l; + real_T Delay_DSTATE_m; + real_T Delay_DSTATE_k2; + real_T Delay1_DSTATE_n; + real_T Delay_DSTATE_mz; + real_T Delay_DSTATE_jh; + real_T Delay_DSTATE_dy; + real_T Delay_DSTATE_e5; + real_T Delay_DSTATE_gz; + real_T Delay_DSTATE_lf; + real_T Delay_DSTATE_ho; + real_T Delay_DSTATE_ds; + real_T Delay_DSTATE_jt; + real_T Delay_DSTATE_o; + real_T Delay_DSTATE_ej; + real_T Delay_DSTATE_e4; + real_T Delay_DSTATE_cl; + uint8_T is_active_c6_PitchNormalLaw; + uint8_T is_c6_PitchNormalLaw; + uint8_T is_active_c7_PitchNormalLaw; + uint8_T is_c7_PitchNormalLaw; + uint8_T is_active_c8_PitchNormalLaw; + uint8_T is_c8_PitchNormalLaw; + uint8_T is_active_c9_PitchNormalLaw; + uint8_T is_c9_PitchNormalLaw; + uint8_T is_active_c2_PitchNormalLaw; + uint8_T is_c2_PitchNormalLaw; + boolean_T icLoad; + boolean_T icLoad_p; + rtDW_RateLimiter_PitchNormalLaw_T sf_RateLimiter_b; + rtDW_RateLimiter_PitchNormalLaw_o_T sf_RateLimiter_ct; + rtDW_LagFilter_PitchNormalLaw_d_T sf_LagFilter_f; + rtDW_WashoutFilter_PitchNormalLaw_T sf_WashoutFilter_h; + rtDW_RateLimiter_PitchNormalLaw_T sf_RateLimiter_l; + rtDW_RateLimiter_PitchNormalLaw_o_T sf_RateLimiter_nx; + rtDW_RateLimiter_PitchNormalLaw_o_T sf_RateLimiter_d; + rtDW_RateLimiter_PitchNormalLaw_o_T sf_RateLimiter_c2; + rtDW_RateLimiter_PitchNormalLaw_T sf_RateLimiter_o; + rtDW_LagFilter_PitchNormalLaw_T sf_LagFilter_m; + rtDW_WashoutFilter_PitchNormalLaw_T sf_WashoutFilter_c; + rtDW_LagFilter_PitchNormalLaw_d_T sf_LagFilter_g3; + rtDW_WashoutFilter_PitchNormalLaw_T sf_WashoutFilter_d; + rtDW_LagFilter_PitchNormalLaw_d_T sf_LagFilter_g; + rtDW_WashoutFilter_PitchNormalLaw_T sf_WashoutFilter_l; + rtDW_LagFilter_PitchNormalLaw_d_T sf_LagFilter_i; + rtDW_WashoutFilter_PitchNormalLaw_T sf_WashoutFilter_k; + rtDW_LagFilter_PitchNormalLaw_d_T sf_LagFilter_k; + rtDW_WashoutFilter_PitchNormalLaw_T sf_WashoutFilter; + rtDW_LagFilter_PitchNormalLaw_d_T sf_LagFilter_n; + rtDW_eta_trim_limit_lofreeze_PitchNormalLaw_T sf_eta_trim_limit_upfreeze; + rtDW_eta_trim_limit_lofreeze_PitchNormalLaw_T sf_eta_trim_limit_lofreeze; + rtDW_RateLimiter_PitchNormalLaw_T sf_RateLimiter_n; + rtDW_RateLimiter_PitchNormalLaw_T sf_RateLimiter_c; + rtDW_RateLimiter_PitchNormalLaw_T sf_RateLimiter_p; + rtDW_RateLimiter_PitchNormalLaw_T sf_RateLimiter; + rtDW_LagFilter_PitchNormalLaw_T sf_LagFilter; + }; + + struct Parameters_PitchNormalLaw_T { + real_T ScheduledGain_BreakpointsForDimension1[4]; + real_T ScheduledGain_BreakpointsForDimension1_n[4]; + real_T ScheduledGain_BreakpointsForDimension1_h[7]; + real_T ScheduledGain_BreakpointsForDimension1_c[4]; + real_T ScheduledGain_BreakpointsForDimension1_f[4]; + real_T ScheduledGain_BreakpointsForDimension1_b[4]; + real_T ScheduledGain_BreakpointsForDimension1_d[5]; + real_T LagFilter_C1; + real_T LagFilter_C1_i; + real_T WashoutFilter_C1; + real_T LagFilter_C1_p; + real_T WashoutFilter_C1_n; + real_T LagFilter1_C1; + real_T WashoutFilter_C1_b; + real_T Subsystem1_C1; + real_T Subsystem3_C1; + real_T Subsystem2_C1; + real_T Subsystem_C1; + real_T LagFilter_C1_pt; + real_T WashoutFilter_C1_l; + real_T LagFilter_C1_l; + real_T WashoutFilter_C1_h; + real_T LagFilter_C1_f; + real_T WashoutFilter_C1_j; + real_T LagFilter_C1_k; + real_T DiscreteDerivativeVariableTs1_Gain; + real_T DiscreteDerivativeVariableTs_Gain; + real_T DiscreteDerivativeVariableTs2_Gain; + real_T DiscreteDerivativeVariableTs1_Gain_i; + real_T DiscreteDerivativeVariableTs_Gain_j; + real_T DiscreteDerivativeVariableTs2_Gain_e; + real_T Subsystem1_Gain; + real_T Subsystem3_Gain; + real_T DiscreteDerivativeVariableTs1_Gain_m; + real_T Subsystem2_Gain; + real_T Subsystem_Gain; + real_T DiscreteDerivativeVariableTs_Gain_b; + real_T DiscreteDerivativeVariableTs2_Gain_c; + real_T DiscreteDerivativeVariableTs1_Gain_c; + real_T DiscreteDerivativeVariableTs_Gain_p; + real_T DiscreteDerivativeVariableTs2_Gain_a; + real_T DiscreteDerivativeVariableTs1_Gain_k; + real_T DiscreteDerivativeVariableTs_Gain_c; + real_T DiscreteDerivativeVariableTs2_Gain_p; + real_T DiscreteTimeIntegratorVariableTs_Gain; + real_T DiscreteDerivativeVariableTs_Gain_j3; + real_T DiscreteDerivativeVariableTs_Gain_g; + real_T DiscreteTimeIntegratorVariableTs_Gain_j; + real_T RateLimiterVariableTs_InitialCondition; + real_T RateLimiterVariableTs1_InitialCondition; + real_T RateLimiterVariableTs2_InitialCondition; + real_T RateLimiterVariableTs3_InitialCondition; + real_T RateLimiterDynamicVariableTs_InitialCondition; + real_T RateLimiterVariableTs6_InitialCondition; + real_T DiscreteDerivativeVariableTs1_InitialCondition; + real_T DiscreteDerivativeVariableTs_InitialCondition; + real_T DiscreteDerivativeVariableTs2_InitialCondition; + real_T DiscreteDerivativeVariableTs1_InitialCondition_l; + real_T DiscreteDerivativeVariableTs_InitialCondition_o; + real_T DiscreteDerivativeVariableTs2_InitialCondition_d; + real_T RateLimiterVariableTs2_InitialCondition_f; + real_T DiscreteDerivativeVariableTs2_InitialCondition_h; + real_T DiscreteDerivativeVariableTs2_InitialCondition_m; + real_T RateLimiterVariableTs5_InitialCondition; + real_T DiscreteDerivativeVariableTs1_InitialCondition_j; + real_T RateLimiterVariableTs1_InitialCondition_l; + real_T RateLimiterVariableTs_InitialCondition_o; + real_T RateLimiterVariableTs3_InitialCondition_e; + real_T DiscreteDerivativeVariableTs2_InitialCondition_f; + real_T DiscreteDerivativeVariableTs2_InitialCondition_e; + real_T RateLimiterVariableTs4_InitialCondition; + real_T RateLimiterVariableTs6_InitialCondition_f; + real_T DiscreteDerivativeVariableTs_InitialCondition_a; + real_T DiscreteDerivativeVariableTs2_InitialCondition_di; + real_T DiscreteDerivativeVariableTs1_InitialCondition_f; + real_T DiscreteDerivativeVariableTs_InitialCondition_g; + real_T DiscreteDerivativeVariableTs2_InitialCondition_c; + real_T DiscreteDerivativeVariableTs1_InitialCondition_g; + real_T DiscreteDerivativeVariableTs_InitialCondition_h; + real_T DiscreteDerivativeVariableTs2_InitialCondition_a; + real_T RateLimiterVariableTs_InitialCondition_m; + real_T DiscreteDerivativeVariableTs_InitialCondition_b; + real_T DiscreteDerivativeVariableTs_InitialCondition_p; + real_T RateLimitereta_InitialCondition; + real_T DiscreteTimeIntegratorVariableTs_LowerLimit; + real_T DiscreteTimeIntegratorVariableTs_LowerLimit_h; + real_T ScheduledGain_Table[4]; + real_T ScheduledGain_Table_b[4]; + real_T ScheduledGain_Table_j[7]; + real_T ScheduledGain_Table_g[4]; + real_T ScheduledGain_Table_h[4]; + real_T ScheduledGain_Table_e[4]; + real_T ScheduledGain_Table_hh[5]; + real_T DiscreteTimeIntegratorVariableTs_UpperLimit; + real_T DiscreteTimeIntegratorVariableTs_UpperLimit_p; + real_T RateLimiterVariableTs_lo; + real_T RateLimiterVariableTs1_lo; + real_T RateLimiterVariableTs2_lo; + real_T RateLimiterVariableTs3_lo; + real_T RateLimiterVariableTs6_lo; + real_T RateLimiterVariableTs2_lo_k; + real_T RateLimiterVariableTs5_lo; + real_T RateLimiterVariableTs1_lo_g; + real_T RateLimiterVariableTs_lo_c; + real_T RateLimiterVariableTs3_lo_b; + real_T RateLimiterVariableTs4_lo; + real_T RateLimiterVariableTs6_lo_p; + real_T RateLimiterVariableTs_lo_i; + real_T RateLimitereta_lo; + real_T RateLimiterVariableTs_up; + real_T RateLimiterVariableTs1_up; + real_T RateLimiterVariableTs2_up; + real_T RateLimiterVariableTs3_up; + real_T RateLimiterVariableTs6_up; + real_T RateLimiterVariableTs2_up_m; + real_T RateLimiterVariableTs5_up; + real_T RateLimiterVariableTs1_up_d; + real_T RateLimiterVariableTs_up_n; + real_T RateLimiterVariableTs3_up_i; + real_T RateLimiterVariableTs4_up; + real_T RateLimiterVariableTs6_up_n; + real_T RateLimiterVariableTs_up_na; + real_T RateLimitereta_up; + boolean_T CompareToConstant_const; + real_T Constant2_Value; + real_T Constant3_Value; + real_T Constant_Value; + real_T qk_dot_gain1_Gain; + real_T qk_gain_HSP_Gain; + real_T v_dot_gain_HSP_Gain; + real_T Gain6_Gain; + real_T precontrol_gain_HSP_Gain; + real_T HSP_gain_Gain; + real_T Saturation4_UpperSat; + real_T Saturation4_LowerSat; + real_T Saturation8_UpperSat; + real_T Saturation8_LowerSat; + real_T Constant1_Value; + real_T Loaddemand_tableData[3]; + real_T Loaddemand_bp01Data[3]; + real_T Constant_Value_l; + real_T Constant_Value_g; + real_T Saturation_UpperSat; + real_T Saturation_LowerSat; + real_T Gain_Gain; + real_T Saturation_UpperSat_g; + real_T Saturation_LowerSat_d; + real_T Constant_Value_m; + real_T Saturation_UpperSat_e; + real_T Saturation_LowerSat_m; + real_T Switch2_Threshold; + real_T Saturation_UpperSat_f; + real_T Saturation_LowerSat_p; + real_T Constant1_Value_h; + real_T Constant_Value_o; + real_T Constant1_Value_k; + real_T Constant_Value_p; + real_T Saturation_UpperSat_c; + real_T Saturation_LowerSat_n; + real_T Saturation1_UpperSat; + real_T Saturation1_LowerSat; + real_T Gain_Gain_a; + real_T Constant_Value_j; + real_T Constant_Value_c; + real_T Constant_Value_a; + real_T Constant_Value_mx; + real_T Constant_Value_mo; + real_T Gain2_Gain; + real_T Gain1_Gain; + real_T Saturation1_UpperSat_i; + real_T Saturation1_LowerSat_h; + real_T Loaddemand1_tableData[3]; + real_T Loaddemand1_bp01Data[3]; + real_T Gain1_Gain_c; + real_T Gain1_Gain_l; + real_T uDLookupTable_tableData[7]; + real_T uDLookupTable_bp01Data[7]; + real_T Saturation3_UpperSat; + real_T Saturation3_LowerSat; + real_T Gain5_Gain; + real_T Bias_Bias; + real_T Gain1_Gain_e; + real_T Vm_currentms_Value; + real_T Gain_Gain_c; + real_T PLUT_tableData[2]; + real_T PLUT_bp01Data[2]; + real_T Gain3_Gain; + real_T DLUT_tableData[2]; + real_T DLUT_bp01Data[2]; + real_T SaturationV_dot_UpperSat; + real_T SaturationV_dot_LowerSat; + real_T Gain_Gain_l; + real_T SaturationSpoilers_UpperSat; + real_T SaturationSpoilers_LowerSat; + real_T Saturation_UpperSat_h; + real_T Saturation_LowerSat_o; + real_T Gain3_Gain_m; + real_T Gain1_Gain_o; + real_T Vm_currentms_Value_e; + real_T Gain_Gain_al; + real_T uDLookupTable_tableData_e[7]; + real_T uDLookupTable_bp01Data_o[7]; + real_T Saturation3_UpperSat_a; + real_T Saturation3_LowerSat_l; + real_T Gain5_Gain_d; + real_T Bias_Bias_a; + real_T PLUT_tableData_b[2]; + real_T PLUT_bp01Data_b[2]; + real_T DLUT_tableData_p[2]; + real_T DLUT_bp01Data_h[2]; + real_T SaturationV_dot_UpperSat_j; + real_T SaturationV_dot_LowerSat_e; + real_T Gain_Gain_j; + real_T SaturationSpoilers_UpperSat_g; + real_T SaturationSpoilers_LowerSat_j; + real_T Saturation_UpperSat_hc; + real_T Saturation_LowerSat_a; + real_T Delay_InitialCondition; + real_T Constant_Value_f; + real_T Delay1_InitialCondition; + real_T precontrol_gain_Gain; + real_T alpha_err_gain_Gain; + real_T Delay_InitialCondition_e; + real_T Constant_Value_b; + real_T Delay1_InitialCondition_g; + real_T v_dot_gain_Gain; + real_T qk_gain_Gain; + real_T qk_dot_gain_Gain; + real_T Saturation3_UpperSat_f; + real_T Saturation3_LowerSat_c; + real_T Saturation_UpperSat_eo; + real_T Saturation_LowerSat_h; + real_T Constant_Value_fe; + real_T Gain3_Gain_c; + real_T Gain1_Gain_en; + real_T Vm_currentms_Value_h; + real_T Gain_Gain_b; + real_T uDLookupTable_tableData_h[7]; + real_T uDLookupTable_bp01Data_b[7]; + real_T Saturation3_UpperSat_b; + real_T Saturation3_LowerSat_e; + real_T Gain5_Gain_e; + real_T Bias_Bias_f; + real_T Delay_InitialCondition_c; + real_T Constant_Value_ja; + real_T Delay1_InitialCondition_gf; + real_T Delay_InitialCondition_h; + real_T Constant_Value_jj; + real_T Delay1_InitialCondition_e; + real_T Saturation_UpperSat_f1; + real_T Saturation_LowerSat_o1; + real_T Gain1_Gain_lm; + real_T PLUT_tableData_k[2]; + real_T PLUT_bp01Data_f[2]; + real_T DLUT_tableData_a[2]; + real_T DLUT_bp01Data_m[2]; + real_T SaturationV_dot_UpperSat_b; + real_T SaturationV_dot_LowerSat_m; + real_T Gain_Gain_f; + real_T SaturationSpoilers_UpperSat_o; + real_T SaturationSpoilers_LowerSat_jl; + real_T Saturation_UpperSat_k; + real_T Saturation_LowerSat_p1; + real_T Gain3_Gain_b; + real_T Gain1_Gain_b; + real_T Vm_currentms_Value_p; + real_T Gain_Gain_p; + real_T uDLookupTable_tableData_p[7]; + real_T uDLookupTable_bp01Data_a[7]; + real_T Saturation3_UpperSat_n; + real_T Saturation3_LowerSat_a; + real_T Gain5_Gain_n; + real_T Bias_Bias_ai; + real_T PLUT_tableData_o[2]; + real_T PLUT_bp01Data_a[2]; + real_T DLUT_tableData_e[2]; + real_T DLUT_bp01Data_k[2]; + real_T SaturationV_dot_UpperSat_m; + real_T SaturationV_dot_LowerSat_ek; + real_T Gain_Gain_k; + real_T SaturationSpoilers_UpperSat_h; + real_T SaturationSpoilers_LowerSat_l; + real_T Saturation_UpperSat_j; + real_T Saturation_LowerSat_dw; + real_T Gain3_Gain_n; + real_T Gain1_Gain_lk; + real_T Vm_currentms_Value_b; + real_T Gain_Gain_jq; + real_T uDLookupTable_tableData_a[7]; + real_T uDLookupTable_bp01Data_m[7]; + real_T Saturation3_UpperSat_e; + real_T Saturation3_LowerSat_k; + real_T Gain5_Gain_m; + real_T Bias_Bias_m; + real_T Theta_max3_Value; + real_T Gain3_Gain_g; + real_T Saturation2_UpperSat; + real_T Saturation2_LowerSat; + real_T Loaddemand2_tableData[3]; + real_T Loaddemand2_bp01Data[3]; + real_T PLUT_tableData_g[2]; + real_T PLUT_bp01Data_e[2]; + real_T DLUT_tableData_l[2]; + real_T DLUT_bp01Data_hw[2]; + real_T SaturationV_dot_UpperSat_j2; + real_T SaturationV_dot_LowerSat_n; + real_T Gain_Gain_l0; + real_T SaturationSpoilers_UpperSat_m; + real_T SaturationSpoilers_LowerSat_d; + real_T Saturation_UpperSat_a; + real_T Saturation_LowerSat_k; + real_T Switch_Threshold; + real_T Gain_Gain_cy; + real_T Saturation_UpperSat_l; + real_T Saturation_LowerSat_kp; + real_T Constant_Value_o1; + real_T Constant2_Value_k; + real_T uDLookupTable_tableData_e5[25]; + real_T uDLookupTable_bp01Data_l[5]; + real_T uDLookupTable_bp02Data[5]; + real_T Saturation3_UpperSat_l; + real_T Saturation3_LowerSat_h; + real_T PitchRateDemand_tableData[3]; + real_T PitchRateDemand_bp01Data[3]; + real_T Gain3_Gain_e; + real_T Gain_Gain_pt; + real_T Gain1_Gain_d; + real_T Gain1_Gain_a; + real_T Gain5_Gain_h; + real_T Gain4_Gain; + real_T Gain6_Gain_g; + real_T Constant_Value_jk; + real_T Saturation_UpperSat_m; + real_T Saturation_LowerSat_b; + real_T Constant_Value_h; + real_T Saturation_UpperSat_kp; + real_T Saturation_LowerSat_a4; + uint32_T uDLookupTable_maxIndex[2]; + uint8_T ManualSwitch_CurrentSetting; + uint8_T ManualSwitch1_CurrentSetting; + }; + + void init(); + PitchNormalLaw(PitchNormalLaw const&) = delete; + PitchNormalLaw& operator= (PitchNormalLaw const&) & = delete; + PitchNormalLaw(PitchNormalLaw &&) = delete; + PitchNormalLaw& operator= (PitchNormalLaw &&) = delete; + void step(const real_T *rtu_In_time_dt, const real_T *rtu_In_nz_g, const real_T *rtu_In_Theta_deg, const real_T + *rtu_In_Phi_deg, const real_T *rtu_In_qk_deg_s, const real_T *rtu_In_qk_dot_deg_s2, const real_T + *rtu_In_eta_deg, const real_T *rtu_In_eta_trim_deg, const real_T *rtu_In_alpha_deg, const real_T + *rtu_In_V_ias_kn, const real_T *rtu_In_V_tas_kn, const real_T *rtu_In_H_radio_ft, const real_T + *rtu_In_flaps_handle_index, const real_T *rtu_In_spoilers_left_pos, const real_T *rtu_In_spoilers_right_pos, + const real_T *rtu_In_thrust_lever_1_pos, const real_T *rtu_In_thrust_lever_2_pos, const boolean_T + *rtu_In_tailstrike_protection_on, const real_T *rtu_In_VLS_kn, const real_T *rtu_In_delta_eta_pos, const + boolean_T *rtu_In_on_ground, const real_T *rtu_In_in_flight, const boolean_T *rtu_In_tracking_mode_on, const + boolean_T *rtu_In_high_aoa_prot_active, const boolean_T *rtu_In_high_speed_prot_active, const real_T + *rtu_In_alpha_prot, const real_T *rtu_In_alpha_max, const real_T *rtu_In_high_speed_prot_high_kn, const + real_T *rtu_In_high_speed_prot_low_kn, const real_T *rtu_In_ap_theta_c_deg, const boolean_T + *rtu_In_any_ap_engaged, real_T *rty_Out_eta_deg, real_T *rty_Out_eta_trim_dot_deg_s, real_T + *rty_Out_eta_trim_limit_lo, real_T *rty_Out_eta_trim_limit_up); + void reset(); + PitchNormalLaw(); + ~PitchNormalLaw(); + private: + BlockIO_PitchNormalLaw_T PitchNormalLaw_B; + D_Work_PitchNormalLaw_T PitchNormalLaw_DWork; + static Parameters_PitchNormalLaw_T PitchNormalLaw_rtP; + static void PitchNormalLaw_LagFilter_Reset(rtDW_LagFilter_PitchNormalLaw_T *localDW); + static void PitchNormalLaw_LagFilter(const real_T *rtu_U, real_T rtu_C1, const real_T *rtu_dt, real_T *rty_Y, + rtDW_LagFilter_PitchNormalLaw_T *localDW); + static void PitchNormalLaw_RateLimiter_Reset(rtDW_RateLimiter_PitchNormalLaw_T *localDW); + static void PitchNormalLaw_RateLimiter(real_T rtu_u, real_T rtu_up, real_T rtu_lo, const real_T *rtu_Ts, real_T + rtu_init, real_T *rty_Y, rtDW_RateLimiter_PitchNormalLaw_T *localDW); + static void PitchNormalLaw_eta_trim_limit_lofreeze_Reset(rtDW_eta_trim_limit_lofreeze_PitchNormalLaw_T *localDW); + static void PitchNormalLaw_eta_trim_limit_lofreeze(const real_T *rtu_eta_trim, const boolean_T *rtu_trigger, real_T + *rty_y, rtDW_eta_trim_limit_lofreeze_PitchNormalLaw_T *localDW); + static void PitchNormalLaw_LagFilter_i_Reset(rtDW_LagFilter_PitchNormalLaw_d_T *localDW); + static void PitchNormalLaw_LagFilter_n(real_T rtu_U, real_T rtu_C1, const real_T *rtu_dt, real_T *rty_Y, + rtDW_LagFilter_PitchNormalLaw_d_T *localDW); + static void PitchNormalLaw_WashoutFilter_Reset(rtDW_WashoutFilter_PitchNormalLaw_T *localDW); + static void PitchNormalLaw_WashoutFilter(real_T rtu_U, real_T rtu_C1, const real_T *rtu_dt, real_T *rty_Y, + rtDW_WashoutFilter_PitchNormalLaw_T *localDW); + static void PitchNormalLaw_RateLimiter_l_Reset(rtDW_RateLimiter_PitchNormalLaw_o_T *localDW); + static void PitchNormalLaw_RateLimiter_c(const real_T *rtu_u, real_T rtu_up, real_T rtu_lo, const real_T *rtu_Ts, + real_T rtu_init, real_T *rty_Y, rtDW_RateLimiter_PitchNormalLaw_o_T *localDW); + static void PitchNormalLaw_VoterAttitudeProtection(real_T rtu_input, real_T rtu_input_l, real_T rtu_input_o, real_T + *rty_vote); +}; + +extern PitchNormalLaw::Parameters_PitchNormalLaw_T PitchNormalLaw_rtP; + +#endif + diff --git a/src/fbw/src/model/PitchNormalLaw_private.h b/src/fbw/src/model/PitchNormalLaw_private.h new file mode 100644 index 000000000000..80bc5498d851 --- /dev/null +++ b/src/fbw/src/model/PitchNormalLaw_private.h @@ -0,0 +1,5 @@ +#ifndef RTW_HEADER_PitchNormalLaw_private_h_ +#define RTW_HEADER_PitchNormalLaw_private_h_ +#include "rtwtypes.h" +#endif + diff --git a/src/fbw/src/model/PitchNormalLaw_types.h b/src/fbw/src/model/PitchNormalLaw_types.h new file mode 100644 index 000000000000..52edbf2fd858 --- /dev/null +++ b/src/fbw/src/model/PitchNormalLaw_types.h @@ -0,0 +1,161 @@ +#ifndef RTW_HEADER_PitchNormalLaw_types_h_ +#define RTW_HEADER_PitchNormalLaw_types_h_ +#include "rtwtypes.h" + +#ifndef DEFINED_TYPEDEF_FOR_base_time_ +#define DEFINED_TYPEDEF_FOR_base_time_ + +struct base_time +{ + real_T dt; + real_T simulation_time; + real_T monotonic_time; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_pitch_normal_input_ +#define DEFINED_TYPEDEF_FOR_pitch_normal_input_ + +struct pitch_normal_input +{ + base_time time; + real_T nz_g; + real_T Theta_deg; + real_T Phi_deg; + real_T qk_deg_s; + real_T qk_dot_deg_s2; + real_T eta_deg; + real_T eta_trim_deg; + real_T alpha_deg; + real_T V_ias_kn; + real_T V_tas_kn; + real_T H_radio_ft; + real_T CG_percent_MAC; + real_T total_weight_kg; + real_T flaps_handle_index; + real_T spoilers_left_pos; + real_T spoilers_right_pos; + real_T thrust_lever_1_pos; + real_T thrust_lever_2_pos; + boolean_T tailstrike_protection_on; + real_T VLS_kn; + real_T delta_eta_pos; + boolean_T on_ground; + real_T in_flight; + boolean_T tracking_mode_on; + boolean_T high_aoa_prot_active; + boolean_T high_speed_prot_active; + real_T alpha_prot; + real_T alpha_max; + real_T high_speed_prot_high_kn; + real_T high_speed_prot_low_kn; + real_T ap_theta_c_deg; + boolean_T any_ap_engaged; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_data_computed_ +#define DEFINED_TYPEDEF_FOR_base_pitch_data_computed_ + +struct base_pitch_data_computed +{ + real_T eta_trim_deg_limit_lo; + real_T eta_trim_deg_limit_up; + real_T delta_eta_deg; + real_T in_flight; + real_T in_rotation; + real_T in_flare; + real_T in_flight_gain; + real_T in_rotation_gain; + real_T nz_limit_up_g; + real_T nz_limit_lo_g; + boolean_T eta_trim_deg_should_freeze; + boolean_T eta_trim_deg_reset; + real_T eta_trim_deg_reset_deg; + boolean_T eta_trim_deg_should_write; + real_T eta_trim_deg_rate_limit_up_deg_s; + real_T eta_trim_deg_rate_limit_lo_deg_s; + real_T flare_Theta_deg; + real_T flare_Theta_c_deg; + real_T flare_Theta_c_rate_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_rotation_ +#define DEFINED_TYPEDEF_FOR_base_pitch_rotation_ + +struct base_pitch_rotation +{ + real_T qk_c_deg_s; + real_T eta_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_normal_ +#define DEFINED_TYPEDEF_FOR_base_pitch_normal_ + +struct base_pitch_normal +{ + real_T nz_c_g; + real_T Cstar_g; + real_T protection_alpha_c_deg; + real_T protection_V_c_kn; + real_T eta_dot_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_law_output_ +#define DEFINED_TYPEDEF_FOR_base_pitch_law_output_ + +struct base_pitch_law_output +{ + real_T eta_dot_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_integrated_ +#define DEFINED_TYPEDEF_FOR_base_pitch_integrated_ + +struct base_pitch_integrated +{ + real_T eta_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_output_ +#define DEFINED_TYPEDEF_FOR_base_pitch_output_ + +struct base_pitch_output +{ + real_T eta_deg; + real_T eta_trim_dot_deg_s; + real_T eta_trim_limit_lo; + real_T eta_trim_limit_up; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_pitch_normal_output_ +#define DEFINED_TYPEDEF_FOR_pitch_normal_output_ + +struct pitch_normal_output +{ + pitch_normal_input input; + base_pitch_data_computed data_computed; + base_pitch_rotation law_rotation; + base_pitch_normal law_normal; + base_pitch_law_output vote; + base_pitch_integrated integrated; + base_pitch_output output; +}; + +#endif +#endif + diff --git a/src/fbw/src/model/SecComputer.cpp b/src/fbw/src/model/SecComputer.cpp new file mode 100644 index 000000000000..7146ff27c940 --- /dev/null +++ b/src/fbw/src/model/SecComputer.cpp @@ -0,0 +1,1433 @@ +#include "SecComputer.h" +#include "SecComputer_types.h" +#include "rtwtypes.h" +#include +#include "look1_binlxpw.h" +#include "LateralDirectLaw.h" +#include "PitchAlternateLaw.h" +#include "PitchDirectLaw.h" + +const uint8_T SecComputer_IN_InAir{ 1U }; + +const uint8_T SecComputer_IN_NO_ACTIVE_CHILD{ 0U }; + +const uint8_T SecComputer_IN_OnGround{ 2U }; + +const uint8_T SecComputer_IN_Flight{ 1U }; + +const uint8_T SecComputer_IN_FlightToGroundTransition{ 2U }; + +const uint8_T SecComputer_IN_Ground{ 3U }; + +const real_T SecComputer_RGND{ 0.0 }; + +void SecComputer::SecComputer_MATLABFunction(const base_arinc_429 *rtu_u, real_T rtu_bit, uint32_T *rty_y) +{ + real32_T tmp; + uint32_T a; + tmp = std::round(rtu_u->Data); + if (tmp < 4.2949673E+9F) { + if (tmp >= 0.0F) { + a = static_cast(tmp); + } else { + a = 0U; + } + } else { + a = MAX_uint32_T; + } + + if (-(rtu_bit - 1.0) >= 0.0) { + if (-(rtu_bit - 1.0) <= 31.0) { + a <<= static_cast(-(rtu_bit - 1.0)); + } else { + a = 0U; + } + } else if (-(rtu_bit - 1.0) >= -31.0) { + a >>= static_cast(rtu_bit - 1.0); + } else { + a = 0U; + } + + *rty_y = a & 1U; +} + +void SecComputer::SecComputer_RateLimiter_Reset(rtDW_RateLimiter_SecComputer_T *localDW) +{ + localDW->pY_not_empty = false; +} + +void SecComputer::SecComputer_RateLimiter(real_T rtu_u, real_T rtu_up, real_T rtu_lo, real_T rtu_Ts, real_T rtu_init, + real_T *rty_Y, rtDW_RateLimiter_SecComputer_T *localDW) +{ + if (!localDW->pY_not_empty) { + localDW->pY = rtu_init; + localDW->pY_not_empty = true; + } + + localDW->pY += std::fmax(std::fmin(rtu_u - localDW->pY, std::abs(rtu_up) * rtu_Ts), -std::abs(rtu_lo) * rtu_Ts); + *rty_Y = localDW->pY; +} + +void SecComputer::SecComputer_RateLimiter_n_Reset(rtDW_RateLimiter_SecComputer_m_T *localDW) +{ + localDW->pY_not_empty = false; +} + +void SecComputer::SecComputer_RateLimiter_b(real_T rtu_u, real_T rtu_up, real_T rtu_lo, real_T rtu_Ts, real_T rtu_init, + boolean_T rtu_reset, real_T *rty_Y, rtDW_RateLimiter_SecComputer_m_T *localDW) +{ + if ((!localDW->pY_not_empty) || rtu_reset) { + localDW->pY = rtu_init; + localDW->pY_not_empty = true; + } + + if (rtu_reset) { + *rty_Y = rtu_init; + } else { + *rty_Y = std::fmax(std::fmin(rtu_u - localDW->pY, std::abs(rtu_up) * rtu_Ts), -std::abs(rtu_lo) * rtu_Ts) + + localDW->pY; + } + + localDW->pY = *rty_Y; +} + +void SecComputer::SecComputer_MATLABFunction_g_Reset(rtDW_MATLABFunction_SecComputer_l_T *localDW) +{ + localDW->previousInput_not_empty = false; +} + +void SecComputer::SecComputer_MATLABFunction_e(boolean_T rtu_u, boolean_T rtu_isRisingEdge, boolean_T *rty_y, + rtDW_MATLABFunction_SecComputer_l_T *localDW) +{ + if (!localDW->previousInput_not_empty) { + localDW->previousInput = rtu_isRisingEdge; + localDW->previousInput_not_empty = true; + } + + if (rtu_isRisingEdge) { + *rty_y = (rtu_u && (!localDW->previousInput)); + } else { + *rty_y = ((!rtu_u) && localDW->previousInput); + } + + localDW->previousInput = rtu_u; +} + +void SecComputer::SecComputer_MATLABFunction_e_Reset(rtDW_MATLABFunction_SecComputer_o_T *localDW) +{ + localDW->output = false; + localDW->timeSinceCondition = 0.0; +} + +void SecComputer::SecComputer_MATLABFunction_n(boolean_T rtu_u, real_T rtu_Ts, boolean_T rtu_isRisingEdge, real_T + rtu_timeDelay, boolean_T *rty_y, rtDW_MATLABFunction_SecComputer_o_T *localDW) +{ + if (rtu_u == rtu_isRisingEdge) { + localDW->timeSinceCondition += rtu_Ts; + if (localDW->timeSinceCondition >= rtu_timeDelay) { + localDW->output = rtu_u; + } + } else { + localDW->timeSinceCondition = 0.0; + localDW->output = rtu_u; + } + + *rty_y = localDW->output; +} + +void SecComputer::SecComputer_MATLABFunction_l(const base_arinc_429 *rtu_u, boolean_T *rty_y) +{ + *rty_y = (rtu_u->SSM != static_cast(SignStatusMatrix::FailureWarning)); +} + +void SecComputer::SecComputer_MATLABFunction_c(const boolean_T rtu_u[19], real32_T *rty_y) +{ + uint32_T out; + out = 0U; + for (int32_T i{0}; i < 19; i++) { + out |= static_cast(rtu_u[i]) << (i + 10); + } + + *rty_y = static_cast(out); +} + +void SecComputer::step() +{ + real_T rtb_eta_deg; + real_T rtb_eta_trim_dot_deg_s; + real_T rtb_eta_trim_limit_lo; + real_T rtb_eta_trim_limit_up; + base_arinc_429 rtb_Switch8; + real_T pair1SpdBrkCommand; + real_T rtb_BusAssignment_f_logic_ir_computation_data_n_z_g; + real_T rtb_BusAssignment_f_logic_ir_computation_data_theta_dot_deg_s; + real_T rtb_BusConversion_InsertedFor_BusAssignment_at_inport_8_BusCreator1_V_tas_kn; + real_T rtb_BusConversion_InsertedFor_BusAssignment_at_inport_8_BusCreator1_mach; + real_T rtb_Switch3; + real_T rtb_Switch4; + real_T rtb_Switch5; + real_T rtb_Switch6_m; + real_T rtb_Switch7; + real_T rtb_eta_trim_limit_lo_d; + real_T rtb_zeta_deg; + real_T u0; + real32_T rtb_y_fp; + real32_T rtb_y_h; + uint32_T rtb_DataTypeConversion1; + uint32_T rtb_Switch7_c; + uint32_T rtb_Switch9_c; + uint32_T rtb_y; + uint32_T rtb_y_af; + uint32_T rtb_y_mx; + boolean_T rtb_VectorConcatenate[19]; + boolean_T rtb_AND1_h; + boolean_T rtb_AND4_a; + boolean_T rtb_NOT_bl; + boolean_T rtb_OR; + boolean_T rtb_OR6; + boolean_T rtb_y_am; + boolean_T rtb_y_b; + boolean_T rtb_y_k4; + boolean_T rtb_y_l; + boolean_T rtb_y_m; + if (SecComputer_U.in.sim_data.computer_running) { + real_T pair1RollCommand; + real_T rtb_NOT_oi; + real32_T rtb_V_ias; + real32_T rtb_V_tas; + real32_T rtb_alpha; + real32_T rtb_mach; + real32_T rtb_n_x; + real32_T rtb_n_y; + real32_T rtb_n_z; + real32_T rtb_phi; + real32_T rtb_phi_dot; + real32_T rtb_q; + real32_T rtb_r; + real32_T rtb_theta; + real32_T rtb_theta_dot; + boolean_T abnormalCondition; + boolean_T canEngageInPitch; + boolean_T hasPriorityInPitch; + boolean_T leftElevatorAvail; + boolean_T rightElevatorAvail; + boolean_T rtb_AND2_j; + boolean_T rtb_BusAssignment_n_logic_any_landing_gear_not_uplocked; + boolean_T rtb_NOT2_b; + boolean_T rtb_NOT_g; + boolean_T rtb_OR1; + boolean_T rtb_OR3; + boolean_T rtb_doubleAdrFault; + boolean_T rtb_doubleIrFault; + boolean_T rtb_isEngagedInPitch; + boolean_T rtb_isEngagedInRoll; + boolean_T rtb_singleAdrFault; + boolean_T rtb_singleIrFault; + boolean_T rtb_thsAvail; + boolean_T spoilerPair1SupplyAvail; + boolean_T spoilerPair2SupplyAvail; + pitch_efcs_law rtb_activePitchLaw; + pitch_efcs_law rtb_pitchLawCapability; + if (!SecComputer_DWork.Runtime_MODE) { + SecComputer_DWork.Delay_DSTATE_c = SecComputer_P.Delay_InitialCondition_c; + SecComputer_DWork.Delay1_DSTATE = SecComputer_P.Delay1_InitialCondition; + SecComputer_DWork.Memory_PreviousInput = SecComputer_P.SRFlipFlop_initial_condition; + SecComputer_DWork.Memory_PreviousInput_f = SecComputer_P.SRFlipFlop_initial_condition_c; + SecComputer_DWork.Memory_PreviousInput_n = SecComputer_P.SRFlipFlop_initial_condition_k; + SecComputer_DWork.Delay1_DSTATE_i = SecComputer_P.Delay1_InitialCondition_l; + SecComputer_DWork.Delay_DSTATE_n = SecComputer_P.Delay_InitialCondition_j; + SecComputer_DWork.Delay_DSTATE = SecComputer_P.Delay_InitialCondition; + SecComputer_DWork.icLoad = true; + SecComputer_MATLABFunction_e_Reset(&SecComputer_DWork.sf_MATLABFunction_jk); + SecComputer_MATLABFunction_e_Reset(&SecComputer_DWork.sf_MATLABFunction_dw); + SecComputer_DWork.is_active_c8_SecComputer = 0U; + SecComputer_DWork.is_c8_SecComputer = SecComputer_IN_NO_ACTIVE_CHILD; + SecComputer_DWork.is_active_c30_SecComputer = 0U; + SecComputer_DWork.is_c30_SecComputer = SecComputer_IN_NO_ACTIVE_CHILD; + SecComputer_DWork.on_ground_time = 0.0; + SecComputer_B.in_flight = 0.0; + SecComputer_MATLABFunction_e_Reset(&SecComputer_DWork.sf_MATLABFunction_ndv); + SecComputer_MATLABFunction_e_Reset(&SecComputer_DWork.sf_MATLABFunction_gf); + SecComputer_MATLABFunction_e_Reset(&SecComputer_DWork.sf_MATLABFunction_h); + SecComputer_MATLABFunction_g_Reset(&SecComputer_DWork.sf_MATLABFunction_g4b); + SecComputer_MATLABFunction_g_Reset(&SecComputer_DWork.sf_MATLABFunction_nu); + SecComputer_DWork.pLeftStickDisabled = false; + SecComputer_DWork.pRightStickDisabled = false; + SecComputer_MATLABFunction_e_Reset(&SecComputer_DWork.sf_MATLABFunction_j2); + SecComputer_MATLABFunction_e_Reset(&SecComputer_DWork.sf_MATLABFunction_g24); + SecComputer_MATLABFunction_e_Reset(&SecComputer_DWork.sf_MATLABFunction_k4); + SecComputer_DWork.abnormalConditionWasActive = false; + SecComputer_MATLABFunction_g_Reset(&SecComputer_DWork.sf_MATLABFunction_b4); + SecComputer_MATLABFunction_e_Reset(&SecComputer_DWork.sf_MATLABFunction_fh); + SecComputer_MATLABFunction_g_Reset(&SecComputer_DWork.sf_MATLABFunction_nd); + SecComputer_MATLABFunction_g_Reset(&SecComputer_DWork.sf_MATLABFunction_n); + SecComputer_MATLABFunction_g_Reset(&SecComputer_DWork.sf_MATLABFunction_a); + SecComputer_MATLABFunction_g_Reset(&SecComputer_DWork.sf_MATLABFunction_e3); + SecComputer_RateLimiter_Reset(&SecComputer_DWork.sf_RateLimiter_c); + SecComputer_RateLimiter_Reset(&SecComputer_DWork.sf_RateLimiter); + LawMDLOBJ1.reset(); + SecComputer_RateLimiter_n_Reset(&SecComputer_DWork.sf_RateLimiter_b); + SecComputer_RateLimiter_n_Reset(&SecComputer_DWork.sf_RateLimiter_a); + SecComputer_RateLimiter_n_Reset(&SecComputer_DWork.sf_RateLimiter_k); + SecComputer_RateLimiter_n_Reset(&SecComputer_DWork.sf_RateLimiter_b4); + LawMDLOBJ2.reset(); + LawMDLOBJ3.reset(); + SecComputer_MATLABFunction_e_Reset(&SecComputer_DWork.sf_MATLABFunction_i); + SecComputer_DWork.Runtime_MODE = true; + } + + rtb_OR1 = ((SecComputer_U.in.bus_inputs.adr_1_bus.mach.SSM == static_cast(SignStatusMatrix::FailureWarning)) + || (SecComputer_U.in.bus_inputs.adr_1_bus.airspeed_computed_kn.SSM == static_cast + (SignStatusMatrix::FailureWarning)) || (SecComputer_U.in.bus_inputs.adr_1_bus.airspeed_true_kn.SSM == + static_cast(SignStatusMatrix::FailureWarning)) || + (SecComputer_U.in.bus_inputs.adr_1_bus.aoa_corrected_deg.SSM == static_cast(SignStatusMatrix:: + FailureWarning)) || SecComputer_P.Constant2_Value_c || SecComputer_P.Constant2_Value_c); + rtb_OR3 = ((SecComputer_U.in.bus_inputs.adr_2_bus.mach.SSM == static_cast(SignStatusMatrix::FailureWarning)) + || (SecComputer_U.in.bus_inputs.adr_2_bus.airspeed_computed_kn.SSM == static_cast + (SignStatusMatrix::FailureWarning)) || (SecComputer_U.in.bus_inputs.adr_2_bus.airspeed_true_kn.SSM == + static_cast(SignStatusMatrix::FailureWarning)) || + (SecComputer_U.in.bus_inputs.adr_2_bus.aoa_corrected_deg.SSM == static_cast(SignStatusMatrix:: + FailureWarning)) || SecComputer_P.Constant2_Value_c || SecComputer_P.Constant2_Value_c); + rtb_singleAdrFault = (rtb_OR1 || rtb_OR3); + rtb_doubleAdrFault = (rtb_OR1 && rtb_OR3); + rtb_OR = ((SecComputer_U.in.bus_inputs.ir_1_bus.pitch_angle_deg.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (SecComputer_U.in.bus_inputs.ir_1_bus.roll_angle_deg.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || (SecComputer_U.in.bus_inputs.ir_1_bus.body_yaw_rate_deg_s.SSM != + static_cast(SignStatusMatrix::NormalOperation)) || + (SecComputer_U.in.bus_inputs.ir_1_bus.body_long_accel_g.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (SecComputer_U.in.bus_inputs.ir_1_bus.body_lat_accel_g.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || (SecComputer_U.in.bus_inputs.ir_1_bus.body_normal_accel_g.SSM != + static_cast(SignStatusMatrix::NormalOperation)) || + (SecComputer_U.in.bus_inputs.ir_1_bus.pitch_att_rate_deg_s.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (SecComputer_U.in.bus_inputs.ir_1_bus.roll_att_rate_deg_s.SSM != + static_cast(SignStatusMatrix::NormalOperation)) || SecComputer_P.Constant_Value_l); + rtb_OR6 = ((SecComputer_U.in.bus_inputs.ir_2_bus.pitch_angle_deg.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (SecComputer_U.in.bus_inputs.ir_2_bus.roll_angle_deg.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || (SecComputer_U.in.bus_inputs.ir_2_bus.body_yaw_rate_deg_s.SSM != + static_cast(SignStatusMatrix::NormalOperation)) || + (SecComputer_U.in.bus_inputs.ir_2_bus.body_long_accel_g.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (SecComputer_U.in.bus_inputs.ir_2_bus.body_lat_accel_g.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || (SecComputer_U.in.bus_inputs.ir_2_bus.body_normal_accel_g.SSM != + static_cast(SignStatusMatrix::NormalOperation)) || + (SecComputer_U.in.bus_inputs.ir_2_bus.pitch_att_rate_deg_s.SSM != static_cast(SignStatusMatrix:: + NormalOperation)) || (SecComputer_U.in.bus_inputs.ir_2_bus.roll_att_rate_deg_s.SSM != static_cast + (SignStatusMatrix::NormalOperation)) || SecComputer_P.Constant_Value_l); + rtb_singleIrFault = (rtb_OR || rtb_OR6); + rtb_doubleIrFault = (rtb_OR && rtb_OR6); + rtb_y_l = !rtb_OR3; + if ((!rtb_OR1) && rtb_y_l) { + rtb_V_ias = (SecComputer_U.in.bus_inputs.adr_2_bus.airspeed_computed_kn.Data + + SecComputer_U.in.bus_inputs.adr_2_bus.airspeed_computed_kn.Data) / 2.0F; + rtb_V_tas = (SecComputer_U.in.bus_inputs.adr_2_bus.airspeed_true_kn.Data + + SecComputer_U.in.bus_inputs.adr_2_bus.airspeed_true_kn.Data) / 2.0F; + rtb_mach = (SecComputer_U.in.bus_inputs.adr_2_bus.mach.Data + SecComputer_U.in.bus_inputs.adr_2_bus.mach.Data) / + 2.0F; + rtb_alpha = (SecComputer_U.in.bus_inputs.adr_2_bus.aoa_corrected_deg.Data + + SecComputer_U.in.bus_inputs.adr_2_bus.aoa_corrected_deg.Data) / 2.0F; + } else if ((!rtb_OR1) && rtb_OR3) { + rtb_V_ias = SecComputer_U.in.bus_inputs.adr_1_bus.airspeed_computed_kn.Data; + rtb_V_tas = SecComputer_U.in.bus_inputs.adr_1_bus.airspeed_true_kn.Data; + rtb_mach = SecComputer_U.in.bus_inputs.adr_1_bus.mach.Data; + rtb_alpha = SecComputer_U.in.bus_inputs.adr_1_bus.aoa_corrected_deg.Data; + } else if (rtb_OR1 && rtb_y_l) { + rtb_V_ias = SecComputer_U.in.bus_inputs.adr_2_bus.airspeed_computed_kn.Data; + rtb_V_tas = SecComputer_U.in.bus_inputs.adr_2_bus.airspeed_true_kn.Data; + rtb_mach = SecComputer_U.in.bus_inputs.adr_2_bus.mach.Data; + rtb_alpha = SecComputer_U.in.bus_inputs.adr_2_bus.aoa_corrected_deg.Data; + } else { + rtb_V_ias = 0.0F; + rtb_V_tas = 0.0F; + rtb_mach = 0.0F; + rtb_alpha = 0.0F; + } + + rtb_eta_trim_limit_lo_d = rtb_V_ias; + rtb_BusConversion_InsertedFor_BusAssignment_at_inport_8_BusCreator1_V_tas_kn = rtb_V_tas; + rtb_BusConversion_InsertedFor_BusAssignment_at_inport_8_BusCreator1_mach = rtb_mach; + rtb_y_l = !rtb_OR; + rtb_OR1 = !rtb_OR6; + if (rtb_y_l && rtb_OR1) { + rtb_theta = (SecComputer_U.in.bus_inputs.ir_1_bus.pitch_angle_deg.Data + + SecComputer_U.in.bus_inputs.ir_2_bus.pitch_angle_deg.Data) / 2.0F; + rtb_phi = (SecComputer_U.in.bus_inputs.ir_1_bus.roll_angle_deg.Data + + SecComputer_U.in.bus_inputs.ir_2_bus.roll_angle_deg.Data) / 2.0F; + rtb_q = (SecComputer_U.in.bus_inputs.ir_1_bus.body_pitch_rate_deg_s.Data + + SecComputer_U.in.bus_inputs.ir_2_bus.body_pitch_rate_deg_s.Data) / 2.0F; + rtb_r = (SecComputer_U.in.bus_inputs.ir_1_bus.body_yaw_rate_deg_s.Data + + SecComputer_U.in.bus_inputs.ir_2_bus.body_yaw_rate_deg_s.Data) / 2.0F; + rtb_n_x = (SecComputer_U.in.bus_inputs.ir_1_bus.body_long_accel_g.Data + + SecComputer_U.in.bus_inputs.ir_2_bus.body_long_accel_g.Data) / 2.0F; + rtb_n_y = (SecComputer_U.in.bus_inputs.ir_1_bus.body_lat_accel_g.Data + + SecComputer_U.in.bus_inputs.ir_2_bus.body_lat_accel_g.Data) / 2.0F; + rtb_n_z = (SecComputer_U.in.bus_inputs.ir_1_bus.body_normal_accel_g.Data + + SecComputer_U.in.bus_inputs.ir_2_bus.body_normal_accel_g.Data) / 2.0F; + rtb_theta_dot = (SecComputer_U.in.bus_inputs.ir_1_bus.pitch_att_rate_deg_s.Data + + SecComputer_U.in.bus_inputs.ir_2_bus.pitch_att_rate_deg_s.Data) / 2.0F; + rtb_phi_dot = (SecComputer_U.in.bus_inputs.ir_1_bus.roll_att_rate_deg_s.Data + + SecComputer_U.in.bus_inputs.ir_2_bus.roll_att_rate_deg_s.Data) / 2.0F; + } else if (rtb_y_l && rtb_OR6) { + rtb_theta = SecComputer_U.in.bus_inputs.ir_1_bus.pitch_angle_deg.Data; + rtb_phi = SecComputer_U.in.bus_inputs.ir_1_bus.roll_angle_deg.Data; + rtb_q = SecComputer_U.in.bus_inputs.ir_1_bus.body_pitch_rate_deg_s.Data; + rtb_r = SecComputer_U.in.bus_inputs.ir_1_bus.body_yaw_rate_deg_s.Data; + rtb_n_x = SecComputer_U.in.bus_inputs.ir_1_bus.body_long_accel_g.Data; + rtb_n_y = SecComputer_U.in.bus_inputs.ir_1_bus.body_lat_accel_g.Data; + rtb_n_z = SecComputer_U.in.bus_inputs.ir_1_bus.body_normal_accel_g.Data; + rtb_theta_dot = SecComputer_U.in.bus_inputs.ir_1_bus.pitch_att_rate_deg_s.Data; + rtb_phi_dot = SecComputer_U.in.bus_inputs.ir_1_bus.roll_att_rate_deg_s.Data; + } else if (rtb_OR && rtb_OR1) { + rtb_theta = SecComputer_U.in.bus_inputs.ir_2_bus.pitch_angle_deg.Data; + rtb_phi = SecComputer_U.in.bus_inputs.ir_2_bus.roll_angle_deg.Data; + rtb_q = SecComputer_U.in.bus_inputs.ir_2_bus.body_pitch_rate_deg_s.Data; + rtb_r = SecComputer_U.in.bus_inputs.ir_2_bus.body_yaw_rate_deg_s.Data; + rtb_n_x = SecComputer_U.in.bus_inputs.ir_2_bus.body_long_accel_g.Data; + rtb_n_y = SecComputer_U.in.bus_inputs.ir_2_bus.body_lat_accel_g.Data; + rtb_n_z = SecComputer_U.in.bus_inputs.ir_2_bus.body_normal_accel_g.Data; + rtb_theta_dot = SecComputer_U.in.bus_inputs.ir_2_bus.pitch_att_rate_deg_s.Data; + rtb_phi_dot = SecComputer_U.in.bus_inputs.ir_2_bus.roll_att_rate_deg_s.Data; + } else { + rtb_theta = 0.0F; + rtb_phi = 0.0F; + rtb_q = 0.0F; + rtb_r = 0.0F; + rtb_n_x = 0.0F; + rtb_n_y = 0.0F; + rtb_n_z = 0.0F; + rtb_theta_dot = 0.0F; + rtb_phi_dot = 0.0F; + } + + rtb_Switch5 = rtb_theta; + rtb_Switch6_m = rtb_phi; + rtb_Switch4 = rtb_n_x; + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_3, &rtb_OR); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_3, + SecComputer_P.BitfromLabel13_bit, &rtb_Switch7_c); + rtb_OR6 = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_3, + SecComputer_P.BitfromLabel12_bit, &rtb_Switch7_c); + rtb_y_m = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_3, + SecComputer_P.BitfromLabel10_bit, &rtb_Switch7_c); + rtb_OR6 = (rtb_OR6 || rtb_y_m || (rtb_Switch7_c != 0U)); + rtb_y_k4 = (rtb_OR && rtb_OR6); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_3, &rtb_NOT_bl); + rtb_OR1 = (rtb_y_k4 && (!rtb_NOT_bl)); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_3, &rtb_y_b); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_3, SecComputer_P.BitfromLabel5_bit, + &rtb_Switch7_c); + rtb_y_m = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_3, SecComputer_P.BitfromLabel4_bit, + &rtb_Switch7_c); + rtb_y_am = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_3, SecComputer_P.BitfromLabel2_bit, + &rtb_Switch7_c); + rtb_y_m = (rtb_y_m || rtb_y_am || (rtb_Switch7_c != 0U)); + rtb_AND4_a = (rtb_y_b && rtb_y_m); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_3, &rtb_NOT_bl); + rtb_AND4_a = (rtb_OR1 || (rtb_AND4_a && (!rtb_NOT_bl)) || (rtb_y_k4 && rtb_AND4_a)); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_3, + SecComputer_P.BitfromLabel15_bit, &rtb_Switch7_c); + rtb_y_am = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_3, &rtb_y_l); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_3, + SecComputer_P.BitfromLabel14_bit, &rtb_Switch7_c); + rtb_y_k4 = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_3, + SecComputer_P.BitfromLabel11_bit, &rtb_Switch7_c); + rtb_AND1_h = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_3, SecComputer_P.BitfromLabel8_bit, + &rtb_Switch7_c); + rtb_OR1 = ((!rtb_y_k4) && (!rtb_AND1_h) && (rtb_Switch7_c == 0U)); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_3, + SecComputer_P.BitfromLabel16_bit, &rtb_Switch7_c); + rtb_NOT_bl = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction_n((rtb_Switch7_c != 0U) && rtb_OR && rtb_OR6, SecComputer_U.in.time.dt, + SecComputer_P.ConfirmNode_isRisingEdge, SecComputer_P.ConfirmNode_timeDelay, &rtb_OR6, + &SecComputer_DWork.sf_MATLABFunction_jk); + rtb_OR = (rtb_y_am && rtb_y_l && rtb_OR1 && rtb_OR6); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_3, &rtb_AND1_h); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_3, &rtb_NOT_bl); + rtb_OR6 = ((!rtb_AND1_h) && (!rtb_NOT_bl)); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_3, SecComputer_P.BitfromLabel7_bit, + &rtb_Switch7_c); + rtb_NOT_bl = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_3, &rtb_y_l); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_3, SecComputer_P.BitfromLabel6_bit, + &rtb_Switch7_c); + rtb_AND1_h = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_3, SecComputer_P.BitfromLabel3_bit, + &rtb_Switch7_c); + rtb_y_am = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_3, SecComputer_P.BitfromLabel1_bit, + &rtb_Switch7_c); + rtb_OR1 = (rtb_NOT_bl && rtb_y_l && ((!rtb_AND1_h) && (!rtb_y_am) && (rtb_Switch7_c == 0U))); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_3, SecComputer_P.BitfromLabel9_bit, + &rtb_Switch7_c); + rtb_NOT_bl = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction_n((rtb_Switch7_c != 0U) && rtb_y_b && rtb_y_m, SecComputer_U.in.time.dt, + SecComputer_P.ConfirmNode1_isRisingEdge, SecComputer_P.ConfirmNode1_timeDelay, &rtb_NOT_bl, + &SecComputer_DWork.sf_MATLABFunction_dw); + rtb_OR6 = (rtb_OR || rtb_OR6 || (rtb_OR1 && rtb_NOT_bl)); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_2, + SecComputer_P.BitfromLabel4_bit_c, &rtb_Switch7_c); + rtb_NOT_bl = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_2, + SecComputer_P.BitfromLabel6_bit_l, &rtb_Switch7_c); + rtb_OR = (rtb_NOT_bl || (rtb_Switch7_c != 0U)); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_2, + SecComputer_P.BitfromLabel5_bit_a, &rtb_Switch7_c); + rtb_NOT_bl = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_2, + SecComputer_P.BitfromLabel7_bit_m, &rtb_Switch7_c); + rtb_AND1_h = (rtb_Switch7_c != 0U); + rtb_OR1 = (rtb_NOT_bl || (rtb_Switch7_c != 0U)); + if (SecComputer_DWork.is_active_c8_SecComputer == 0U) { + SecComputer_DWork.is_active_c8_SecComputer = 1U; + SecComputer_DWork.is_c8_SecComputer = SecComputer_IN_OnGround; + rtb_OR1 = true; + } else if (SecComputer_DWork.is_c8_SecComputer == SecComputer_IN_InAir) { + if ((static_cast(rtb_OR) > 0.1) || (static_cast(rtb_OR1) > 0.1)) { + SecComputer_DWork.is_c8_SecComputer = SecComputer_IN_OnGround; + rtb_OR1 = true; + } else { + rtb_OR1 = false; + } + } else if ((!rtb_OR) && (!rtb_OR1)) { + SecComputer_DWork.is_c8_SecComputer = SecComputer_IN_InAir; + rtb_OR1 = false; + } else { + rtb_OR1 = true; + } + + rtb_OR3 = (SecComputer_U.in.sim_data.slew_on || SecComputer_U.in.sim_data.pause_on || + SecComputer_U.in.sim_data.tracking_mode_on_override); + if (SecComputer_DWork.is_active_c30_SecComputer == 0U) { + SecComputer_DWork.is_active_c30_SecComputer = 1U; + SecComputer_DWork.is_c30_SecComputer = SecComputer_IN_Ground; + SecComputer_B.in_flight = 0.0; + } else { + switch (SecComputer_DWork.is_c30_SecComputer) { + case SecComputer_IN_Flight: + if (rtb_OR1 && (rtb_theta < 2.5F)) { + SecComputer_DWork.on_ground_time = SecComputer_U.in.time.simulation_time; + SecComputer_DWork.is_c30_SecComputer = SecComputer_IN_FlightToGroundTransition; + } else { + SecComputer_B.in_flight = 1.0; + } + break; + + case SecComputer_IN_FlightToGroundTransition: + if (SecComputer_U.in.time.simulation_time - SecComputer_DWork.on_ground_time >= 5.0) { + SecComputer_DWork.is_c30_SecComputer = SecComputer_IN_Ground; + SecComputer_B.in_flight = 0.0; + } else if ((!rtb_OR1) || (rtb_theta >= 2.5F)) { + SecComputer_DWork.on_ground_time = 0.0; + SecComputer_DWork.is_c30_SecComputer = SecComputer_IN_Flight; + SecComputer_B.in_flight = 1.0; + } + break; + + default: + if (((!rtb_OR1) && (rtb_theta > 8.0F)) || (SecComputer_P.Constant_Value_m > 400.0)) { + SecComputer_DWork.on_ground_time = 0.0; + SecComputer_DWork.is_c30_SecComputer = SecComputer_IN_Flight; + SecComputer_B.in_flight = 1.0; + } else { + SecComputer_B.in_flight = 0.0; + } + break; + } + } + + rtb_NOT_bl = (SecComputer_B.in_flight != 0.0); + SecComputer_MATLABFunction_n(!SecComputer_U.in.discrete_inputs.yellow_low_pressure, SecComputer_U.in.time.dt, + SecComputer_P.ConfirmNode_isRisingEdge_a, SecComputer_P.ConfirmNode_timeDelay_c, &rtb_OR, + &SecComputer_DWork.sf_MATLABFunction_ndv); + SecComputer_MATLABFunction_n(!SecComputer_U.in.discrete_inputs.blue_low_pressure, SecComputer_U.in.time.dt, + SecComputer_P.ConfirmNode1_isRisingEdge_j, SecComputer_P.ConfirmNode1_timeDelay_k, &rtb_y_l, + &SecComputer_DWork.sf_MATLABFunction_gf); + SecComputer_MATLABFunction_n(!SecComputer_U.in.discrete_inputs.green_low_pressure, SecComputer_U.in.time.dt, + SecComputer_P.ConfirmNode2_isRisingEdge, SecComputer_P.ConfirmNode2_timeDelay, &rtb_y_b, + &SecComputer_DWork.sf_MATLABFunction_h); + rtb_BusAssignment_f_logic_ir_computation_data_n_z_g = rtb_n_z; + rtb_BusAssignment_f_logic_ir_computation_data_theta_dot_deg_s = rtb_theta_dot; + SecComputer_MATLABFunction_e(SecComputer_U.in.discrete_inputs.capt_priority_takeover_pressed, + SecComputer_P.PulseNode_isRisingEdge, &rtb_AND1_h, &SecComputer_DWork.sf_MATLABFunction_g4b); + SecComputer_MATLABFunction_e(SecComputer_U.in.discrete_inputs.fo_priority_takeover_pressed, + SecComputer_P.PulseNode1_isRisingEdge, &rtb_NOT_bl, &SecComputer_DWork.sf_MATLABFunction_nu); + if (rtb_AND1_h) { + SecComputer_DWork.pRightStickDisabled = true; + SecComputer_DWork.pLeftStickDisabled = false; + } else if (rtb_NOT_bl) { + SecComputer_DWork.pLeftStickDisabled = true; + SecComputer_DWork.pRightStickDisabled = false; + } + + if (SecComputer_DWork.pRightStickDisabled && ((!SecComputer_U.in.discrete_inputs.capt_priority_takeover_pressed) && + (!SecComputer_DWork.Delay1_DSTATE))) { + SecComputer_DWork.pRightStickDisabled = false; + } else if (SecComputer_DWork.pLeftStickDisabled) { + SecComputer_DWork.pLeftStickDisabled = (SecComputer_U.in.discrete_inputs.fo_priority_takeover_pressed || + SecComputer_DWork.Delay_DSTATE_c); + } + + SecComputer_MATLABFunction_n(SecComputer_DWork.pLeftStickDisabled && + (SecComputer_U.in.discrete_inputs.fo_priority_takeover_pressed || SecComputer_DWork.Delay_DSTATE_c), + SecComputer_U.in.time.dt, SecComputer_P.ConfirmNode1_isRisingEdge_k, SecComputer_P.ConfirmNode1_timeDelay_a, + &SecComputer_DWork.Delay_DSTATE_c, &SecComputer_DWork.sf_MATLABFunction_j2); + SecComputer_MATLABFunction_n(SecComputer_DWork.pRightStickDisabled && + (SecComputer_U.in.discrete_inputs.capt_priority_takeover_pressed || SecComputer_DWork.Delay1_DSTATE), + SecComputer_U.in.time.dt, SecComputer_P.ConfirmNode_isRisingEdge_j, SecComputer_P.ConfirmNode_timeDelay_a, + &SecComputer_DWork.Delay1_DSTATE, &SecComputer_DWork.sf_MATLABFunction_g24); + if (SecComputer_DWork.pLeftStickDisabled) { + rtb_zeta_deg = SecComputer_P.Constant1_Value_p; + } else { + rtb_zeta_deg = SecComputer_U.in.analog_inputs.capt_roll_stick_pos; + } + + if (!SecComputer_DWork.pRightStickDisabled) { + rtb_Switch7 = SecComputer_U.in.analog_inputs.fo_roll_stick_pos; + } else { + rtb_Switch7 = SecComputer_P.Constant1_Value_p; + } + + pair1SpdBrkCommand = rtb_Switch7 + rtb_zeta_deg; + if (pair1SpdBrkCommand > SecComputer_P.Saturation1_UpperSat) { + pair1SpdBrkCommand = SecComputer_P.Saturation1_UpperSat; + } else if (pair1SpdBrkCommand < SecComputer_P.Saturation1_LowerSat) { + pair1SpdBrkCommand = SecComputer_P.Saturation1_LowerSat; + } + + if (SecComputer_U.in.discrete_inputs.is_unit_1) { + leftElevatorAvail = ((!SecComputer_U.in.discrete_inputs.l_elev_servo_failed) && rtb_y_l); + rightElevatorAvail = ((!SecComputer_U.in.discrete_inputs.r_elev_servo_failed) && rtb_y_l); + } else { + leftElevatorAvail = ((!SecComputer_U.in.discrete_inputs.l_elev_servo_failed) && rtb_y_b); + rightElevatorAvail = ((!SecComputer_U.in.discrete_inputs.r_elev_servo_failed) && rtb_OR); + } + + rtb_thsAvail = ((!SecComputer_U.in.discrete_inputs.ths_motor_fault) && (rtb_OR || rtb_y_b)); + canEngageInPitch = ((leftElevatorAvail || rightElevatorAvail) && (!SecComputer_U.in.discrete_inputs.is_unit_3)); + if (SecComputer_U.in.discrete_inputs.is_unit_1) { + hasPriorityInPitch = (SecComputer_U.in.discrete_inputs.pitch_not_avail_elac_1 && + SecComputer_U.in.discrete_inputs.pitch_not_avail_elac_2 && + SecComputer_U.in.discrete_inputs.left_elev_not_avail_sec_opp && + SecComputer_U.in.discrete_inputs.right_elev_not_avail_sec_opp); + spoilerPair1SupplyAvail = rtb_y_l; + spoilerPair2SupplyAvail = rtb_OR; + } else { + hasPriorityInPitch = (SecComputer_U.in.discrete_inputs.is_unit_2 && + (SecComputer_U.in.discrete_inputs.pitch_not_avail_elac_1 && + SecComputer_U.in.discrete_inputs.pitch_not_avail_elac_2)); + if (SecComputer_U.in.discrete_inputs.is_unit_2) { + spoilerPair1SupplyAvail = rtb_y_b; + spoilerPair2SupplyAvail = false; + } else { + spoilerPair1SupplyAvail = rtb_y_b; + spoilerPair2SupplyAvail = rtb_OR; + } + } + + rtb_isEngagedInPitch = (canEngageInPitch && hasPriorityInPitch); + spoilerPair1SupplyAvail = ((!SecComputer_U.in.discrete_inputs.l_spoiler_1_servo_failed) && + (!SecComputer_U.in.discrete_inputs.r_spoiler_1_servo_failed) && spoilerPair1SupplyAvail); + spoilerPair2SupplyAvail = ((!SecComputer_U.in.discrete_inputs.l_spoiler_2_servo_failed) && + (!SecComputer_U.in.discrete_inputs.r_spoiler_2_servo_failed) && spoilerPair2SupplyAvail); + rtb_isEngagedInRoll = ((spoilerPair1SupplyAvail || spoilerPair2SupplyAvail) && + SecComputer_U.in.discrete_inputs.digital_output_failed_elac_1 && + SecComputer_U.in.discrete_inputs.digital_output_failed_elac_2); + rtb_BusAssignment_n_logic_any_landing_gear_not_uplocked = rtb_AND4_a; + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_1, + SecComputer_P.BitfromLabel_bit, &rtb_Switch7_c); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_1, &rtb_AND4_a); + rtb_y_k4 = ((rtb_Switch7_c != 0U) && rtb_AND4_a); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_1, + SecComputer_P.BitfromLabel1_bit_g, &rtb_Switch7_c); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_1, &rtb_AND1_h); + rtb_AND2_j = ((rtb_Switch7_c != 0U) && rtb_AND1_h); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_actual_position_word, + SecComputer_P.BitfromLabel2_bit_k, &rtb_Switch7_c); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_actual_position_word, &rtb_AND4_a); + rtb_AND4_a = ((rtb_Switch7_c != 0U) && rtb_AND4_a); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.sfcc_2_bus.slat_flap_actual_position_word, + SecComputer_P.BitfromLabel3_bit_o, &rtb_Switch7_c); + rtb_NOT_bl = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.sfcc_2_bus.slat_flap_actual_position_word, &rtb_AND1_h); + SecComputer_MATLABFunction_n(SecComputer_U.in.sim_data.slew_on, SecComputer_U.in.time.dt, + SecComputer_P.ConfirmNode_isRisingEdge_g, SecComputer_P.ConfirmNode_timeDelay_e, &rtb_NOT_bl, + &SecComputer_DWork.sf_MATLABFunction_k4); + rtb_y_am = !rtb_OR1; + abnormalCondition = ((!rtb_NOT_bl) && rtb_y_am && (((!rtb_doubleAdrFault) && ((rtb_mach > 0.91) || (rtb_alpha < + -10.0F) || (rtb_alpha > 40.0F) || (rtb_V_ias > 440.0F) || (rtb_V_ias < 60.0F))) || ((!rtb_doubleIrFault) && + ((!rtb_singleIrFault) || (!SecComputer_P.Constant_Value_l)) && ((std::abs(static_cast(rtb_phi)) > 125.0) || + ((rtb_theta > 50.0F) || (rtb_theta < -30.0F)))))); + SecComputer_DWork.abnormalConditionWasActive = (abnormalCondition || (rtb_y_am && + SecComputer_DWork.abnormalConditionWasActive)); + if (rtb_doubleIrFault || ((SecComputer_B.in_flight != 0.0) && + ((rtb_BusAssignment_n_logic_any_landing_gear_not_uplocked && (!rtb_OR6)) || ((rtb_AND4_a || ((rtb_Switch7_c != + 0U) && rtb_AND1_h)) && rtb_OR6)))) { + rtb_pitchLawCapability = pitch_efcs_law::DirectLaw; + } else if ((rtb_singleAdrFault && SecComputer_P.Constant2_Value_c) || rtb_doubleAdrFault || + SecComputer_DWork.abnormalConditionWasActive || ((!rtb_y_k4) && (!rtb_AND2_j) && ((!leftElevatorAvail) || + (!rightElevatorAvail)))) { + rtb_pitchLawCapability = pitch_efcs_law::AlternateLaw2; + } else { + rtb_pitchLawCapability = pitch_efcs_law::AlternateLaw1; + } + + if (rtb_isEngagedInPitch) { + rtb_activePitchLaw = rtb_pitchLawCapability; + } else { + rtb_activePitchLaw = pitch_efcs_law::None; + } + + if (!SecComputer_DWork.pRightStickDisabled) { + rtb_Switch7 = SecComputer_U.in.analog_inputs.fo_pitch_stick_pos; + } else { + rtb_Switch7 = SecComputer_P.Constant_Value_p; + } + + if (SecComputer_DWork.pLeftStickDisabled) { + u0 = SecComputer_P.Constant_Value_p; + } else { + u0 = SecComputer_U.in.analog_inputs.capt_pitch_stick_pos; + } + + u0 += rtb_Switch7; + if (u0 > SecComputer_P.Saturation_UpperSat_d) { + u0 = SecComputer_P.Saturation_UpperSat_d; + } else if (u0 < SecComputer_P.Saturation_LowerSat_h) { + u0 = SecComputer_P.Saturation_LowerSat_h; + } + + SecComputer_MATLABFunction_e(SecComputer_B.in_flight != 0.0, SecComputer_P.PulseNode_isRisingEdge_h, &rtb_y_k4, + &SecComputer_DWork.sf_MATLABFunction_b4); + rtb_y_am = (SecComputer_U.in.discrete_inputs.pitch_not_avail_elac_1 && + SecComputer_U.in.discrete_inputs.pitch_not_avail_elac_2); + rtb_AND1_h = (SecComputer_U.in.discrete_inputs.is_unit_1 && rtb_thsAvail && rtb_y_am); + SecComputer_DWork.Memory_PreviousInput = SecComputer_P.Logic_table[((((!rtb_AND1_h) || (std::abs + (SecComputer_U.in.analog_inputs.ths_pos_deg) <= SecComputer_P.CompareToConstant1_const) || + SecComputer_U.in.discrete_inputs.ths_override_active) + (static_cast(rtb_y_k4) << 1)) << 1) + + SecComputer_DWork.Memory_PreviousInput]; + rtb_NOT_bl = (rtb_AND1_h && SecComputer_DWork.Memory_PreviousInput); + rtb_AND4_a = !abnormalCondition; + rtb_AND1_h = ((rtb_isEngagedInPitch && (SecComputer_B.in_flight != 0.0) && ((rtb_activePitchLaw != + SecComputer_P.EnumeratedConstant_Value_f) && rtb_AND4_a)) || rtb_NOT_bl); + rtb_y_k4 = rtb_AND1_h; + rtb_AND2_j = rtb_NOT_bl; + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_2, + SecComputer_P.BitfromLabel7_bit_g, &rtb_Switch7_c); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_2, &rtb_AND1_h); + rtb_NOT_bl = ((rtb_Switch7_c != 0U) && rtb_AND1_h); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_2, + SecComputer_P.BitfromLabel6_bit_f, &rtb_Switch7_c); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_2, &rtb_AND4_a); + rtb_y_m = ((rtb_Switch7_c != 0U) && rtb_AND4_a); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_actual_position_word, + SecComputer_P.BitfromLabel_bit_l, &rtb_Switch7_c); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_actual_position_word, &rtb_AND4_a); + rtb_AND4_a = ((rtb_Switch7_c != 0U) && rtb_AND4_a); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.sfcc_2_bus.slat_flap_actual_position_word, + SecComputer_P.BitfromLabel1_bit_p, &rtb_Switch7_c); + SecComputer_MATLABFunction_l(&SecComputer_U.in.bus_inputs.sfcc_2_bus.slat_flap_actual_position_word, &rtb_AND1_h); + rtb_AND1_h = ((rtb_Switch7_c != 0U) && rtb_AND1_h); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_1, + SecComputer_P.BitfromLabel5_bit_p, &rtb_Switch7_c); + rtb_NOT2_b = (rtb_Switch7_c == 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_1, + SecComputer_P.BitfromLabel4_bit_e, &rtb_Switch7_c); + rtb_NOT2_b = (rtb_NOT2_b || (rtb_Switch7_c == 0U)); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_1, + SecComputer_P.BitfromLabel3_bit_oz, &rtb_Switch7_c); + rtb_NOT_g = (rtb_Switch7_c == 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_1, + SecComputer_P.BitfromLabel2_bit_p, &rtb_Switch7_c); + rtb_NOT_bl = (rtb_NOT_bl || rtb_y_m || (rtb_AND4_a || rtb_AND1_h) || (((!rtb_y_am) && rtb_NOT2_b && (rtb_NOT_g || + (rtb_Switch7_c == 0U))) || (rtb_y_am && ((SecComputer_U.in.discrete_inputs.left_elev_not_avail_sec_opp && + (!leftElevatorAvail)) || (SecComputer_U.in.discrete_inputs.right_elev_not_avail_sec_opp && (!rightElevatorAvail))))) + || ((SecComputer_U.in.analog_inputs.thr_lever_1_pos >= SecComputer_P.CompareToConstant3_const) || + (SecComputer_U.in.analog_inputs.thr_lever_2_pos >= SecComputer_P.CompareToConstant4_const))); + SecComputer_MATLABFunction_n(SecComputer_U.in.analog_inputs.spd_brk_lever_pos < + SecComputer_P.CompareToConstant_const, SecComputer_U.in.time.dt, SecComputer_P.ConfirmNode_isRisingEdge_e, + SecComputer_P.ConfirmNode_timeDelay_eq, &rtb_y_m, &SecComputer_DWork.sf_MATLABFunction_fh); + SecComputer_DWork.Memory_PreviousInput_f = SecComputer_P.Logic_table_i[(((static_cast(rtb_NOT_bl) << 1) + + rtb_y_m) << 1) + SecComputer_DWork.Memory_PreviousInput_f]; + rtb_y_m = (rtb_NOT_bl || SecComputer_DWork.Memory_PreviousInput_f); + rtb_NOT2_b = rtb_y_l; + rtb_NOT_g = rtb_y_b; + SecComputer_MATLABFunction_e(rtb_OR1, SecComputer_P.PulseNode3_isRisingEdge, &rtb_y_b, + &SecComputer_DWork.sf_MATLABFunction_nd); + rtb_NOT_bl = (rtb_y_b || ((SecComputer_U.in.analog_inputs.wheel_speed_left < SecComputer_P.CompareToConstant11_const) + && (SecComputer_U.in.analog_inputs.wheel_speed_right < SecComputer_P.CompareToConstant12_const))); + SecComputer_MATLABFunction_e(rtb_OR1, SecComputer_P.PulseNode2_isRisingEdge, &rtb_y_b, + &SecComputer_DWork.sf_MATLABFunction_n); + SecComputer_DWork.Memory_PreviousInput_n = SecComputer_P.Logic_table_ii[(((static_cast(rtb_NOT_bl) << 1) + + rtb_y_b) << 1) + SecComputer_DWork.Memory_PreviousInput_n]; + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_2, + SecComputer_P.BitfromLabel4_bit_a, &rtb_Switch7_c); + rtb_AND1_h = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_2, + SecComputer_P.BitfromLabel6_bit_d, &rtb_Switch7_c); + rtb_y_b = (rtb_AND1_h && (rtb_Switch7_c != 0U)); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_2, + SecComputer_P.BitfromLabel5_bit_i, &rtb_Switch7_c); + rtb_AND1_h = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_2, + SecComputer_P.BitfromLabel7_bit_ms, &rtb_Switch7_c); + SecComputer_MATLABFunction_e(rtb_y_b || (rtb_AND1_h && (rtb_Switch7_c != 0U)), + SecComputer_P.PulseNode1_isRisingEdge_k, &rtb_y_l, &SecComputer_DWork.sf_MATLABFunction_a); + rtb_NOT_bl = (SecComputer_U.in.analog_inputs.spd_brk_lever_pos < SecComputer_P.CompareToConstant_const_m); + SecComputer_DWork.Delay1_DSTATE_i = (((((SecComputer_U.in.analog_inputs.spd_brk_lever_pos > + SecComputer_P.CompareToConstant15_const) || rtb_NOT_bl) && ((SecComputer_U.in.analog_inputs.thr_lever_1_pos <= + SecComputer_P.CompareToConstant1_const_l) || (SecComputer_U.in.analog_inputs.thr_lever_2_pos <= + SecComputer_P.CompareToConstant2_const))) || (((SecComputer_U.in.analog_inputs.thr_lever_1_pos < + SecComputer_P.CompareToConstant3_const_a) && (SecComputer_U.in.analog_inputs.thr_lever_2_pos <= + SecComputer_P.CompareToConstant4_const_j)) || ((SecComputer_U.in.analog_inputs.thr_lever_1_pos <= + SecComputer_P.CompareToConstant13_const) && (SecComputer_U.in.analog_inputs.thr_lever_2_pos < + SecComputer_P.CompareToConstant14_const)))) && (rtb_y_l || ((SecComputer_U.in.analog_inputs.wheel_speed_left >= + SecComputer_P.CompareToConstant5_const) && (SecComputer_U.in.analog_inputs.wheel_speed_right >= + SecComputer_P.CompareToConstant6_const) && SecComputer_DWork.Memory_PreviousInput_n) || + SecComputer_DWork.Delay1_DSTATE_i)); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_2, + SecComputer_P.BitfromLabel_bit_g, &rtb_Switch7_c); + rtb_AND1_h = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_2, + SecComputer_P.BitfromLabel2_bit_l, &rtb_Switch7_c); + rtb_y_b = (rtb_AND1_h || (rtb_Switch7_c != 0U)); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_1_bus.discrete_word_2, + SecComputer_P.BitfromLabel1_bit_a, &rtb_Switch7_c); + rtb_AND1_h = (rtb_Switch7_c != 0U); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.lgciu_2_bus.discrete_word_2, + SecComputer_P.BitfromLabel3_bit_m, &rtb_Switch7_c); + SecComputer_MATLABFunction_e(rtb_y_b || (rtb_AND1_h || (rtb_Switch7_c != 0U)), + SecComputer_P.PulseNode_isRisingEdge_hj, &rtb_y_l, &SecComputer_DWork.sf_MATLABFunction_e3); + SecComputer_DWork.Delay_DSTATE_n = (((((SecComputer_U.in.analog_inputs.spd_brk_lever_pos > + SecComputer_P.CompareToConstant10_const) || rtb_NOT_bl) && ((SecComputer_U.in.analog_inputs.thr_lever_1_pos <= + SecComputer_P.CompareToConstant7_const) && (SecComputer_U.in.analog_inputs.thr_lever_2_pos <= + SecComputer_P.CompareToConstant16_const))) || (((SecComputer_U.in.analog_inputs.thr_lever_1_pos < + SecComputer_P.CompareToConstant17_const) && (SecComputer_U.in.analog_inputs.thr_lever_2_pos <= + SecComputer_P.CompareToConstant18_const)) || ((SecComputer_U.in.analog_inputs.thr_lever_1_pos <= + SecComputer_P.CompareToConstant8_const) && (SecComputer_U.in.analog_inputs.thr_lever_2_pos < + SecComputer_P.CompareToConstant9_const)))) && (rtb_y_l || SecComputer_DWork.Delay_DSTATE_n)); + rtb_AND1_h = ((!SecComputer_DWork.Delay1_DSTATE_i) && SecComputer_DWork.Delay_DSTATE_n); + SecComputer_Y.out.logic.total_sidestick_roll_command = pair1SpdBrkCommand; + rtb_y_b = rtb_NOT_bl; + if (SecComputer_DWork.Delay1_DSTATE_i) { + rtb_Switch7 = SecComputer_P.Constant_Value; + } else if (rtb_AND1_h) { + rtb_Switch7 = SecComputer_P.Constant1_Value; + } else { + rtb_Switch7 = SecComputer_P.Constant2_Value; + } + + SecComputer_RateLimiter(rtb_Switch7, SecComputer_P.RateLimiterVariableTs6_up, + SecComputer_P.RateLimiterVariableTs6_lo, SecComputer_U.in.time.dt, + SecComputer_P.RateLimiterVariableTs6_InitialCondition, &rtb_Switch4, &SecComputer_DWork.sf_RateLimiter_c); + rtb_NOT_bl = (SecComputer_DWork.Delay1_DSTATE_i || rtb_AND1_h); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_2, + SecComputer_P.BitfromLabel4_bit_m, &rtb_Switch7_c); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_2, + SecComputer_P.BitfromLabel5_bit_h, &rtb_y); + if (rtb_y_m) { + rtb_Switch3 = SecComputer_P.Constant3_Value; + } else { + if ((rtb_Switch7_c != 0U) || (rtb_y != 0U)) { + rtb_Switch7 = SecComputer_P.Constant4_Value_k; + } else { + rtb_Switch7 = SecComputer_P.Constant5_Value; + } + + if (SecComputer_U.in.analog_inputs.spd_brk_lever_pos <= rtb_Switch7) { + rtb_Switch7 *= SecComputer_P.Gain_Gain; + if (SecComputer_U.in.analog_inputs.spd_brk_lever_pos >= rtb_Switch7) { + rtb_Switch7 = SecComputer_U.in.analog_inputs.spd_brk_lever_pos; + } + } + + rtb_Switch3 = look1_binlxpw(rtb_Switch7, SecComputer_P.uDLookupTable_bp01Data, + SecComputer_P.uDLookupTable_tableData, 4U); + } + + SecComputer_RateLimiter(rtb_Switch3, SecComputer_P.RateLimiterVariableTs1_up, + SecComputer_P.RateLimiterVariableTs1_lo, SecComputer_U.in.time.dt, + SecComputer_P.RateLimiterVariableTs1_InitialCondition, &rtb_Switch7, &SecComputer_DWork.sf_RateLimiter); + LawMDLOBJ1.step(&SecComputer_U.in.time.dt, &pair1SpdBrkCommand, &rtb_Switch3, &rtb_zeta_deg); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_1, + SecComputer_P.BitfromLabel_bit_a, &rtb_Switch7_c); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_1, + SecComputer_P.BitfromLabel1_bit_c, &rtb_y); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.elac_1_bus.discrete_status_word_1, + SecComputer_P.BitfromLabel2_bit_o, &rtb_y_af); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.elac_2_bus.discrete_status_word_1, + SecComputer_P.BitfromLabel3_bit_j, &rtb_y_mx); + if (SecComputer_U.in.bus_inputs.elac_1_bus.roll_spoiler_command_deg.SSM == static_cast(SignStatusMatrix:: + NormalOperation)) { + pair1SpdBrkCommand = SecComputer_U.in.bus_inputs.elac_1_bus.roll_spoiler_command_deg.Data; + rtb_y_l = (rtb_Switch7_c != 0U); + rtb_AND4_a = (rtb_y_af != 0U); + } else if (SecComputer_U.in.bus_inputs.elac_2_bus.roll_spoiler_command_deg.SSM == static_cast + (SignStatusMatrix::NormalOperation)) { + pair1SpdBrkCommand = SecComputer_U.in.bus_inputs.elac_2_bus.roll_spoiler_command_deg.Data; + rtb_y_l = (rtb_y != 0U); + rtb_AND4_a = (rtb_y_mx != 0U); + } else { + pair1SpdBrkCommand = rtb_Switch3 * 35.0 / 25.0; + rtb_y_l = true; + rtb_AND4_a = true; + } + + rtb_zeta_deg = std::fmax(std::fmin(pair1SpdBrkCommand, 35.0), -35.0); + if (SecComputer_U.in.discrete_inputs.is_unit_1) { + if (rtb_y_l) { + pair1RollCommand = rtb_zeta_deg; + } else { + pair1RollCommand = 0.0; + } + + rtb_Switch3 = rtb_zeta_deg; + pair1SpdBrkCommand = rtb_Switch7; + } else if (SecComputer_U.in.discrete_inputs.is_unit_2) { + pair1RollCommand = rtb_zeta_deg; + rtb_Switch3 = 0.0; + pair1SpdBrkCommand = 0.0; + rtb_Switch7 = 0.0; + } else { + pair1RollCommand = 0.0; + if (rtb_AND4_a) { + rtb_Switch3 = rtb_zeta_deg; + } else { + rtb_Switch3 = 0.0; + } + + pair1SpdBrkCommand = 0.0; + rtb_Switch7 /= 2.0; + } + + if (rtb_zeta_deg >= 0.0) { + rtb_zeta_deg = pair1SpdBrkCommand - pair1RollCommand; + pair1RollCommand = rtb_Switch7 - rtb_Switch3; + } else { + rtb_zeta_deg = pair1SpdBrkCommand; + pair1SpdBrkCommand += pair1RollCommand; + pair1RollCommand = rtb_Switch7; + rtb_Switch7 += rtb_Switch3; + } + + if (rtb_NOT_bl) { + rtb_NOT_oi = rtb_Switch4; + } else { + rtb_NOT_oi = std::fmax(rtb_zeta_deg - (pair1SpdBrkCommand - std::fmax(pair1SpdBrkCommand, -50.0)), -50.0); + } + + if (rtb_NOT_oi > SecComputer_P.Saturation_UpperSat_n) { + rtb_NOT_oi = SecComputer_P.Saturation_UpperSat_n; + } else if (rtb_NOT_oi < SecComputer_P.Saturation_LowerSat_n) { + rtb_NOT_oi = SecComputer_P.Saturation_LowerSat_n; + } + + SecComputer_RateLimiter_b(rtb_NOT_oi, SecComputer_P.RateLimiterGenericVariableTs_up, + SecComputer_P.RateLimiterGenericVariableTs_lo, SecComputer_U.in.time.dt, + SecComputer_U.in.analog_inputs.left_spoiler_1_pos_deg, !spoilerPair1SupplyAvail, &rtb_Switch3, + &SecComputer_DWork.sf_RateLimiter_b); + if (rtb_NOT_bl) { + rtb_NOT_oi = rtb_Switch4; + } else { + rtb_NOT_oi = std::fmax(pair1SpdBrkCommand - (rtb_zeta_deg - std::fmax(rtb_zeta_deg, -50.0)), -50.0); + } + + if (rtb_NOT_oi > SecComputer_P.Saturation1_UpperSat_e) { + rtb_NOT_oi = SecComputer_P.Saturation1_UpperSat_e; + } else if (rtb_NOT_oi < SecComputer_P.Saturation1_LowerSat_f) { + rtb_NOT_oi = SecComputer_P.Saturation1_LowerSat_f; + } + + SecComputer_RateLimiter_b(rtb_NOT_oi, SecComputer_P.RateLimiterGenericVariableTs1_up, + SecComputer_P.RateLimiterGenericVariableTs1_lo, SecComputer_U.in.time.dt, + SecComputer_U.in.analog_inputs.right_spoiler_1_pos_deg, !spoilerPair1SupplyAvail, &rtb_zeta_deg, + &SecComputer_DWork.sf_RateLimiter_a); + if (rtb_NOT_bl) { + rtb_NOT_oi = rtb_Switch4; + } else { + rtb_NOT_oi = std::fmax(pair1RollCommand - (rtb_Switch7 - std::fmax(rtb_Switch7, -50.0)), -50.0); + } + + if (rtb_NOT_oi > SecComputer_P.Saturation2_UpperSat) { + rtb_NOT_oi = SecComputer_P.Saturation2_UpperSat; + } else if (rtb_NOT_oi < SecComputer_P.Saturation2_LowerSat) { + rtb_NOT_oi = SecComputer_P.Saturation2_LowerSat; + } + + SecComputer_RateLimiter_b(rtb_NOT_oi, SecComputer_P.RateLimiterGenericVariableTs2_up, + SecComputer_P.RateLimiterGenericVariableTs2_lo, SecComputer_U.in.time.dt, + SecComputer_U.in.analog_inputs.left_spoiler_2_pos_deg, !spoilerPair2SupplyAvail, &pair1SpdBrkCommand, + &SecComputer_DWork.sf_RateLimiter_k); + if (rtb_NOT_bl) { + rtb_Switch7 = rtb_Switch4; + } else { + rtb_Switch7 = std::fmax(rtb_Switch7 - (pair1RollCommand - std::fmax(pair1RollCommand, -50.0)), -50.0); + } + + if (rtb_Switch7 > SecComputer_P.Saturation3_UpperSat) { + rtb_Switch4 = SecComputer_P.Saturation3_UpperSat; + } else if (rtb_Switch7 < SecComputer_P.Saturation3_LowerSat) { + rtb_Switch4 = SecComputer_P.Saturation3_LowerSat; + } else { + rtb_Switch4 = rtb_Switch7; + } + + SecComputer_RateLimiter_b(rtb_Switch4, SecComputer_P.RateLimiterGenericVariableTs3_up, + SecComputer_P.RateLimiterGenericVariableTs3_lo, SecComputer_U.in.time.dt, + SecComputer_U.in.analog_inputs.right_spoiler_2_pos_deg, !spoilerPair2SupplyAvail, &rtb_Switch7, + &SecComputer_DWork.sf_RateLimiter_b4); + rtb_Switch4 = rtb_zeta_deg; + pair1RollCommand = pair1SpdBrkCommand; + rtb_NOT_oi = rtb_Switch7; + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + SecComputer_P.BitfromLabel_bit_a1, &rtb_y_mx); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + SecComputer_P.BitfromLabel1_bit_gf, &rtb_y_af); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + SecComputer_P.BitfromLabel2_bit_n, &rtb_y); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + SecComputer_P.BitfromLabel3_bit_l, &rtb_Switch9_c); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + SecComputer_P.BitfromLabel4_bit_n, &rtb_DataTypeConversion1); + SecComputer_MATLABFunction(&SecComputer_U.in.bus_inputs.sfcc_1_bus.slat_flap_system_status_word, + SecComputer_P.BitfromLabel5_bit_m, &rtb_Switch7_c); + if (rtb_y_mx != 0U) { + rtb_Switch7 = 0.0; + } else if ((rtb_y_af != 0U) && (rtb_Switch7_c != 0U)) { + rtb_Switch7 = 1.0; + } else if ((rtb_y_af != 0U) && (rtb_Switch7_c == 0U)) { + rtb_Switch7 = 2.0; + } else if (rtb_y != 0U) { + rtb_Switch7 = 3.0; + } else if (rtb_Switch9_c != 0U) { + rtb_Switch7 = 4.0; + } else if (rtb_DataTypeConversion1 != 0U) { + rtb_Switch7 = 5.0; + } else { + rtb_Switch7 = 0.0; + } + + pair1SpdBrkCommand = (SecComputer_B.in_flight != 0.0); + rtb_y_l = (rtb_OR3 || ((static_cast(rtb_activePitchLaw) != SecComputer_P.CompareToConstant2_const_f) && ( + static_cast(rtb_activePitchLaw) != SecComputer_P.CompareToConstant3_const_o))); + rtb_AND4_a = (rtb_activePitchLaw != SecComputer_P.EnumeratedConstant_Value_i); + LawMDLOBJ2.step(&SecComputer_U.in.time.dt, &rtb_BusAssignment_f_logic_ir_computation_data_n_z_g, &rtb_Switch5, + &rtb_Switch6_m, &rtb_BusAssignment_f_logic_ir_computation_data_theta_dot_deg_s, (const_cast + (&SecComputer_RGND)), &SecComputer_U.in.analog_inputs.ths_pos_deg, &rtb_eta_trim_limit_lo_d, + &rtb_BusConversion_InsertedFor_BusAssignment_at_inport_8_BusCreator1_mach, + &rtb_BusConversion_InsertedFor_BusAssignment_at_inport_8_BusCreator1_V_tas_kn, &rtb_Switch7, ( + const_cast(&SecComputer_RGND)), (const_cast(&SecComputer_RGND)), &u0, &pair1SpdBrkCommand, + &rtb_y_l, &rtb_AND4_a, &rtb_eta_deg, &rtb_eta_trim_dot_deg_s, &rtb_eta_trim_limit_lo, + &rtb_eta_trim_limit_up); + LawMDLOBJ3.step(&SecComputer_U.in.time.dt, &u0, &rtb_Switch7, &rtb_zeta_deg, &rtb_eta_trim_limit_lo_d, + &pair1SpdBrkCommand); + switch (static_cast(rtb_activePitchLaw)) { + case 1: + case 2: + rtb_Switch7 = rtb_eta_deg; + break; + + case 3: + break; + + default: + rtb_Switch7 = SecComputer_P.Constant1_Value_px; + break; + } + + switch (static_cast(rtb_activePitchLaw)) { + case 1: + case 2: + pair1SpdBrkCommand = rtb_eta_trim_limit_up; + break; + + case 3: + break; + + default: + pair1SpdBrkCommand = SecComputer_P.Constant2_Value_g; + break; + } + + if (rtb_AND2_j) { + rtb_zeta_deg = SecComputer_P.Gain_Gain_m * SecComputer_DWork.Delay_DSTATE; + if (rtb_zeta_deg > SecComputer_P.Saturation_UpperSat) { + rtb_zeta_deg = SecComputer_P.Saturation_UpperSat; + } else if (rtb_zeta_deg < SecComputer_P.Saturation_LowerSat) { + rtb_zeta_deg = SecComputer_P.Saturation_LowerSat; + } + } else if (SecComputer_U.in.discrete_inputs.ths_override_active) { + rtb_zeta_deg = SecComputer_P.Constant_Value_a; + } else { + switch (static_cast(rtb_activePitchLaw)) { + case 1: + case 2: + rtb_zeta_deg = rtb_eta_trim_dot_deg_s; + break; + + case 3: + break; + + default: + rtb_zeta_deg = SecComputer_P.Constant1_Value_px; + break; + } + } + + rtb_zeta_deg = SecComputer_P.DiscreteTimeIntegratorVariableTsLimit_Gain * rtb_zeta_deg * SecComputer_U.in.time.dt; + SecComputer_DWork.icLoad = ((!rtb_y_k4) || SecComputer_DWork.icLoad); + if (SecComputer_DWork.icLoad) { + SecComputer_DWork.Delay_DSTATE_l = SecComputer_U.in.analog_inputs.ths_pos_deg - rtb_zeta_deg; + } + + SecComputer_DWork.Delay_DSTATE = rtb_zeta_deg + SecComputer_DWork.Delay_DSTATE_l; + if (SecComputer_DWork.Delay_DSTATE > pair1SpdBrkCommand) { + SecComputer_DWork.Delay_DSTATE = pair1SpdBrkCommand; + } else { + switch (static_cast(rtb_activePitchLaw)) { + case 1: + case 2: + rtb_eta_trim_limit_lo_d = rtb_eta_trim_limit_lo; + break; + + case 3: + break; + + default: + rtb_eta_trim_limit_lo_d = SecComputer_P.Constant3_Value_i; + break; + } + + if (SecComputer_DWork.Delay_DSTATE < rtb_eta_trim_limit_lo_d) { + SecComputer_DWork.Delay_DSTATE = rtb_eta_trim_limit_lo_d; + } + } + + SecComputer_Y.out.laws.pitch_law_outputs.elevator_command_deg = rtb_Switch7; + if (SecComputer_U.in.discrete_inputs.is_unit_1) { + rtb_Switch8 = SecComputer_U.in.bus_inputs.elac_2_bus.elevator_double_pressurization_command_deg; + } else if (SecComputer_U.in.discrete_inputs.is_unit_2) { + rtb_Switch8 = SecComputer_U.in.bus_inputs.elac_1_bus.elevator_double_pressurization_command_deg; + } else { + rtb_y_fp = std::fmod(std::floor(SecComputer_P.Constant1_Value_m), 4.2949673E+9F); + rtb_Switch8.SSM = rtb_y_fp < 0.0F ? static_cast(-static_cast(static_cast(-rtb_y_fp))) + : static_cast(rtb_y_fp); + rtb_Switch8.Data = SecComputer_P.Constant1_Value_m; + } + + SecComputer_MATLABFunction_l(&rtb_Switch8, &rtb_AND4_a); + if (SecComputer_U.in.discrete_inputs.is_unit_1) { + rtb_y_l = SecComputer_U.in.discrete_inputs.pitch_not_avail_elac_1; + } else { + rtb_y_l = SecComputer_U.in.discrete_inputs.pitch_not_avail_elac_2; + } + + rtb_NOT_bl = (rtb_y_l && (!rtb_isEngagedInPitch) && rtb_AND4_a); + if (rtb_NOT_bl) { + rtb_Switch7 = rtb_Switch8.Data; + } + + SecComputer_MATLABFunction_n(rtb_OR || rtb_NOT2_b || rtb_NOT_g, SecComputer_U.in.time.dt, + SecComputer_P.ConfirmNode_isRisingEdge_c, SecComputer_P.ConfirmNode_timeDelay_m, &rtb_y_am, + &SecComputer_DWork.sf_MATLABFunction_i); + rtb_y_fp = static_cast(SecComputer_U.in.analog_inputs.thr_lever_2_pos); + rtb_VectorConcatenate[0] = (SecComputer_U.in.discrete_inputs.l_spoiler_1_servo_failed || + SecComputer_U.in.discrete_inputs.r_spoiler_1_servo_failed); + rtb_VectorConcatenate[1] = (SecComputer_U.in.discrete_inputs.l_spoiler_2_servo_failed || + SecComputer_U.in.discrete_inputs.r_spoiler_2_servo_failed); + rtb_VectorConcatenate[2] = SecComputer_U.in.discrete_inputs.l_elev_servo_failed; + rtb_VectorConcatenate[3] = SecComputer_U.in.discrete_inputs.r_elev_servo_failed; + rtb_VectorConcatenate[4] = spoilerPair1SupplyAvail; + rtb_VectorConcatenate[5] = spoilerPair2SupplyAvail; + rtb_VectorConcatenate[6] = leftElevatorAvail; + rtb_VectorConcatenate[7] = rightElevatorAvail; + rtb_VectorConcatenate[8] = (rtb_activePitchLaw == pitch_efcs_law::AlternateLaw2); + rtb_VectorConcatenate[9] = ((rtb_activePitchLaw == pitch_efcs_law::AlternateLaw1) || (rtb_activePitchLaw == + pitch_efcs_law::AlternateLaw2)); + rtb_VectorConcatenate[10] = (rtb_activePitchLaw == pitch_efcs_law::DirectLaw); + rtb_VectorConcatenate[11] = rtb_isEngagedInRoll; + rtb_VectorConcatenate[12] = rtb_isEngagedInPitch; + rtb_VectorConcatenate[13] = SecComputer_P.Constant8_Value; + rtb_VectorConcatenate[14] = SecComputer_DWork.Delay1_DSTATE_i; + rtb_VectorConcatenate[15] = rtb_y_b; + rtb_VectorConcatenate[16] = SecComputer_P.Constant8_Value; + rtb_VectorConcatenate[17] = SecComputer_P.Constant8_Value; + rtb_VectorConcatenate[18] = SecComputer_P.Constant8_Value; + SecComputer_MATLABFunction_c(rtb_VectorConcatenate, &rtb_y_h); + rtb_VectorConcatenate[0] = SecComputer_P.Constant7_Value; + rtb_VectorConcatenate[1] = SecComputer_P.Constant7_Value; + rtb_VectorConcatenate[2] = SecComputer_DWork.pLeftStickDisabled; + rtb_VectorConcatenate[3] = SecComputer_DWork.pRightStickDisabled; + rtb_VectorConcatenate[4] = SecComputer_DWork.Delay_DSTATE_c; + rtb_VectorConcatenate[5] = SecComputer_DWork.Delay1_DSTATE; + rtb_VectorConcatenate[6] = rtb_OR6; + rtb_VectorConcatenate[7] = rtb_BusAssignment_n_logic_any_landing_gear_not_uplocked; + rtb_VectorConcatenate[8] = SecComputer_P.Constant10_Value; + rtb_VectorConcatenate[9] = SecComputer_P.Constant10_Value; + rtb_VectorConcatenate[10] = SecComputer_P.Constant10_Value; + rtb_VectorConcatenate[11] = SecComputer_P.Constant10_Value; + rtb_VectorConcatenate[12] = SecComputer_P.Constant10_Value; + rtb_VectorConcatenate[13] = SecComputer_P.Constant10_Value; + rtb_VectorConcatenate[14] = SecComputer_P.Constant10_Value; + rtb_VectorConcatenate[15] = SecComputer_P.Constant10_Value; + rtb_VectorConcatenate[16] = SecComputer_P.Constant10_Value; + rtb_VectorConcatenate[17] = SecComputer_P.Constant10_Value; + rtb_VectorConcatenate[18] = SecComputer_P.Constant10_Value; + SecComputer_MATLABFunction_c(rtb_VectorConcatenate, &rtb_y_fp); + SecComputer_Y.out.data = SecComputer_U.in; + SecComputer_Y.out.laws.lateral_law_outputs.left_spoiler_1_command_deg = rtb_Switch3; + SecComputer_Y.out.laws.lateral_law_outputs.right_spoiler_1_command_deg = rtb_Switch4; + SecComputer_Y.out.laws.lateral_law_outputs.left_spoiler_2_command_deg = pair1RollCommand; + SecComputer_Y.out.laws.lateral_law_outputs.right_spoiler_2_command_deg = rtb_NOT_oi; + SecComputer_Y.out.laws.pitch_law_outputs.ths_command_deg = SecComputer_DWork.Delay_DSTATE; + SecComputer_Y.out.logic.on_ground = rtb_OR1; + SecComputer_Y.out.logic.pitch_law_in_flight = (SecComputer_B.in_flight != 0.0); + SecComputer_Y.out.logic.tracking_mode_on = rtb_OR3; + SecComputer_Y.out.logic.pitch_law_capability = rtb_pitchLawCapability; + SecComputer_Y.out.logic.active_pitch_law = rtb_activePitchLaw; + SecComputer_Y.out.logic.abnormal_condition_law_active = abnormalCondition; + SecComputer_Y.out.logic.is_engaged_in_pitch = rtb_isEngagedInPitch; + SecComputer_Y.out.logic.can_engage_in_pitch = canEngageInPitch; + SecComputer_Y.out.logic.has_priority_in_pitch = hasPriorityInPitch; + SecComputer_Y.out.logic.left_elevator_avail = leftElevatorAvail; + SecComputer_Y.out.logic.right_elevator_avail = rightElevatorAvail; + SecComputer_Y.out.logic.ths_avail = rtb_thsAvail; + SecComputer_Y.out.logic.ths_active_commanded = rtb_y_k4; + SecComputer_Y.out.logic.ths_ground_setting_active = rtb_AND2_j; + SecComputer_Y.out.logic.is_engaged_in_roll = rtb_isEngagedInRoll; + SecComputer_Y.out.logic.spoiler_pair_1_avail = spoilerPair1SupplyAvail; + SecComputer_Y.out.logic.spoiler_pair_2_avail = spoilerPair2SupplyAvail; + SecComputer_Y.out.logic.is_yellow_hydraulic_power_avail = rtb_OR; + SecComputer_Y.out.logic.is_blue_hydraulic_power_avail = rtb_NOT2_b; + SecComputer_Y.out.logic.is_green_hydraulic_power_avail = rtb_NOT_g; + SecComputer_Y.out.logic.left_sidestick_disabled = SecComputer_DWork.pLeftStickDisabled; + SecComputer_Y.out.logic.right_sidestick_disabled = SecComputer_DWork.pRightStickDisabled; + SecComputer_Y.out.logic.left_sidestick_priority_locked = SecComputer_DWork.Delay_DSTATE_c; + SecComputer_Y.out.logic.right_sidestick_priority_locked = SecComputer_DWork.Delay1_DSTATE; + SecComputer_Y.out.logic.total_sidestick_pitch_command = u0; + SecComputer_Y.out.logic.ground_spoilers_armed = rtb_y_b; + SecComputer_Y.out.logic.ground_spoilers_out = SecComputer_DWork.Delay1_DSTATE_i; + SecComputer_Y.out.logic.partial_lift_dumping_active = rtb_AND1_h; + SecComputer_Y.out.logic.speed_brake_inhibited = rtb_y_m; + SecComputer_Y.out.logic.single_adr_failure = rtb_singleAdrFault; + SecComputer_Y.out.logic.double_adr_failure = rtb_doubleAdrFault; + SecComputer_Y.out.logic.cas_or_mach_disagree = SecComputer_P.Constant2_Value_c; + SecComputer_Y.out.logic.alpha_disagree = SecComputer_P.Constant2_Value_c; + SecComputer_Y.out.logic.single_ir_failure = rtb_singleIrFault; + SecComputer_Y.out.logic.double_ir_failure = rtb_doubleIrFault; + SecComputer_Y.out.logic.ir_disagree = SecComputer_P.Constant_Value_l; + SecComputer_Y.out.logic.adr_computation_data.V_ias_kn = rtb_V_ias; + SecComputer_Y.out.logic.adr_computation_data.V_tas_kn = rtb_V_tas; + SecComputer_Y.out.logic.adr_computation_data.mach = rtb_mach; + SecComputer_Y.out.logic.adr_computation_data.alpha_deg = rtb_alpha; + SecComputer_Y.out.logic.ir_computation_data.theta_deg = rtb_theta; + SecComputer_Y.out.logic.ir_computation_data.phi_deg = rtb_phi; + SecComputer_Y.out.logic.ir_computation_data.q_deg_s = rtb_q; + SecComputer_Y.out.logic.ir_computation_data.r_deg_s = rtb_r; + SecComputer_Y.out.logic.ir_computation_data.n_x_g = rtb_n_x; + SecComputer_Y.out.logic.ir_computation_data.n_y_g = rtb_n_y; + SecComputer_Y.out.logic.ir_computation_data.n_z_g = rtb_n_z; + SecComputer_Y.out.logic.ir_computation_data.theta_dot_deg_s = rtb_theta_dot; + SecComputer_Y.out.logic.ir_computation_data.phi_dot_deg_s = rtb_phi_dot; + SecComputer_Y.out.logic.any_landing_gear_not_uplocked = rtb_BusAssignment_n_logic_any_landing_gear_not_uplocked; + SecComputer_Y.out.logic.lgciu_uplock_disagree_or_fault = rtb_OR6; + SecComputer_Y.out.discrete_outputs.thr_reverse_selected = SecComputer_P.Constant1_Value_g; + SecComputer_Y.out.discrete_outputs.left_elevator_ok = leftElevatorAvail; + SecComputer_Y.out.discrete_outputs.right_elevator_ok = rightElevatorAvail; + SecComputer_Y.out.discrete_outputs.ground_spoiler_out = SecComputer_DWork.Delay1_DSTATE_i; + SecComputer_Y.out.discrete_outputs.sec_failed = SecComputer_P.Constant2_Value_n; + SecComputer_Y.out.discrete_outputs.left_elevator_damping_mode = (rtb_isEngagedInPitch && leftElevatorAvail); + SecComputer_Y.out.discrete_outputs.right_elevator_damping_mode = (rtb_isEngagedInPitch && rightElevatorAvail); + SecComputer_Y.out.discrete_outputs.ths_active = (rtb_y_k4 && rtb_thsAvail); + SecComputer_Y.out.discrete_outputs.batt_power_supply = rtb_y_am; + rtb_y_l = (rtb_isEngagedInPitch || rtb_NOT_bl); + if (rtb_y_l && leftElevatorAvail) { + SecComputer_Y.out.analog_outputs.left_elev_pos_order_deg = rtb_Switch7; + } else { + SecComputer_Y.out.analog_outputs.left_elev_pos_order_deg = SecComputer_P.Constant_Value_h; + } + + if (rtb_y_l && rightElevatorAvail) { + SecComputer_Y.out.analog_outputs.right_elev_pos_order_deg = rtb_Switch7; + } else { + SecComputer_Y.out.analog_outputs.right_elev_pos_order_deg = SecComputer_P.Constant_Value_h; + } + + if (rtb_y_k4 && rtb_thsAvail) { + SecComputer_Y.out.analog_outputs.ths_pos_order_deg = SecComputer_DWork.Delay_DSTATE; + } else { + SecComputer_Y.out.analog_outputs.ths_pos_order_deg = SecComputer_P.Constant_Value_h; + } + + if (spoilerPair1SupplyAvail) { + SecComputer_Y.out.analog_outputs.left_spoiler_1_pos_order_deg = rtb_Switch3; + SecComputer_Y.out.analog_outputs.right_spoiler_1_pos_order_deg = rtb_Switch4; + } else { + SecComputer_Y.out.analog_outputs.left_spoiler_1_pos_order_deg = SecComputer_P.Constant2_Value_f; + SecComputer_Y.out.analog_outputs.right_spoiler_1_pos_order_deg = SecComputer_P.Constant2_Value_f; + } + + if (spoilerPair2SupplyAvail) { + SecComputer_Y.out.analog_outputs.left_spoiler_2_pos_order_deg = pair1RollCommand; + SecComputer_Y.out.analog_outputs.right_spoiler_2_pos_order_deg = rtb_NOT_oi; + } else { + SecComputer_Y.out.analog_outputs.left_spoiler_2_pos_order_deg = SecComputer_P.Constant2_Value_f; + SecComputer_Y.out.analog_outputs.right_spoiler_2_pos_order_deg = SecComputer_P.Constant2_Value_f; + } + + if (SecComputer_U.in.discrete_inputs.l_spoiler_1_servo_failed) { + SecComputer_Y.out.bus_outputs.left_spoiler_1_position_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant_Value); + SecComputer_Y.out.bus_outputs.left_spoiler_1_position_deg.Data = static_cast + (SecComputer_P.Constant_Value_j); + } else { + SecComputer_Y.out.bus_outputs.left_spoiler_1_position_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.left_spoiler_1_position_deg.Data = static_cast + (SecComputer_U.in.analog_inputs.left_spoiler_1_pos_deg); + } + + if (SecComputer_U.in.discrete_inputs.r_spoiler_1_servo_failed) { + SecComputer_Y.out.bus_outputs.right_spoiler_1_position_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant_Value); + SecComputer_Y.out.bus_outputs.right_spoiler_1_position_deg.Data = static_cast + (SecComputer_P.Constant1_Value_d); + } else { + SecComputer_Y.out.bus_outputs.right_spoiler_1_position_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.right_spoiler_1_position_deg.Data = static_cast + (SecComputer_U.in.analog_inputs.right_spoiler_1_pos_deg); + } + + if (SecComputer_U.in.discrete_inputs.l_spoiler_2_servo_failed) { + SecComputer_Y.out.bus_outputs.left_spoiler_2_position_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant_Value); + SecComputer_Y.out.bus_outputs.left_spoiler_2_position_deg.Data = static_cast + (SecComputer_P.Constant5_Value_m); + } else { + SecComputer_Y.out.bus_outputs.left_spoiler_2_position_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.left_spoiler_2_position_deg.Data = static_cast + (SecComputer_U.in.analog_inputs.left_spoiler_2_pos_deg); + } + + if (SecComputer_U.in.discrete_inputs.r_spoiler_2_servo_failed) { + SecComputer_Y.out.bus_outputs.right_spoiler_2_position_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant_Value); + SecComputer_Y.out.bus_outputs.right_spoiler_2_position_deg.Data = static_cast + (SecComputer_P.Constant6_Value); + } else { + SecComputer_Y.out.bus_outputs.right_spoiler_2_position_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.right_spoiler_2_position_deg.Data = static_cast + (SecComputer_U.in.analog_inputs.right_spoiler_2_pos_deg); + } + + if (SecComputer_U.in.discrete_inputs.l_elev_servo_failed) { + SecComputer_Y.out.bus_outputs.left_elevator_position_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant_Value); + SecComputer_Y.out.bus_outputs.left_elevator_position_deg.Data = static_cast + (SecComputer_P.Constant2_Value_b); + } else { + SecComputer_Y.out.bus_outputs.left_elevator_position_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.left_elevator_position_deg.Data = static_cast + (SecComputer_U.in.analog_inputs.left_elevator_pos_deg); + } + + if (SecComputer_U.in.discrete_inputs.r_elev_servo_failed) { + SecComputer_Y.out.bus_outputs.right_elevator_position_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant_Value); + SecComputer_Y.out.bus_outputs.right_elevator_position_deg.Data = static_cast + (SecComputer_P.Constant3_Value_f); + } else { + SecComputer_Y.out.bus_outputs.right_elevator_position_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.right_elevator_position_deg.Data = static_cast + (SecComputer_U.in.analog_inputs.right_elevator_pos_deg); + } + + if (SecComputer_U.in.discrete_inputs.ths_motor_fault) { + SecComputer_Y.out.bus_outputs.ths_position_deg.SSM = static_cast(SecComputer_P.EnumeratedConstant_Value); + SecComputer_Y.out.bus_outputs.ths_position_deg.Data = static_cast(SecComputer_P.Constant4_Value_i); + } else { + SecComputer_Y.out.bus_outputs.ths_position_deg.SSM = static_cast(SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.ths_position_deg.Data = static_cast + (SecComputer_U.in.analog_inputs.ths_pos_deg); + } + + SecComputer_Y.out.bus_outputs.left_sidestick_pitch_command_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.left_sidestick_pitch_command_deg.Data = SecComputer_P.Gain_Gain_b * + static_cast(SecComputer_U.in.analog_inputs.capt_pitch_stick_pos); + SecComputer_Y.out.bus_outputs.right_sidestick_pitch_command_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.right_sidestick_pitch_command_deg.Data = SecComputer_P.Gain1_Gain * + static_cast(SecComputer_U.in.analog_inputs.fo_pitch_stick_pos); + SecComputer_Y.out.bus_outputs.left_sidestick_roll_command_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.left_sidestick_roll_command_deg.Data = SecComputer_P.Gain2_Gain * static_cast + (SecComputer_U.in.analog_inputs.capt_roll_stick_pos); + SecComputer_Y.out.bus_outputs.right_sidestick_roll_command_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.right_sidestick_roll_command_deg.Data = SecComputer_P.Gain3_Gain * + static_cast(SecComputer_U.in.analog_inputs.fo_roll_stick_pos); + SecComputer_Y.out.bus_outputs.speed_brake_lever_command_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.speed_brake_lever_command_deg.Data = SecComputer_P.Gain4_Gain * static_cast + (SecComputer_U.in.analog_inputs.spd_brk_lever_pos); + SecComputer_Y.out.bus_outputs.thrust_lever_angle_1_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.thrust_lever_angle_1_deg.Data = static_cast + (SecComputer_U.in.analog_inputs.thr_lever_1_pos); + SecComputer_Y.out.bus_outputs.thrust_lever_angle_2_deg.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.thrust_lever_angle_2_deg.Data = static_cast + (SecComputer_U.in.analog_inputs.thr_lever_2_pos); + SecComputer_Y.out.bus_outputs.discrete_status_word_1.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.discrete_status_word_1.Data = rtb_y_h; + SecComputer_Y.out.bus_outputs.discrete_status_word_2.SSM = static_cast + (SecComputer_P.EnumeratedConstant1_Value); + SecComputer_Y.out.bus_outputs.discrete_status_word_2.Data = rtb_y_fp; + SecComputer_DWork.icLoad = false; + SecComputer_DWork.Delay_DSTATE_l = SecComputer_DWork.Delay_DSTATE; + } else { + SecComputer_DWork.Runtime_MODE = false; + } +} + +void SecComputer::initialize() +{ + SecComputer_DWork.Delay_DSTATE_c = SecComputer_P.Delay_InitialCondition_c; + SecComputer_DWork.Delay1_DSTATE = SecComputer_P.Delay1_InitialCondition; + SecComputer_DWork.Memory_PreviousInput = SecComputer_P.SRFlipFlop_initial_condition; + SecComputer_DWork.Memory_PreviousInput_f = SecComputer_P.SRFlipFlop_initial_condition_c; + SecComputer_DWork.Memory_PreviousInput_n = SecComputer_P.SRFlipFlop_initial_condition_k; + SecComputer_DWork.Delay1_DSTATE_i = SecComputer_P.Delay1_InitialCondition_l; + SecComputer_DWork.Delay_DSTATE_n = SecComputer_P.Delay_InitialCondition_j; + SecComputer_DWork.Delay_DSTATE = SecComputer_P.Delay_InitialCondition; + SecComputer_DWork.icLoad = true; + LawMDLOBJ2.init(); + SecComputer_Y.out = SecComputer_P.out_Y0; +} + +void SecComputer::terminate() +{ +} + +SecComputer::SecComputer(): + SecComputer_U(), + SecComputer_Y(), + SecComputer_B(), + SecComputer_DWork() +{ +} + +SecComputer::~SecComputer() +{ +} diff --git a/src/fbw/src/model/SecComputer.h b/src/fbw/src/model/SecComputer.h new file mode 100644 index 000000000000..ae7935deeffc --- /dev/null +++ b/src/fbw/src/model/SecComputer.h @@ -0,0 +1,333 @@ +#ifndef RTW_HEADER_SecComputer_h_ +#define RTW_HEADER_SecComputer_h_ +#include "rtwtypes.h" +#include "SecComputer_types.h" +#include "LateralDirectLaw.h" +#include "PitchAlternateLaw.h" +#include "PitchDirectLaw.h" + +extern const real_T SecComputer_RGND; +extern base_sec_logic_outputs rtP_sec_logic_output_MATLABStruct; +extern base_sec_analog_outputs rtP_sec_analog_output_MATLABStruct; +extern base_sec_laws_outputs rtP_sec_laws_output_MATLABStruct; +extern base_sec_discrete_outputs rtP_sec_discrete_output_MATLABStruct; +class SecComputer final +{ + public: + struct rtDW_RateLimiter_SecComputer_T { + real_T pY; + boolean_T pY_not_empty; + }; + + struct rtDW_RateLimiter_SecComputer_m_T { + real_T pY; + boolean_T pY_not_empty; + }; + + struct rtDW_MATLABFunction_SecComputer_l_T { + boolean_T previousInput; + boolean_T previousInput_not_empty; + }; + + struct rtDW_MATLABFunction_SecComputer_o_T { + real_T timeSinceCondition; + boolean_T output; + }; + + struct BlockIO_SecComputer_T { + real_T in_flight; + }; + + struct D_Work_SecComputer_T { + real_T Delay_DSTATE; + real_T Delay_DSTATE_l; + real_T on_ground_time; + boolean_T Delay_DSTATE_c; + boolean_T Delay1_DSTATE; + boolean_T Delay1_DSTATE_i; + boolean_T Delay_DSTATE_n; + uint8_T is_active_c30_SecComputer; + uint8_T is_c30_SecComputer; + uint8_T is_active_c8_SecComputer; + uint8_T is_c8_SecComputer; + boolean_T Memory_PreviousInput; + boolean_T Memory_PreviousInput_f; + boolean_T Memory_PreviousInput_n; + boolean_T icLoad; + boolean_T pLeftStickDisabled; + boolean_T pRightStickDisabled; + boolean_T abnormalConditionWasActive; + boolean_T Runtime_MODE; + rtDW_MATLABFunction_SecComputer_o_T sf_MATLABFunction_i; + rtDW_MATLABFunction_SecComputer_l_T sf_MATLABFunction_b4; + rtDW_MATLABFunction_SecComputer_o_T sf_MATLABFunction_fh; + rtDW_MATLABFunction_SecComputer_l_T sf_MATLABFunction_nu; + rtDW_MATLABFunction_SecComputer_l_T sf_MATLABFunction_g4b; + rtDW_MATLABFunction_SecComputer_o_T sf_MATLABFunction_j2; + rtDW_MATLABFunction_SecComputer_o_T sf_MATLABFunction_g24; + rtDW_MATLABFunction_SecComputer_o_T sf_MATLABFunction_k4; + rtDW_MATLABFunction_SecComputer_o_T sf_MATLABFunction_dw; + rtDW_MATLABFunction_SecComputer_o_T sf_MATLABFunction_jk; + rtDW_MATLABFunction_SecComputer_o_T sf_MATLABFunction_h; + rtDW_MATLABFunction_SecComputer_o_T sf_MATLABFunction_gf; + rtDW_MATLABFunction_SecComputer_o_T sf_MATLABFunction_ndv; + rtDW_MATLABFunction_SecComputer_l_T sf_MATLABFunction_nd; + rtDW_MATLABFunction_SecComputer_l_T sf_MATLABFunction_n; + rtDW_MATLABFunction_SecComputer_l_T sf_MATLABFunction_a; + rtDW_MATLABFunction_SecComputer_l_T sf_MATLABFunction_e3; + rtDW_RateLimiter_SecComputer_m_T sf_RateLimiter_b4; + rtDW_RateLimiter_SecComputer_m_T sf_RateLimiter_k; + rtDW_RateLimiter_SecComputer_m_T sf_RateLimiter_a; + rtDW_RateLimiter_SecComputer_m_T sf_RateLimiter_b; + rtDW_RateLimiter_SecComputer_T sf_RateLimiter_c; + rtDW_RateLimiter_SecComputer_T sf_RateLimiter; + }; + + struct ExternalInputs_SecComputer_T { + sec_inputs in; + }; + + struct ExternalOutputs_SecComputer_T { + sec_outputs out; + }; + + struct Parameters_SecComputer_T { + real_T DiscreteTimeIntegratorVariableTsLimit_Gain; + real_T RateLimiterVariableTs6_InitialCondition; + real_T RateLimiterVariableTs1_InitialCondition; + real_T BitfromLabel13_bit; + real_T BitfromLabel12_bit; + real_T BitfromLabel10_bit; + real_T BitfromLabel5_bit; + real_T BitfromLabel4_bit; + real_T BitfromLabel2_bit; + real_T BitfromLabel15_bit; + real_T BitfromLabel14_bit; + real_T BitfromLabel11_bit; + real_T BitfromLabel8_bit; + real_T BitfromLabel16_bit; + real_T BitfromLabel7_bit; + real_T BitfromLabel6_bit; + real_T BitfromLabel3_bit; + real_T BitfromLabel1_bit; + real_T BitfromLabel9_bit; + real_T BitfromLabel4_bit_c; + real_T BitfromLabel6_bit_l; + real_T BitfromLabel5_bit_a; + real_T BitfromLabel7_bit_m; + real_T BitfromLabel_bit; + real_T BitfromLabel1_bit_g; + real_T BitfromLabel2_bit_k; + real_T BitfromLabel3_bit_o; + real_T BitfromLabel7_bit_g; + real_T BitfromLabel6_bit_f; + real_T BitfromLabel_bit_l; + real_T BitfromLabel1_bit_p; + real_T BitfromLabel5_bit_p; + real_T BitfromLabel4_bit_e; + real_T BitfromLabel3_bit_oz; + real_T BitfromLabel2_bit_p; + real_T BitfromLabel4_bit_a; + real_T BitfromLabel6_bit_d; + real_T BitfromLabel5_bit_i; + real_T BitfromLabel7_bit_ms; + real_T BitfromLabel_bit_g; + real_T BitfromLabel2_bit_l; + real_T BitfromLabel1_bit_a; + real_T BitfromLabel3_bit_m; + real_T BitfromLabel4_bit_m; + real_T BitfromLabel5_bit_h; + real_T BitfromLabel_bit_a; + real_T BitfromLabel1_bit_c; + real_T BitfromLabel2_bit_o; + real_T BitfromLabel3_bit_j; + real_T BitfromLabel_bit_a1; + real_T BitfromLabel1_bit_gf; + real_T BitfromLabel2_bit_n; + real_T BitfromLabel3_bit_l; + real_T BitfromLabel4_bit_n; + real_T BitfromLabel5_bit_m; + real_T CompareToConstant1_const; + real_T CompareToConstant3_const; + real_T CompareToConstant4_const; + real_T CompareToConstant_const; + real_T CompareToConstant11_const; + real_T CompareToConstant12_const; + real_T CompareToConstant5_const; + real_T CompareToConstant6_const; + real_T CompareToConstant_const_m; + real_T CompareToConstant15_const; + real_T CompareToConstant1_const_l; + real_T CompareToConstant2_const; + real_T CompareToConstant3_const_a; + real_T CompareToConstant4_const_j; + real_T CompareToConstant13_const; + real_T CompareToConstant14_const; + real_T CompareToConstant10_const; + real_T CompareToConstant7_const; + real_T CompareToConstant16_const; + real_T CompareToConstant17_const; + real_T CompareToConstant18_const; + real_T CompareToConstant8_const; + real_T CompareToConstant9_const; + real_T CompareToConstant2_const_f; + real_T CompareToConstant3_const_o; + real_T CompareToConstant1_const_p; + real_T RateLimiterVariableTs6_lo; + real_T RateLimiterVariableTs1_lo; + real_T RateLimiterGenericVariableTs_lo; + real_T RateLimiterGenericVariableTs1_lo; + real_T RateLimiterGenericVariableTs2_lo; + real_T RateLimiterGenericVariableTs3_lo; + real_T ConfirmNode_timeDelay; + real_T ConfirmNode1_timeDelay; + real_T ConfirmNode_timeDelay_c; + real_T ConfirmNode1_timeDelay_k; + real_T ConfirmNode2_timeDelay; + real_T ConfirmNode1_timeDelay_a; + real_T ConfirmNode_timeDelay_a; + real_T ConfirmNode_timeDelay_e; + real_T ConfirmNode_timeDelay_eq; + real_T ConfirmNode_timeDelay_m; + real_T RateLimiterVariableTs6_up; + real_T RateLimiterVariableTs1_up; + real_T RateLimiterGenericVariableTs_up; + real_T RateLimiterGenericVariableTs1_up; + real_T RateLimiterGenericVariableTs2_up; + real_T RateLimiterGenericVariableTs3_up; + SignStatusMatrix EnumeratedConstant_Value; + SignStatusMatrix EnumeratedConstant1_Value; + pitch_efcs_law EnumeratedConstant_Value_f; + pitch_efcs_law EnumeratedConstant_Value_i; + boolean_T SRFlipFlop_initial_condition; + boolean_T SRFlipFlop_initial_condition_c; + boolean_T SRFlipFlop_initial_condition_k; + boolean_T ConfirmNode_isRisingEdge; + boolean_T ConfirmNode1_isRisingEdge; + boolean_T ConfirmNode_isRisingEdge_a; + boolean_T ConfirmNode1_isRisingEdge_j; + boolean_T ConfirmNode2_isRisingEdge; + boolean_T PulseNode_isRisingEdge; + boolean_T PulseNode1_isRisingEdge; + boolean_T ConfirmNode1_isRisingEdge_k; + boolean_T ConfirmNode_isRisingEdge_j; + boolean_T ConfirmNode_isRisingEdge_g; + boolean_T PulseNode_isRisingEdge_h; + boolean_T ConfirmNode_isRisingEdge_e; + boolean_T PulseNode3_isRisingEdge; + boolean_T PulseNode2_isRisingEdge; + boolean_T PulseNode1_isRisingEdge_k; + boolean_T PulseNode_isRisingEdge_hj; + boolean_T ConfirmNode_isRisingEdge_c; + sec_outputs out_Y0; + base_sec_out_bus Constant4_Value; + real_T Constant_Value; + real_T Constant1_Value; + real_T Constant2_Value; + real_T Constant3_Value; + real_T Gain_Gain; + real_T Constant4_Value_k; + real_T Constant5_Value; + real_T uDLookupTable_tableData[5]; + real_T uDLookupTable_bp01Data[5]; + real_T Constant2_Value_g; + real_T Constant3_Value_i; + real_T Gain_Gain_m; + real_T Saturation_UpperSat; + real_T Saturation_LowerSat; + real_T Constant_Value_a; + real_T Constant5_Value_m; + real_T Constant6_Value; + real_T Constant1_Value_d; + real_T Constant2_Value_b; + real_T Constant3_Value_f; + real_T Constant4_Value_i; + real_T Constant_Value_j; + real_T Constant_Value_m; + real_T Constant_Value_p; + real_T Saturation_UpperSat_d; + real_T Saturation_LowerSat_h; + real_T Constant1_Value_p; + real_T Saturation1_UpperSat; + real_T Saturation1_LowerSat; + real_T Saturation_UpperSat_n; + real_T Saturation_LowerSat_n; + real_T Saturation1_UpperSat_e; + real_T Saturation1_LowerSat_f; + real_T Saturation2_UpperSat; + real_T Saturation2_LowerSat; + real_T Saturation3_UpperSat; + real_T Saturation3_LowerSat; + real_T Constant1_Value_px; + real_T Delay_InitialCondition; + real_T Constant_Value_h; + real_T Constant2_Value_f; + real32_T Constant1_Value_m; + real32_T Gain_Gain_b; + real32_T Gain1_Gain; + real32_T Gain2_Gain; + real32_T Gain3_Gain; + real32_T Gain4_Gain; + boolean_T Constant2_Value_c; + boolean_T Constant_Value_l; + boolean_T Delay_InitialCondition_c; + boolean_T Delay1_InitialCondition; + boolean_T Logic_table[16]; + boolean_T Logic_table_i[16]; + boolean_T Logic_table_ii[16]; + boolean_T Delay1_InitialCondition_l; + boolean_T Delay_InitialCondition_j; + boolean_T Constant1_Value_g; + boolean_T Constant2_Value_n; + boolean_T Constant8_Value; + boolean_T Constant7_Value; + boolean_T Constant10_Value; + }; + + SecComputer(SecComputer const&) = delete; + SecComputer& operator= (SecComputer const&) & = delete; + SecComputer(SecComputer &&) = delete; + SecComputer& operator= (SecComputer &&) = delete; + void setExternalInputs(const ExternalInputs_SecComputer_T *pExternalInputs_SecComputer_T) + { + SecComputer_U = *pExternalInputs_SecComputer_T; + } + + const ExternalOutputs_SecComputer_T &getExternalOutputs() const + { + return SecComputer_Y; + } + + void initialize(); + void step(); + static void terminate(); + SecComputer(); + ~SecComputer(); + private: + ExternalInputs_SecComputer_T SecComputer_U; + ExternalOutputs_SecComputer_T SecComputer_Y; + BlockIO_SecComputer_T SecComputer_B; + D_Work_SecComputer_T SecComputer_DWork; + static Parameters_SecComputer_T SecComputer_P; + static void SecComputer_MATLABFunction(const base_arinc_429 *rtu_u, real_T rtu_bit, uint32_T *rty_y); + static void SecComputer_RateLimiter_Reset(rtDW_RateLimiter_SecComputer_T *localDW); + static void SecComputer_RateLimiter(real_T rtu_u, real_T rtu_up, real_T rtu_lo, real_T rtu_Ts, real_T rtu_init, real_T + *rty_Y, rtDW_RateLimiter_SecComputer_T *localDW); + static void SecComputer_RateLimiter_n_Reset(rtDW_RateLimiter_SecComputer_m_T *localDW); + static void SecComputer_RateLimiter_b(real_T rtu_u, real_T rtu_up, real_T rtu_lo, real_T rtu_Ts, real_T rtu_init, + boolean_T rtu_reset, real_T *rty_Y, rtDW_RateLimiter_SecComputer_m_T *localDW); + static void SecComputer_MATLABFunction_g_Reset(rtDW_MATLABFunction_SecComputer_l_T *localDW); + static void SecComputer_MATLABFunction_e(boolean_T rtu_u, boolean_T rtu_isRisingEdge, boolean_T *rty_y, + rtDW_MATLABFunction_SecComputer_l_T *localDW); + static void SecComputer_MATLABFunction_e_Reset(rtDW_MATLABFunction_SecComputer_o_T *localDW); + static void SecComputer_MATLABFunction_n(boolean_T rtu_u, real_T rtu_Ts, boolean_T rtu_isRisingEdge, real_T + rtu_timeDelay, boolean_T *rty_y, rtDW_MATLABFunction_SecComputer_o_T *localDW); + static void SecComputer_MATLABFunction_l(const base_arinc_429 *rtu_u, boolean_T *rty_y); + static void SecComputer_MATLABFunction_c(const boolean_T rtu_u[19], real32_T *rty_y); + LateralDirectLaw LawMDLOBJ1; + PitchAlternateLaw LawMDLOBJ2; + PitchDirectLaw LawMDLOBJ3; +}; + +#endif + diff --git a/src/fbw/src/model/SecComputer_data.cpp b/src/fbw/src/model/SecComputer_data.cpp new file mode 100644 index 000000000000..aefd98e4ef36 --- /dev/null +++ b/src/fbw/src/model/SecComputer_data.cpp @@ -0,0 +1,1729 @@ +#include "SecComputer.h" + +base_sec_logic_outputs rtP_sec_logic_output_MATLABStruct{ + false, + false, + false, + pitch_efcs_law::None, + pitch_efcs_law::None, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + 0.0, + 0.0, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + + { + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + }, + false, + false +} ; + +base_sec_analog_outputs rtP_sec_analog_output_MATLABStruct{ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 +} ; + +base_sec_laws_outputs rtP_sec_laws_output_MATLABStruct{ + { + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + 0.0, + 0.0 + } +} ; + +base_sec_discrete_outputs rtP_sec_discrete_output_MATLABStruct{ + false, + false, + false, + false, + false, + false, + false, + false, + false +} ; + +SecComputer::Parameters_SecComputer_T SecComputer::SecComputer_P{ + + 1.0, + + 0.0, + + 0.0, + + 11.0, + + 12.0, + + 13.0, + + 11.0, + + 12.0, + + 13.0, + + 14.0, + + 11.0, + + 12.0, + + 13.0, + + 14.0, + + 14.0, + + 11.0, + + 12.0, + + 13.0, + + 14.0, + + 14.0, + + 14.0, + + 13.0, + + 13.0, + + 20.0, + + 20.0, + + 20.0, + + 20.0, + + 23.0, + + 23.0, + + 23.0, + + 23.0, + + 17.0, + + 18.0, + + 17.0, + + 18.0, + + 13.0, + + 14.0, + + 13.0, + + 14.0, + + 13.0, + + 14.0, + + 13.0, + + 14.0, + + 22.0, + + 22.0, + + 28.0, + + 28.0, + + 29.0, + + 29.0, + + 17.0, + + 18.0, + + 19.0, + + 20.0, + + 21.0, + + 26.0, + + 0.02, + + 35.0, + + 35.0, + + 0.05, + + 23.0, + + 23.0, + + 72.0, + + 72.0, + + -0.02, + + 0.05, + + 0.0, + + 0.0, + + 0.0, + + 35.0, + + 35.0, + + 0.0, + + 0.05, + + 0.0, + + 0.0, + + 0.0, + + 35.0, + + 35.0, + + 0.0, + + 1.0, + + 2.0, + + 3.0, + + -20.0, + + -5.0, + + -50.0, + + -50.0, + + -50.0, + + -50.0, + + 10.0, + + 10.0, + + 0.5, + + 0.5, + + 0.5, + + 30.0, + + 30.0, + + 0.2, + + 10.0, + + 30.0, + + 20.0, + + 5.0, + + 50.0, + + 50.0, + + 50.0, + + 50.0, + + SignStatusMatrix::NoComputedData, + + SignStatusMatrix::NormalOperation, + + pitch_efcs_law::DirectLaw, + + pitch_efcs_law::AlternateLaw2, + + false, + + false, + + false, + + true, + + true, + + true, + + true, + + true, + + true, + + true, + + true, + + true, + + false, + + false, + + true, + + true, + + false, + + true, + + true, + + false, + + + { + { + { + 0.0, + 0.0, + 0.0 + }, + + { + false, + false, + false, + false, + false + }, + + { + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false + }, + + { + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + } + } + }, + + { + { + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + 0.0, + 0.0 + } + }, + + { + false, + false, + false, + pitch_efcs_law::NormalLaw, + pitch_efcs_law::NormalLaw, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + 0.0, + 0.0, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + + { + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + }, + false, + false + }, + + { + false, + false, + false, + false, + false, + false, + false, + false, + false + }, + + { + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + }, + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + } + }, + + + { + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + }, + + { + 0U, + 0.0F + } + }, + + -50.0, + + -10.0, + + 0.0, + + 0.0, + + -1.0, + + 0.5, + + 1.0, + + + { 0.0, 0.0, -2.0, -25.0, -40.0 }, + + + { 0.0, 0.05, 0.051, 0.5, 1.0 }, + + 3.5, + + -11.0, + + -2.0, + + 0.7, + + -0.7, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 1.0, + + -1.0, + + 0.0, + + 1.0, + + -1.0, + + 0.0, + + -50.0, + + 0.0, + + -50.0, + + 0.0, + + -50.0, + + 0.0, + + -50.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0, + + 0.0F, + + 16.0F, + + 16.0F, + + 20.0F, + + 20.0F, + + 30.0F, + + false, + + false, + + false, + + false, + + + { false, true, false, false, true, true, false, false, true, false, true, true, false, false, false, false }, + + + { false, true, false, false, true, true, false, false, true, false, true, true, false, false, false, false }, + + + { false, true, false, false, true, true, false, false, true, false, true, true, false, false, false, false }, + + false, + + false, + + false, + + false, + + false, + + false, + + false +}; diff --git a/src/fbw/src/model/SecComputer_private.h b/src/fbw/src/model/SecComputer_private.h new file mode 100644 index 000000000000..549b209d030a --- /dev/null +++ b/src/fbw/src/model/SecComputer_private.h @@ -0,0 +1,5 @@ +#ifndef RTW_HEADER_SecComputer_private_h_ +#define RTW_HEADER_SecComputer_private_h_ +#include "rtwtypes.h" +#endif + diff --git a/src/fbw/src/model/SecComputer_types.h b/src/fbw/src/model/SecComputer_types.h new file mode 100644 index 000000000000..4df8ed2335a8 --- /dev/null +++ b/src/fbw/src/model/SecComputer_types.h @@ -0,0 +1,668 @@ +#ifndef RTW_HEADER_SecComputer_types_h_ +#define RTW_HEADER_SecComputer_types_h_ +#include "rtwtypes.h" + +#ifndef DEFINED_TYPEDEF_FOR_pitch_efcs_law_ +#define DEFINED_TYPEDEF_FOR_pitch_efcs_law_ + +enum class pitch_efcs_law + : int32_T { + NormalLaw = 0, + AlternateLaw1, + AlternateLaw2, + DirectLaw, + None +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_SignStatusMatrix_ +#define DEFINED_TYPEDEF_FOR_SignStatusMatrix_ + +enum class SignStatusMatrix + : int32_T { + FailureWarning = 0, + NoComputedData, + FunctionalTest, + NormalOperation +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_time_ +#define DEFINED_TYPEDEF_FOR_base_time_ + +struct base_time +{ + real_T dt; + real_T simulation_time; + real_T monotonic_time; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sim_data_ +#define DEFINED_TYPEDEF_FOR_base_sim_data_ + +struct base_sim_data +{ + boolean_T slew_on; + boolean_T pause_on; + boolean_T tracking_mode_on_override; + boolean_T tailstrike_protection_on; + boolean_T computer_running; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sec_discrete_inputs_ +#define DEFINED_TYPEDEF_FOR_base_sec_discrete_inputs_ + +struct base_sec_discrete_inputs +{ + boolean_T sec_engaged_from_switch; + boolean_T sec_in_emergency_powersupply; + boolean_T is_unit_1; + boolean_T is_unit_2; + boolean_T is_unit_3; + boolean_T pitch_not_avail_elac_1; + boolean_T pitch_not_avail_elac_2; + boolean_T left_elev_not_avail_sec_opp; + boolean_T digital_output_failed_elac_1; + boolean_T right_elev_not_avail_sec_opp; + boolean_T green_low_pressure; + boolean_T blue_low_pressure; + boolean_T yellow_low_pressure; + boolean_T sfcc_1_slats_out; + boolean_T sfcc_2_slats_out; + boolean_T digital_output_failed_elac_2; + boolean_T ths_motor_fault; + boolean_T l_elev_servo_failed; + boolean_T r_elev_servo_failed; + boolean_T l_spoiler_1_servo_failed; + boolean_T r_spoiler_1_servo_failed; + boolean_T l_spoiler_2_servo_failed; + boolean_T r_spoiler_2_servo_failed; + boolean_T ths_override_active; + boolean_T capt_priority_takeover_pressed; + boolean_T fo_priority_takeover_pressed; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sec_analog_inputs_ +#define DEFINED_TYPEDEF_FOR_base_sec_analog_inputs_ + +struct base_sec_analog_inputs +{ + real_T capt_pitch_stick_pos; + real_T fo_pitch_stick_pos; + real_T capt_roll_stick_pos; + real_T fo_roll_stick_pos; + real_T spd_brk_lever_pos; + real_T thr_lever_1_pos; + real_T thr_lever_2_pos; + real_T left_elevator_pos_deg; + real_T right_elevator_pos_deg; + real_T ths_pos_deg; + real_T left_spoiler_1_pos_deg; + real_T right_spoiler_1_pos_deg; + real_T left_spoiler_2_pos_deg; + real_T right_spoiler_2_pos_deg; + real_T load_factor_acc_1_g; + real_T load_factor_acc_2_g; + real_T wheel_speed_left; + real_T wheel_speed_right; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_arinc_429_ +#define DEFINED_TYPEDEF_FOR_base_arinc_429_ + +struct base_arinc_429 +{ + uint32_T SSM; + real32_T Data; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_adr_bus_ +#define DEFINED_TYPEDEF_FOR_base_adr_bus_ + +struct base_adr_bus +{ + base_arinc_429 altitude_standard_ft; + base_arinc_429 altitude_corrected_ft; + base_arinc_429 mach; + base_arinc_429 airspeed_computed_kn; + base_arinc_429 airspeed_true_kn; + base_arinc_429 vertical_speed_ft_min; + base_arinc_429 aoa_corrected_deg; + base_arinc_429 corrected_average_static_pressure; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_ir_bus_ +#define DEFINED_TYPEDEF_FOR_base_ir_bus_ + +struct base_ir_bus +{ + base_arinc_429 discrete_word_1; + base_arinc_429 latitude_deg; + base_arinc_429 longitude_deg; + base_arinc_429 ground_speed_kn; + base_arinc_429 track_angle_true_deg; + base_arinc_429 heading_true_deg; + base_arinc_429 wind_speed_kn; + base_arinc_429 wind_direction_true_deg; + base_arinc_429 track_angle_magnetic_deg; + base_arinc_429 heading_magnetic_deg; + base_arinc_429 drift_angle_deg; + base_arinc_429 flight_path_angle_deg; + base_arinc_429 flight_path_accel_g; + base_arinc_429 pitch_angle_deg; + base_arinc_429 roll_angle_deg; + base_arinc_429 body_pitch_rate_deg_s; + base_arinc_429 body_roll_rate_deg_s; + base_arinc_429 body_yaw_rate_deg_s; + base_arinc_429 body_long_accel_g; + base_arinc_429 body_lat_accel_g; + base_arinc_429 body_normal_accel_g; + base_arinc_429 track_angle_rate_deg_s; + base_arinc_429 pitch_att_rate_deg_s; + base_arinc_429 roll_att_rate_deg_s; + base_arinc_429 inertial_alt_ft; + base_arinc_429 along_track_horiz_acc_g; + base_arinc_429 cross_track_horiz_acc_g; + base_arinc_429 vertical_accel_g; + base_arinc_429 inertial_vertical_speed_ft_s; + base_arinc_429 north_south_velocity_kn; + base_arinc_429 east_west_velocity_kn; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_elac_out_bus_ +#define DEFINED_TYPEDEF_FOR_base_elac_out_bus_ + +struct base_elac_out_bus +{ + base_arinc_429 left_aileron_position_deg; + base_arinc_429 right_aileron_position_deg; + base_arinc_429 left_elevator_position_deg; + base_arinc_429 right_elevator_position_deg; + base_arinc_429 ths_position_deg; + base_arinc_429 left_sidestick_pitch_command_deg; + base_arinc_429 right_sidestick_pitch_command_deg; + base_arinc_429 left_sidestick_roll_command_deg; + base_arinc_429 right_sidestick_roll_command_deg; + base_arinc_429 rudder_pedal_position_deg; + base_arinc_429 aileron_command_deg; + base_arinc_429 roll_spoiler_command_deg; + base_arinc_429 yaw_damper_command_deg; + base_arinc_429 elevator_double_pressurization_command_deg; + base_arinc_429 discrete_status_word_1; + base_arinc_429 discrete_status_word_2; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_fcdc_bus_ +#define DEFINED_TYPEDEF_FOR_base_fcdc_bus_ + +struct base_fcdc_bus +{ + base_arinc_429 efcs_status_word_1; + base_arinc_429 efcs_status_word_2; + base_arinc_429 efcs_status_word_3; + base_arinc_429 efcs_status_word_4; + base_arinc_429 efcs_status_word_5; + base_arinc_429 capt_roll_command_deg; + base_arinc_429 fo_roll_command_deg; + base_arinc_429 rudder_pedal_position_deg; + base_arinc_429 capt_pitch_command_deg; + base_arinc_429 fo_pitch_command_deg; + base_arinc_429 aileron_left_pos_deg; + base_arinc_429 elevator_left_pos_deg; + base_arinc_429 aileron_right_pos_deg; + base_arinc_429 elevator_right_pos_deg; + base_arinc_429 horiz_stab_trim_pos_deg; + base_arinc_429 spoiler_1_left_pos_deg; + base_arinc_429 spoiler_2_left_pos_deg; + base_arinc_429 spoiler_3_left_pos_deg; + base_arinc_429 spoiler_4_left_pos_deg; + base_arinc_429 spoiler_5_left_pos_deg; + base_arinc_429 spoiler_1_right_pos_deg; + base_arinc_429 spoiler_2_right_pos_deg; + base_arinc_429 spoiler_3_right_pos_deg; + base_arinc_429 spoiler_4_right_pos_deg; + base_arinc_429 spoiler_5_right_pos_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sfcc_bus_ +#define DEFINED_TYPEDEF_FOR_base_sfcc_bus_ + +struct base_sfcc_bus +{ + base_arinc_429 slat_flap_component_status_word; + base_arinc_429 slat_flap_system_status_word; + base_arinc_429 slat_flap_actual_position_word; + base_arinc_429 slat_actual_position_deg; + base_arinc_429 flap_actual_position_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_lgciu_bus_ +#define DEFINED_TYPEDEF_FOR_base_lgciu_bus_ + +struct base_lgciu_bus +{ + base_arinc_429 discrete_word_1; + base_arinc_429 discrete_word_2; + base_arinc_429 discrete_word_3; + base_arinc_429 discrete_word_4; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sec_bus_inputs_ +#define DEFINED_TYPEDEF_FOR_base_sec_bus_inputs_ + +struct base_sec_bus_inputs +{ + base_adr_bus adr_1_bus; + base_adr_bus adr_2_bus; + base_ir_bus ir_1_bus; + base_ir_bus ir_2_bus; + base_elac_out_bus elac_1_bus; + base_fcdc_bus fcdc_1_bus; + base_fcdc_bus fcdc_2_bus; + base_elac_out_bus elac_2_bus; + base_sfcc_bus sfcc_1_bus; + base_sfcc_bus sfcc_2_bus; + base_lgciu_bus lgciu_1_bus; + base_lgciu_bus lgciu_2_bus; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_sec_inputs_ +#define DEFINED_TYPEDEF_FOR_sec_inputs_ + +struct sec_inputs +{ + base_time time; + base_sim_data sim_data; + base_sec_discrete_inputs discrete_inputs; + base_sec_analog_inputs analog_inputs; + base_sec_bus_inputs bus_inputs; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sec_lateral_law_outputs_ +#define DEFINED_TYPEDEF_FOR_base_sec_lateral_law_outputs_ + +struct base_sec_lateral_law_outputs +{ + real_T left_spoiler_1_command_deg; + real_T right_spoiler_1_command_deg; + real_T left_spoiler_2_command_deg; + real_T right_spoiler_2_command_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sec_pitch_law_outputs_ +#define DEFINED_TYPEDEF_FOR_base_sec_pitch_law_outputs_ + +struct base_sec_pitch_law_outputs +{ + real_T elevator_command_deg; + real_T ths_command_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sec_laws_outputs_ +#define DEFINED_TYPEDEF_FOR_base_sec_laws_outputs_ + +struct base_sec_laws_outputs +{ + base_sec_lateral_law_outputs lateral_law_outputs; + base_sec_pitch_law_outputs pitch_law_outputs; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sec_adr_computation_data_ +#define DEFINED_TYPEDEF_FOR_base_sec_adr_computation_data_ + +struct base_sec_adr_computation_data +{ + real_T V_ias_kn; + real_T V_tas_kn; + real_T mach; + real_T alpha_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sec_ir_computation_data_ +#define DEFINED_TYPEDEF_FOR_base_sec_ir_computation_data_ + +struct base_sec_ir_computation_data +{ + real_T theta_deg; + real_T phi_deg; + real_T q_deg_s; + real_T r_deg_s; + real_T n_x_g; + real_T n_y_g; + real_T n_z_g; + real_T theta_dot_deg_s; + real_T phi_dot_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sec_logic_outputs_ +#define DEFINED_TYPEDEF_FOR_base_sec_logic_outputs_ + +struct base_sec_logic_outputs +{ + boolean_T on_ground; + boolean_T pitch_law_in_flight; + boolean_T tracking_mode_on; + pitch_efcs_law pitch_law_capability; + pitch_efcs_law active_pitch_law; + boolean_T abnormal_condition_law_active; + boolean_T is_engaged_in_pitch; + boolean_T can_engage_in_pitch; + boolean_T has_priority_in_pitch; + boolean_T left_elevator_avail; + boolean_T right_elevator_avail; + boolean_T ths_avail; + boolean_T ths_active_commanded; + boolean_T ths_ground_setting_active; + boolean_T is_engaged_in_roll; + boolean_T spoiler_pair_1_avail; + boolean_T spoiler_pair_2_avail; + boolean_T is_yellow_hydraulic_power_avail; + boolean_T is_blue_hydraulic_power_avail; + boolean_T is_green_hydraulic_power_avail; + boolean_T left_sidestick_disabled; + boolean_T right_sidestick_disabled; + boolean_T left_sidestick_priority_locked; + boolean_T right_sidestick_priority_locked; + real_T total_sidestick_pitch_command; + real_T total_sidestick_roll_command; + boolean_T ground_spoilers_armed; + boolean_T ground_spoilers_out; + boolean_T partial_lift_dumping_active; + boolean_T speed_brake_inhibited; + boolean_T single_adr_failure; + boolean_T double_adr_failure; + boolean_T cas_or_mach_disagree; + boolean_T alpha_disagree; + boolean_T single_ir_failure; + boolean_T double_ir_failure; + boolean_T ir_disagree; + base_sec_adr_computation_data adr_computation_data; + base_sec_ir_computation_data ir_computation_data; + boolean_T any_landing_gear_not_uplocked; + boolean_T lgciu_uplock_disagree_or_fault; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sec_discrete_outputs_ +#define DEFINED_TYPEDEF_FOR_base_sec_discrete_outputs_ + +struct base_sec_discrete_outputs +{ + boolean_T thr_reverse_selected; + boolean_T left_elevator_ok; + boolean_T right_elevator_ok; + boolean_T ground_spoiler_out; + boolean_T sec_failed; + boolean_T left_elevator_damping_mode; + boolean_T right_elevator_damping_mode; + boolean_T ths_active; + boolean_T batt_power_supply; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sec_analog_outputs_ +#define DEFINED_TYPEDEF_FOR_base_sec_analog_outputs_ + +struct base_sec_analog_outputs +{ + real_T left_elev_pos_order_deg; + real_T right_elev_pos_order_deg; + real_T ths_pos_order_deg; + real_T left_spoiler_1_pos_order_deg; + real_T right_spoiler_1_pos_order_deg; + real_T left_spoiler_2_pos_order_deg; + real_T right_spoiler_2_pos_order_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_sec_out_bus_ +#define DEFINED_TYPEDEF_FOR_base_sec_out_bus_ + +struct base_sec_out_bus +{ + base_arinc_429 left_spoiler_1_position_deg; + base_arinc_429 right_spoiler_1_position_deg; + base_arinc_429 left_spoiler_2_position_deg; + base_arinc_429 right_spoiler_2_position_deg; + base_arinc_429 left_elevator_position_deg; + base_arinc_429 right_elevator_position_deg; + base_arinc_429 ths_position_deg; + base_arinc_429 left_sidestick_pitch_command_deg; + base_arinc_429 right_sidestick_pitch_command_deg; + base_arinc_429 left_sidestick_roll_command_deg; + base_arinc_429 right_sidestick_roll_command_deg; + base_arinc_429 speed_brake_lever_command_deg; + base_arinc_429 thrust_lever_angle_1_deg; + base_arinc_429 thrust_lever_angle_2_deg; + base_arinc_429 discrete_status_word_1; + base_arinc_429 discrete_status_word_2; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_sec_outputs_ +#define DEFINED_TYPEDEF_FOR_sec_outputs_ + +struct sec_outputs +{ + sec_inputs data; + base_sec_laws_outputs laws; + base_sec_logic_outputs logic; + base_sec_discrete_outputs discrete_outputs; + base_sec_analog_outputs analog_outputs; + base_sec_out_bus bus_outputs; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_lateral_direct_input_ +#define DEFINED_TYPEDEF_FOR_lateral_direct_input_ + +struct lateral_direct_input +{ + base_time time; + real_T delta_xi_pos; + boolean_T tracking_mode_on; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_roll_output_ +#define DEFINED_TYPEDEF_FOR_base_roll_output_ + +struct base_roll_output +{ + real_T xi_deg; + real_T zeta_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_pitch_alternate_input_ +#define DEFINED_TYPEDEF_FOR_pitch_alternate_input_ + +struct pitch_alternate_input +{ + base_time time; + real_T nz_g; + real_T Theta_deg; + real_T Phi_deg; + real_T qk_deg_s; + real_T eta_deg; + real_T eta_trim_deg; + real_T V_ias_kn; + real_T mach; + real_T V_tas_kn; + real_T CG_percent_MAC; + real_T total_weight_kg; + real_T flaps_handle_index; + real_T spoilers_left_pos; + real_T spoilers_right_pos; + real_T delta_eta_pos; + boolean_T on_ground; + real_T in_flight; + boolean_T tracking_mode_on; + boolean_T stabilities_available; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_output_ +#define DEFINED_TYPEDEF_FOR_base_pitch_output_ + +struct base_pitch_output +{ + real_T eta_deg; + real_T eta_trim_dot_deg_s; + real_T eta_trim_limit_lo; + real_T eta_trim_limit_up; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_alternate_data_computed_ +#define DEFINED_TYPEDEF_FOR_base_pitch_alternate_data_computed_ + +struct base_pitch_alternate_data_computed +{ + real_T eta_trim_deg_limit_lo; + real_T eta_trim_deg_limit_up; + real_T delta_eta_deg; + real_T nz_limit_up_g; + real_T nz_limit_lo_g; + boolean_T eta_trim_deg_should_freeze; + boolean_T eta_trim_deg_reset; + real_T eta_trim_deg_reset_deg; + boolean_T eta_trim_deg_should_write; + real_T eta_trim_deg_rate_limit_up_deg_s; + real_T eta_trim_deg_rate_limit_lo_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_normal_ +#define DEFINED_TYPEDEF_FOR_base_pitch_normal_ + +struct base_pitch_normal +{ + real_T nz_c_g; + real_T Cstar_g; + real_T protection_alpha_c_deg; + real_T protection_V_c_kn; + real_T eta_dot_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_law_output_ +#define DEFINED_TYPEDEF_FOR_base_pitch_law_output_ + +struct base_pitch_law_output +{ + real_T eta_dot_deg_s; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_base_pitch_integrated_ +#define DEFINED_TYPEDEF_FOR_base_pitch_integrated_ + +struct base_pitch_integrated +{ + real_T eta_deg; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_pitch_direct_input_ +#define DEFINED_TYPEDEF_FOR_pitch_direct_input_ + +struct pitch_direct_input +{ + base_time time; + real_T eta_deg; + real_T flaps_handle_index; + real_T delta_eta_pos; + boolean_T tracking_mode_on; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_struct_2OohiAWrazWy5wDS5iisgF_ +#define DEFINED_TYPEDEF_FOR_struct_2OohiAWrazWy5wDS5iisgF_ + +struct struct_2OohiAWrazWy5wDS5iisgF +{ + real_T SSM; + real_T Data; +}; + +#endif + +#ifndef DEFINED_TYPEDEF_FOR_struct_7m2qji9xkXJ1tCsn7SH04E_ +#define DEFINED_TYPEDEF_FOR_struct_7m2qji9xkXJ1tCsn7SH04E_ + +struct struct_7m2qji9xkXJ1tCsn7SH04E +{ + struct_2OohiAWrazWy5wDS5iisgF left_spoiler_1_position_deg; + struct_2OohiAWrazWy5wDS5iisgF right_spoiler_1_position_deg; + struct_2OohiAWrazWy5wDS5iisgF left_spoiler_2_position_deg; + struct_2OohiAWrazWy5wDS5iisgF right_spoiler_2_position_deg; + struct_2OohiAWrazWy5wDS5iisgF left_elevator_position_deg; + struct_2OohiAWrazWy5wDS5iisgF right_elevator_position_deg; + struct_2OohiAWrazWy5wDS5iisgF ths_position_deg; + struct_2OohiAWrazWy5wDS5iisgF left_sidestick_pitch_command_deg; + struct_2OohiAWrazWy5wDS5iisgF right_sidestick_pitch_command_deg; + struct_2OohiAWrazWy5wDS5iisgF left_sidestick_roll_command_deg; + struct_2OohiAWrazWy5wDS5iisgF right_sidestick_roll_command_deg; + struct_2OohiAWrazWy5wDS5iisgF speed_brake_lever_command_deg; + struct_2OohiAWrazWy5wDS5iisgF thrust_lever_angle_1_deg; + struct_2OohiAWrazWy5wDS5iisgF thrust_lever_angle_2_deg; + struct_2OohiAWrazWy5wDS5iisgF discrete_status_word_1; + struct_2OohiAWrazWy5wDS5iisgF discrete_status_word_2; +}; + +#endif +#endif + diff --git a/src/fbw/src/sec/Sec.cpp b/src/fbw/src/sec/Sec.cpp new file mode 100644 index 000000000000..c6bbf5feee92 --- /dev/null +++ b/src/fbw/src/sec/Sec.cpp @@ -0,0 +1,159 @@ +#include "Sec.h" +#include + +Sec::Sec(bool isUnit1, bool isUnit3) : isUnit1(isUnit1), isUnit3(isUnit3) { + secComputer.initialize(); +} + +Sec::Sec(const Sec& obj) : isUnit1(obj.isUnit1), isUnit3(obj.isUnit3) { + secComputer.initialize(); +} + +// If the power supply is valid, perform the self-test-sequence. +// If at least one hydraulic source is pressurised, perform a short test. +// If no hydraulic supply is pressurised, and the outage was more than 3 seconds (or the switch was turned off), +// perform a long selft-test. +// Else, perform a short self-test. +void Sec::initSelfTests() { + if (powerSupplyFault) + return; + + clearMemory(); + selfTestTimer = selfTestDuration; +} + +// After the self-test is complete, erase all data in RAM. +void Sec::clearMemory() {} + +// Main update cycle. Surface position through parameters here is temporary. +void Sec::update(double deltaTime, double simulationTime, bool faultActive, bool isPowered) { + monitorPowerSupply(deltaTime, isPowered); + + updateSelfTest(deltaTime); + monitorSelf(faultActive); + + secComputer.setExternalInputs(&modelInputs); + secComputer.step(); + modelOutputs = secComputer.getExternalOutputs().out; +} + +// Perform self monitoring. If +void Sec::monitorSelf(bool faultActive) { + cpuStopped = cpuStoppedFlipFlop.update(faultActive || powerSupplyFault, cpuStopped && selfTestComplete && !powerSupplyFault); + if (cpuStopped) { + modelInputs.in.sim_data.computer_running = false; + } + + bool shouldReset = cpuStopped && resetPulseNode.update(modelInputs.in.discrete_inputs.sec_engaged_from_switch) && !powerSupplyFault; + if (shouldReset) { + initSelfTests(); + } + + monitoringHealthy = !cpuStopped && !powerSupplyFault && modelInputs.in.discrete_inputs.sec_engaged_from_switch; +} + +// Monitor the power supply and record the outage time (used for self test and healthy logic). +// If an outage lasts more than 10ms, stop the program execution. +// If the power has been restored after an outage that lasted longer than 10ms, reset the RAM and +// perform the startup sequence. +void Sec::monitorPowerSupply(double deltaTime, bool isPowered) { + if (!isPowered) { + powerSupplyOutageTime += deltaTime; + } + if (powerSupplyOutageTime > minimumPowerOutageTimeForFailure) { + powerSupplyFault = true; + } + if (isPowered && powerSupplyFault) { + powerSupplyFault = false; + initSelfTests(); + powerSupplyOutageTime = 0; + } +} + +// Update the Self-test-Sequence +void Sec::updateSelfTest(double deltaTime) { + if (selfTestTimer > 0) { + selfTestTimer -= deltaTime; + + // If the self-test sequence has just been completed, reset RAM. + if (selfTestTimer <= 0) { + } + } + if (selfTestTimer <= 0) { + selfTestComplete = true; + modelInputs.in.sim_data.computer_running = true; + } else { + selfTestComplete = false; + modelInputs.in.sim_data.computer_running = false; + } +} + +// Write the bus output data and return it. +base_sec_out_bus Sec::getBusOutputs() { + base_sec_out_bus output = {}; + + if (!monitoringHealthy) { + output.left_spoiler_1_position_deg.SSM = Arinc429SignStatus::FailureWarning; + output.right_spoiler_1_position_deg.SSM = Arinc429SignStatus::FailureWarning; + output.left_spoiler_2_position_deg.SSM = Arinc429SignStatus::FailureWarning; + output.right_spoiler_2_position_deg.SSM = Arinc429SignStatus::FailureWarning; + output.left_elevator_position_deg.SSM = Arinc429SignStatus::FailureWarning; + output.right_elevator_position_deg.SSM = Arinc429SignStatus::FailureWarning; + output.ths_position_deg.SSM = Arinc429SignStatus::FailureWarning; + output.left_sidestick_pitch_command_deg.SSM = Arinc429SignStatus::FailureWarning; + output.right_sidestick_pitch_command_deg.SSM = Arinc429SignStatus::FailureWarning; + output.left_sidestick_roll_command_deg.SSM = Arinc429SignStatus::FailureWarning; + output.right_sidestick_roll_command_deg.SSM = Arinc429SignStatus::FailureWarning; + output.speed_brake_lever_command_deg.SSM = Arinc429SignStatus::FailureWarning; + output.thrust_lever_angle_1_deg.SSM = Arinc429SignStatus::FailureWarning; + output.thrust_lever_angle_2_deg.SSM = Arinc429SignStatus::FailureWarning; + output.discrete_status_word_1.SSM = Arinc429SignStatus::FailureWarning; + output.discrete_status_word_2.SSM = Arinc429SignStatus::FailureWarning; + + return output; + } + + output = modelOutputs.bus_outputs; + + return output; +} + +// Write the discrete output data and return it. +base_sec_discrete_outputs Sec::getDiscreteOutputs() { + base_sec_discrete_outputs output = {}; + + output.sec_failed = !monitoringHealthy; + if (!monitoringHealthy) { + output.thr_reverse_selected = false; + output.left_elevator_ok = false; + output.right_elevator_ok = false; + output.ground_spoiler_out = false; + output.left_elevator_damping_mode = false; + output.right_elevator_damping_mode = false; + output.ths_active = false; + output.batt_power_supply = false; + } else { + output = modelOutputs.discrete_outputs; + } + + return output; +} + +// Write the analog outputs and return it. +base_sec_analog_outputs Sec::getAnalogOutputs() { + base_sec_analog_outputs output = {}; + + if (!monitoringHealthy) { + output.left_elev_pos_order_deg = 0; + output.right_elev_pos_order_deg = 0; + output.ths_pos_order_deg = 0; + output.left_spoiler_1_pos_order_deg = 0; + output.right_spoiler_1_pos_order_deg = 0; + output.left_spoiler_2_pos_order_deg = 0; + output.right_spoiler_2_pos_order_deg = 0; + } else { + output = modelOutputs.analog_outputs; + } + + return output; +} diff --git a/src/fbw/src/sec/Sec.h b/src/fbw/src/sec/Sec.h new file mode 100644 index 000000000000..2a2d1a1ffa32 --- /dev/null +++ b/src/fbw/src/sec/Sec.h @@ -0,0 +1,67 @@ +#pragma once + +#include "SecIO.h" + +#include "../Arinc429.h" +#include "../model/SecComputer.h" +#include "../utils/ConfirmNode.h" +#include "../utils/PulseNode.h" +#include "../utils/SRFlipFlop.h" + +class Sec { + public: + Sec(bool isUnit1, bool isUnit3); + + Sec(const Sec&); + + void update(double deltaTime, double simulationTime, bool faultActive, bool isPowered); + + base_sec_out_bus getBusOutputs(); + + base_sec_discrete_outputs getDiscreteOutputs(); + + base_sec_analog_outputs getAnalogOutputs(); + + SecComputer::ExternalInputs_SecComputer_T modelInputs = {}; + + private: + void initSelfTests(); + + void clearMemory(); + + void monitorPowerSupply(double deltaTime, bool isPowered); + + void monitorSelf(bool faultActive); + + void updateSelfTest(double deltaTime); + + // Model + SecComputer secComputer; + sec_outputs modelOutputs; + + // Computer Self-monitoring vars + bool monitoringHealthy; + + bool cpuStopped; + + SRFlipFlop cpuStoppedFlipFlop = SRFlipFlop(true); + + PulseNode resetPulseNode = PulseNode(false); + + // Power Supply monitoring + double powerSupplyOutageTime; + + bool powerSupplyFault; + + // Selftest vars + double selfTestTimer; + + bool selfTestComplete; + + // Constants + const bool isUnit1; + const bool isUnit3; + + const double minimumPowerOutageTimeForFailure = 0.02; + const double selfTestDuration = 4; +}; diff --git a/src/fbw/src/sec/SecIO.h b/src/fbw/src/sec/SecIO.h new file mode 100644 index 000000000000..e8687339c6ec --- /dev/null +++ b/src/fbw/src/sec/SecIO.h @@ -0,0 +1,150 @@ +#pragma once + +#include "../busStructures/busStructures.h" + +struct SecAnalogInputs { + // ANI 1-1 - SEC 1&2 only + double capPitchStickPos; + // ANI 1-2 - SEC 1&2 only + double foPitchStickPos; + // ANI 1-3 + double capRollStickPos; + // ANI 1-4 + double foRollStickPos; + // ANI 1-5 + double spdBrkLeverPos; + // ANI 1-6 + double thrLever1Pos; + // ANI 1-7 + double thrLever2Pos; + // ANI 2-1 - SEC 1&2 only + double leftElevatorPos; + // ANI 2-2 - SEC 1&2 only + double rightElevatorPos; + // ANI 2-3 - SEC 1&2 only + double thsPos; + // ANI 2-4 + double leftSpoiler1Pos; + // ANI 2-5 + double rightSpoiler1Pos; + // ANI 2-6 + double leftSpoiler2Pos; + // ANI 2-7 + double rightSpoiler2Pos; + // ANI 4-1 - SEC 1&2 only + double loadFactorAcc1; + // ANI 4-2 - SEC 1&2 only + double loadFactorAcc2; + // ANI 6 + double wheelSpeedLeft; + // ANI 6 + double wheelSpeedRight; +}; + +struct SecDiscreteInputs { + // DSI 1 + bool secEngagedFromSwitch; + // DSI 3 - SEC 1 only + bool secInemergencyPwrSply; + // DSI 10 - SEC 1&2 only + bool pitchNotAvailElac1; + // DSI 11 - SEC 1&2 only + bool pitchNotAvailElac2; + // DSI 13 - SEC 1&2 only + bool leftElevNotAvailSecOpp; + // DSI 14 + bool digitalOutputFailedElac1; + // DSI 15 + bool rightElevNotAvailSecOpp; + // DSI 17 + bool greenLowPressure; + // DSI 18 + bool blueLowPressure; + // DSI 19 + bool yellowLowPressure; + // DSI 20 + bool sfcc1SlatOut; + // DSI 20 + bool sfcc2SlatOut; + // DSI 21 - SEC 1&2 only + bool digitalOutputFailedElac2; + // DSI 25 - SEC 1&2 only + bool thsMotorFault; + + bool lElevServoFailed; + + bool rElevServoFailed; + + bool lSpoiler1ServoFailed; + + bool rSpoiler1ServoFailed; + + bool lSpoiler2ServoFailed; + + bool rSpoiler2ServoFailed; + // DSI 24 + bool thsOverrideActive; + // DSI 29 + bool captPriorityTakeoverPressed; + // DSI 30 + bool foPriorityTakeoverPressed; +}; + +struct SecAnalogOutputs { + // ANO 1 - SEC 1&2 only + double leftElevPosOrder; + // ANO 2 - SEC 1&2 only + double rightElevPosOrder; + // ANO 3 - SEC 1&2 only + double thsPosOrder; + // ANO 4 + double leftSpoiler1Order; + // ANO 5 + double rightSpoiler1Order; + // ANO 6 + double leftSpoiler2Order; + // ANO 7 + double rightSpoiler2Order; +}; + +struct SecDiscreteOutputs { + // DSO 4 - SEC 1&2 only + bool thrReverseSelected; + // DSO 5 - SEC 1&2 only + bool leftElevOk; + // DSO 6 - SEC 1&2 only + bool rightElevOk; + // DSO 7 - SEC 1&2 only + bool groundSpoilerOut; + // DSO 30 + bool secFailed; + + // Relays + bool leftElevatorDampingMode; + + bool rightElevatorDampingMode; + + bool thsActive; +}; + +struct SecBusInputs { + AdirsBusses adirs1; + + AdirsBusses adirs2; + + base_elac_out_bus elac1; + + FcdcBus fcdc1; + + FcdcBus fcdc2; + + base_elac_out_bus elac2; + + SfccBus sfcc1; + + SfccBus sfcc2; + + LgciuBus lgciu1; + + LgciuBus lgciu2; +}; diff --git a/src/fbw/src/utils/ConfirmNode.cpp b/src/fbw/src/utils/ConfirmNode.cpp new file mode 100644 index 000000000000..71fd84dbaa2b --- /dev/null +++ b/src/fbw/src/utils/ConfirmNode.cpp @@ -0,0 +1,20 @@ +#include "ConfirmNode.h" + +ConfirmNode::ConfirmNode(bool isRisingEdge, double timeDelay) : isRisingEdge(isRisingEdge), timeDelay(timeDelay) {} + +bool ConfirmNode::update(bool value, double deltaTime) { + bool conditionMet = value == isRisingEdge; + + if (conditionMet) { + timeSinceCondition += deltaTime; + output = timeSinceCondition >= timeDelay ? value : output; + } else { + timeSinceCondition = 0; + output = value; + } + return output; +} + +bool ConfirmNode::getOutput() { + return output; +} diff --git a/src/fbw/src/utils/ConfirmNode.h b/src/fbw/src/utils/ConfirmNode.h new file mode 100644 index 000000000000..1c2e3121f52d --- /dev/null +++ b/src/fbw/src/utils/ConfirmNode.h @@ -0,0 +1,16 @@ +#pragma once + +class ConfirmNode { + public: + ConfirmNode(bool isRisingEdge, double timeDelay); + + bool update(bool value, double deltaTime); + + bool getOutput(); + + private: + bool output = false; + bool isRisingEdge; + double timeDelay; + double timeSinceCondition = 0; +}; diff --git a/src/fbw/src/utils/HysteresisNode.cpp b/src/fbw/src/utils/HysteresisNode.cpp new file mode 100644 index 000000000000..88c5bb9b9508 --- /dev/null +++ b/src/fbw/src/utils/HysteresisNode.cpp @@ -0,0 +1,17 @@ +#include "HysteresisNode.h" + +HysteresisNode::HysteresisNode(double highTrigger, double lowTrigger) : highTrigger(highTrigger), lowTrigger(lowTrigger) {} + +bool HysteresisNode::update(double value) { + if (!output && value >= highTrigger) { + output = true; + } else if (output && value <= lowTrigger) { + output = false; + } + + return output; +} + +bool HysteresisNode::getOutput() { + return output; +} diff --git a/src/fbw/src/utils/HysteresisNode.h b/src/fbw/src/utils/HysteresisNode.h new file mode 100644 index 000000000000..a261480651d3 --- /dev/null +++ b/src/fbw/src/utils/HysteresisNode.h @@ -0,0 +1,15 @@ +#pragma once + +class HysteresisNode { + public: + HysteresisNode(double highTrigger, double lowTrigger); + + bool update(double value); + + bool getOutput(); + + private: + bool output = false; + double highTrigger; + double lowTrigger; +}; diff --git a/src/fbw/src/utils/PulseNode.cpp b/src/fbw/src/utils/PulseNode.cpp new file mode 100644 index 000000000000..832f1c2d3550 --- /dev/null +++ b/src/fbw/src/utils/PulseNode.cpp @@ -0,0 +1,19 @@ +#include "PulseNode.h" + +PulseNode::PulseNode(bool isRisingEdge) : isRisingEdge(isRisingEdge) {} + +bool PulseNode::update(bool value) { + if (output) { + output = false; + } else if (isRisingEdge) { + output = value && !previousInput; + } else { + output = !value && previousInput; + } + previousInput = value; + return output; +} + +bool PulseNode::getOutput() { + return output; +} diff --git a/src/fbw/src/utils/PulseNode.h b/src/fbw/src/utils/PulseNode.h new file mode 100644 index 000000000000..18a2732ef235 --- /dev/null +++ b/src/fbw/src/utils/PulseNode.h @@ -0,0 +1,15 @@ +#pragma once + +class PulseNode { + public: + PulseNode(bool isRisingEdge); + + bool update(bool value); + + bool getOutput(); + + private: + bool output = false; + bool previousInput = false; + bool isRisingEdge; +}; diff --git a/src/fbw/src/utils/SRFlipFLop.cpp b/src/fbw/src/utils/SRFlipFLop.cpp new file mode 100644 index 000000000000..7fb8431171ab --- /dev/null +++ b/src/fbw/src/utils/SRFlipFLop.cpp @@ -0,0 +1,18 @@ +#include "SRFlipFlop.h" + +SRFlipFlop::SRFlipFlop(bool hasSetPrecedence) : hasSetPrecedence(hasSetPrecedence) {} + +bool SRFlipFlop::update(bool set, bool reset) { + if (set && reset) { + output = hasSetPrecedence; + } else if (set) { + output = true; + } else if (reset) { + output = false; + } + return output; +} + +bool SRFlipFlop::getOutput() { + return output; +} diff --git a/src/fbw/src/utils/SRFlipFlop.h b/src/fbw/src/utils/SRFlipFlop.h new file mode 100644 index 000000000000..342c172c11f7 --- /dev/null +++ b/src/fbw/src/utils/SRFlipFlop.h @@ -0,0 +1,15 @@ +#pragma once + +class SRFlipFlop { + public: + SRFlipFlop(bool hasSetPrecedence); + + bool update(bool set, bool reset); + + bool getOutput(); + + private: + bool output = false; + + const bool hasSetPrecedence; +}; diff --git a/src/fdr2csv/src/FlightDataRecorderConverter.cpp b/src/fdr2csv/src/FlightDataRecorderConverter.cpp index 5eaf2a98567d..319eab1430d4 100644 --- a/src/fdr2csv/src/FlightDataRecorderConverter.cpp +++ b/src/fdr2csv/src/FlightDataRecorderConverter.cpp @@ -1,7 +1,7 @@ #include "FlightDataRecorderConverter.h" -#include "fmt/core.h" -#include "fmt/ostream.h" +#include "fmt/include/fmt/core.h" +#include "fmt/include/fmt/ostream.h" using namespace std; @@ -358,142 +358,6 @@ void FlightDataRecorderConverter::writeHeader(ofstream& out, const string& delim fmt::print(out, "athr.output.mode_message{}", delimiter); fmt::print(out, "athr.output.thrust_lever_warning_flex{}", delimiter); fmt::print(out, "athr.output.thrust_lever_warning_toga{}", delimiter); - fmt::print(out, "fbw.sim.time.monotonic_time{}", delimiter); - fmt::print(out, "fbw.sim.time.dt{}", delimiter); - fmt::print(out, "fbw.sim.time.simulation_time{}", delimiter); - fmt::print(out, "fbw.sim.time.monotonic_time{}", delimiter); - fmt::print(out, "fbw.sim.data.nz_g{}", delimiter); - fmt::print(out, "fbw.sim.data.Theta_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.Phi_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.q_deg_s{}", delimiter); - fmt::print(out, "fbw.sim.data.r_deg_s{}", delimiter); - fmt::print(out, "fbw.sim.data.p_deg_s{}", delimiter); - fmt::print(out, "fbw.sim.data.qk_deg_s{}", delimiter); - fmt::print(out, "fbw.sim.data.rk_deg_s{}", delimiter); - fmt::print(out, "fbw.sim.data.pk_deg_s{}", delimiter); - fmt::print(out, "fbw.sim.data.qk_dot_deg_s2{}", delimiter); - fmt::print(out, "fbw.sim.data.rk_dot_deg_s2{}", delimiter); - fmt::print(out, "fbw.sim.data.pk_dot_deg_s2{}", delimiter); - fmt::print(out, "fbw.sim.data.psi_magnetic_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.psi_true_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.eta_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.eta_trim_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.xi_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.zeta_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.zeta_trim_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.alpha_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.beta_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.beta_dot_deg_s{}", delimiter); - fmt::print(out, "fbw.sim.data.V_ias_kn{}", delimiter); - fmt::print(out, "fbw.sim.data.V_tas_kn{}", delimiter); - fmt::print(out, "fbw.sim.data.V_mach{}", delimiter); - fmt::print(out, "fbw.sim.data.H_ft{}", delimiter); - fmt::print(out, "fbw.sim.data.H_ind_ft{}", delimiter); - fmt::print(out, "fbw.sim.data.H_radio_ft{}", delimiter); - fmt::print(out, "fbw.sim.data.CG_percent_MAC{}", delimiter); - fmt::print(out, "fbw.sim.data.total_weight_kg{}", delimiter); - fmt::print(out, "fbw.sim.data.gear_strut_compression_0{}", delimiter); - fmt::print(out, "fbw.sim.data.gear_strut_compression_1{}", delimiter); - fmt::print(out, "fbw.sim.data.gear_strut_compression_2{}", delimiter); - fmt::print(out, "fbw.sim.data.flaps_handle_index{}", delimiter); - fmt::print(out, "fbw.sim.data.spoilers_left_pos{}", delimiter); - fmt::print(out, "fbw.sim.data.spoilers_right_pos{}", delimiter); - fmt::print(out, "fbw.sim.data.autopilot_master_on{}", delimiter); - fmt::print(out, "fbw.sim.data.slew_on{}", delimiter); - fmt::print(out, "fbw.sim.data.pause_on{}", delimiter); - fmt::print(out, "fbw.sim.data.tracking_mode_on_override{}", delimiter); - fmt::print(out, "fbw.sim.data.autopilot_custom_on{}", delimiter); - fmt::print(out, "fbw.sim.data.autopilot_custom_Theta_c_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.autopilot_custom_Phi_c_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.autopilot_custom_Beta_c_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.simulation_rate{}", delimiter); - fmt::print(out, "fbw.sim.data.ice_structure_percent{}", delimiter); - fmt::print(out, "fbw.sim.data.linear_cl_alpha_per_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.alpha_stall_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.alpha_zero_lift_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.ambient_density_kg_per_m3{}", delimiter); - fmt::print(out, "fbw.sim.data.ambient_pressure_mbar{}", delimiter); - fmt::print(out, "fbw.sim.data.ambient_temperature_celsius{}", delimiter); - fmt::print(out, "fbw.sim.data.ambient_wind_x_kn{}", delimiter); - fmt::print(out, "fbw.sim.data.ambient_wind_y_kn{}", delimiter); - fmt::print(out, "fbw.sim.data.ambient_wind_z_kn{}", delimiter); - fmt::print(out, "fbw.sim.data.ambient_wind_velocity_kn{}", delimiter); - fmt::print(out, "fbw.sim.data.ambient_wind_direction_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.total_air_temperature_celsius{}", delimiter); - fmt::print(out, "fbw.sim.data.latitude_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.longitude_deg{}", delimiter); - fmt::print(out, "fbw.sim.data.engine_1_thrust_lbf{}", delimiter); - fmt::print(out, "fbw.sim.data.engine_2_thrust_lbf{}", delimiter); - fmt::print(out, "fbw.sim.data.thrust_lever_1_pos{}", delimiter); - fmt::print(out, "fbw.sim.data.thrust_lever_2_pos{}", delimiter); - fmt::print(out, "fbw.sim.data_computed.on_ground{}", delimiter); - fmt::print(out, "fbw.sim.data_computed.tracking_mode_on{}", delimiter); - fmt::print(out, "fbw.sim.data_computed.high_aoa_prot_active{}", delimiter); - fmt::print(out, "fbw.sim.data_computed.alpha_floor_command{}", delimiter); - fmt::print(out, "fbw.sim.data_computed.protection_ap_disc{}", delimiter); - fmt::print(out, "fbw.sim.data_computed.high_speed_prot_active{}", delimiter); - fmt::print(out, "fbw.sim.data_computed.high_speed_prot_low_kn{}", delimiter); - fmt::print(out, "fbw.sim.data_computed.high_speed_prot_high_kn{}", delimiter); - fmt::print(out, "fbw.sim.data_speeds_aoa.v_alpha_max_kn{}", delimiter); - fmt::print(out, "fbw.sim.data_speeds_aoa.alpha_max_deg{}", delimiter); - fmt::print(out, "fbw.sim.data_speeds_aoa.v_alpha_prot_kn{}", delimiter); - fmt::print(out, "fbw.sim.data_speeds_aoa.alpha_prot_deg{}", delimiter); - fmt::print(out, "fbw.sim.data_speeds_aoa.alpha_floor_deg{}", delimiter); - fmt::print(out, "fbw.sim.data_speeds_aoa.alpha_filtered_deg{}", delimiter); - fmt::print(out, "fbw.sim.input.delta_eta_pos{}", delimiter); - fmt::print(out, "fbw.sim.input.delta_xi_pos{}", delimiter); - fmt::print(out, "fbw.sim.input.delta_zeta_pos{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.eta_trim_deg_limit_lo{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.eta_trim_deg_limit_up{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.delta_eta_deg{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.in_flight{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.in_rotation{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.in_flare{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.in_flight_gain{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.in_rotation_gain{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.nz_limit_up_g{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.nz_limit_lo_g{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.eta_trim_deg_should_freeze{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.eta_trim_deg_reset{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.eta_trim_deg_reset_deg{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.eta_trim_deg_should_write{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.eta_trim_deg_rate_limit_up_deg_s{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.eta_trim_deg_rate_limit_lo_deg_s{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.flare_Theta_deg{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.flare_Theta_c_deg{}", delimiter); - fmt::print(out, "fbw.pitch.data_computed.flare_Theta_c_rate_deg_s{}", delimiter); - fmt::print(out, "fbw.pitch.law_rotation.qk_c_deg_s{}", delimiter); - fmt::print(out, "fbw.pitch.law_rotation.eta_deg{}", delimiter); - fmt::print(out, "fbw.pitch.law_normal.nz_c_g{}", delimiter); - fmt::print(out, "fbw.pitch.law_normal.Cstar_g{}", delimiter); - fmt::print(out, "fbw.pitch.law_normal.protection_alpha_c_deg{}", delimiter); - fmt::print(out, "fbw.pitch.law_normal.protection_V_c_kn{}", delimiter); - fmt::print(out, "fbw.pitch.law_normal.eta_dot_deg_s{}", delimiter); - fmt::print(out, "fbw.pitch.vote.eta_dot_deg_s{}", delimiter); - fmt::print(out, "fbw.pitch.integrated.eta_deg{}", delimiter); - fmt::print(out, "fbw.pitch.output.eta_deg{}", delimiter); - fmt::print(out, "fbw.pitch.output.eta_trim_deg{}", delimiter); - fmt::print(out, "fbw.roll.data_computed.delta_xi_deg{}", delimiter); - fmt::print(out, "fbw.roll.data_computed.delta_zeta_deg{}", delimiter); - fmt::print(out, "fbw.roll.data_computed.in_flight{}", delimiter); - fmt::print(out, "fbw.roll.data_computed.in_flight_gain{}", delimiter); - fmt::print(out, "fbw.roll.data_computed.zeta_trim_deg_should_write{}", delimiter); - fmt::print(out, "fbw.roll.data_computed.beta_target_deg{}", delimiter); - fmt::print(out, "fbw.roll.law_normal.pk_c_deg_s{}", delimiter); - fmt::print(out, "fbw.roll.law_normal.Phi_c_deg{}", delimiter); - fmt::print(out, "fbw.roll.law_normal.xi_deg{}", delimiter); - fmt::print(out, "fbw.roll.law_normal.zeta_deg{}", delimiter); - fmt::print(out, "fbw.roll.law_normal.zeta_tc_yd_deg{}", delimiter); - fmt::print(out, "fbw.roll.output.xi_deg{}", delimiter); - fmt::print(out, "fbw.roll.output.zeta_deg{}", delimiter); - fmt::print(out, "fbw.roll.output.zeta_trim_deg{}", delimiter); - fmt::print(out, "fbw.output.eta_pos{}", delimiter); - fmt::print(out, "fbw.output.eta_trim_deg{}", delimiter); - fmt::print(out, "fbw.output.eta_trim_deg_should_write{}", delimiter); - fmt::print(out, "fbw.output.xi_pos{}", delimiter); - fmt::print(out, "fbw.output.zeta_pos{}", delimiter); - fmt::print(out, "fbw.output.zeta_trim_pos{}", delimiter); - fmt::print(out, "fbw.output.zeta_trim_pos_should_write{}", delimiter); fmt::print(out, "engine.simOnGround{}", delimiter); fmt::print(out, "engine.generalEngineElapsedTime_1{}", delimiter); fmt::print(out, "engine.generalEngineElapsedTime_2{}", delimiter); @@ -575,6 +439,10 @@ void FlightDataRecorderConverter::writeHeader(ofstream& out, const string& delim fmt::print(out, "data.realisticTillerEnabled{}", delimiter); fmt::print(out, "data.tillerHandlePosition{}", delimiter); fmt::print(out, "data.noseWheelPosition{}", delimiter); + fmt::print(out, "data.syncFoEfisEnabled{}", delimiter); + fmt::print(out, "data.ls1Active{}", delimiter); + fmt::print(out, "data.ls2Active{}", delimiter); + fmt::print(out, "data.IsisLsActive{}", delimiter); fmt::print(out, "\n"); } @@ -583,7 +451,6 @@ void FlightDataRecorderConverter::writeStruct(ofstream& out, const ap_sm_output& ap_sm, const ap_raw_output& ap_law, const athr_out& athr, - const fbw_output& fbw, const EngineData& engine, const AdditionalData& data) { fmt::print(out, "{}{}", ap_sm.time.dt, delimiter); @@ -938,142 +805,6 @@ void FlightDataRecorderConverter::writeStruct(ofstream& out, fmt::print(out, "{}{}", athr.output.mode_message, delimiter); fmt::print(out, "{}{}", static_cast(athr.output.thrust_lever_warning_flex), delimiter); fmt::print(out, "{}{}", static_cast(athr.output.thrust_lever_warning_toga), delimiter); - fmt::print(out, "{}{}", fbw.sim.time.monotonic_time, delimiter); - fmt::print(out, "{}{}", fbw.sim.time.dt, delimiter); - fmt::print(out, "{}{}", fbw.sim.time.simulation_time, delimiter); - fmt::print(out, "{}{}", fbw.sim.time.monotonic_time, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.nz_g, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.Theta_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.Phi_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.q_deg_s, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.r_deg_s, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.p_deg_s, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.qk_deg_s, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.rk_deg_s, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.pk_deg_s, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.qk_dot_deg_s2, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.rk_dot_deg_s2, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.pk_dot_deg_s2, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.psi_magnetic_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.psi_true_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.eta_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.eta_trim_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.xi_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.zeta_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.zeta_trim_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.alpha_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.beta_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.beta_dot_deg_s, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.V_ias_kn, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.V_tas_kn, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.V_mach, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.H_ft, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.H_ind_ft, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.H_radio_ft, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.CG_percent_MAC, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.total_weight_kg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.gear_strut_compression_0, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.gear_strut_compression_1, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.gear_strut_compression_2, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.flaps_handle_index, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.spoilers_left_pos, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.spoilers_right_pos, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.autopilot_master_on, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.slew_on, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.pause_on, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.tracking_mode_on_override, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.autopilot_custom_on, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.autopilot_custom_Theta_c_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.autopilot_custom_Phi_c_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.autopilot_custom_Beta_c_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.simulation_rate, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.ice_structure_percent, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.linear_cl_alpha_per_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.alpha_stall_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.alpha_zero_lift_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.ambient_density_kg_per_m3, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.ambient_pressure_mbar, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.ambient_temperature_celsius, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.ambient_wind_x_kn, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.ambient_wind_y_kn, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.ambient_wind_z_kn, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.ambient_wind_velocity_kn, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.ambient_wind_direction_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.total_air_temperature_celsius, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.latitude_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.longitude_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.engine_1_thrust_lbf, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.engine_2_thrust_lbf, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.thrust_lever_1_pos, delimiter); - fmt::print(out, "{}{}", fbw.sim.data.thrust_lever_2_pos, delimiter); - fmt::print(out, "{}{}", fbw.sim.data_computed.on_ground, delimiter); - fmt::print(out, "{}{}", fbw.sim.data_computed.tracking_mode_on, delimiter); - fmt::print(out, "{}{}", fbw.sim.data_computed.high_aoa_prot_active, delimiter); - fmt::print(out, "{}{}", fbw.sim.data_computed.alpha_floor_command, delimiter); - fmt::print(out, "{}{}", fbw.sim.data_computed.protection_ap_disc, delimiter); - fmt::print(out, "{}{}", fbw.sim.data_computed.high_speed_prot_active, delimiter); - fmt::print(out, "{}{}", fbw.sim.data_computed.high_speed_prot_low_kn, delimiter); - fmt::print(out, "{}{}", fbw.sim.data_computed.high_speed_prot_high_kn, delimiter); - fmt::print(out, "{}{}", fbw.sim.data_speeds_aoa.v_alpha_max_kn, delimiter); - fmt::print(out, "{}{}", fbw.sim.data_speeds_aoa.alpha_max_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data_speeds_aoa.v_alpha_prot_kn, delimiter); - fmt::print(out, "{}{}", fbw.sim.data_speeds_aoa.alpha_prot_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data_speeds_aoa.alpha_floor_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.data_speeds_aoa.alpha_filtered_deg, delimiter); - fmt::print(out, "{}{}", fbw.sim.input.delta_eta_pos, delimiter); - fmt::print(out, "{}{}", fbw.sim.input.delta_xi_pos, delimiter); - fmt::print(out, "{}{}", fbw.sim.input.delta_zeta_pos, delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.eta_trim_deg_limit_lo, delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.eta_trim_deg_limit_up, delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.delta_eta_deg, delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.in_flight, delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.in_rotation, delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.in_flare, delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.in_flight_gain, delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.in_rotation_gain, delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.nz_limit_up_g, delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.nz_limit_lo_g, delimiter); - fmt::print(out, "{}{}", static_cast(fbw.pitch.data_computed.eta_trim_deg_should_freeze), delimiter); - fmt::print(out, "{}{}", static_cast(fbw.pitch.data_computed.eta_trim_deg_reset), delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.eta_trim_deg_reset_deg, delimiter); - fmt::print(out, "{}{}", static_cast(fbw.pitch.data_computed.eta_trim_deg_should_write), delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.eta_trim_deg_rate_limit_up_deg_s, delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.eta_trim_deg_rate_limit_lo_deg_s, delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.flare_Theta_deg, delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.flare_Theta_c_deg, delimiter); - fmt::print(out, "{}{}", fbw.pitch.data_computed.flare_Theta_c_rate_deg_s, delimiter); - fmt::print(out, "{}{}", fbw.pitch.law_rotation.qk_c_deg_s, delimiter); - fmt::print(out, "{}{}", fbw.pitch.law_rotation.eta_deg, delimiter); - fmt::print(out, "{}{}", fbw.pitch.law_normal.nz_c_g, delimiter); - fmt::print(out, "{}{}", fbw.pitch.law_normal.Cstar_g, delimiter); - fmt::print(out, "{}{}", fbw.pitch.law_normal.protection_alpha_c_deg, delimiter); - fmt::print(out, "{}{}", fbw.pitch.law_normal.protection_V_c_kn, delimiter); - fmt::print(out, "{}{}", fbw.pitch.law_normal.eta_dot_deg_s, delimiter); - fmt::print(out, "{}{}", fbw.pitch.vote.eta_dot_deg_s, delimiter); - fmt::print(out, "{}{}", fbw.pitch.integrated.eta_deg, delimiter); - fmt::print(out, "{}{}", fbw.pitch.output.eta_deg, delimiter); - fmt::print(out, "{}{}", fbw.pitch.output.eta_trim_deg, delimiter); - fmt::print(out, "{}{}", fbw.roll.data_computed.delta_xi_deg, delimiter); - fmt::print(out, "{}{}", fbw.roll.data_computed.delta_zeta_deg, delimiter); - fmt::print(out, "{}{}", fbw.roll.data_computed.in_flight, delimiter); - fmt::print(out, "{}{}", fbw.roll.data_computed.in_flight_gain, delimiter); - fmt::print(out, "{}{}", static_cast(fbw.roll.data_computed.zeta_trim_deg_should_write), delimiter); - fmt::print(out, "{}{}", fbw.roll.data_computed.beta_target_deg, delimiter); - fmt::print(out, "{}{}", fbw.roll.law_normal.pk_c_deg_s, delimiter); - fmt::print(out, "{}{}", fbw.roll.law_normal.Phi_c_deg, delimiter); - fmt::print(out, "{}{}", fbw.roll.law_normal.xi_deg, delimiter); - fmt::print(out, "{}{}", fbw.roll.law_normal.zeta_deg, delimiter); - fmt::print(out, "{}{}", fbw.roll.law_normal.zeta_tc_yd_deg, delimiter); - fmt::print(out, "{}{}", fbw.roll.output.xi_deg, delimiter); - fmt::print(out, "{}{}", fbw.roll.output.zeta_deg, delimiter); - fmt::print(out, "{}{}", fbw.roll.output.zeta_trim_deg, delimiter); - fmt::print(out, "{}{}", fbw.output.eta_pos, delimiter); - fmt::print(out, "{}{}", fbw.output.eta_trim_deg, delimiter); - fmt::print(out, "{}{}", static_cast(fbw.output.eta_trim_deg_should_write), delimiter); - fmt::print(out, "{}{}", fbw.output.xi_pos, delimiter); - fmt::print(out, "{}{}", fbw.output.zeta_pos, delimiter); - fmt::print(out, "{}{}", fbw.output.zeta_trim_pos, delimiter); - fmt::print(out, "{}{}", static_cast(fbw.output.zeta_trim_pos_should_write), delimiter); fmt::print(out, "{}{}", engine.simOnGround, delimiter); fmt::print(out, "{}{}", engine.generalEngineElapsedTime_1, delimiter); fmt::print(out, "{}{}", engine.generalEngineElapsedTime_2, delimiter); @@ -1155,5 +886,9 @@ void FlightDataRecorderConverter::writeStruct(ofstream& out, fmt::print(out, "{}{}", data.realisticTillerEnabled, delimiter); fmt::print(out, "{}{}", data.tillerHandlePosition, delimiter); fmt::print(out, "{}{}", data.noseWheelPosition, delimiter); + fmt::print(out, "{}{}", data.syncFoEfisEnabled, delimiter); + fmt::print(out, "{}{}", data.ls1Active, delimiter); + fmt::print(out, "{}{}", data.ls2Active, delimiter); + fmt::print(out, "{}{}", data.IsisLsActive, delimiter); fmt::print(out, "\n"); } diff --git a/src/fdr2csv/src/FlightDataRecorderConverter.h b/src/fdr2csv/src/FlightDataRecorderConverter.h index 6a03e6d009d0..26a1c3a19e60 100644 --- a/src/fdr2csv/src/FlightDataRecorderConverter.h +++ b/src/fdr2csv/src/FlightDataRecorderConverter.h @@ -7,7 +7,6 @@ #include "AutopilotStateMachine_types.h" #include "Autothrust_types.h" #include "EngineData.h" -#include "FlyByWire_types.h" class FlightDataRecorderConverter { public: @@ -20,7 +19,6 @@ class FlightDataRecorderConverter { const ap_sm_output& ap_sm, const ap_raw_output& ap_law, const athr_out& athr, - const fbw_output& fbw, const EngineData& engine, const AdditionalData& data); }; diff --git a/src/fdr2csv/src/main.cpp b/src/fdr2csv/src/main.cpp index f530ace6cacb..fd28185b3ea9 100644 --- a/src/fdr2csv/src/main.cpp +++ b/src/fdr2csv/src/main.cpp @@ -5,17 +5,16 @@ #include "AutopilotLaws_types.h" #include "AutopilotStateMachine_types.h" #include "Autothrust_types.h" -#include "CommandLine.hpp" #include "EngineData.h" #include "FlightDataRecorderConverter.h" -#include "FlyByWire_types.h" -#include "fmt/core.h" +#include "commandline/CommandLine.hpp" +#include "fmt/include/fmt/core.h" #include "zfstream.h" using namespace std; // IMPORTANT: this constant needs to increased with every interface change -const uint64_t INTERFACE_VERSION = 21; +const uint64_t INTERFACE_VERSION = 23; int main(int argc, char* argv[]) { // variables for command line parameters @@ -52,11 +51,6 @@ int main(int argc, char* argv[]) { return 0; } - // print size of struct - if (printStructSize) { - fmt::print("Size of struct for reading is '{}' bytes\n", sizeof(fbw_output)); - } - // check parameters if (inFilePath.empty()) { fmt::print("Input file parameter missing!\n"); @@ -117,13 +111,11 @@ int main(int argc, char* argv[]) { // calculate number of entries auto counter = 0; - auto numberOfEntries = filesystem::file_size(inFilePath) / sizeof(fbw_output); // struct for reading ap_sm_output data_ap_sm = {}; ap_raw_output data_ap_laws = {}; athr_out data_athr = {}; - fbw_output data_fbw = {}; EngineData data_engine = {}; AdditionalData data_additional = {}; @@ -133,11 +125,10 @@ int main(int argc, char* argv[]) { in->read(reinterpret_cast(&data_ap_sm), sizeof(ap_sm_output)); in->read(reinterpret_cast(&data_ap_laws), sizeof(ap_raw_output)); in->read(reinterpret_cast(&data_athr), sizeof(athr_out)); - in->read(reinterpret_cast(&data_fbw), sizeof(fbw_output)); in->read(reinterpret_cast(&data_engine), sizeof(EngineData)); in->read(reinterpret_cast(&data_additional), sizeof(AdditionalData)); // write struct to csv file - FlightDataRecorderConverter::writeStruct(out, delimiter, data_ap_sm, data_ap_laws, data_athr, data_fbw, data_engine, data_additional); + FlightDataRecorderConverter::writeStruct(out, delimiter, data_ap_sm, data_ap_laws, data_athr, data_engine, data_additional); // print progress if (++counter % 1000 == 0) { fmt::print("Processed {} entries...\r", counter); diff --git a/src/fmgc/src/components/fms-messages/FmsMessages.ts b/src/fmgc/src/components/fms-messages/FmsMessages.ts index 01e95ab28d3c..51b9c96b8ee2 100644 --- a/src/fmgc/src/components/fms-messages/FmsMessages.ts +++ b/src/fmgc/src/components/fms-messages/FmsMessages.ts @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: GPL-3.0 +import { TurnAreaExceedanceLeft, TurnAreaExceedanceRight } from '@fmgc/components/fms-messages/TurnAreaExceedance'; +import { FlightPlanManager } from '@shared/flightplan'; import { FMMessage, FMMessageTriggers } from '@shared/FmMessages'; import { FmgcComponent } from '../FmgcComponent'; import { GpsPrimary } from './GpsPrimary'; @@ -23,6 +25,8 @@ import { MapPartlyDisplayedLeft, MapPartlyDisplayedRight } from './MapPartlyDisp export class FmsMessages implements FmgcComponent { private listener = RegisterViewListener('JS_LISTENER_SIMVARS', null, true); + private baseInstrument: BaseInstrument; + private ndMessageFlags: Record<'L' | 'R', number> = { L: 0, R: 0, @@ -33,10 +37,18 @@ export class FmsMessages implements FmgcComponent { new GpsPrimaryLost(), new MapPartlyDisplayedLeft(), new MapPartlyDisplayedRight(), + new TurnAreaExceedanceLeft(), + new TurnAreaExceedanceRight(), ]; - init(): void { - // Do nothing + init(baseInstrument: BaseInstrument, _flightPlanManager: FlightPlanManager): void { + this.baseInstrument = baseInstrument; + + for (const selector of this.messageSelectors) { + if (selector.init) { + selector.init(this.baseInstrument); + } + } } update(deltaTime: number): void { @@ -173,6 +185,8 @@ export interface FMMessageSelector { efisSide?: 'L' | 'R'; + init?(baseInstrument: BaseInstrument): void; + /** * Optionally triggers a message when there isn't any other system or Redux update triggering it. */ diff --git a/src/fmgc/src/components/fms-messages/TurnAreaExceedance.ts b/src/fmgc/src/components/fms-messages/TurnAreaExceedance.ts new file mode 100644 index 000000000000..f65428226d6a --- /dev/null +++ b/src/fmgc/src/components/fms-messages/TurnAreaExceedance.ts @@ -0,0 +1,59 @@ +import { GuidanceController } from '@fmgc/guidance/GuidanceController'; +import { PILeg } from '@fmgc/guidance/lnav/legs/PI'; +import { Navigation } from '@fmgc/navigation/Navigation'; +import { FMMessage, FMMessageTypes } from '@shared/FmMessages'; +import { Trigger } from '@shared/logic'; +import { FMMessageSelector, FMMessageUpdate } from './FmsMessages'; + +abstract class TurnAreaExceedance implements FMMessageSelector { + message: FMMessage = FMMessageTypes.TurnAreaExceedance; + + abstract efisSide: 'L' | 'R'; + + private trigRising = new Trigger(true); + + private trigFalling = new Trigger(true); + + private guidanceController: GuidanceController; + + private navigation: Navigation; + + init(baseInstrument: BaseInstrument): void { + this.guidanceController = baseInstrument.guidanceController; + this.navigation = baseInstrument.navigation; + } + + process(deltaTime: number): FMMessageUpdate { + const gs = this.navigation.groundSpeed; + const dtg = this.guidanceController.activeLegDtg ?? Infinity; + const ttg = gs > 10 ? 3600 * dtg / gs : Infinity; + const nextLeg = this.guidanceController.activeGeometry.legs.get(this.guidanceController.activeLegIndex + 1); + + // if within 1.5 min of PI and it's path goes outside the coded distance limit + const turnAreaExceeded = ttg <= 90 && nextLeg instanceof PILeg && nextLeg.turnAreaExceeded; + + this.trigRising.input = turnAreaExceeded; + this.trigRising.update(deltaTime); + + this.trigFalling.input = !turnAreaExceeded; + this.trigFalling.update(deltaTime); + + if (this.trigRising.output) { + return FMMessageUpdate.SEND; + } + + if (this.trigFalling.output) { + return FMMessageUpdate.RECALL; + } + + return FMMessageUpdate.NO_ACTION; + } +} + +export class TurnAreaExceedanceLeft extends TurnAreaExceedance { + efisSide: 'L' | 'R' = 'L'; +} + +export class TurnAreaExceedanceRight extends TurnAreaExceedance { + efisSide: 'L' | 'R' = 'R'; +} diff --git a/src/fmgc/src/efis/EfisSymbols.ts b/src/fmgc/src/efis/EfisSymbols.ts index 18240b9b5513..f604d4848e99 100644 --- a/src/fmgc/src/efis/EfisSymbols.ts +++ b/src/fmgc/src/efis/EfisSymbols.ts @@ -13,6 +13,7 @@ import { PathVector, PathVectorType } from '@fmgc/guidance/lnav/PathVector'; import { SegmentType } from '@fmgc/wtsdk'; import { distanceTo } from 'msfs-geo'; import { FlowEventSync } from '@shared/FlowEventSync'; +import { LnavConfig } from '@fmgc/guidance/LnavConfig'; import { LegType, RunwaySurface, TurnDirection, VorType } from '../types/fstypes/FSEnums'; import { NearbyFacilities } from './NearbyFacilities'; @@ -306,7 +307,7 @@ export class EfisSymbols { const legType = wp.additionalData.legType; if ( legType === LegType.CA || legType === LegType.CR || legType === LegType.CI - || legType === LegType.FM + || legType === LegType.FM || legType === LegType.PI || legType === LegType.VA || legType === LegType.VI || legType === LegType.VM ) { continue; @@ -337,12 +338,14 @@ export class EfisSymbols { const constraints = []; let direction; - // TODO PI leg - const isCourseReversal = wp.additionalData.legType === LegType.HA || wp.additionalData.legType === LegType.HF || wp.additionalData.legType === LegType.HM; + const isCourseReversal = wp.additionalData.legType === LegType.HA + || wp.additionalData.legType === LegType.HF + || wp.additionalData.legType === LegType.HM + || wp.additionalData.legType === LegType.PI; if (i === activeFp.activeWaypointIndex) { type |= NdSymbolTypeFlags.ActiveLegTermination; - } else if (isCourseReversal && i > (activeFp.activeWaypointIndex + 1) && range <= 80) { + } else if (isCourseReversal && i > (activeFp.activeWaypointIndex + 1) && range <= 80 && !LnavConfig.DEBUG_FORCE_INCLUDE_COURSE_REVERSAL_VECTORS) { if (wp.turnDirection === TurnDirection.Left) { type |= NdSymbolTypeFlags.CourseReversalLeft; } else { diff --git a/src/fmgc/src/flightplanning/FlightPlanManager.ts b/src/fmgc/src/flightplanning/FlightPlanManager.ts index 864d72c04757..be515c9cdf7f 100644 --- a/src/fmgc/src/flightplanning/FlightPlanManager.ts +++ b/src/fmgc/src/flightplanning/FlightPlanManager.ts @@ -1577,12 +1577,12 @@ export class FlightPlanManager { /** * Gets the destination runway from the current flight plan. */ - public getDestinationRunway(): OneWayRunway { - const currentFlightPlan = this._flightPlans[this._currentFlightPlanIndex]; + public getDestinationRunway(flightPlanIndex = this._currentFlightPlanIndex): OneWayRunway { + const flightPlan = this._flightPlans[flightPlanIndex]; - const runwayIndex = this.getDestinationRunwayIndex(); + const runwayIndex = this.getDestinationRunwayIndex(flightPlanIndex); if (runwayIndex !== -1) { - return (currentFlightPlan.destinationAirfield.infos as AirportInfo).oneWayRunways[runwayIndex]; + return (flightPlan.destinationAirfield.infos as AirportInfo).oneWayRunways[runwayIndex]; } return undefined; } @@ -1590,17 +1590,17 @@ export class FlightPlanManager { /** * Gets the destination runway index (oneWayRunways) from the current flight plan. */ - public getDestinationRunwayIndex(): number { - const currentFlightPlan = this._flightPlans[this._currentFlightPlanIndex]; + public getDestinationRunwayIndex(flightPlanIndex = this._currentFlightPlanIndex): number { + const flightPlan = this._flightPlans[flightPlanIndex]; - if (currentFlightPlan.procedureDetails.destinationRunwayIndex !== -1 && currentFlightPlan.destinationAirfield) { - return currentFlightPlan.procedureDetails.destinationRunwayIndex; + if (flightPlan.procedureDetails.destinationRunwayIndex !== -1 && flightPlan.destinationAirfield) { + return flightPlan.procedureDetails.destinationRunwayIndex; } - if (currentFlightPlan.hasDestination && currentFlightPlan.procedureDetails.approachIndex !== -1) { + if (flightPlan.hasDestination && flightPlan.procedureDetails.approachIndex !== -1) { console.error('Destination runway index is -1 with valid STAR'); - const approach = (currentFlightPlan.destinationAirfield.infos as AirportInfo).approaches[currentFlightPlan.procedureDetails.approachIndex]; - const runways = (currentFlightPlan.destinationAirfield.infos as AirportInfo).oneWayRunways; + const approach = (flightPlan.destinationAirfield.infos as AirportInfo).approaches[flightPlan.procedureDetails.approachIndex]; + const runways = (flightPlan.destinationAirfield.infos as AirportInfo).oneWayRunways; return runways.findIndex(r => r.number === approach.runwayNumber && r.designator === approach.runwayDesignator); } @@ -1897,14 +1897,14 @@ export class FlightPlanManager { public isWaypointInUse(icao: string): boolean { for (const fp of this._flightPlans) { - for (let i = 0; i < fp.waypoints.length; i++) { + for (let i = 0; i < fp?.waypoints.length; i++) { if (fp.getWaypoint(i).icao === icao) { return true; } } } for (const fixInfo of this._fixInfos) { - if (fixInfo.getRefFix()?.infos.icao === icao) { + if (fixInfo?.getRefFix()?.infos.icao === icao) { return true; } } diff --git a/src/fmgc/src/flightplanning/LegsProcedure.ts b/src/fmgc/src/flightplanning/LegsProcedure.ts index 2ff0d911eb95..e5c7b33ca85b 100644 --- a/src/fmgc/src/flightplanning/LegsProcedure.ts +++ b/src/fmgc/src/flightplanning/LegsProcedure.ts @@ -80,15 +80,15 @@ export class LegsProcedure { ) { for (const leg of this._legs) { if (this.isIcaoValid(leg.fixIcao)) { - this._facilitiesToLoad.set(leg.fixIcao, this._instrument.facilityLoader.getFacilityRaw(leg.fixIcao, 2000)); + this._facilitiesToLoad.set(leg.fixIcao, this._instrument.facilityLoader.getFacilityRaw(leg.fixIcao, 2000, true)); } if (this.isIcaoValid(leg.originIcao)) { - this._facilitiesToLoad.set(leg.originIcao, this._instrument.facilityLoader.getFacilityRaw(leg.originIcao, 2000)); + this._facilitiesToLoad.set(leg.originIcao, this._instrument.facilityLoader.getFacilityRaw(leg.originIcao, 2000, true)); } if (this.isIcaoValid(leg.arcCenterFixIcao)) { - this._facilitiesToLoad.set(leg.arcCenterFixIcao, this._instrument.facilityLoader.getFacilityRaw(leg.arcCenterFixIcao, 2000)); + this._facilitiesToLoad.set(leg.arcCenterFixIcao, this._instrument.facilityLoader.getFacilityRaw(leg.arcCenterFixIcao, 2000, true)); } } } @@ -136,6 +136,7 @@ export class LegsProcedure { try { switch (currentLeg.type) { case LegType.AF: + case LegType.PI: mappedLeg = this.mapExactFix(currentLeg); break; case LegType.CD: @@ -353,7 +354,7 @@ export class LegsProcedure { legDistance * LegsProcedure.distanceNormalFactorNM, prevLeg.infos.coordinates.lat, prevLeg.infos.coordinates.long, ); - const waypoint = this.buildWaypoint(`${originIdent}${Math.trunc(legDistance * LegsProcedure.distanceNormalFactorNM)}`, coordinates); + const waypoint = this.buildWaypoint(`${originIdent.substring(0, 3)}/${Math.round(leg.distance / 1852).toString().padStart(2, '0')}`, coordinates); return waypoint; } diff --git a/src/fmgc/src/flightplanning/ManagedFlightPlan.ts b/src/fmgc/src/flightplanning/ManagedFlightPlan.ts index a73e0fbfa594..b947263d6a7d 100644 --- a/src/fmgc/src/flightplanning/ManagedFlightPlan.ts +++ b/src/fmgc/src/flightplanning/ManagedFlightPlan.ts @@ -1029,20 +1029,24 @@ export class ManagedFlightPlan { } if (arrivalIndex !== -1) { + // string the common legs in the middle of the STAR const arrival: RawArrival = destinationInfo.arrivals[arrivalIndex]; legs.push(...arrival.commonLegs); legAnnotations.push(...arrival.commonLegs.map(_ => arrival.name)); // console.log('MFP: buildArrival - pushing STAR legs ->', legs); - } - if (arrivalIndex !== -1 && arrivalRunwayIndex !== -1) { - const arrival: RawArrival = destinationInfo.arrivals[arrivalIndex]; - const runwayTransition: RawRunwayTransition = destinationInfo.arrivals[arrivalIndex].runwayTransitions[arrivalRunwayIndex]; + // if no runway is selected at all (non-runway-specific approach) + // and the selected STAR only has runway transition legs... string them + // TODO research IRL behaviour + const starHasOneRunwayTrans = arrival.commonLegs.length === 0 && arrival.runwayTransitions.length === 1; + const approachIsRunwaySpecific = this.procedureDetails.destinationRunwayIndex >= 0; + const runwayTransIndex = arrivalRunwayIndex < 0 && starHasOneRunwayTrans && !approachIsRunwaySpecific ? 0 : arrivalRunwayIndex; + + const runwayTransition = arrival.runwayTransitions[runwayTransIndex]; if (runwayTransition) { legs.push(...runwayTransition.legs); legAnnotations.push(...runwayTransition.legs.map(_ => arrival.name)); } - // console.log('MFP: buildArrival - pushing VIA legs ->', legs); } let { _startIndex, segment } = this.truncateSegment(SegmentType.Arrival); @@ -1101,9 +1105,21 @@ export class ManagedFlightPlan { } if (approachIndex !== -1) { + const finalLegs = [...approach.finalLegs]; + // PI legs can only occur in approach vias + // if the via ends in one, we must omit the IF leg at the start of the approach + const viaLastLegType = legs[legs.length - 1]?.type; + if (viaLastLegType === LegType.PI && finalLegs[0]?.type === LegType.IF) { + finalLegs.splice(0, 1); + // @ts-expect-error (ts compiler doesn't see that splice mutates finalLegs) + if (finalLegs[0]?.type !== LegType.CF) { + console.error('PI must be followed by CF!'); + } + } + this.procedureDetails.approachType = approach.approachType; - legs.push(...approach.finalLegs); - legAnnotations.push(...approach.finalLegs.map(_ => approachName)); + legs.push(...finalLegs); + legAnnotations.push(...finalLegs.map(_ => approachName)); missedLegs.push(...approach.missedLegs); } diff --git a/src/fmgc/src/guidance/GuidanceManager.ts b/src/fmgc/src/guidance/GuidanceManager.ts index 21cdabea1f2f..e07631d4c7ef 100644 --- a/src/fmgc/src/guidance/GuidanceManager.ts +++ b/src/fmgc/src/guidance/GuidanceManager.ts @@ -4,6 +4,7 @@ // SPDX-License-Identifier: GPL-3.0 import { HALeg, HFLeg, HMLeg } from '@fmgc/guidance/lnav/legs/HX'; +import { PILeg } from '@fmgc/guidance/lnav/legs/PI'; import { RFLeg } from '@fmgc/guidance/lnav/legs/RF'; import { TFLeg } from '@fmgc/guidance/lnav/legs/TF'; import { VMLeg } from '@fmgc/guidance/lnav/legs/VM'; @@ -137,6 +138,10 @@ export class GuidanceManager { if (to.additionalData?.legType === LegType.HM) { return new HMLeg(to, metadata, segment); } + + if (to.additionalData.legType === LegType.PI) { + return new PILeg(to, nextLeg as CFLeg, metadata, segment); + } } if (to.isVectors) { diff --git a/src/fmgc/src/guidance/lnav/PseudoWaypoints.ts b/src/fmgc/src/guidance/lnav/PseudoWaypoints.ts index b1f266a30a1d..243505f5557f 100644 --- a/src/fmgc/src/guidance/lnav/PseudoWaypoints.ts +++ b/src/fmgc/src/guidance/lnav/PseudoWaypoints.ts @@ -42,6 +42,7 @@ export class PseudoWaypoints implements GuidanceComponent { private recompute() { const geometry = this.guidanceController.activeGeometry; const wptCount = this.guidanceController.flightPlanManager.getWaypointsCount(); + const haveApproach = !!this.guidanceController.vnavDriver.currentApproachProfile; if (!geometry || geometry.legs.size < 1) { this.pseudoWaypoints.length = 0; @@ -69,7 +70,7 @@ export class PseudoWaypoints implements GuidanceComponent { } } - if (VnavConfig.VNAV_EMIT_DECEL) { + if (VnavConfig.VNAV_EMIT_DECEL && haveApproach) { const decel = PseudoWaypoints.pointFromEndOfPath(geometry, wptCount, this.guidanceController.vnavDriver.currentApproachProfile.decel, DEBUG && PWP_IDENT_DECEL); if (decel) { @@ -107,7 +108,7 @@ export class PseudoWaypoints implements GuidanceComponent { // } } - if (VnavConfig.VNAV_DESCENT_MODE === VnavDescentMode.CDA && VnavConfig.VNAV_EMIT_CDA_FLAP_PWP) { + if (VnavConfig.VNAV_DESCENT_MODE === VnavDescentMode.CDA && VnavConfig.VNAV_EMIT_CDA_FLAP_PWP && haveApproach) { const flap1 = PseudoWaypoints.pointFromEndOfPath(geometry, wptCount, this.guidanceController.vnavDriver.currentApproachProfile.flap1, DEBUG && PWP_IDENT_FLAP1); if (flap1) { diff --git a/src/fmgc/src/guidance/lnav/TransitionPicker.ts b/src/fmgc/src/guidance/lnav/TransitionPicker.ts index b2bbd75f5e3e..3665b01a07ac 100644 --- a/src/fmgc/src/guidance/lnav/TransitionPicker.ts +++ b/src/fmgc/src/guidance/lnav/TransitionPicker.ts @@ -21,6 +21,7 @@ import { CRLeg } from '@fmgc/guidance/lnav/legs/CR'; import { CILeg } from '@fmgc/guidance/lnav/legs/CI'; import { AFLeg } from '@fmgc/guidance/lnav/legs/AF'; import { DmeArcTransition } from '@fmgc/guidance/lnav/transitions/DmeArcTransition'; +import { PILeg } from '@fmgc/guidance/lnav/legs/PI'; export class TransitionPicker { static forLegs(from: Leg, to: Leg): Transition | null { @@ -39,6 +40,9 @@ export class TransitionPicker { if (from instanceof HALeg || from instanceof HFLeg || from instanceof HMLeg) { return TransitionPicker.fromHX(from, to); } + if (from instanceof PILeg) { + return TransitionPicker.fromPI(from, to); + } if (from instanceof RFLeg) { return TransitionPicker.fromRF(from, to); } @@ -140,6 +144,9 @@ export class TransitionPicker { if (to instanceof HALeg || to instanceof HFLeg || to instanceof HMLeg) { return new HoldEntryTransition(from, to); } + if (to instanceof PILeg) { + return new FixedRadiusTransition(from, to); + } if (to instanceof TFLeg) { return new FixedRadiusTransition(from, to); } @@ -191,6 +198,9 @@ export class TransitionPicker { if (to instanceof HALeg || to instanceof HFLeg || to instanceof HMLeg) { return new HoldEntryTransition(from, to); } + if (to instanceof PILeg) { + return new FixedRadiusTransition(from, to); + } if (to instanceof CILeg) { return new CourseCaptureTransition(from, to); } @@ -244,6 +254,14 @@ export class TransitionPicker { return null; } + private static fromPI(from: PILeg, to: Leg): Transition | null { + if (!(to instanceof CFLeg)) { + console.error('PI -> !CF', from, to); + } + + return null; + } + private static fromRF(from: RFLeg, to: Leg): Transition | null { if (to instanceof CALeg) { return new CourseCaptureTransition(from, to); @@ -279,6 +297,9 @@ export class TransitionPicker { if (to instanceof HALeg || to instanceof HFLeg || to instanceof HMLeg) { return new HoldEntryTransition(from, to); } + if (to instanceof PILeg) { + return new FixedRadiusTransition(from, to); + } if (to instanceof TFLeg) { return new FixedRadiusTransition(from, to); } diff --git a/src/fmgc/src/guidance/lnav/legs/HX.ts b/src/fmgc/src/guidance/lnav/legs/HX.ts index 6ab88320f93c..f2dbe4a4ef8e 100644 --- a/src/fmgc/src/guidance/lnav/legs/HX.ts +++ b/src/fmgc/src/guidance/lnav/legs/HX.ts @@ -9,7 +9,7 @@ import { GuidanceParameters, LateralPathGuidance } from '@fmgc/guidance/ControlL import { Geometry } from '@fmgc/guidance/Geometry'; import { AltitudeDescriptor, TurnDirection } from '@fmgc/types/fstypes/FSEnums'; import { SegmentType } from '@fmgc/wtsdk'; -import { arcDistanceToGo, arcGuidance, courseToFixDistanceToGo, courseToFixGuidance, maxBank } from '@fmgc/guidance/lnav/CommonGeometry'; +import { arcDistanceToGo, arcGuidance, courseToFixDistanceToGo, courseToFixGuidance, maxBank, reciprocal } from '@fmgc/guidance/lnav/CommonGeometry'; import { XFLeg } from '@fmgc/guidance/lnav/legs/XF'; import { LegMetadata } from '@fmgc/guidance/lnav/legs/index'; import { EntryState, HoldEntryTransition } from '@fmgc/guidance/lnav/transitions/HoldEntryTransition'; @@ -394,7 +394,7 @@ abstract class HXLeg extends XFLeg { updatePrediction() { const windDirection = SimVar.GetSimVarValue('AMBIENT WIND DIRECTION', 'Degrees'); const windSpeed = SimVar.GetSimVarValue('AMBIENT WIND VELOCITY', 'Knots'); - const windAngleToInbound = Math.abs(Avionics.Utils.diffAngle(windDirection, this.inboundCourse)); + const windAngleToInbound = Math.abs(Avionics.Utils.diffAngle(reciprocal(windDirection), this.inboundLegCourse)); this.inboundWindSpeed = Math.cos(windAngleToInbound * Math.PI / 180) * windSpeed; this.currentPredictedTas = this.nextPredictedTas; diff --git a/src/fmgc/src/guidance/lnav/legs/Leg.ts b/src/fmgc/src/guidance/lnav/legs/Leg.ts index 1f50d1d70317..e838f499ecef 100644 --- a/src/fmgc/src/guidance/lnav/legs/Leg.ts +++ b/src/fmgc/src/guidance/lnav/legs/Leg.ts @@ -61,4 +61,8 @@ export abstract class Leg extends Guidable { get overflyTermFix(): boolean { return false; } + + get initialLegTermPoint(): Coordinates { + return this.getPathEndPoint(); + } } diff --git a/src/fmgc/src/guidance/lnav/legs/PI.ts b/src/fmgc/src/guidance/lnav/legs/PI.ts new file mode 100644 index 000000000000..ec1895eeb33c --- /dev/null +++ b/src/fmgc/src/guidance/lnav/legs/PI.ts @@ -0,0 +1,440 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +import { Coordinates } from '@fmgc/flightplanning/data/geo'; +import { GuidanceParameters } from '@fmgc/guidance/ControlLaws'; +import { Geometry } from '@fmgc/guidance/Geometry'; +import { arcDistanceToGo, arcGuidance, courseToFixDistanceToGo, courseToFixGuidance, maxBank } from '@fmgc/guidance/lnav/CommonGeometry'; +import { LegMetadata } from '@fmgc/guidance/lnav/legs'; +import { CFLeg } from '@fmgc/guidance/lnav/legs/CF'; +import { Leg } from '@fmgc/guidance/lnav/legs/Leg'; +import { DebugPointColour, PathVector, PathVectorType } from '@fmgc/guidance/lnav/PathVector'; +import { LnavConfig } from '@fmgc/guidance/LnavConfig'; +import { TurnDirection } from '@fmgc/types/fstypes/FSEnums'; +import { SegmentType } from '@fmgc/wtsdk'; +import { bearingTo, distanceTo, smallCircleGreatCircleIntersection } from 'msfs-geo'; + +interface Segment { + itp?: Coordinates, + arcCentre?: Coordinates, + ftp?: Coordinates, + course?: DegreesTrue, + sweepAngle?: Degrees, + length?: NauticalMiles, +} + +enum PiState { + Straight, + Turn1, + Outbound, + Turn2, + Intercept, +} + +export class PILeg extends Leg { + private radius: NauticalMiles = 1; + + private straight: Segment = {}; + + private turn1: Segment = {}; + + private outbound: Segment = {}; + + private turn2: Segment = {}; + + private intercept: Segment = {}; + + private state: PiState = PiState.Straight; + + private debugPoints: PathVector[] = []; + + constructor( + public fix: WayPoint, + private nextLeg: CFLeg, + public metadata: LegMetadata, + public segment: SegmentType, + ) { + super(); + + this.recomputeWithParameters(false, 220, 220, { lat: 0, long: 0 }, 0); + } + + recomputeWithParameters(isActive: boolean, tas: number, gs: number, _ppos: Coordinates, _trueTrack: number): void { + if (isActive) { + return; + } + + if (this.nextLeg && !(this.nextLeg instanceof CFLeg)) { + throw new Error('PI must be followed by CF!'); + } else if (!this.nextLeg) { + return; + } + + this.debugPoints.length = 0; + + const turn1Sign = this.fix.turnDirection === TurnDirection.Left ? 1 : -1; + const turn2Sign = -1 * turn1Sign; + + const gsMs = gs / 1.94384; + this.radius = (gsMs ** 2 / (9.81 * Math.tan(maxBank(tas, true) * Math.PI / 180)) / 1852); + + const minStraightDist = this.radius * 2; + + const brgToCf = Avionics.Utils.computeGreatCircleHeading( + this.fix.infos.coordinates, + this.nextLeg.fix.infos.coordinates, + ); + + const distToCf = Avionics.Utils.computeGreatCircleDistance( + this.fix.infos.coordinates, + this.nextLeg.fix.infos.coordinates, + ); + + const cfInverseCrs = (this.nextLeg.course + 180) % 360; + this.outbound.course = this.fix.additionalData.course; + + this.straight.itp = this.fix.infos.coordinates; + this.straight.course = cfInverseCrs; + + let tp: Coordinates; + if (Math.abs(Avionics.Utils.diffAngle(cfInverseCrs, brgToCf)) < 90 && distToCf > minStraightDist) { + tp = this.nextLeg.fix.infos.coordinates; + } else { + // find an intercept on the CF at min dist + [tp] = smallCircleGreatCircleIntersection( + this.fix.infos.coordinates, + minStraightDist, + this.nextLeg.fix.infos.coordinates, + cfInverseCrs, + ).filter((p) => Math.abs(Avionics.Utils.diffAngle(cfInverseCrs, bearingTo(this.nextLeg.fix.infos.coordinates, p))) < 90); + + this.straight.course = Avionics.Utils.computeGreatCircleHeading( + this.fix.infos.coordinates, + tp, + ); + } + + this.turn1.sweepAngle = turn1Sign * Math.abs(Avionics.Utils.diffAngle(this.straight.course, this.outbound.course)); + const tpT1FtpDist = this.radius * Math.tan(Math.abs(this.turn1.sweepAngle) * Math.PI / 360); + this.turn1.ftp = Avionics.Utils.bearingDistanceToCoordinates( + this.outbound.course, + tpT1FtpDist, + tp.lat, + tp.long, + ); + this.turn1.arcCentre = Avionics.Utils.bearingDistanceToCoordinates( + (360 + this.outbound.course + turn1Sign * 90) % 360, + this.radius, + this.turn1.ftp.lat, + this.turn1.ftp.long, + ); + this.turn1.itp = Avionics.Utils.bearingDistanceToCoordinates( + (this.straight.course + 180) % 360, + this.radius * (1 - Math.cos(this.turn1.sweepAngle * Math.PI / 180)), + tp.lat, + tp.long, + ); + this.turn1.length = Math.abs(this.turn1.sweepAngle / 180 * this.radius); + + this.straight.ftp = this.turn1.itp; + this.straight.length = Avionics.Utils.computeGreatCircleDistance( + this.fix.infos.coordinates, + this.turn1.itp, + ); + + if (LnavConfig.DEBUG_PREDICTED_PATH) { + this.debugPoints.push({ + type: PathVectorType.DebugPoint, + startPoint: tp, + annotation: 'TP', + colour: DebugPointColour.Yellow, + }); + + this.debugPoints.push({ + type: PathVectorType.DebugPoint, + startPoint: this.turn1.itp, + annotation: 'ITP1', + colour: DebugPointColour.Magenta, + }); + + this.debugPoints.push({ + type: PathVectorType.DebugPoint, + startPoint: this.turn1.arcCentre, + annotation: 'AC1', + colour: DebugPointColour.Magenta, + }); + + this.debugPoints.push({ + type: PathVectorType.DebugPoint, + startPoint: this.turn1.ftp, + annotation: 'FTP1', + colour: DebugPointColour.Magenta, + }); + } + + const theta = Math.abs(Avionics.Utils.diffAngle(this.outbound.course, (this.nextLeg.course + 180) % 360)) * Math.PI / 180; + this.outbound.length = this.radius * (1 / Math.tan(theta / 2)); + this.outbound.itp = this.turn1.ftp; + + this.turn2.itp = Avionics.Utils.bearingDistanceToCoordinates( + this.outbound.course, + this.outbound.length + tpT1FtpDist, + tp.lat, + tp.long, + ); + this.turn2.arcCentre = Avionics.Utils.bearingDistanceToCoordinates( + (360 + this.outbound.course + turn2Sign * 90) % 360, + this.radius, + this.turn2.itp.lat, + this.turn2.itp.long, + ); + this.turn2.sweepAngle = turn2Sign * 180; + this.turn2.ftp = Avionics.Utils.bearingDistanceToCoordinates( + (360 + this.outbound.course + turn2Sign * 90) % 360, + this.radius, + this.turn2.arcCentre.lat, + this.turn2.arcCentre.long, + ); + this.turn2.length = Math.abs(this.turn2.sweepAngle / 180 * this.radius); + + this.outbound.ftp = this.turn2.itp; + + if (LnavConfig.DEBUG_PREDICTED_PATH) { + this.debugPoints.push({ + type: PathVectorType.DebugPoint, + startPoint: this.turn2.itp, + annotation: 'ITP2', + colour: DebugPointColour.Cyan, + }); + + this.debugPoints.push({ + type: PathVectorType.DebugPoint, + startPoint: this.turn2.arcCentre, + annotation: 'AC2', + colour: DebugPointColour.Cyan, + }); + + this.debugPoints.push({ + type: PathVectorType.DebugPoint, + startPoint: this.turn2.ftp, + annotation: 'FTP2', + colour: DebugPointColour.Cyan, + }); + } + + this.intercept.itp = this.turn2.ftp; + this.intercept.ftp = A32NX_Util.greatCircleIntersection( + this.turn2.ftp, + (this.outbound.course + 180) % 360, + tp, + cfInverseCrs, + ); + this.intercept.length = Avionics.Utils.computeGreatCircleDistance( + this.intercept.itp, + this.intercept.ftp, + ); + this.intercept.course = Avionics.Utils.computeGreatCircleHeading( + this.intercept.itp, + this.intercept.ftp, + ); + + this.isComputed = true; + } + + get initialLegTermPoint(): Coordinates { + return this.turn1.itp; + } + + get distanceToTermination(): NauticalMiles { + return this.straight.length; + } + + get distance(): NauticalMiles { + return this.intercept.length + this.turn2.length + this.outbound.length + this.turn1.length + this.straight.length; + } + + /** + * Do we end up further away from the fix than the coded limit + */ + get turnAreaExceeded(): boolean { + if (!this.turn2) { + return false; + } + + const maxExcursion = distanceTo( + this.fix.infos.coordinates, + this.turn2.arcCentre, + ) + this.radius; + + return maxExcursion > this.fix.additionalData.distance; + } + + getDistanceToGo(ppos: Coordinates): NauticalMiles { + switch (this.state) { + case PiState.Intercept: + return courseToFixDistanceToGo(ppos, this.intercept.course, this.intercept.ftp); + case PiState.Turn2: + return this.intercept.length + arcDistanceToGo(ppos, this.turn2.itp, this.turn2.arcCentre, this.turn2.sweepAngle); + case PiState.Outbound: + return this.intercept.length + this.turn2.length + courseToFixDistanceToGo(ppos, this.outbound.course, this.outbound.ftp); + case PiState.Turn1: + return this.intercept.length + this.turn2.length + this.outbound.length + arcDistanceToGo(ppos, this.turn1.itp, this.turn1.arcCentre, this.turn1.sweepAngle); + case PiState.Straight: + return this.intercept.length + this.turn2.length + this.outbound.length + this.turn1.length + courseToFixDistanceToGo(ppos, this.straight.course, this.straight.ftp); + default: + return 1; + } + } + + private dtgCurrentSegment(ppos: Coordinates): NauticalMiles { + switch (this.state) { + case PiState.Intercept: + return courseToFixDistanceToGo(ppos, this.intercept.course, this.intercept.ftp); + case PiState.Turn2: + return arcDistanceToGo(ppos, this.turn2.itp, this.turn2.arcCentre, this.turn2.sweepAngle); + case PiState.Outbound: + return courseToFixDistanceToGo(ppos, this.outbound.course, this.outbound.ftp); + case PiState.Turn1: + return arcDistanceToGo(ppos, this.turn1.itp, this.turn1.arcCentre, this.turn1.sweepAngle); + case PiState.Straight: + return courseToFixDistanceToGo(ppos, this.straight.course, this.straight.ftp); + default: + return 0; + } + } + + private radCurrentSegment(tas: Knots, gs: Knots): [NauticalMiles, Degrees] { + const turn1Sign = this.fix.turnDirection === TurnDirection.Left ? 1 : -1; + const turn2Sign = -1 * turn1Sign; + + let currentBank; + let nextBank; + switch (this.state) { + case PiState.Turn1: + currentBank = turn1Sign * maxBank(tas, true); + nextBank = 0; + break; + case PiState.Turn2: + currentBank = turn2Sign * maxBank(tas, true); + nextBank = 0; + break; + case PiState.Straight: + currentBank = 0; + nextBank = turn1Sign * maxBank(tas, true); + break; + case PiState.Outbound: + currentBank = 0; + nextBank = turn2Sign * maxBank(tas, true); + break; + default: + return [0, 0]; + } + + return [Geometry.getRollAnticipationDistance(gs, currentBank, nextBank), nextBank]; + } + + getGuidanceParameters(ppos: Coordinates, trueTrack: number, tas: number, gs: number): GuidanceParameters { + let dtg = this.dtgCurrentSegment(ppos); + if (dtg <= 0 && this.state < PiState.Intercept) { + this.state++; + dtg = this.dtgCurrentSegment(ppos); + } + + let params; + switch (this.state) { + case PiState.Intercept: + return this.nextLeg?.getGuidanceParameters(ppos, trueTrack, tas); + case PiState.Turn2: + return arcGuidance(ppos, trueTrack, this.turn2.itp, this.turn2.arcCentre, this.turn2.sweepAngle); + case PiState.Outbound: + params = courseToFixGuidance(ppos, trueTrack, this.outbound.course, this.outbound.ftp); + break; + case PiState.Turn1: + params = arcGuidance(ppos, trueTrack, this.turn1.itp, this.turn1.arcCentre, this.turn1.sweepAngle); + break; + case PiState.Straight: + params = courseToFixGuidance(ppos, trueTrack, this.straight.course, this.straight.ftp); + break; + default: + } + + const [rad, nextBank] = this.radCurrentSegment(tas, gs); + + if (params && rad > 0 && dtg <= rad) { + params.phiCommand = nextBank; + } + + return params; + } + + getNominalRollAngle(_gs: number): number { + return 0; + } + + getPathStartPoint(): Coordinates { + return this.inboundGuidable?.isComputed ? this.inboundGuidable.getPathEndPoint() : this.fix.infos.coordinates; + } + + getPathEndPoint(): Coordinates { + return this.intercept.ftp; + } + + get terminationWaypoint(): WayPoint | Coordinates { + return this.intercept.ftp; + } + + get inboundCourse(): number { + return this.straight.course ?? 0; + } + + get outboundCourse(): number { + return this.nextLeg?.course ?? 0; + } + + isAbeam(_ppos: Coordinates): boolean { + return true; // TODO y needed + } + + get predictedPath(): PathVector[] { + return [ + { + type: PathVectorType.Line, + startPoint: this.inboundGuidable?.isComputed ? this.inboundGuidable.getPathEndPoint() : this.fix.infos.coordinates, + endPoint: this.turn1.itp, + }, + { + type: PathVectorType.Arc, + startPoint: this.turn1.itp, + centrePoint: this.turn1.arcCentre, + endPoint: this.turn1.ftp, + sweepAngle: this.turn1.sweepAngle, + }, + { + type: PathVectorType.Line, + startPoint: this.turn1.ftp, + endPoint: this.turn2.itp, + }, + { + type: PathVectorType.Arc, + startPoint: this.turn2.itp, + centrePoint: this.turn2.arcCentre, + endPoint: this.turn2.ftp, + sweepAngle: this.turn2.sweepAngle, + }, + { + type: PathVectorType.Line, + startPoint: this.turn2.ftp, + endPoint: this.intercept.ftp, + }, + ...this.debugPoints, + ]; + } + + get ident(): string { + return 'INTCPT'; + } + + get repr(): string { + return `PI ${this.ident}`; + } +} diff --git a/src/fmgc/src/guidance/lnav/legs/index.ts b/src/fmgc/src/guidance/lnav/legs/index.ts index df133464be47..7007d50fdd4e 100644 --- a/src/fmgc/src/guidance/lnav/legs/index.ts +++ b/src/fmgc/src/guidance/lnav/legs/index.ts @@ -5,6 +5,7 @@ import { HALeg, HFLeg, HMLeg } from '@fmgc/guidance/lnav/legs/HX'; import { Leg } from '@fmgc/guidance/lnav/legs/Leg'; +import { PILeg } from '@fmgc/guidance/lnav/legs/PI'; import { TurnDirection } from '@fmgc/types/fstypes/FSEnums'; export enum AltitudeConstraintType { @@ -86,7 +87,7 @@ export function isHold(leg: Leg): boolean { } export function isCourseReversalLeg(leg: Leg): boolean { - return isHold(leg); // TODO PILeg + return isHold(leg) || leg instanceof PILeg; } /** diff --git a/src/fmgc/src/guidance/lnav/transitions/CourseCaptureTransition.ts b/src/fmgc/src/guidance/lnav/transitions/CourseCaptureTransition.ts index ea604f12f05f..a5e2cc1f0fe3 100644 --- a/src/fmgc/src/guidance/lnav/transitions/CourseCaptureTransition.ts +++ b/src/fmgc/src/guidance/lnav/transitions/CourseCaptureTransition.ts @@ -19,6 +19,7 @@ import { PathVector, PathVectorType } from '@fmgc/guidance/lnav/PathVector'; import { TurnDirection } from '@fmgc/types/fstypes/FSEnums'; import { LnavConfig } from '@fmgc/guidance/LnavConfig'; import { AFLeg } from '@fmgc/guidance/lnav/legs/AF'; +import { ControlLaw } from '@shared/autopilot'; import { arcDistanceToGo, arcLength, maxBank } from '../CommonGeometry'; import { CFLeg } from '../legs/CF'; import { CRLeg } from '../legs/CR'; @@ -78,9 +79,15 @@ export class CourseCaptureTransition extends Transition { public predictedPath: PathVector[] = []; + private forcedTurnComplete = false; + + private computedTurnDirection = TurnDirection.Either; + recomputeWithParameters(_isActive: boolean, tas: Knots, gs: Knots, ppos: Coordinates, _trueTrack: DegreesTrue) { const termFix = this.previousLeg.getPathEndPoint(); + this.computedTurnDirection = TurnDirection.Either; + let courseChange; let initialTurningPoint; if (!this.inboundGuidable) { @@ -128,6 +135,8 @@ export class CourseCaptureTransition extends Transition { return; } + this.computedTurnDirection = this.clockwise ? TurnDirection.Right : TurnDirection.Left; + this.isNull = false; this.isArc = true; this.startPoint = initialTurningPoint; @@ -162,15 +171,7 @@ export class CourseCaptureTransition extends Transition { } isAbeam(ppos: LatLongData): boolean { - const [inbound, outbound] = this.getTurningPoints(); - - const inBearingAc = Avionics.Utils.computeGreatCircleHeading(inbound, ppos); - const inHeadingAc = Math.abs(MathUtils.diffAngle(this.previousLeg.outboundCourse, inBearingAc)); - - const outBearingAc = Avionics.Utils.computeGreatCircleHeading(outbound, ppos); - const outHeadingAc = Math.abs(MathUtils.diffAngle(this.nextLeg.inboundCourse, outBearingAc)); - - return inHeadingAc <= 90 && outHeadingAc >= 90; + return !this.isNull && this.computedTurnDirection !== TurnDirection.Either && !this.forcedTurnComplete && this.previousLeg.getDistanceToGo(ppos) <= 0; } get distance(): NauticalMiles { @@ -192,6 +193,24 @@ export class CourseCaptureTransition extends Transition { } getGuidanceParameters(ppos: LatLongAlt, trueTrack: number, tas: Knots, gs: Knots): GuidanceParameters | null { + if (this.computedTurnDirection !== TurnDirection.Either) { + const turnSign = this.computedTurnDirection === TurnDirection.Left ? -1 : 1; + let trackAngleError = this.nextLeg.inboundCourse - trueTrack; + if (turnSign !== Math.sign(trackAngleError)) { + trackAngleError += turnSign * 360; + } + if (Math.abs(trackAngleError) > 130) { + const phiCommand = turnSign * maxBank(tas, false); + return { + law: ControlLaw.LATERAL_PATH, + trackAngleError: 0, + phiCommand, + crossTrackError: 0, + }; + } + this.forcedTurnComplete = true; + } + // FIXME PPOS guidance and all... return this.nextLeg.getGuidanceParameters(ppos, trueTrack, tas, gs); } diff --git a/src/fmgc/src/guidance/lnav/transitions/FixedRadiusTransition.ts b/src/fmgc/src/guidance/lnav/transitions/FixedRadiusTransition.ts index 5d6545da12b4..e19edb3fa9c4 100644 --- a/src/fmgc/src/guidance/lnav/transitions/FixedRadiusTransition.ts +++ b/src/fmgc/src/guidance/lnav/transitions/FixedRadiusTransition.ts @@ -5,6 +5,7 @@ import { MathUtils } from '@shared/MathUtils'; import { DFLeg } from '@fmgc/guidance/lnav/legs/DF'; +import { PILeg } from '@fmgc/guidance/lnav/legs/PI'; import { TFLeg } from '@fmgc/guidance/lnav/legs/TF'; import { Transition } from '@fmgc/guidance/lnav/Transition'; import { PathCaptureTransition } from '@fmgc/guidance/lnav/transitions/PathCaptureTransition'; @@ -22,7 +23,7 @@ import { PathVector, PathVectorType } from '../PathVector'; import { CFLeg } from '../legs/CF'; type PrevLeg = CILeg | CFLeg | DFLeg | TFLeg; -type NextLeg = CFLeg | /* FALeg | FMLeg | PILeg | */ TFLeg; +type NextLeg = CFLeg | /* FALeg | FMLeg | */ PILeg | TFLeg; const mod = (x: number, n: number) => x - Math.floor(x / n) * n; diff --git a/src/fmgc/src/guidance/lnav/transitions/PathCaptureTransition.ts b/src/fmgc/src/guidance/lnav/transitions/PathCaptureTransition.ts index 122a522c5dab..31bb3f22a973 100644 --- a/src/fmgc/src/guidance/lnav/transitions/PathCaptureTransition.ts +++ b/src/fmgc/src/guidance/lnav/transitions/PathCaptureTransition.ts @@ -37,6 +37,8 @@ import { placeBearingIntersection, smallCircleGreatCircleIntersection, } from 'msfs-geo'; +import { PILeg } from '@fmgc/guidance/lnav/legs/PI'; +import { isCourseReversalLeg } from '@fmgc/guidance/lnav/legs'; import { Leg } from '../legs/Leg'; import { CFLeg } from '../legs/CF'; import { CRLeg } from '../legs/CR'; @@ -44,6 +46,7 @@ import { CRLeg } from '../legs/CR'; export type PrevLeg = AFLeg | CALeg | /* CDLeg | */ CRLeg | /* FALeg | */ HALeg | HFLeg | HMLeg; export type ReversionLeg = CFLeg | CILeg | DFLeg | TFLeg; export type NextLeg = AFLeg | CFLeg | /* FALeg | */ TFLeg; +type NextReversionLeg = PILeg; const cos = (input: Degrees) => Math.cos(input * (Math.PI / 180)); const tan = (input: Degrees) => Math.tan(input * MathUtils.DEGREES_TO_RADIANS); @@ -61,7 +64,7 @@ const compareTurnDirections = (sign: number, data: TurnDirection) => { export class PathCaptureTransition extends Transition { constructor( public previousLeg: PrevLeg | ReversionLeg, - public nextLeg: NextLeg, + public nextLeg: NextLeg | NextReversionLeg, ) { super(previousLeg, nextLeg); } @@ -88,10 +91,12 @@ export class PathCaptureTransition extends Transition { tad: NauticalMiles | undefined; - private forcedTurnRequired = false; - private forcedTurnComplete = false; + private computedTurnDirection = TurnDirection.Either; + + private computedTargetTrack: DegreesTrue = 0; + recomputeWithParameters(_isActive: boolean, tas: Knots, gs: Knots, _ppos: Coordinates, _trueTrack: DegreesTrue) { if (this.isFrozen) { return; @@ -105,6 +110,9 @@ export class PathCaptureTransition extends Transition { const naturalTurnDirectionSign = Math.sign(MathUtils.diffAngle(targetTrack, this.nextLeg.inboundCourse)); + this.computedTurnDirection = TurnDirection.Either; + this.computedTargetTrack = this.nextLeg.inboundCourse; + let prevLegTermFix: LatLongAlt | Coordinates; if (this.previousLeg instanceof AFLeg) { prevLegTermFix = this.previousLeg.arcEndPoint; @@ -141,12 +149,12 @@ export class PathCaptureTransition extends Transition { } const distanceFromItp: NauticalMiles = Geo.distanceToLeg(initialTurningPoint, this.nextLeg); - const deltaTrack: Degrees = MathUtils.diffAngle(targetTrack, this.nextLeg.inboundCourse, this.nextLeg.metadata.turnDirection); + // for some legs the turn direction is not for forced turn onto the leg + const desiredDirection = isCourseReversalLeg(this.nextLeg) ? TurnDirection.Either : this.nextLeg.metadata.turnDirection; + const deltaTrack: Degrees = MathUtils.diffAngle(targetTrack, this.nextLeg.inboundCourse, desiredDirection); this.predictedPath.length = 0; - this.forcedTurnRequired = Math.abs(deltaTrack) > 130; - if (Math.abs(deltaTrack) < 3 && distanceFromItp < 0.1) { this.itp = this.previousLeg.getPathEndPoint(); this.ftp = this.previousLeg.getPathEndPoint(); @@ -299,11 +307,15 @@ export class PathCaptureTransition extends Transition { if (isReverse) { courseChange = CourseChange.reverse(turnDirection, turnCenterDistance, deltaTrack, radius); + this.computedTurnDirection = this.nextLeg.metadata.turnDirection; } else { courseChange = CourseChange.normal(turnDirection, turnCenterDistance, deltaTrack, radius); + this.computedTurnDirection = turnDirection < 0 ? TurnDirection.Left : TurnDirection.Right; } } + this.computedTargetTrack = (360 + this.previousLeg.outboundCourse + courseChange) % 360; + const finalTurningPoint = placeBearingDistance(turnCenter, targetTrack + courseChange - 90 * turnDirection, radius); let intercept; @@ -429,7 +441,7 @@ export class PathCaptureTransition extends Transition { } isAbeam(ppos: LatLongData): boolean { - return !this.isNull && this.forcedTurnRequired && !this.forcedTurnComplete && this.previousLeg.getDistanceToGo(ppos) <= 0; + return !this.isNull && this.computedTurnDirection !== TurnDirection.Either && !this.forcedTurnComplete && this.previousLeg.getDistanceToGo(ppos) <= 0; } distance = 0; @@ -442,10 +454,10 @@ export class PathCaptureTransition extends Transition { return 1; } - getGuidanceParameters(ppos: LatLongAlt, trueTrack: number, tas: Knots): GuidanceParameters | null { - if (this.forcedTurnRequired) { - const turnSign = this.nextLeg.metadata.turnDirection === TurnDirection.Left ? -1 : 1; - let trackAngleError = this.nextLeg.inboundCourse - trueTrack; + getGuidanceParameters(ppos: LatLongAlt, trueTrack: number, tas: Knots, gs: Knots): GuidanceParameters | null { + if (this.computedTurnDirection !== TurnDirection.Either) { + const turnSign = this.computedTurnDirection === TurnDirection.Left ? -1 : 1; + let trackAngleError = this.computedTargetTrack - trueTrack; if (turnSign !== Math.sign(trackAngleError)) { trackAngleError += turnSign * 360; } @@ -461,7 +473,7 @@ export class PathCaptureTransition extends Transition { this.forcedTurnComplete = true; } - return this.nextLeg.getGuidanceParameters(ppos, trueTrack, tas); + return this.nextLeg.getGuidanceParameters(ppos, trueTrack, tas, gs); } getNominalRollAngle(_gs: Knots): Degrees { diff --git a/src/fmgc/src/guidance/vnav/AtmosphericConditions.ts b/src/fmgc/src/guidance/vnav/AtmosphericConditions.ts index 3d4f21165e31..c8767c74bfec 100644 --- a/src/fmgc/src/guidance/vnav/AtmosphericConditions.ts +++ b/src/fmgc/src/guidance/vnav/AtmosphericConditions.ts @@ -4,7 +4,7 @@ import { Common } from './common'; export class AtmosphericConditions { - private ambientTemperatureFromSim: Celcius; + private ambientTemperatureFromSim: Celsius; private altitudeFromSim: Feet; @@ -19,7 +19,7 @@ export class AtmosphericConditions { private windDirectionFromSim: DegreesTrue; - private computedIsaDeviation: Celcius; + private computedIsaDeviation: Celsius; constructor() { this.update(); @@ -37,7 +37,7 @@ export class AtmosphericConditions { this.computedIsaDeviation = this.ambientTemperatureFromSim - Common.getIsaTemp(this.altitudeFromSim); } - get currentStaticAirTemperature(): Celcius { + get currentStaticAirTemperature(): Celsius { return this.ambientTemperatureFromSim; } @@ -65,7 +65,7 @@ export class AtmosphericConditions { return Math.cos(Avionics.Utils.diffAngle(direction, this.currentWindDirection)) * this.currentWindSpeed; } - get isaDeviation(): Celcius { + get isaDeviation(): Celsius { return this.computedIsaDeviation; } diff --git a/src/fmgc/src/guidance/vnav/FlightModel.ts b/src/fmgc/src/guidance/vnav/FlightModel.ts index 3ba3f1b13d7a..2cdb70c6a576 100644 --- a/src/fmgc/src/guidance/vnav/FlightModel.ts +++ b/src/fmgc/src/guidance/vnav/FlightModel.ts @@ -70,7 +70,7 @@ export class FlightModel { break; } - const spdBrkIncrement = spdBrkDeflected ? 0.01 : 0; + const spdBrkIncrement = spdBrkDeflected ? 0.00611 : 0; const gearIncrement = gearExtended ? 0.03 : 0; return baseDrag + spdBrkIncrement + gearIncrement; } diff --git a/src/fmgc/src/guidance/vnav/VnavDriver.ts b/src/fmgc/src/guidance/vnav/VnavDriver.ts index ea0fdebefcd9..64ff323258b2 100644 --- a/src/fmgc/src/guidance/vnav/VnavDriver.ts +++ b/src/fmgc/src/guidance/vnav/VnavDriver.ts @@ -10,6 +10,7 @@ import { RequestedVerticalMode, TargetAltitude, TargetVerticalSpeed } from '@fmg import { AtmosphericConditions } from '@fmgc/guidance/vnav/AtmosphericConditions'; import { VerticalMode } from '@shared/autopilot'; import { CoarsePredictions } from '@fmgc/guidance/vnav/CoarsePredictions'; +import { FlightPlans } from '@fmgc/flightplanning/FlightPlanManager'; import { Geometry } from '../Geometry'; import { GuidanceComponent } from '../GuidanceComponent'; import { ClimbPathBuilder } from './climb/ClimbPathBuilder'; @@ -22,7 +23,7 @@ export class VnavDriver implements GuidanceComponent { currentDescentProfile: TheoreticalDescentPathCharacteristics - currentApproachProfile: DecelPathCharacteristics; + currentApproachProfile?: DecelPathCharacteristics; private guidanceMode: RequestedVerticalMode; @@ -74,7 +75,11 @@ export class VnavDriver implements GuidanceComponent { if (VnavConfig.VNAV_CALCULATE_CLIMB_PROFILE) { this.currentClimbProfile = ClimbPathBuilder.computeClimbPath(geometry); } - this.currentApproachProfile = DecelPathBuilder.computeDecelPath(geometry); + if (this.guidanceController.flightPlanManager.getApproach(FlightPlans.Active)) { + this.currentApproachProfile = DecelPathBuilder.computeDecelPath(geometry); + } else { + this.currentApproachProfile = null; + } this.currentDescentProfile = DescentBuilder.computeDescentPath(geometry, this.currentApproachProfile); this.guidanceController.pseudoWaypoints.acceptVerticalProfile(); diff --git a/src/fmgc/src/guidance/vnav/descent/DescentBuilder.ts b/src/fmgc/src/guidance/vnav/descent/DescentBuilder.ts index 7f3f0a348c30..4eb911283599 100644 --- a/src/fmgc/src/guidance/vnav/descent/DescentBuilder.ts +++ b/src/fmgc/src/guidance/vnav/descent/DescentBuilder.ts @@ -5,10 +5,10 @@ import { DecelPathCharacteristics } from '@fmgc/guidance/vnav/descent/DecelPathB export class DescentBuilder { static computeDescentPath( geometry: Geometry, - decelPath: DecelPathCharacteristics, + decelPath?: DecelPathCharacteristics, ): TheoreticalDescentPathCharacteristics { const cruiseAlt = SimVar.GetSimVarValue('L:AIRLINER_CRUISE_ALTITUDE', 'number'); - const verticalDistance = cruiseAlt - decelPath.top; + const verticalDistance = cruiseAlt - decelPath?.top ?? 0; const fpa = 3; if (DEBUG) { @@ -16,7 +16,7 @@ export class DescentBuilder { console.log(verticalDistance); } - const tod = decelPath.decel + (verticalDistance / Math.tan((fpa * Math.PI) / 180)) * 0.000164579; + const tod = decelPath?.decel ?? 0 + (verticalDistance / Math.tan((fpa * Math.PI) / 180)) * 0.000164579; if (DEBUG) { console.log(`[FMS/VNAV] T/D: ${tod.toFixed(1)}nm`); diff --git a/src/fmgc/src/navigation/Navigation.ts b/src/fmgc/src/navigation/Navigation.ts index 2a80a6e93098..004464779207 100644 --- a/src/fmgc/src/navigation/Navigation.ts +++ b/src/fmgc/src/navigation/Navigation.ts @@ -3,6 +3,7 @@ import { FlightPlanManager } from '@fmgc/index'; import { RequiredPerformance } from '@fmgc/navigation/RequiredPerformance'; +import { Coordinates } from 'msfs-geo'; export class Navigation { requiredPerformance: RequiredPerformance; @@ -11,6 +12,10 @@ export class Navigation { accuracyHigh: boolean = false; + ppos: Coordinates = { lat: 0, long: 0 }; + + groundSpeed: Knots = 0; + constructor(private flightPlanManager: FlightPlanManager) { this.requiredPerformance = new RequiredPerformance(this.flightPlanManager); } @@ -22,6 +27,8 @@ export class Navigation { this.requiredPerformance.update(deltaTime); this.updateCurrentPerformance(); + + this.updatePosition(); } private updateCurrentPerformance(): void { @@ -39,4 +46,11 @@ export class Navigation { SimVar.SetSimVarValue('L:A32NX_FMGC_R_NAV_ACCURACY_HIGH', 'bool', this.accuracyHigh); } } + + private updatePosition(): void { + this.ppos.lat = SimVar.GetSimVarValue('PLANE LATITUDE', 'degree latitude'); + this.ppos.long = SimVar.GetSimVarValue('PLANE LONGITUDE', 'degree longitude'); + + this.groundSpeed = SimVar.GetSimVarValue('GPS GROUND SPEED', 'knots'); + } } diff --git a/src/fmgc/src/types/A32NX_Util.d.ts b/src/fmgc/src/types/A32NX_Util.d.ts index d057b5501f64..bb35e334a95b 100644 --- a/src/fmgc/src/types/A32NX_Util.d.ts +++ b/src/fmgc/src/types/A32NX_Util.d.ts @@ -43,7 +43,7 @@ declare global { function getIsaTemp(alt?: Feet): number; - function getIsaTempDeviation(alt?: Feet, sat?: Celcius): Celcius + function getIsaTempDeviation(alt?: Feet, sat?: Celsius): Celsius class UpdateThrottler { constructor(intervalMs: number); diff --git a/src/fmgc/src/utils/Geo.ts b/src/fmgc/src/utils/Geo.ts index e9750a1de715..95db6da55379 100644 --- a/src/fmgc/src/utils/Geo.ts +++ b/src/fmgc/src/utils/Geo.ts @@ -35,7 +35,7 @@ export class Geo { const intersections1 = placeBearingIntersection( from, Avionics.Utils.clampAngle(leg.outboundCourse - 90), - leg.getPathEndPoint(), + leg.initialLegTermPoint, Avionics.Utils.clampAngle(leg.outboundCourse - 180), ); diff --git a/src/instruments/src/Common/EWDMessages.tsx b/src/instruments/src/Common/EWDMessages.tsx index 4748b8ac74c0..fe534dab8e76 100644 --- a/src/instruments/src/Common/EWDMessages.tsx +++ b/src/instruments/src/Common/EWDMessages.tsx @@ -102,6 +102,7 @@ const EWDMessages = { '000055201': '\x1b<3mCOMPANY MSG', '000056001': '\x1b<3mHI ALT SET', '000068001': '\x1b<3mADIRS SWTG', + '000056701': '\x1b<3mVHF3 VOICE', '213122101': '\x1b<2m\x1b4mCAB PR\x1bm EXCESS CAB ALT', '213122102': '\x1b<5m -CREW OXY MASKS.....USE', '213122103': '\x1b<5m -SIGNS...............ON', @@ -172,6 +173,63 @@ const EWDMessages = { '270008502': '\x1b<2mSLATS NOT IN T.O CONFIG', '270009001': '\x1b<2m\x1b4mCONFIG\x1bm', '270009002': '\x1b<2mFLAPS NOT IN T.O CONFIG', + '270011001': '\x1b<4m\x1b4mF/CTL\x1bm ELAC 1 FAULT', + '270011002': '\x1b<5m -ELAC 1.....OFF THEN ON', + '270011003': '\x1b<7m .IF UNSUCCESSFUL :', + '270011004': '\x1b<5m -ELAC 1.............OFF', + '270011005': '\x1b<5m FUEL CONSUMPT INCRSD', + '270011006': '\x1b<5m FMS PRED UNRELIABLE', + '270012001': '\x1b<4m\x1b4mF/CTL\x1bm ELAC 2 FAULT', + '270012002': '\x1b<5m -ELAC 2.....OFF THEN ON', + '270012003': '\x1b<7m .IF UNSUCCESSFUL :', + '270012004': '\x1b<5m -ELAC 2.............OFF', + '270012005': '\x1b<5m FUEL CONSUMPT INCRSD', + '270012006': '\x1b<5m FMS PRED UNRELIABLE', + '270021001': '\x1b<4m\x1b4mF/CTL\x1bm SEC 1 FAULT', + '270021002': '\x1b<5m -SEC 1......OFF THEN ON', + '270021003': '\x1b<7m .IF UNSUCCESSFUL :', + '270021004': '\x1b<5m -SEC 1..............OFF', + '270021005': '\x1b<5m SPD BRK......DO NOT USE', + '270022001': '\x1b<4m\x1b4mF/CTL\x1bm SEC 2 FAULT', + '270022002': '\x1b<5m -SEC 2......OFF THEN ON', + '270022003': '\x1b<7m .IF UNSUCCESSFUL :', + '270022004': '\x1b<5m -SEC 2..............OFF', + '270023001': '\x1b<4m\x1b4mF/CTL\x1bm SEC 3 FAULT', + '270023002': '\x1b<5m -SEC 3......OFF THEN ON', + '270023003': '\x1b<7m .IF UNSUCCESSFUL :', + '270023004': '\x1b<5m -SEC 3..............OFF', + '270036001': '\x1b<4m\x1b4mF/CTL\x1bm FCDC 1+2 FAULT', + '270036002': '\x1b<5m -MONITOR F/CTL OVHD PNL', + '270036501': '\x1b<4m\x1b4mF/CTL\x1bm DIRECT LAW', + '270036502': '\x1b<4m (PROT LOST)', + '270036503': '\x1b<5m MAX SPEED.......320/.77', + '270036504': '\x1b<5m -MAN PITCH TRIM.....USE', + '270036505': '\x1b<5m MANEUVER WITH CARE', + '270036506': '\x1b<5m MAX FL..............350', + '270036507': '\x1b<5m USE SPD BRK WITH CARE', + '270036508': '\x1b<5m SPD BRK......DO NOT USE', + '270037501': '\x1b<4m\x1b4mF/CTL\x1bm ALTN LAW', + '270037502': '\x1b<4m (PROT LOST)', + '270037503': '\x1b<5m MAX SPEED........320 KT', + '270037504': '\x1b<5m MAX SPEED.......320/.77', + '270037505': '\x1b<5m MANEUVER WITH CARE', + '270037506': '\x1b<5m MAX FL..............350', + '270037507': '\x1b<5m SPD BRK......DO NOT USE', + '270039001': '\x1b<4m\x1b4mF/CTL\x1bm ALTN LAW', + '270039002': '\x1b<4m (PROT LOST)', + '270039003': '\x1b<5m MAX SPEED........320 KT', + '270039004': '\x1b<5m MAX SPEED.......320/.77', + '270039005': '\x1b<5m MANEUVER WITH CARE', + '270039006': '\x1b<5m MAX FL..............350', + '270039007': '\x1b<5m SPD BRK......DO NOT USE', + '270040001': '\x1b<2m\x1b4mF/CTL\x1bm L+R ELEV FAULT', + '270040002': '\x1b<5m MAX SPEED.......320/.77', + '270040003': '\x1b<5m -MAN PITCH TRIM.....USE', + '270040004': '\x1b<5m MANEUVER WITH CARE', + '270040005': '\x1b<5m MAX FL..............350', + '270040006': '\x1b<5m SPD BRK......DO NOT USE', + '270055501': '\x1b<4m\x1b4mF/CTL\x1bm FCDC 1 FAULT', + '270055701': '\x1b<4m\x1b4mF/CTL\x1bm FCDC 2 FAULT', '290031001': '\x1b<4m*HYD', '290031201': '\x1b<4m*HYD', '308118601': '\x1b<4m\x1b4mSEVERE ICE\x1bm DETECTED', diff --git a/src/instruments/src/Common/bitFlags.tsx b/src/instruments/src/Common/bitFlags.tsx new file mode 100644 index 000000000000..da45bce87bf5 --- /dev/null +++ b/src/instruments/src/Common/bitFlags.tsx @@ -0,0 +1,21 @@ +import { useLocalStorage } from '@instruments/common/persistence'; +import { BitFlags } from '@shared/bitFlags'; +import { useCallback, useState } from 'react'; + +type BitFlagsSetter = (oldValue: T) => T; + +export const useBitFlags = ( + name: string, +): [BitFlags, (setter: BitFlags +) => void] => { + const [storage, setStorage] = useLocalStorage(name, ''); + const [stateValue, setValue] = useState(storage ? JSON.parse(storage) : 0); + + const setter = useCallback((valueOrSetter: BitFlags | BitFlagsSetter) => { + const executedValue: number = typeof valueOrSetter === 'function' ? valueOrSetter(stateValue) : valueOrSetter.toDouble(); + setValue(executedValue); + setStorage(JSON.stringify(executedValue)); + }, [name, stateValue]); + + return [new BitFlags(stateValue), setter]; +}; diff --git a/src/instruments/src/Common/simVars.tsx b/src/instruments/src/Common/simVars.tsx index bf6948793b5d..4f1d12652776 100644 --- a/src/instruments/src/Common/simVars.tsx +++ b/src/instruments/src/Common/simVars.tsx @@ -3,7 +3,7 @@ import { useInteractionEvents, useUpdate } from './hooks'; type SimVarSetter = (oldValue: T) => T; -type UnitName = string | any; // once typings is next to tsconfig.json, use those units +type UnitName = SimVar.SimVarUnit; type SimVarValue = number | any; /** diff --git a/src/instruments/src/EFB/Assets/GroundServiceOutline.tsx b/src/instruments/src/EFB/Assets/GroundServiceOutline.tsx new file mode 100644 index 000000000000..56d1713f3130 --- /dev/null +++ b/src/instruments/src/EFB/Assets/GroundServiceOutline.tsx @@ -0,0 +1,574 @@ +/* eslint-disable max-len */ +import * as React from 'react'; +// viewBox="0 0 777 814" +export const GroundServiceOutline = ({ className }: {className: string}) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/src/instruments/src/EFB/Assets/Plane.tsx b/src/instruments/src/EFB/Assets/Plane.tsx new file mode 100644 index 000000000000..41dc75381fe0 --- /dev/null +++ b/src/instruments/src/EFB/Assets/Plane.tsx @@ -0,0 +1,128 @@ +/* eslint-disable react/no-unknown-property */ +/* eslint-disable max-len */ +import React from 'react'; + +export const Plane = ({ className }: { className: string }) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/src/instruments/src/EFB/Assets/Seat.tsx b/src/instruments/src/EFB/Assets/Seat.tsx new file mode 100644 index 000000000000..922c3ea47bad --- /dev/null +++ b/src/instruments/src/EFB/Assets/Seat.tsx @@ -0,0 +1,15 @@ +/* eslint-disable react/no-unknown-property */ +/* eslint-disable max-len */ +import React from 'react'; + +interface SeatProps { + fill: string; + stroke: string; + opacity: string; +} + +export const Seat = ({ fill, stroke, opacity }: SeatProps) => ( + + + +); diff --git a/src/instruments/src/EFB/Assets/SeatOutlineBg.tsx b/src/instruments/src/EFB/Assets/SeatOutlineBg.tsx new file mode 100644 index 000000000000..d0412f929856 --- /dev/null +++ b/src/instruments/src/EFB/Assets/SeatOutlineBg.tsx @@ -0,0 +1,211 @@ +/* eslint-disable max-len */ +import React from 'react'; + +interface SeatOutlineBgProps { + stroke: string; + highlight: string; +} + +export const SeatOutlineBg = ({ stroke, highlight }: SeatOutlineBgProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/src/instruments/src/EFB/Assets/seat.svg b/src/instruments/src/EFB/Assets/seat.svg new file mode 100644 index 000000000000..93d31247794c --- /dev/null +++ b/src/instruments/src/EFB/Assets/seat.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/src/instruments/src/EFB/Efb.tsx b/src/instruments/src/EFB/Efb.tsx index 1f0ec2622028..d8b89b3aec43 100644 --- a/src/instruments/src/EFB/Efb.tsx +++ b/src/instruments/src/EFB/Efb.tsx @@ -98,6 +98,14 @@ const Efb = () => { const [dc2BusIsPowered] = useSimVar('L:A32NX_ELEC_DC_2_BUS_IS_POWERED', 'bool'); const [batteryLevel, setBatteryLevel] = useState({ level: 100, lastChangeTimestamp: absoluteTime, isCharging: dc2BusIsPowered }); + const [ac1BusIsPowered] = useSimVar('L:A32NX_ELEC_AC_1_BUS_IS_POWERED', 'number', 1000); + const [, setLoadLightingPresetVar] = useSimVar('L:A32NX_LOAD_LIGHTING_PRESET', 'number', 200); + const [timeOfDay] = useSimVar('E:TIME OF DAY', 'number', 5000); + const [autoLoadLightingPresetEnabled] = usePersistentNumberProperty('LIGHT_PRESET_AUTOLOAD', 0); + const [autoLoadDayLightingPresetID] = usePersistentNumberProperty('LIGHT_PRESET_AUTOLOAD_DAY', 0); + const [autoLoadDawnDuskLightingPresetID] = usePersistentNumberProperty('LIGHT_PRESET_AUTOLOAD_DAWNDUSK', 0); + const [autoLoadNightLightingPresetID] = usePersistentNumberProperty('LIGHT_PRESET_AUTOLOAD_NIGHT', 0); + const [lat] = useSimVar('PLANE LATITUDE', 'degree latitude', 4000); const [long] = useSimVar('PLANE LONGITUDE', 'degree longitude', 4000); @@ -199,6 +207,31 @@ const Efb = () => { } }, [powerState]); + // Automatically load a lighting preset + useEffect(() => { + if (ac1BusIsPowered && autoLoadLightingPresetEnabled) { + switch (timeOfDay) { + case 1: + if (autoLoadDayLightingPresetID !== 0) { + setLoadLightingPresetVar(autoLoadDayLightingPresetID); + } + break; + case 2: + if (autoLoadDawnDuskLightingPresetID !== 0) { + setLoadLightingPresetVar(autoLoadDawnDuskLightingPresetID); + } + break; + case 3: + if (autoLoadNightLightingPresetID !== 0) { + setLoadLightingPresetVar(autoLoadNightLightingPresetID); + } + break; + default: + break; + } + } + }, [ac1BusIsPowered, autoLoadLightingPresetEnabled]); + useInterval(() => { if (!autoFillChecklists) return; diff --git a/src/instruments/src/EFB/Failures/Pages/Comfort/index.tsx b/src/instruments/src/EFB/Failures/Pages/Comfort/index.tsx index 56be58df9e5c..723a5dd3ba66 100644 --- a/src/instruments/src/EFB/Failures/Pages/Comfort/index.tsx +++ b/src/instruments/src/EFB/Failures/Pages/Comfort/index.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { Route } from 'react-router'; import { Link } from 'react-router-dom'; import { Failure } from '@flybywiresim/failures'; +import { ScrollableContainer } from '../../../UtilComponents/ScrollableContainer'; import { t } from '../../../translation'; import { pathify } from '../../../Utils/routing'; import { AtaChapterPage } from './AtaChapterPage'; @@ -61,13 +62,15 @@ interface ComfortUIProps { export const ComfortUI = ({ filteredChapters, allChapters, failures }: ComfortUIProps) => ( <> - {filteredChapters.map((chapter) => ( - - ))} + + {filteredChapters.map((chapter) => ( + + ))} + {filteredChapters.length === 0 && (

{t('Failures.NoItemsFound')}

diff --git a/src/instruments/src/EFB/Ground/Ground.tsx b/src/instruments/src/EFB/Ground/Ground.tsx index 28bb9162a24f..efa78f5dd9c5 100644 --- a/src/instruments/src/EFB/Ground/Ground.tsx +++ b/src/instruments/src/EFB/Ground/Ground.tsx @@ -5,16 +5,25 @@ import { Navbar } from '../UtilComponents/Navbar'; import { ServicesPage } from './Pages/ServicesPage'; import { PushbackPage } from './Pages/PushbackPage'; import { FuelPage } from './Pages/FuelPage'; +import { Payload } from './Pages/Payload/Payload'; + +export interface StatefulButton { + id: string, + state: string, + callBack, + value: number, +} export const Ground = () => { const tabs: PageLink[] = [ { name: 'Services', alias: t('Ground.Services.Title'), component: }, { name: 'Pushback', alias: t('Ground.Pushback.Title'), component: }, { name: 'Fuel', alias: t('Ground.Fuel.Title'), component: }, + { name: 'Payload', alias: t('Ground.Payload.Title'), component: }, ]; return ( -
+

{t('Ground.Title')}

= ({ + width, height, envelope, limits, + totalWeight, cg, + mldw, mldwCg, + zfw, zfwCg, +}) => { + const { usingMetric } = Units; + const canvasRef = useRef(null); + const [ctx, setCtx] = useState(null); + const [theme] = usePersistentProperty('EFB_UI_THEME', 'blue'); + const [flightPhase] = useSimVar('L:A32NX_FMGC_FLIGHT_PHASE', 'enum'); + + const getTheme = (theme: string): [string, string, string, string] => { + let base = '#fff'; + let primary = '#00C9E4'; + let secondary = '#84CC16'; + let alt = '#000'; + switch (theme) { + case 'dark': + base = '#fff'; + primary = '#3B82F6'; + secondary = '#84CC16'; + alt = '#000'; + break; + case 'light': + base = '#000'; + primary = '#3B82F6'; + secondary = '#84CC16'; + alt = '#fff'; + break; + default: + break; + } + return [base, primary, secondary, alt]; + }; + + const draw = () => { + if (!ctx) return; + + const [base, primary, secondary, alt] = getTheme(theme); + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.fillStyle = '#C9C9C9'; + ctx.strokeStyle = '#2B313B'; + ctx.lineWidth = 1; + + const yStep = height / limits.weight.lines; + const xStep = width / limits.cg.lines; + const shiftX = (width / 18); + + const weightToY = (weight: number) => (limits.weight.max - weight) * yStep / (limits.weight.scale); + const cgToX = (cg: number) => ((cg - limits.cg.min) * xStep); + const cgWeightToXY = (cg: number, weight: number): [number, number] => { + const xStart = cgToX(cg); + const y = weightToY(weight); + const x = shiftX + xStart + ((CanvasConst.yScale - y) * Math.tan(15 / 16 * Math.PI + (cg - limits.cg.min) * limits.cg.angleRad)); + return [x, y]; + }; + + const drawWeightLines = () => { + ctx.lineWidth = 1; + ctx.strokeStyle = '#394049'; + for (let y = yStep; y < height; y += yStep) { + ctx.beginPath(); + ctx.moveTo(0, y); + ctx.lineTo(width, y); + ctx.closePath(); + ctx.stroke(); + } + }; + + const drawCgLines = () => { + ctx.lineWidth = 1; + ctx.globalAlpha = (theme !== 'light') ? 0.5 : 0.25; + const cgWidth = width - shiftX; + for (let cgPercent = limits.cg.min, x = 0; x < cgWidth; x += xStep, cgPercent++) { + if (x > 0 && (x < cgWidth)) { + ctx.lineWidth = cgPercent % limits.cg.highlight ? 0.25 : 1; + ctx.strokeStyle = cgPercent % limits.cg.highlight ? '#2B313B' : '#394049'; + + const [x1, y1] = cgWeightToXY(cgPercent, 35000); + const [x2, y2] = cgWeightToXY(cgPercent, 80000); + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.closePath(); + ctx.stroke(); + } + } + ctx.globalAlpha = 1; + }; + + const drawMzfw = () => { + ctx.lineWidth = 2; + ctx.strokeStyle = base; + const mzfw = envelope.mzfw; + const [x, y] = cgWeightToXY(mzfw[0][0], mzfw[0][1]); + ctx.beginPath(); + ctx.moveTo(x, y); + for (let i = 1; i < mzfw.length; i++) { + const [x, y] = cgWeightToXY(mzfw[i][0], mzfw[i][1]); + ctx.lineTo(x, y); + } + ctx.stroke(); + ctx.closePath(); + }; + + const drawMlw = () => { + ctx.lineWidth = 4; + ctx.strokeStyle = secondary; + const mlw = envelope.mlw; + const [x, y] = cgWeightToXY(mlw[0][0], mlw[0][1]); + ctx.beginPath(); + ctx.moveTo(x, y); + for (let i = 1; i < mlw.length; i++) { + const [x, y] = cgWeightToXY(mlw[i][0], mlw[i][1]); + ctx.lineTo(x, y); + } + ctx.stroke(); + ctx.globalAlpha = 1; + }; + + const drawMtow = () => { + ctx.lineWidth = 4; + ctx.strokeStyle = primary; + const mtow = envelope.mtow; + const [x, y] = cgWeightToXY(mtow[0][0], mtow[0][1]); + ctx.beginPath(); + ctx.moveTo(x, y); + for (let i = 1; i < mtow.length; i++) { + const [x, y] = cgWeightToXY(mtow[i][0], mtow[i][1]); + ctx.lineTo(x, y); + } + ctx.stroke(); + }; + + const drawFlight = () => { + ctx.lineWidth = 4; + ctx.strokeStyle = primary; + const mtow = envelope.flight; + const [x, y] = cgWeightToXY(mtow[0][0], mtow[0][1]); + ctx.beginPath(); + ctx.moveTo(x, y); + for (let i = 1; i < mtow.length; i++) { + const [x, y] = cgWeightToXY(mtow[i][0], mtow[i][1]); + ctx.lineTo(x, y); + } + ctx.stroke(); + }; + + const drawPoints = () => { + const drawDiamond = (cg: number, weight: number, color: string) => { + ctx.fillStyle = color; + ctx.strokeStyle = alt; + ctx.lineWidth = 1; + ctx.beginPath(); + const [cgX, cgY] = cgWeightToXY(cg, weight); + ctx.moveTo(cgX, cgY - CanvasConst.diamondHeight); + ctx.lineTo(cgX - CanvasConst.diamondWidth, cgY); + ctx.lineTo(cgX, cgY + CanvasConst.diamondHeight); + ctx.lineTo(cgX + CanvasConst.diamondWidth, cgY); + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + }; + + // MLW + drawDiamond(mldwCg, mldw, secondary); + // MTOW + drawDiamond(cg, totalWeight, primary); + // MZFW + drawDiamond(zfwCg, zfw, base); + }; + + drawWeightLines(); + drawCgLines(); + if (flightPhase > 1 && flightPhase < 7) { + drawFlight(); + } + drawMzfw(); + drawMlw(); + if (flightPhase <= 1 || flightPhase >= 7) { + drawMtow(); + } + drawPoints(); + }; + + useEffect(() => { + const canvas = canvasRef.current; + let frameId; + + if (!canvas) { + return undefined; + } + + const w = width; + const h = height; + const { devicePixelRatio: ratio = 1 } = window; + setCtx(canvas.getContext('2d')); + canvas.width = w * ratio; + canvas.height = h * ratio; + ctx?.scale(ratio, ratio); + + const render = () => { + draw(); + // workaround for rendering bug + if (!frameId || frameId < 10) { + frameId = window.requestAnimationFrame(render); + } + }; + + render(); + + return () => { + if (frameId) { + window.cancelAnimationFrame(frameId); + } + }; + }, [draw]); + + const mtow = { transform: `translateX(${(zfwCg < limits.cg.overlap ? limits.labels.mtow.x1 : limits.labels.mtow.x2) * width}px) translateY(${height * limits.labels.mtow.y}px)` }; + const mlw = { transform: `translateX(${(zfwCg < limits.cg.overlap ? limits.labels.mlw.x1 : limits.labels.mlw.x2) * width}px) translateY(${height * limits.labels.mlw.y}px)` }; + const mzfw = { transform: `translateX(${(zfwCg < limits.cg.overlap ? limits.labels.mzfw.x1 : limits.labels.mzfw.x2) * width}px) translateY(${height * limits.labels.mzfw.y}px)` }; + + const [cgRows, setCgRows] = useState([]); + const [weightRows, setWeightRows] = useState([]); + + const weightUnits = { transform: `translateX(${CanvasConst.weightAxis.units.x * width}px) translateY(${CanvasConst.weightAxis.units.y * height}px)` }; + + useEffect(() => { + const cg: Object[] = []; + for (let i = 0; i < limits.cg.values.length; i++) { + const newRow = { transform: `translateX(${(CanvasConst.cgAxis.xOffset + i * CanvasConst.cgAxis.xSpacing) * width}px) translateY(${height * CanvasConst.cgAxis.y}px)` }; + cg.push(newRow); + } + setCgRows(cg); + const wg: Object[] = []; + for (let i = 0; i < limits.weight.values.length; i++) { + const newRow = { transform: `translateX(${CanvasConst.weightAxis.x * width}px) translateY(${height * (CanvasConst.weightAxis.yOffset + i * CanvasConst.weightAxis.ySpacing)}px)` }; + wg.push(newRow); + } + setWeightRows(wg); + }, []); + + const cgAxis = cgRows.map((cgRow, i) =>

{`${limits.cg.values[i]}%`}

); + const weightAxis = weightRows.map((weightRow, i) => ( +

{Math.round(Units.kilogramToUser(limits.weight.values[i] * 1000) / 1000)}

+ )); + + return ( +
+ + {cgAxis} + {weightAxis} +

{usingMetric ? 'x 1000 kgs' : 'x 1000 lbs'}

+

{flightPhase <= 1 || flightPhase >= 7 ? 'MTOW' : 'FLIGHT'}

+

MLDW

+

MZFW

+
+ ); +}; diff --git a/src/instruments/src/EFB/Ground/Pages/Payload/Chart/Constants.ts b/src/instruments/src/EFB/Ground/Pages/Payload/Chart/Constants.ts new file mode 100644 index 000000000000..8d364654e1a0 --- /dev/null +++ b/src/instruments/src/EFB/Ground/Pages/Payload/Chart/Constants.ts @@ -0,0 +1,63 @@ +export const CanvasConst = Object.freeze({ + yScale: 200, + cgAngle: Math.PI / 224, + diamondWidth: 10, + diamondHeight: 10, + cgAxis: { + xOffset: 0.02, + xSpacing: 0.18, + y: -0.08, + }, + weightAxis: { + x: -0.09, + yOffset: -0.02, + ySpacing: 0.22, + units: { + x: -0.17, + y: 0.95, + }, + }, +}); + +export interface PerformanceEnvelope { + mlw: number[][], + mzfw: number[][], + mtow: number[][], + flight: number[][], +} + +export interface ChartLimitsWeight { + min: number, + max: number, + lines: number, + scale: number, + values: number[], +} + +export interface ChartLimitsCg { + angleRad: number, + min: number, + max: number, + lines: number, + scale: number, + values: number[], + overlap: number, + highlight: number, +} + +export interface ChartLabels { + mtow: EnvelopeLabel, + mlw: EnvelopeLabel, + mzfw: EnvelopeLabel, +} + +export interface EnvelopeLabel { + x1: number, + x2: number, + y: number +} +export interface ChartLimits { + weight: ChartLimitsWeight, + cg: ChartLimitsCg, + labels: ChartLabels +} diff --git a/src/instruments/src/EFB/Ground/Pages/Payload/Loadsheet/a20nv55.json b/src/instruments/src/EFB/Ground/Pages/Payload/Loadsheet/a20nv55.json new file mode 100644 index 000000000000..5a8d86df9b82 --- /dev/null +++ b/src/instruments/src/EFB/Ground/Pages/Payload/Loadsheet/a20nv55.json @@ -0,0 +1,1573 @@ +{ + "specs": { + "prefix": "A32NX", + "emptyPosition": -8.75, + "macSize": 13.454, + "leMacZ": -5.386, + "weights": { + "maxZfw": 64300, + "minZfw": 42500 + }, + "pax": { + "defaultPaxWeight": 80, + "defaultBagWeight": 15, + "minPaxWeight": 10, + "maxPaxWeight": 250, + "minBagWeight": 1, + "maxBagWeight": 250 + } + }, + "seatMap": [ + { + "name": "ROWS [1-6]", + "rows": [ + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + } + ], + "index": 0, + "stationIndex": 1, + "position": 21.98, + "fill": 0.19, + "simVar": "A32NX_PAX_TOTAL_ROWS_1_6" + }, + { + "name": "ROWS [7-13]", + "rows": [ + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 1, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 1, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 1, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 1, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 1, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 1, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 1, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 1, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 1, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 1, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 1, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 1, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + } + ], + "index": 1, + "stationIndex": 2, + "position": 2.86, + "fill": 0.25, + "simVar": "A32NX_PAX_TOTAL_ROWS_7_13" + }, + { + "name": "ROWS [14-21]", + "rows": [ + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + } + ], + "index": 2, + "stationIndex": 3, + "position": -15.34, + "fill": 0.28, + "simVar": "A32NX_PAX_TOTAL_ROWS_14_21" + }, + { + "name": "ROWS [22-29]", + "rows": [ + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + }, + { + "seats": [ + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 19 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + }, + { + "type": 0, + "x": 0, + "y": 0, + "yOffset": 0 + } + ], + "x": 0, + "y": 0, + "xOffset": 0, + "yOffset": 0 + } + ], + "fill": 0.28, + "index": 3, + "stationIndex": 4, + "position": -32.81, + "simVar": "A32NX_PAX_TOTAL_ROWS_22_29" + } + ], + "cargoMap": [ + { + "name": "FWD BAGGAGE/CONTAINER", + "weight": 3402, + "index": 0, + "stationIndex": 5, + "position": 18.28, + "progressBarWidth": 160, + "simVar": "A32NX_CARGO_FWD_BAGGAGE_CONTAINER" + }, + { + "name": "AFT CONTAINER", + "weight": 2426, + "index": 1, + "stationIndex": 6, + "position": -15.96, + "progressBarWidth": 100, + "simVar": "A32NX_CARGO_AFT_CONTAINER" + }, + { + "name": "AFT BAGGAGE", + "weight": 2110, + "index": 2, + "stationIndex": 7, + "position": -27.1, + "progressBarWidth": 100, + "simVar": "A32NX_CARGO_AFT_BAGGAGE" + }, + { + "name": "AFT BULK/LOOSE", + "weight": 1497, + "index": 3, + "stationIndex": 8, + "position": -37.35, + "progressBarWidth": 100, + "simVar": "A32NX_CARGO_AFT_BULK_LOOSE" + } + ], + "chart": { + "performanceEnvelope": { + "mlw": [ + [ + 15, + 42500 + ], + [ + 15, + 53000 + ], + [ + 17, + 63000 + ], + [ + 17, + 67400 + ], + [ + 40, + 67400 + ], + [ + 40, + 50000 + ], + [ + 35, + 46000 + ], + [ + 35, + 42500 + ] + ], + "mzfw": [ + [ + 17, + 64300 + ], + [ + 40, + 64300 + ], + [ + 40, + 50000 + ], + [ + 35, + 46000 + ], + [ + 35, + 42500 + ], + [ + 15, + 42500 + ] + ], + "mtow": [ + [ + 15, + 42500 + ], + [ + 15, + 53000 + ], + [ + 17, + 63000 + ], + [ + 17, + 72000 + ], + [ + 27, + 79000 + ], + [ + 36, + 79000 + ], + [ + 40, + 73500 + ], + [ + 40, + 58000 + ], + [ + 33, + 42500 + ] + ], + "flight": [ + [ + 13, + 42500 + ], + [ + 13, + 52500 + ], + [ + 15, + 63000 + ], + [ + 15, + 72000 + ], + [ + 25, + 79000 + ], + [ + 38, + 79000 + ], + [ + 41, + 74500 + ], + [ + 41, + 51000 + ], + [ + 35, + 46000 + ], + [ + 35, + 42500 + ], + [ + 13, + 42500 + ] + ] + }, + "chartLimits": { + "weight": { + "min": 40000, + "max": 80000, + "lines": 9, + "scale": 5000, + "values": [ + 80, + 70, + 60, + 50, + 40 + ] + }, + "cg": { + "angleRad": 0.014025, + "min": 12, + "max": 47, + "overlap": 32, + "highlight": 5, + "lines": 35, + "scale": 1, + "values": [ + 15, + 20, + 25, + 30, + 35, + 40 + ] + }, + "labels": { + "mtow": { + "x1": 0.65, + "x2": 0.22, + "y": 0.02 + }, + "mlw": { + "x1": 0.65, + "x2": 0.22, + "y": 0.22 + }, + "mzfw": { + "x1": 0.65, + "x2": 0.22, + "y": 0.29 + } + } + } + } +} diff --git a/src/instruments/src/EFB/Ground/Pages/Payload/Payload.tsx b/src/instruments/src/EFB/Ground/Pages/Payload/Payload.tsx new file mode 100644 index 000000000000..16b1fc06e100 --- /dev/null +++ b/src/instruments/src/EFB/Ground/Pages/Payload/Payload.tsx @@ -0,0 +1,877 @@ +/* eslint-disable max-len */ +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { BriefcaseFill, CloudArrowDown, PersonFill, PlayFill, StopCircleFill } from 'react-bootstrap-icons'; +import { useSimVar } from '@instruments/common/simVars'; +import { Units } from '@shared/units'; +import { usePersistentProperty } from '@instruments/common/persistence'; +import { BitFlags } from '@shared/bitFlags'; +import { useBitFlags } from '@instruments/common/bitFlags'; +import { round } from 'lodash'; +import { CargoWidget } from './Seating/CargoWidget'; +import { ChartWidget } from './Chart/ChartWidget'; +import { PaxStationInfo, CargoStationInfo } from './Seating/Constants'; +import { t } from '../../../translation'; +import { TooltipWrapper } from '../../../UtilComponents/TooltipWrapper'; +import { SimpleInput } from '../../../UtilComponents/Form/SimpleInput/SimpleInput'; +import Loadsheet from './Loadsheet/a20nv55.json'; +import Card from '../../../UtilComponents/Card/Card'; +import { SelectGroup, SelectItem } from '../../../UtilComponents/Form/Select'; +import { SeatMapWidget } from './Seating/SeatMapWidget'; +import { isSimbriefDataLoaded } from '../../../Store/features/simBrief'; +import { useAppSelector } from '../../../Store/store'; + +export const Payload = () => { + const { usingMetric } = Units; + + const massUnitForDisplay = usingMetric ? 'KGS' : 'LBS'; + + const simbriefDataLoaded = isSimbriefDataLoaded(); + const [boardingStarted, setBoardingStarted] = useSimVar('L:A32NX_BOARDING_STARTED_BY_USR', 'Bool', 200); + const [boardingRate, setBoardingRate] = usePersistentProperty('CONFIG_BOARDING_RATE', 'REAL'); + const [paxWeight, setPaxWeight] = useSimVar('L:A32NX_WB_PER_PAX_WEIGHT', 'Number', 200); + const [paxBagWeight, setPaxBagWeight] = useSimVar('L:A32NX_WB_PER_BAG_WEIGHT', 'Number', 200); + const [galToKg] = useSimVar('FUEL WEIGHT PER GALLON', 'kilograms', 2_000); + const [destEfob] = useSimVar('L:A32NX_DESTINATION_FUEL_ON_BOARD', 'Kilograms', 5_000); + + const [emptyWeight] = useSimVar('A:EMPTY WEIGHT', usingMetric ? 'Kilograms' : 'Pounds', 2_000); + + const [paxA] = useSimVar('L:A32NX_PAX_TOTAL_ROWS_1_6', 'Number'); + const [paxB] = useSimVar('L:A32NX_PAX_TOTAL_ROWS_7_13', 'Number'); + const [paxC] = useSimVar('L:A32NX_PAX_TOTAL_ROWS_14_21', 'Number'); + const [paxD] = useSimVar('L:A32NX_PAX_TOTAL_ROWS_22_29', 'Number'); + + const pax = [paxA, paxB, paxC, paxD]; + + const [paxADesired, setPaxADesired] = useSimVar('L:A32NX_PAX_TOTAL_ROWS_1_6_DESIRED', 'number'); + const [paxBDesired, setPaxBDesired] = useSimVar('L:A32NX_PAX_TOTAL_ROWS_7_13_DESIRED', 'number'); + const [paxCDesired, setPaxCDesired] = useSimVar('L:A32NX_PAX_TOTAL_ROWS_14_21_DESIRED', 'number'); + const [paxDDesired, setPaxDDesired] = useSimVar('L:A32NX_PAX_TOTAL_ROWS_22_29_DESIRED', 'number'); + + const [stationSize, setStationLen] = useState([]); + const totalPax = useMemo(() => pax && pax.length > 0 && pax.reduce((a, b) => a + b), [...pax]); + const maxPax = useMemo(() => ((stationSize && stationSize.length > 0) ? stationSize.reduce((a, b) => a + b) : -1), [stationSize]); + + const [aFlags, setAFlags] = useBitFlags('PAX_FLAGS_A'); + const [bFlags, setBFlags] = useBitFlags('PAX_FLAGS_B'); + const [cFlags, setCFlags] = useBitFlags('PAX_FLAGS_C'); + const [dFlags, setDFlags] = useBitFlags('PAX_FLAGS_D'); + + const paxDesired = [paxADesired, paxBDesired, paxCDesired, paxDDesired]; + const [setPaxDesired] = useState([setPaxADesired, setPaxBDesired, setPaxCDesired, setPaxDDesired]); + const totalPaxDesired = useMemo(() => (paxDesired && paxDesired.length > 0 && paxDesired.reduce((a, b) => parseInt(a) + parseInt(b))), [...paxDesired]); + + const [aFlagsDesired, setAFlagsDesired] = useBitFlags('PAX_FLAGS_A_DESIRED'); + const [bFlagsDesired, setBFlagsDesired] = useBitFlags('PAX_FLAGS_B_DESIRED'); + const [cFlagsDesired, setCFlagsDesired] = useBitFlags('PAX_FLAGS_C_DESIRED'); + const [dFlagsDesired, setDFlagsDesired] = useBitFlags('PAX_FLAGS_D_DESIRED'); + + const activeFlags = [aFlags, bFlags, cFlags, dFlags]; + const desiredFlags = [aFlagsDesired, bFlagsDesired, cFlagsDesired, dFlagsDesired]; + const setActiveFlags = useMemo(() => [setAFlags, setBFlags, setCFlags, setDFlags], []); + const setDesiredFlags = useMemo(() => [setAFlagsDesired, setBFlagsDesired, setCFlagsDesired, setDFlagsDesired], []); + + const [clicked, setClicked] = useState(false); + + const [fwdBag] = useSimVar('L:A32NX_CARGO_FWD_BAGGAGE_CONTAINER', 'Number', 200); + const [aftCont] = useSimVar('L:A32NX_CARGO_AFT_CONTAINER', 'Number', 200); + const [aftBag] = useSimVar('L:A32NX_CARGO_AFT_BAGGAGE', 'Number', 200); + const [aftBulk] = useSimVar('L:A32NX_CARGO_AFT_BULK_LOOSE', 'Number', 200); + + const cargo = [fwdBag, aftCont, aftBag, aftBulk]; + + const [fwdBagDesired, setFwdBagDesired] = useSimVar('L:A32NX_CARGO_FWD_BAGGAGE_CONTAINER_DESIRED', 'Number', 200); + const [aftContDesired, setAftContDesired] = useSimVar('L:A32NX_CARGO_AFT_CONTAINER_DESIRED', 'Number', 200); + const [aftBagDesired, setAftBagDesired] = useSimVar('L:A32NX_CARGO_AFT_BAGGAGE_DESIRED', 'Number', 200); + const [aftBulkDesired, setAftBulkDesired] = useSimVar('L:A32NX_CARGO_AFT_BULK_LOOSE_DESIRED', 'Number', 200); + + const cargoDesired = [fwdBagDesired, aftContDesired, aftBagDesired, aftBulkDesired]; + const setCargoDesired = useMemo(() => [setFwdBagDesired, setAftContDesired, setAftBagDesired, setAftBulkDesired], []); + const totalCargoDesired = useMemo(() => ((cargoDesired && cargoDesired.length > 0) ? cargoDesired.reduce((a, b) => parseInt(a) + parseInt(b)) : -1), [...cargoDesired, ...paxDesired]); + + const [cargoStationSize, setCargoStationLen] = useState([]); + + const totalCargo = useMemo(() => ((cargo && cargo.length > 0) ? cargo.reduce((a, b) => parseInt(a) + parseInt(b)) : -1), [...cargo, ...pax]); + const maxCargo = useMemo(() => ((cargoStationSize && cargoStationSize.length > 0) ? cargoStationSize.reduce((a, b) => a + b) : -1), [cargoStationSize]); + + const [centerCurrent] = useSimVar('FUEL TANK CENTER QUANTITY', 'Gallons', 2_000); + const [LInnCurrent] = useSimVar('FUEL TANK LEFT MAIN QUANTITY', 'Gallons', 2_000); + const [LOutCurrent] = useSimVar('FUEL TANK LEFT AUX QUANTITY', 'Gallons', 2_000); + const [RInnCurrent] = useSimVar('FUEL TANK RIGHT MAIN QUANTITY', 'Gallons', 2_000); + const [ROutCurrent] = useSimVar('FUEL TANK RIGHT AUX QUANTITY', 'Gallons', 2_000); + + const fuel = [centerCurrent, LInnCurrent, LOutCurrent, RInnCurrent, ROutCurrent]; + + // Units + // Weight/CG + const [zfw, setZfw] = useState(0); + const [zfwCg, setZfwCg] = useState(0); + const [zfwDesired, setZfwDesired] = useState(0); + const [zfwDesiredCg, setZfwDesiredCg] = useState(0); + const [totalWeight, setTotalWeight] = useState(emptyWeight); + const [cg, setCg] = useState(25); + const [totalDesiredWeight, setTotalDesiredWeight] = useState(0); + const [desiredCg, setDesiredCg] = useState(0); + const [mlw, setMlw] = useState(0); + const [mlwCg, setMlwCg] = useState(0); + const [mlwDesired, setMlwDesired] = useState(0); + const [mlwDesiredCg, setMlwDesiredCg] = useState(0); + + const [seatMap] = useState(Loadsheet.seatMap); + const [cargoMap] = useState(Loadsheet.cargoMap); + + const totalCurrentGallon = useMemo(() => round(Math.max(LInnCurrent + LOutCurrent + RInnCurrent + ROutCurrent + centerCurrent, 0)), [fuel]); + + const simbriefUnits = useAppSelector((state) => state.simbrief.data.units); + const simbriefBagWeight = parseInt(useAppSelector((state) => state.simbrief.data.weights.bagWeight)); + const simbriefPaxWeight = parseInt(useAppSelector((state) => state.simbrief.data.weights.passengerWeight)); + const simbriefPax = parseInt(useAppSelector((state) => state.simbrief.data.weights.passengerCount)); + const simbriefBag = parseInt(useAppSelector((state) => state.simbrief.data.weights.bagCount)); + const simbriefFreight = parseInt(useAppSelector((state) => state.simbrief.data.weights.freight)); + + const setSimBriefValues = () => { + if (simbriefUnits === 'kgs') { + const perBagWeight = Units.kilogramToUser(simbriefBagWeight); + setPaxBagWeight(perBagWeight); + setPaxWeight(Units.kilogramToUser(simbriefPaxWeight)); + // TODO: Popup showing that maximum passengers number is incorrect if input is greater than maximum pax count + setTargetPax(simbriefPax > maxPax ? maxPax : simbriefPax); + setTargetCargo(simbriefBag, Units.kilogramToUser(simbriefFreight), perBagWeight); + } else { + const perBagWeight = Units.poundToUser(simbriefBagWeight); + setPaxBagWeight(perBagWeight); + setPaxWeight(Units.poundToUser(simbriefPaxWeight)); + setTargetPax(simbriefPax); + setTargetCargo(simbriefBag, Units.poundToUser(simbriefFreight), perBagWeight); + } + }; + + const [busDC2] = useSimVar('L:A32NX_ELEC_DC_2_BUS_IS_POWERED', 'Bool', 2_000); + const [busDCHot1] = useSimVar('L:A32NX_ELEC_DC_HOT_1_BUS_IS_POWERED', 'Bool', 2_000); + const [simGroundSpeed] = useSimVar('GPS GROUND SPEED', 'knots', 2_000); + const [isOnGround] = useSimVar('SIM ON GROUND', 'Bool', 2_000); + const [eng1Running] = useSimVar('ENG COMBUSTION:1', 'Bool', 2_000); + const [eng2Running] = useSimVar('ENG COMBUSTION:2', 'Bool', 2_000); + const [coldAndDark, setColdAndDark] = useState(true); + + const returnSeats = useCallback((stationIndex: number, empty: boolean, flags: BitFlags[]): number[] => { + const seats: number[] = []; + const bitFlags: BitFlags = flags[stationIndex]; + for (let seatId = 0; seatId < stationSize[stationIndex]; seatId++) { + if (!empty && bitFlags.getBitIndex(seatId)) { + seats.push(seatId); + } else if (empty && !bitFlags.getBitIndex(seatId)) { + seats.push(seatId); + } + } + return seats; + }, [...desiredFlags, ...activeFlags]); + + const returnNumSeats = useCallback((stationIndex: number, empty: boolean, flags: BitFlags[]): number => { + let count = 0; + const bitFlags: BitFlags = flags[stationIndex]; + for (let seatId = 0; seatId < stationSize[stationIndex]; seatId++) { + if (!empty && bitFlags.getBitIndex(seatId)) { + count++; + } else if (empty && !bitFlags.getBitIndex(seatId)) { + count++; + } + } + return count; + }, [...desiredFlags, ...activeFlags]); + + const chooseSeats = useCallback((stationIndex: number, choices: number[], numChoose: number) => { + const bitFlags: BitFlags = activeFlags[stationIndex]; + for (let i = 0; i < numChoose; i++) { + if (choices.length > 0) { + const chosen = ~~(Math.random() * choices.length); + bitFlags.toggleBitIndex(choices[chosen]); + choices.splice(chosen, 1); + } + } + setActiveFlags[stationIndex](bitFlags); + }, [...pax, ...activeFlags]); + + const chooseDesiredSeats = useCallback((stationIndex: number, choices: number[], numChoose: number) => { + const bitFlags: BitFlags = desiredFlags[stationIndex]; + for (let i = 0; i < numChoose; i++) { + if (choices.length > 0) { + const chosen = ~~(Math.random() * choices.length); + bitFlags.toggleBitIndex(choices[chosen]); + choices.splice(chosen, 1); + } + } + setDesiredFlags[stationIndex](bitFlags); + }, [...paxDesired, ...desiredFlags]); + + const calculateSeatOptions = useCallback((stationIndex: number, increase: boolean): number[] => { + const plannedSeats = returnSeats(stationIndex, increase, desiredFlags); + const activeSeats = returnSeats(stationIndex, !increase, activeFlags); + + const intersection = activeSeats.filter((element) => plannedSeats.includes(element)); + return intersection; + }, [...activeFlags, ...desiredFlags]); + + const setTargetPax = useCallback((numOfPax: number) => { + if (!stationSize || numOfPax === totalPaxDesired || numOfPax > maxPax || numOfPax < 0) return; + + let paxRemaining = numOfPax; + + const fillStation = (stationIndex: number, percent: number, paxToFill: number) => { + const pax = Math.min(Math.trunc(percent * paxToFill), stationSize[stationIndex]); + setPaxDesired[stationIndex](pax); + paxRemaining -= pax; + + const paxCount = returnNumSeats(stationIndex, false, activeFlags); + const seats: number[] = returnSeats(stationIndex, pax[stationIndex] > paxCount, activeFlags); + chooseDesiredSeats(stationIndex, seats, Math.abs(paxCount - pax[stationIndex])); + }; + + for (let i = pax.length - 1; i > 0; i--) { + fillStation(i, seatMap[i].fill, numOfPax); + } + fillStation(0, 1, paxRemaining); + }, [...paxDesired, totalPaxDesired, maxPax, ...stationSize, ...seatMap]); + + const setTargetCargo = useCallback((numberOfPax: number, freight: number, perBagWeight: number = paxBagWeight) => { + const bagWeight = numberOfPax * perBagWeight; + const loadableCargoWeight = Math.min(bagWeight + Math.round(freight), maxCargo); + + let remainingWeight = loadableCargoWeight; + + async function fillCargo(station: number, percent: number, loadableCargoWeight: number) { + const c = Math.round(percent * loadableCargoWeight); + remainingWeight -= c; + setCargoDesired[station](c); + } + + for (let i = cargoDesired.length - 1; i > 0; i--) { + fillCargo(i, cargoStationSize[i] / maxCargo, loadableCargoWeight); + } + fillCargo(0, 1, remainingWeight); + }, [...cargoDesired, paxBagWeight, ...cargoStationSize]); + + const calculatePaxMoment = useCallback(() => { + let paxMoment = 0; + pax.forEach((station, i) => { + paxMoment += station * paxWeight * seatMap[i].position; + }); + return paxMoment; + }, [paxWeight, ...pax, seatMap]); + + const calculatePaxDesiredMoment = useCallback(() => { + let paxMoment = 0; + paxDesired.forEach((station, i) => { + paxMoment += station * paxWeight * seatMap[i].position; + }); + return paxMoment; + }, [paxWeight, ...paxDesired, seatMap]); + + const calculateCargoMoment = useCallback(() => { + let cargoMoment = 0; + cargo.forEach((station, i) => { + cargoMoment += station * cargoMap[i].position; + }); + return cargoMoment; + }, [...cargo, cargoMap]); + + const calculateCargoDesiredMoment = useCallback(() => { + let cargoMoment = 0; + cargoDesired.forEach((station, i) => { + cargoMoment += station * cargoMap[i].position; + }); + return cargoMoment; + }, [...cargoDesired, cargoMap]); + + const calculateCg = useCallback((mass: number, moment: number) => -100 * ((moment / mass - Loadsheet.specs.leMacZ) / Loadsheet.specs.macSize), []); + + const processZfw = useCallback((newZfw) => { + let paxCargoWeight = newZfw - emptyWeight; + + // Load pax first + const pWeight = paxWeight + paxBagWeight; + const newPax = Math.min(Math.round(paxCargoWeight / pWeight), maxPax); + + paxCargoWeight -= newPax * pWeight; + const newCargo = Math.min(paxCargoWeight, maxCargo); + + setTargetPax(newPax); + setTargetCargo(newPax, newCargo); + }, [emptyWeight, paxWeight, paxBagWeight, maxPax, maxCargo]); + + const onClickCargo = useCallback((cargoStation, e) => { + const cargoPercent = Math.min(Math.max(0, e.nativeEvent.offsetX / cargoMap[cargoStation].progressBarWidth), 1); + setCargoDesired[cargoStation](Math.round(Units.kilogramToUser(cargoMap[cargoStation].weight) * cargoPercent)); + }, [cargoMap]); + + const onClickSeat = useCallback((station: number, seatId: number) => { + setClicked(true); + let newPax = totalPaxDesired; + // TODO FIXME: This calculation does not work correctly if user clicks on many seats in rapid succession + const freight = Math.max(totalCargoDesired - totalPaxDesired * paxBagWeight, 0); + const bitFlags: BitFlags = desiredFlags[station]; + + if (bitFlags.getBitIndex(seatId)) { + newPax -= 1; + setPaxDesired[station](Math.max(paxDesired[station] - 1, 0)); + } else { + newPax += 1; + setPaxDesired[station](Math.min(paxDesired[station] + 1, stationSize[station])); + } + + setTargetCargo(newPax, freight); + bitFlags.toggleBitIndex(seatId); + setDesiredFlags[station](bitFlags); + setTimeout(() => setClicked(false), 500); + }, [ + totalPaxDesired, paxBagWeight, + totalCargoDesired, totalPaxDesired, + ...cargoDesired, ...paxDesired, + ...desiredFlags, ...stationSize, + ]); + + const boardingStatusClass = useMemo(() => { + if (!boardingStarted) { + return 'text-theme-highlight'; + } + return (totalPaxDesired * paxWeight + totalCargoDesired) >= (totalPax * paxWeight + totalCargo) ? 'text-green-500' : 'text-yellow-500'; + }, [boardingStarted, paxWeight, totalPaxDesired, totalCargoDesired, totalPax, totalCargo]); + + // Init + useEffect(() => { + if (paxWeight === 0) { + setPaxWeight(Math.round(Units.kilogramToUser(Loadsheet.specs.pax.defaultPaxWeight))); + } + if (paxBagWeight === 0) { + setPaxBagWeight(Math.round(Units.kilogramToUser(Loadsheet.specs.pax.defaultBagWeight))); + } + }, []); + + // Set Cold and Dark State + useEffect(() => { + if (simGroundSpeed > 0.1 || eng1Running || eng2Running || !isOnGround || (!busDC2 && !busDCHot1)) { + setColdAndDark(false); + } else { + setColdAndDark(true); + } + }, [simGroundSpeed, eng1Running, eng2Running, isOnGround, busDC2, busDCHot1]); + + useEffect(() => { + if (boardingRate !== 'INSTANT') { + if (!coldAndDark) { + setBoardingRate('INSTANT'); + } + } + }, [coldAndDark, boardingRate]); + + // Init the seating map + useEffect(() => { + const stationSize: number[] = []; + for (let i = 0; i < seatMap.length; i++) { + stationSize.push(0); + } + seatMap.forEach((station, i) => { + station.rows.forEach((row) => { + row.seats.forEach(() => { + stationSize[i]++; + }); + }); + }); + setStationLen(stationSize); + }, [seatMap]); + + // Init the cargo map + useEffect(() => { + const cargoSize: number[] = []; + for (let i = 0; i < cargoMap.length; i++) { + cargoSize.push(0); + } + cargoMap.forEach((station) => { + cargoSize[station.index] = Units.kilogramToUser(station.weight); + }); + setCargoStationLen(cargoSize); + }, [cargoMap]); + + // Check that pax data and bitflags are valid + useEffect(() => { + pax.forEach((stationPaxNum: number, stationIndex: number) => { + const paxCount = returnNumSeats(stationIndex, false, activeFlags); + if (stationPaxNum === 0 && paxCount !== stationPaxNum) { + setActiveFlags[stationIndex](new BitFlags(0)); + } + }); + + paxDesired.forEach((stationPaxNum, stationIndex) => { + const paxCount = returnNumSeats(stationIndex, false, desiredFlags); + if (stationPaxNum === 0 && paxCount !== stationPaxNum) { + setDesiredFlags[stationIndex](new BitFlags(0)); + } + }); + if (!boardingStarted) { + setTargetPax(totalPax); + setTargetCargo(0, totalCargo); + } + }, [stationSize]); + + // Adjusted desired passenger seating layout to match station passenger count on change + useEffect(() => { + paxDesired.forEach((stationNumPax, stationIndex) => { + const paxCount = returnNumSeats(stationIndex, false, desiredFlags); + if (!clicked && stationNumPax !== paxCount) { + const seatOptions = calculateSeatOptions(stationIndex, stationNumPax > paxCount); + const seatDelta = Math.abs(paxCount - stationNumPax); + + if (seatOptions.length >= seatDelta) { + chooseDesiredSeats(stationIndex, seatOptions, seatDelta); + } else if (seatOptions.length && seatOptions.length < seatDelta) { + // Fallback if we don't have enough seat options using desired as reference + const leftOver = seatDelta - seatOptions.length; + chooseDesiredSeats(stationIndex, seatOptions, seatOptions.length); + const seats: number[] = returnSeats(stationIndex, stationNumPax > paxCount, desiredFlags); + chooseDesiredSeats(stationIndex, seats, leftOver); + } else { + // Fallback if no seat options using desired as reference + const seats: number[] = returnSeats(stationIndex, stationNumPax > paxCount, desiredFlags); + chooseDesiredSeats(stationIndex, seats, seatDelta); + } + } + }); + }, [...paxDesired]); + + // Adjust actual passenger seating layout to match station passenger count on change + useEffect(() => { + pax.forEach((stationNumPax: number, stationIndex: number) => { + const paxCount = returnNumSeats(stationIndex, false, activeFlags); + if (!clicked && stationNumPax !== paxCount) { + const seatOptions = calculateSeatOptions(stationIndex, stationNumPax < paxCount); + const seatDelta = Math.abs(paxCount - stationNumPax); + if (seatOptions.length >= seatDelta) { + chooseSeats(stationIndex, seatOptions, seatDelta); + } else if (seatOptions.length && seatOptions.length < seatDelta) { + // Fallback if we don't have enough seat options using desired as reference + const leftOver = seatDelta - seatOptions.length; + chooseSeats(stationIndex, seatOptions, seatOptions.length); + const seats: number[] = returnSeats(stationIndex, stationNumPax > paxCount, activeFlags); + chooseSeats(stationIndex, seats, leftOver); + } else { + // Fallback if no seat options using desired as reference + const seats: number[] = returnSeats(stationIndex, stationNumPax > paxCount, activeFlags); + chooseSeats(stationIndex, seats, seatDelta); + } + } + }); + }, [...pax]); + + useEffect(() => { + pax.forEach((stationNumPax: number, stationIndex: number) => { + // Sync active to desired layout if pax is equal to desired + if (stationNumPax === parseInt(paxDesired[stationIndex])) { + setActiveFlags[stationIndex](desiredFlags[stationIndex]); + } + }); + }, [boardingStarted]); + + useEffect(() => { + const centerTankMoment = -6; + const innerTankMoment = -8; + const outerTankMoment = -13; + // Adjust ZFW CG Values based on payload + const newZfw = emptyWeight + totalPax * paxWeight + totalCargo; + const newZfwDesired = emptyWeight + totalPaxDesired * paxWeight + totalCargoDesired; + const newZfwMoment = Loadsheet.specs.emptyPosition * emptyWeight + calculatePaxMoment() + calculateCargoMoment(); + const newZfwDesiredMoment = Loadsheet.specs.emptyPosition * emptyWeight + calculatePaxDesiredMoment() + calculateCargoDesiredMoment(); + const newZfwCg = calculateCg(newZfw, newZfwMoment); + const newZfwDesiredCg = calculateCg(newZfwDesired, newZfwDesiredMoment); + const totalFuel = round(totalCurrentGallon * galToKg); + + const totalFuelMoment = centerCurrent * galToKg * centerTankMoment + (LOutCurrent + ROutCurrent) * galToKg * outerTankMoment + (LInnCurrent + RInnCurrent) * galToKg * innerTankMoment; + const newTotalWeight = newZfw + totalFuel; + const newTotalMoment = newZfwMoment + totalFuelMoment; + const newCg = calculateCg(newTotalWeight, newTotalMoment); + + const newTotalWeightDesired = newZfwDesired + totalFuel; + const newTotalDesiredMoment = newZfwDesiredMoment + totalFuelMoment; + const newDesiredCg = calculateCg(newTotalWeightDesired, newTotalDesiredMoment); + + setZfw(newZfw); + setZfwCg(newZfwCg); + setZfwDesired(newZfwDesired); + setZfwDesiredCg(newZfwDesiredCg); + setTotalWeight(newTotalWeight); + setCg(newCg); + setTotalDesiredWeight(newTotalWeightDesired); + setDesiredCg(newDesiredCg); + + // TODO: Better fuel burn algorithm for estimation - consider this placeholder logic + // Adjust MLW CG values based on estimated fuel burn + if (destEfob > 0) { + const OUTER_CELL_KG = 228 * galToKg; + const INNER_CELL_KG = 1816 * galToKg; + let centerTank = 0; + let outerTanks = 0; + let innerTanks = 0; + let f = destEfob; + + f -= (OUTER_CELL_KG) * 2; + outerTanks = ((OUTER_CELL_KG) * 2) + Math.min(f, 0); + if (f > 0) { + f -= (INNER_CELL_KG) * 2; + innerTanks = ((INNER_CELL_KG) * 2) + Math.min(f, 0); + if (f > 0) { + centerTank = f; + } + } + + const newMlw = newZfw + destEfob; + const destFuelMoment = centerTank * centerTankMoment + outerTanks * outerTankMoment + innerTanks * innerTankMoment; + const newMlwMoment = newZfwMoment + destFuelMoment; + const newMlwDesired = newZfwDesired + destEfob; + const newMlwDesiredMoment = newZfwDesiredMoment + destFuelMoment; + + const newMlwCg = calculateCg(newMlw, newMlwMoment); + const newMlwDesiredCg = calculateCg(newMlwDesired, newMlwDesiredMoment); + + setMlw(newMlw); + setMlwCg(newMlwCg); + setMlwDesired(newMlwDesired); + setMlwDesiredCg(newMlwDesiredCg); + } else { + setMlw(newTotalWeight); + setMlwCg(newCg); + setMlwDesired(newTotalWeightDesired); + setMlwDesiredCg(newDesiredCg); + } + }, [ + ...pax, ...paxDesired, + ...cargo, ...cargoDesired, + ...fuel, destEfob, + paxWeight, paxBagWeight, + emptyWeight, + ]); + + return ( +
+
+
+ +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {t('Ground.Payload.Planned')} + + {t('Ground.Payload.Current')} +
+ {t('Ground.Payload.Passengers')} + + +
+ 0 ? maxPax : 999} + value={totalPaxDesired} + onBlur={(x) => { + if (!Number.isNaN(parseInt(x) || parseInt(x) === 0)) { + setTargetPax(parseInt(x)); + setTargetCargo(parseInt(x), 0); + } + }} + unit="PAX" + /> +
+
+
+ +
+ {t('Ground.Payload.Cargo')} + + +
+ 0 ? Math.round(maxCargo) : 99999} + value={totalCargoDesired} + onBlur={(x) => { + if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) { + setTargetCargo(0, parseInt(x)); + } + }} + unit={massUnitForDisplay} + /> +
+
+
+ +
+ {t('Ground.Payload.ZFW')} + + +
+ { + if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) processZfw(parseInt(x)); + }} + unit={massUnitForDisplay} + /> +
+
+
+ +
+ {t('Ground.Payload.ZFWCG')} + + +
+ {/* TODO FIXME: Setting pax/cargo given desired ZFWCG, ZFW, total pax, total cargo */} +
+ {`${zfwDesiredCg.toFixed(2)} %`} +
+ {/* + 0 ? maxPax : 999} + value={zfwCg.toFixed(2)} + onBlur={{(x) => processZfwCg(x)} + /> + */} +
+
+
+ {`${zfwCg.toFixed(2)} %`} +
+ +
+ +
+ +
+ + { + if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) setPaxWeight(parseInt(x)); + }} + /> +
{usingMetric ? 'KG' : 'LB'}
+
+
+ + +
+ + { + if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) setPaxBagWeight(parseInt(x)); + }} + /> +
{usingMetric ? 'KG' : 'LB'}
+
+
+ + + + +
+
+ + {simbriefDataLoaded + && (simbriefPax !== totalPaxDesired + || simbriefFreight + simbriefBag * simbriefBagWeight !== totalCargoDesired + || simbriefPaxWeight !== paxWeight + || simbriefBagWeight !== paxBagWeight) + && ( + +
+ +
+
+ )} +
+ +
+ +
+
Loading Time
+ + + setBoardingRate('INSTANT')} + > + {t('Settings.Instant')} + + + +
+ setBoardingRate('FAST')} + > + {t('Settings.Fast')} + +
+
+ +
+ setBoardingRate('REAL')} + > + {t('Settings.Real')} + +
+
+
+
+ + {/* */} + {/* */} + {/* */} +
+
+
+ +
+
+
+
+ ); +}; + +interface PayloadValueInputProps { + min: number, + max: number, + value: number + onBlur: (v: string) => void, + unit: string, +} + +const PayloadValueInput: FC = ({ min, max, value, onBlur, unit }) => ( +
+ +
{unit}
+
+); + +interface NumberUnitDisplayProps { + /** + * The value to show + */ + value: number, + + /** + * The amount of leading zeroes to pad with + */ + padTo: number, + + /** + * The unit to show at the end + */ + unit: string, +} + +const PayloadValueUnitDisplay: FC = ({ value, padTo, unit }) => { + const fixedValue = value.toFixed(0); + const leadingZeroCount = Math.max(0, padTo - fixedValue.length); + + return ( + + + {'0'.repeat(leadingZeroCount)} + {fixedValue} + + {' '} + {unit} + + ); +}; diff --git a/src/instruments/src/EFB/Ground/Pages/Payload/Seating/CargoWidget.tsx b/src/instruments/src/EFB/Ground/Pages/Payload/Seating/CargoWidget.tsx new file mode 100644 index 000000000000..c82559df1b59 --- /dev/null +++ b/src/instruments/src/EFB/Ground/Pages/Payload/Seating/CargoWidget.tsx @@ -0,0 +1,98 @@ +/* eslint-disable max-len */ +import React from 'react'; +import { BriefcaseFill, CaretDownFill } from 'react-bootstrap-icons'; +import { CargoStationInfo } from './Constants'; +import { ProgressBar } from '../../../../UtilComponents/Progress/Progress'; + +interface SeatMapProps { + cargo: number[], + cargoDesired: number[], + cargoStationSize: number[], + cargoMap: CargoStationInfo[], + onClickCargo: (cargoStation: number, event: any) => void, +} + +enum CargoStation { + FwdBag, + AftCont, + AftBag, + AftBulk +} + +// TODO FIXME: Remove hard-coding +export const CargoWidget: React.FC = ({ cargo, cargoDesired, cargoMap, cargoStationSize, onClickCargo }) => ( + <> +
+
+
onClickCargo(CargoStation.FwdBag, e)}> + + +
+
+
+
+
onClickCargo(CargoStation.AftCont, e)}> + + +
+
onClickCargo(CargoStation.AftBag, e)}> + + +
+
onClickCargo(CargoStation.AftBulk, e)}> + + +
+
+ +); diff --git a/src/instruments/src/EFB/Ground/Pages/Payload/Seating/Constants.ts b/src/instruments/src/EFB/Ground/Pages/Payload/Seating/Constants.ts new file mode 100644 index 000000000000..d328c77ddc01 --- /dev/null +++ b/src/instruments/src/EFB/Ground/Pages/Payload/Seating/Constants.ts @@ -0,0 +1,66 @@ +export const TYPE = Object.freeze({ ECO: 0, ECO_EMERG: 1 }); + +export const CanvasConst = Object.freeze({ + xTransform: '243px', + yTransform: '78px', + width: 1000, + height: 150, +}); +export interface SeatInfo { + type: number, + x: number, + y: number, + yOffset: number +} + +export interface RowInfo { + x: number, + y: number, + xOffset: number, + yOffset: number, + seats: SeatInfo[], +} + +export interface PaxStationInfo { + name: string, + rows: RowInfo[], + simVar: string, + index: number, + fill: number, + stationIndex: number, + position: number, +} + +export interface CargoStationInfo { + name: string, + weight: number, + simVar: string, + index: number, + stationIndex: number, + progressBarWidth: number, + position: number, +} + +export const SeatConstants = Object.freeze({ + [TYPE.ECO]: { + len: 19.2, + wid: 19.2, + padX: 13, + padY: 0, + imageX: 25.4, + imageY: 19.2, + }, + [TYPE.ECO_EMERG]: { + len: 19.2, + wid: 19.2, + padX: 20, + padY: 0, + imageX: 25.4, + imageY: 19.2, + }, +}); + +export const Status = Object.freeze({ + Planned: 0, + Loaded: 1, +}); diff --git a/src/instruments/src/EFB/Ground/Pages/Payload/Seating/SeatMapWidget.tsx b/src/instruments/src/EFB/Ground/Pages/Payload/Seating/SeatMapWidget.tsx new file mode 100644 index 000000000000..3cf83f6f9081 --- /dev/null +++ b/src/instruments/src/EFB/Ground/Pages/Payload/Seating/SeatMapWidget.tsx @@ -0,0 +1,200 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { BitFlags } from '@shared/bitFlags'; +import * as ReactDOMServer from 'react-dom/server'; +import { usePersistentProperty } from '@instruments/common/persistence'; +import { CanvasConst, RowInfo, SeatConstants, SeatInfo, PaxStationInfo, TYPE } from './Constants'; +import { Seat } from '../../../../Assets/Seat'; +import { SeatOutlineBg } from '../../../../Assets/SeatOutlineBg'; + +interface SeatMapProps { + seatMap: PaxStationInfo[], + desiredFlags: BitFlags[], + activeFlags: BitFlags[], + onClickSeat: (paxStation: number, section: number) => void, +} + +const useCanvasEvent = (canvas: HTMLCanvasElement | null, event: string, handler: (e) => void, passive = false) => { + useEffect(() => { + canvas?.addEventListener(event, handler, passive); + + return function cleanup() { + canvas?.removeEventListener(event, handler); + }; + }); +}; + +export const SeatMapWidget: React.FC = ({ seatMap, desiredFlags, activeFlags, onClickSeat }) => { + const canvasRef = useRef(null); + const [ctx, setCtx] = useState(null); + + const getTheme = (theme: string): [string, string, string] => { + let base = '#fff'; + let primary = '#00C9E4'; + let secondary = '#84CC16'; + switch (theme) { + case 'dark': + base = '#fff'; + primary = '#3B82F6'; + secondary = '#84CC16'; + break; + case 'light': + base = '#000000'; + primary = '#3B82F6'; + secondary = '#84CC16'; + break; + default: + break; + } + return [base, primary, secondary]; + }; + + const [theme] = usePersistentProperty('EFB_UI_THEME', 'blue'); + const [base, primary] = getTheme(theme); + + const getImageFromComponent = (component: React.ReactElement): HTMLImageElement => { + const imageElement = new Image(); + imageElement.src = `data:image/svg+xml; charset=utf8, ${encodeURIComponent(ReactDOMServer.renderToStaticMarkup(component))}`; + return imageElement; + }; + const seatEmptyImg = useRef(getImageFromComponent()); + const seatMinusImg = useRef(getImageFromComponent()); + const seatAddImg = useRef(getImageFromComponent()); + const seatFilledImg = useRef(getImageFromComponent()); + + const [xYMap, setXYMap] = useState([]); + + const addXOffset = (xOff: number, station: number, row: number) => { + let seatType = TYPE.ECO; + xOff += seatMap[station].rows[row].xOffset; + for (let seat = 0; seat < seatMap[station].rows[row].seats.length; seat++) { + if (seatType < seatMap[station].rows[row].seats[seat].type) { + seatType = seatMap[station].rows[row].seats[seat].type; + } + } + if (row !== 0 || station !== 0) { + xOff += (SeatConstants[seatType].padX + SeatConstants[seatType].len); + } + return xOff; + }; + + const addYOffset = (yOff: number, station: number, row: number, seat: number) => { + yOff += seatMap[station].rows[row].yOffset; + yOff += seatMap[station].rows[row].seats[seat].yOffset; + const seatType = seatMap[station].rows[row].seats[seat].type; + if (seat !== 0) { + yOff += (SeatConstants[seatType].padY + SeatConstants[seatType].wid); + } + return yOff; + }; + + const draw = () => { + if (ctx) { + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.fillStyle = '#fff'; + ctx.beginPath(); + + let xOff = 0; + for (let station = 0; station < seatMap.length; station++) { + let seatId = 0; + for (let row = 0; row < seatMap[station].rows.length; row++) { + xOff = addXOffset(xOff, station, row); + drawRow(xOff, station, row, seatMap[station].rows[row], seatId); + seatId += seatMap[station].rows[row].seats.length; + } + } + ctx.fill(); + } + }; + + const drawRow = (x: number, station: number, rowI: number, rowInfo: RowInfo, seatId: number) => { + const seatsInfo: SeatInfo[] = rowInfo.seats; + for (let seat = 0, yOff = 0; seat < seatsInfo.length; seat++) { + yOff = addYOffset(yOff, station, rowI, seat); + if (!xYMap[station]) { + xYMap[station] = []; + } + xYMap[station][seatId] = [x + SeatConstants[seatsInfo[seat].type].imageX / 2, yOff + SeatConstants[seatsInfo[seat].type].imageY / 2]; + setXYMap(xYMap); + drawSeat(x, yOff, SeatConstants[seatsInfo[seat].type].imageX, SeatConstants[seatsInfo[seat].type].imageY, station, seatId++); + } + }; + + const drawSeat = (x: number, y: number, imageX: number, imageY: number, station: number, seatId: number) => { + if (ctx && seatEmptyImg && seatMinusImg && seatAddImg && seatFilledImg) { + if (desiredFlags[station].getBitIndex(seatId) && activeFlags[station].getBitIndex(seatId)) { + ctx.drawImage(seatFilledImg.current, x, y, imageX, imageY); + } else if (activeFlags[station].getBitIndex(seatId)) { + ctx.drawImage(seatMinusImg.current, x, y, imageX, imageY); + } else if (desiredFlags[station].getBitIndex(seatId)) { + ctx.drawImage(seatAddImg.current, x, y, imageX, imageY); + } else { + ctx.drawImage(seatEmptyImg.current, x, y, imageX, imageY); + } + } + }; + + const mouseEvent = (e) => { + let selectedStation = -1; + let selectedSeat = -1; + let shortestDistance = Number.POSITIVE_INFINITY; + xYMap.forEach((station, i) => { + station.forEach((seat, j) => { + const distance = distSquared(e.offsetX, e.offsetY, seat[0], seat[1]); + if (distance < shortestDistance) { + selectedStation = i; + selectedSeat = j; + shortestDistance = distance; + } + }); + }); + + if (selectedStation !== -1 && selectedSeat !== -1) { + onClickSeat(selectedStation, selectedSeat); + } + }; + + useCanvasEvent(canvasRef.current, 'click', mouseEvent); + + useEffect(() => { + const canvas = canvasRef.current; + let frameId; + + if (!canvas) { + return undefined; + } + + const width = CanvasConst.width; + const height = CanvasConst.height; + const { devicePixelRatio: ratio = 1 } = window; + setCtx(canvas.getContext('2d')); + canvas.width = width * ratio; + canvas.height = height * ratio; + ctx?.scale(ratio, ratio); + const render = () => { + draw(); + // workaround for rendering bug + if (!frameId || frameId < 10) { + frameId = window.requestAnimationFrame(render); + } + }; + render(); + return () => { + if (frameId) { + window.cancelAnimationFrame(frameId); + } + }; + }, [draw]); + + const distSquared = (x1: number, y1: number, x2: number, y2: number): number => { + const diffX = x1 - x2; + const diffY = y1 - y2; + return (diffX * diffX + diffY * diffY); + }; + + return ( +
+ + +
+ ); +}; diff --git a/src/instruments/src/EFB/Ground/Pages/PushbackMap.tsx b/src/instruments/src/EFB/Ground/Pages/PushbackMap.tsx index 3a1ef49b0423..e98af2cdcbd9 100644 --- a/src/instruments/src/EFB/Ground/Pages/PushbackMap.tsx +++ b/src/instruments/src/EFB/Ground/Pages/PushbackMap.tsx @@ -252,7 +252,7 @@ export const PushbackMap = () => { )} { const { showModal } = useModals(); @@ -43,6 +45,7 @@ export const PushbackPage = () => { const [pushbackAttached] = useSimVar('Pushback Attached', 'bool', 100); const [pushbackAngle] = useSimVar('PUSHBACK ANGLE', 'Radians', 100); + const [useControllerInput, setUseControllerInput] = usePersistentNumberProperty('PUSHBACK_USE_CONTROLLER_INPUT', 1); const [rudderPosition] = useSimVar('L:A32NX_RUDDER_PEDAL_POSITION', 'number', 50); const [elevatorPosition] = useSimVar('L:A32NX_SIDESTICK_POSITION_Y', 'number', 50); @@ -174,7 +177,7 @@ export const PushbackPage = () => { // Update commanded heading from rudder input useEffect(() => { - if (!pushbackActive) { + if (!pushbackActive || !useControllerInput) { return; } // create deadzone @@ -187,7 +190,7 @@ export const PushbackPage = () => { // Update commanded speed from elevator input useEffect(() => { - if (!pushbackActive) { + if (!pushbackActive || !useControllerInput) { return; } // create deadzone @@ -395,7 +398,7 @@ export const PushbackPage = () => { )} {/* Manual Pushback Controls */} -
+
{/* Pushback System enabled On/Off */} @@ -616,6 +619,15 @@ export const PushbackPage = () => {
+ +
+ +
+ {t('Pushback.UseControllerInput')} +
+ (setUseControllerInput(value ? 1 : 0))} /> +
+
diff --git a/src/instruments/src/EFB/Ground/Pages/ServicesPage.tsx b/src/instruments/src/EFB/Ground/Pages/ServicesPage.tsx index 35e286b96e2e..f02a363a09d6 100644 --- a/src/instruments/src/EFB/Ground/Pages/ServicesPage.tsx +++ b/src/instruments/src/EFB/Ground/Pages/ServicesPage.tsx @@ -17,7 +17,7 @@ import { } from 'react-bootstrap-icons'; import { ActionCreatorWithOptionalPayload } from '@reduxjs/toolkit'; import { t } from '../../translation'; -import { UprightOutline } from '../../Assets/UprightOutline'; +import { GroundServiceOutline } from '../../Assets/GroundServiceOutline'; import { useAppDispatch, useAppSelector } from '../../Store/store'; import { setAftDoorButtonState, @@ -476,7 +476,7 @@ export const ServicesPage = () => { return (
- + diff --git a/src/instruments/src/EFB/Localization/ar.json b/src/instruments/src/EFB/Localization/ar.json index 81ede5b4703a..6843ad75172a 100644 --- a/src/instruments/src/EFB/Localization/ar.json +++ b/src/instruments/src/EFB/Localization/ar.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "تغيير مصدر ATIS/ATC", "ConnectHoppieACARS": "قم بتوصيل لHoppie ACARS", "NoInformationAvailableForThisFrequency": "لا توجد معلومات متاحة لهذا التردد", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "هذه الصفحة متاحة فقط عند تحديد IVAO أو VATSIM كمصدر ATIS/ATC في صفحة الإعدادات.", "SetActive": "شغل", "SetStandby": "تعيين الاستعداد", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "الإستعداد", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "مراقبة الحركة الجوية" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "كمية الوقود الكاملة ", "Unavailable": "غير متوفر" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "دفع الى الخلف" }, @@ -162,6 +196,10 @@ "Altn": "البديل ", "ExitFullscreenMode": "الخروج من الشاشة الكاملة ", "From": "من", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "كلاهما", "EstablishingConnection": "جارٍ انشاء اتصال…", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "يجب أن تكون الطائرات تعمل بالطاقة لتحميل الإعدادات المسبقة", "AircraftNeedsToBePoweredToSavePresets": "تحتاج الطائرات إلى أن تكون مدعومة بالطاقه لحفظ الإعدادات المسبقة", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "تحميل الإعداد المسبق", "LoadingPreset": "تحميل الإعداد المسبق", "NewNameConfirmationDialogMsg": "يرجى تأكيد الاسم الجديد للإعداد المسبق للإضاءة", @@ -386,13 +430,15 @@ "SliderSpeed": "تحرك للحصول على السرعة المطلوبة", "SystemEnabledOff": "انقر لتشغيل نظام الدفع.", "SystemEnabledOn": "انقر لإيقاف تشغيل نظام الدفع تمامًا.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "تكبير", "ZoomOut": "تصغير" }, "TugAttached": "وحدة الدفع متصلة", "TugDirection": "اتجاه الدفع ", "TugInTransit": "Waiting for Tug/Pin", - "TugSpeed": "سرعة الدفع " + "TugSpeed": "سرعة الدفع ", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC Lat/Lon Waypoint Format", "PaxSigns": "علامات الركاب", "RmpVhfSpacing": "تباعد RMP VHF", + "Satcom": "Satcom", "ThrustReductionHeight": "ارتفاع تخفيض النفاث (قدم)", "Title": "Aircraft Options / Pin Programs", "WeightUnit": "وحدة الوزن" @@ -451,12 +498,15 @@ "AutofillChecklists": "ملئ تلقائي لقوائم الفحص", "BoardingTime": "وقت الصعود", "DmcSelfTestTime": "وقت الاختبار الذاتي DMC", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "وضع قمرة القيادة المنزلية", "McduFocusTimeout": "مهلة التركيز MCDU (ثواني)", "McduKeyboardInput": "لوحة مفاتيح تدخيل MCDU", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "فصل الحارث عن مدخلات الدفة", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "الواقعية" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "اقماع الامان", "DefaultBarometerUnit": "وحدة البارومتر الافتراضية", "DynamicRegistrationDecal": "ملصق تسجيل ديناميكي", + "EnableSimBridge": "تمكين اتصال خادم MCDU (إلغاء التنشيط التلقائي بعد 5 دقائق إذا لم يكن هناك اتصال ناجح)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "تمكين اتصال خادم MCDU (إلغاء التنشيط التلقائي بعد 5 دقائق إذا لم يكن هناك اتصال ناجح)", "ExternalMcduServerPort": "منفذ خادم MCDU خارجي", "Hpa": "هيكتو باسكال", @@ -474,6 +525,7 @@ "None": "لا شيء", "Off": "إيقاف", "Save": "حفظ", + "SimBridgePort": "منفذ خادم MCDU خارجي", "SyncMsfsFlightPlan": "مزامنة خطة رحلة MSFS", "ThrottleDetents": "محددات دفة تحكم النفاث ", "Title": "خيارات المحاكاة ", diff --git a/src/instruments/src/EFB/Localization/cs.json b/src/instruments/src/EFB/Localization/cs.json index 033ccd5ad09c..76101c0dafeb 100644 --- a/src/instruments/src/EFB/Localization/cs.json +++ b/src/instruments/src/EFB/Localization/cs.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Změnit zdroj ATIS/ATC", "ConnectHoppieACARS": "Připojit ACARS Hoppie", "NoInformationAvailableForThisFrequency": "Pro tuto frekvenci nejsou dostupné žádné informace", + "SearchPlaceholder": "Hledat", "SelectCorrectATISATCSource": "Tato stránka je dostupná pouze pokud je v nastavení jako zdroj ATIS/ATC vybrána možnost IVAO nebo VATSIM.", "SetActive": "Nastavit aktivní", "SetStandby": "Nastavit standby", + "ShowAll": "Vše", + "ShowApproach": "Přiblížení", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Odlet", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Věž", "Standby": "Standby", + "TT": { + "AtcCallSignSearch": "Vyhledávání volacího znaku ATC", + "AtcTypeFilter": "Filtrovat na" + }, "Title": "Řízení letového provozu" }, "Checklists": { @@ -64,7 +77,7 @@ "YourFlight": { "Alternate": "Náhradní letiště", "AverageWind": "Průměrný vítr", - "CompanyRoute": "Company Route", + "CompanyRoute": "Linka společnosti", "CostIndex": "Cost Index", "CruiseAlt": "Letová hladina", "ImportSimBriefData": "Importovat SimBrief data", @@ -134,6 +147,27 @@ "TotalFuel": "Celkem paliva", "Unavailable": "Nedostupné" }, + "Payload": { + "BoardingTime": "Čas nastupování", + "Cargo": "Náklad", + "Current": "Současné", + "Passengers": "Cestující", + "Planned": "Plánované", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Pro změnu délky nastupování musí být letadlo na zemi s vypnutými motory", + "FillPayloadFromSimbrief": "Načíst informace o naložení ze SimBriefu", + "MaxCargo": "Maximum nákladu", + "MaxPassengers": "Maximum cestujících", + "MaxZFW": "Maximální ZFW", + "MaxZFWCG": "Maximální ZFWCG", + "PerPaxBagWeight": "Hmotnost zavazadel na cestujícího", + "PerPaxWeight": "Váha cestujících", + "StartBoarding": "Zahájit nastupování" + }, + "Title": "Náklad", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Vytlačení" }, @@ -162,6 +196,10 @@ "Altn": "Náhr.", "ExitFullscreenMode": "Ukončit režim celé obrazovky", "From": "Od", + "LoadingImage": "Načítání obrázku", + "LoadingImageFailed": "Načítání obrázku selhalo", + "LoadingPdf": "Načítání PDF", + "LoadingPdfFailed": "Načítání PDF selhalo", "LocalFiles": { "Both": "Obojí", "EstablishingConnection": "Navazuji spojení", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "K načtení předvoleb musí být letadlo napájené", "AircraftNeedsToBePoweredToSavePresets": "K uložení předvoleb musí být letadlo napájené", + "AutoLoadDawnDusk": "Svítání/Soumrak:", + "AutoLoadDay": "Den:", + "AutoLoadLightingHelpTitle": "Automatické načítání předvoleb", + "AutoLoadLightingPreset": "Povolit automatické načítání předvoleb osvětlení:", + "AutoLoadNight": "Noc:", + "AutoLoadNoneSelection": "Žádné", "LoadPreset": "Načíst předvolbu", "LoadingPreset": "Načítání předvolby", "NewNameConfirmationDialogMsg": "Prosím potvrďte změnu názvu předvolby osvětlení", @@ -386,13 +430,15 @@ "SliderSpeed": "Posuňte pro požadovanou rychlost", "SystemEnabledOff": "Klikněte pro zapnutí systému pro vytlačení.", "SystemEnabledOn": "Klikněte pro úplné vypnutí systému pro vytlačení.", + "UseControllerInput": "Zapne nebo vypne vstup z ovládání směrovky a výškovky", "ZoomIn": "Přiblížit", "ZoomOut": "Oddálit" }, "TugAttached": "Pushback je připojen", "TugDirection": "Směr vytlačení", "TugInTransit": "Čekání na pushback/pin", - "TugSpeed": "Rychlost vytlačení" + "TugSpeed": "Rychlost vytlačení", + "UseControllerInput": "Používat vstup z ovladače" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "Formát FMGC Lat/Lon Waypointů", "PaxSigns": "Znamení cestujícím", "RmpVhfSpacing": "RMP VHF rozteč", + "Satcom": "Satcom", "ThrustReductionHeight": "Výška snížení tahu (ft AGL)", "Title": "Možnosti letadla / Pin programy", "WeightUnit": "Jednotky hmotnosti" @@ -451,12 +498,15 @@ "AutofillChecklists": "Automaticky vyplňovat checklisty", "BoardingTime": "Čas nastupování cestujících", "DmcSelfTestTime": "Inicializační test DMC", + "FirstOfficerAvatar": "Zobrazit avatar prvního důstojníka", "HomeCockpitMode": "Režim domácího kokpitu", "McduFocusTimeout": "Časový limit vstupu MCDU z klávesnice (sekundy)", "McduKeyboardInput": "MCDU zadávání pomocí klávesnice", "PauseAtTod": "Pauza na T/D", "PauseAtTodDistance": "Vzdálenost zapauzování před T/D (nm)", + "PilotAvatar": "Zobrazit avatar pilota", "SeparateTillerFromRudderInputs": "Oddělit vstupy pro tiller a směrovku", + "SyncEfis": "Synchronizovat EFIS mezi kapitánem a FO", "Title": "Realismus" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Výstražné kužely", "DefaultBarometerUnit": "Výchozí jednotky tlaku vzduchu", "DynamicRegistrationDecal": "Dynamická imatrikulace letadla", + "EnableSimBridge": "Povolit připojení SimBridge (Bez úspěšného připojení se po 5 minutách automaticky vypne)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Povolit připojení k serveru MCDU (Bez úspěšného připojení se po 5 minutách automaticky vypne)", "ExternalMcduServerPort": "Port externího serveru MCDU", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Žádné", "Off": "Vypnuto", "Save": "Uložit", + "SimBridgePort": "Externí port SimBridge", "SyncMsfsFlightPlan": "Synchronizace letového plánu MSFS", "ThrottleDetents": "Zarážky plynu", "Title": "Možnosti simulace", @@ -545,8 +597,8 @@ "Sep": "Zář", "Sun": "Ne", "TT": { - "ConnectedToLocalApi": "Připojeno k místnímu API", - "DisconnectedFromLocalApi": "Odpojeno od místního API", + "ConnectedToLocalApi": "Připojeno k SimBridge", + "DisconnectedFromLocalApi": "Odpojeno od SimBridge", "TurnOffOrShutdownEfb": "Uspat nebo vypnout EFB" }, "Thu": "Čt", diff --git a/src/instruments/src/EFB/Localization/da-DK.json b/src/instruments/src/EFB/Localization/da-DK.json index 43b1941afe7e..dc474c8fe4b1 100644 --- a/src/instruments/src/EFB/Localization/da-DK.json +++ b/src/instruments/src/EFB/Localization/da-DK.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Skift ATIS/ATC-kilde", "ConnectHoppieACARS": "Forbind Hoppie ACARS", "NoInformationAvailableForThisFrequency": "Ingen information tilgængelig for denne frekvens", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Denne side er kun tilgængelig, når IVAO eller VATSIM er valgt som ATIS/ATC kilde på indstillingssiden.", "SetActive": "Sæt Aktiv", "SetStandby": "Sæt Standby", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Standby", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Lufttrafik Kontrol" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Total brændstof", "Unavailable": "Ikke tilgængelig" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "Altn", "ExitFullscreenMode": "Afslut fuldskærmstilstand", "From": "Fra", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Begge", "EstablishingConnection": "Opretter forbindelse", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Flyet skal tændt for at indlæse forudindstillinger", "AircraftNeedsToBePoweredToSavePresets": "Flyet skal være tændt for at gemme forudindstillinger", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Indlæs forudindstilling", "LoadingPreset": "Indlæsning af forudindstilling", "NewNameConfirmationDialogMsg": "Bekræft venligst det nye navn for lysforindstillingen", @@ -386,13 +430,15 @@ "SliderSpeed": "Sæt den ønskede hastighed", "SystemEnabledOff": "Tryk for at aktivere skubbe systemet", "SystemEnabledOn": "Tryk for at deaktivere skubbe systemet helt", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Zoom ind", "ZoomOut": "Zoom ud" }, "TugAttached": "Traktoren er forbundet til flyet", "TugDirection": "Skubber Retning", "TugInTransit": "Venter på traktoren/styrestift", - "TugSpeed": "Skubber hastighed" + "TugSpeed": "Skubber hastighed", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC Lat/Lon Waypoint Format", "PaxSigns": "Passager skilte", "RmpVhfSpacing": "RMP VHF Afstand", + "Satcom": "Satcom", "ThrustReductionHeight": "Thrustreduktionshøjde (ft)", "Title": "Muligheder for fly / Pin programmer", "WeightUnit": "Vægt enhed" @@ -451,12 +498,15 @@ "AutofillChecklists": "Tjeklister til autoudfyldning", "BoardingTime": "Boarding tid", "DmcSelfTestTime": "DMC Selv Test Tid", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Hjemmecockpit tilstand", "McduFocusTimeout": "MCDU Fokus Timeout (sekunder)", "McduKeyboardInput": "MCDU Tastatur Input", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Separat Tiller fra Rudder Inputs", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Realisme" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Safety Cones", "DefaultBarometerUnit": "Standard Barometer enhed", "DynamicRegistrationDecal": "Dynamisk registrerings mærke", + "EnableSimBridge": "Aktiver MCDU Server Forbindelse (Deaktiveres automatisk efter 5 minutter, hvis der ikke oprettes succesfuld forbindelse)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Aktiver MCDU Server Forbindelse (Deaktiveres automatisk efter 5 minutter, hvis der ikke oprettes succesfuld forbindelse)", "ExternalMcduServerPort": "Ekstern MCDU serverport", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Ingen", "Off": "Fra", "Save": "Gem", + "SimBridgePort": "Ekstern MCDU serverport", "SyncMsfsFlightPlan": "Synkroniser MSFS-flyveplan", "ThrottleDetents": "Gashåndtagspositioner", "Title": "Sim-instillinger", diff --git a/src/instruments/src/EFB/Localization/de.json b/src/instruments/src/EFB/Localization/de.json index 861d1bccb83f..9376a6b2b690 100644 --- a/src/instruments/src/EFB/Localization/de.json +++ b/src/instruments/src/EFB/Localization/de.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "ATIS/ATC-Quelle ändern", "ConnectHoppieACARS": "Hoppie ACARS verbinden", "NoInformationAvailableForThisFrequency": "Für diese Frequenz sind keine Informationen verfügbar", + "SearchPlaceholder": "Suche", "SelectCorrectATISATCSource": "Diese Seite ist nur verfügbar, wenn IVAO oder VATSIM auf der Einstellungsseite als ATIS/ATC-Quelle ausgewählt wurde.", "SetActive": "Als Aktiv", "SetStandby": "Als Standby", + "ShowAll": "Alle", + "ShowApproach": "Anflug", + "ShowAtis": "ATIS", + "ShowDelivery": "Freigabe", + "ShowDeparture": "Abflug", + "ShowGround": "Vorfeld", + "ShowRadar": "Radar", + "ShowTower": "Turm", "Standby": "Standby-Frequenz", + "TT": { + "AtcCallSignSearch": "ATC-Rufzeichen-Suche", + "AtcTypeFilter": "Filtern nach" + }, "Title": "Flugsicherung" }, "Checklists": { @@ -85,7 +98,7 @@ "YouHaveNotYetImportedAnySimBriefData": "Du hast noch keine SimBrief-Daten importiert." }, "Overview": { - "ActualGW": "Actual GW", + "ActualGW": "Gesamtgewicht", "Engines": "Triebwerke", "MMO": "MMO", "MRW": "MRW", @@ -134,6 +147,27 @@ "TotalFuel": "Treibstoff gesamt", "Unavailable": "Nicht verfügbar" }, + "Payload": { + "BoardingTime": "Boarding-Dauer", + "Cargo": "Fracht", + "Current": "Aktuell", + "Passengers": "Passagiere", + "Planned": "Geplant", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Flugzeug muss am Boden sein und die Triebwerke müssen ausgeschaltet sein, um die Boarding-Dauer zu ändern", + "FillPayloadFromSimbrief": "Beladungsdaten von Simbrief laden", + "MaxCargo": "Maximale Fracht", + "MaxPassengers": "Maximale Passagieranzahl", + "MaxZFW": "Maximales ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Gepäckgewicht per Passagier", + "PerPaxWeight": "Gewicht pro Passagier", + "StartBoarding": "Boarding starten" + }, + "Title": "Nutzlast", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "ALTN", "ExitFullscreenMode": "Vollbildmodus beenden", "From": "Von", + "LoadingImage": "Bild wird geladen", + "LoadingImageFailed": "Laden des Bildes fehlgeschlagen", + "LoadingPdf": "PDF wird geladen", + "LoadingPdfFailed": "Laden des PDFs fehlgeschlagen", "LocalFiles": { "Both": "Beide", "EstablishingConnection": "Verbindung herstellen...", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Das Flugzeug muss mit Strom versorgt werden, um Voreinstellungen zu laden", "AircraftNeedsToBePoweredToSavePresets": "Das Flugzeug muss mit Strom versorgt sein, um Voreinstellungen zu speichern", + "AutoLoadDawnDusk": "Dämmerung", + "AutoLoadDay": "Tag", + "AutoLoadLightingHelpTitle": "Automatisches Laden der Beleuchtungsvoreinstellung", + "AutoLoadLightingPreset": "Automatisches Laden von Beleuchtungsvoreinstellungen aktivieren:", + "AutoLoadNight": "Nacht", + "AutoLoadNoneSelection": "Keine", "LoadPreset": "Voreinstellung laden", "LoadingPreset": "Voreinstellung wird geladen", "NewNameConfirmationDialogMsg": "Bestätige bitte den neuen Namen der Voreinstellung", @@ -386,13 +430,15 @@ "SliderSpeed": "Schieberegler verschieben, um die gewünschte Geschwindigkeit einzustellen", "SystemEnabledOff": "Klicken Sie, um das Pushback-System einzuschalten.", "SystemEnabledOn": "Klicken Sie, um das Pushback-System vollständig auszuschalten.", + "UseControllerInput": "Controller-Eingabe von Ruder und Höhenruder ein-/ausschalten", "ZoomIn": "Vergrößern", "ZoomOut": "Verkleinern" }, "TugAttached": "Schlepper ist befestigt", "TugDirection": "Schlepprichtung", "TugInTransit": "Warten auf Schlepper und Pin", - "TugSpeed": "Schleppgeschwindigkeit" + "TugSpeed": "Schleppgeschwindigkeit", + "UseControllerInput": "Controller-Eingabe verwenden" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC Lat/Lon Waypoint Format", "PaxSigns": "Passagier-Anzeigen", "RmpVhfSpacing": "RMP VHF Kanalraster", + "Satcom": "Satcom anzeigen", "ThrustReductionHeight": "Flughöhe für Schubreduzierung (Fuß)", "Title": "Flugzeug-Optionen/Pin Programming", "WeightUnit": "Gewichtseinheit" @@ -451,12 +498,15 @@ "AutofillChecklists": "Checklisten automatisch ausfüllen", "BoardingTime": "Boarding-Dauer", "DmcSelfTestTime": "DMC-Selbsttestdauer", + "FirstOfficerAvatar": "Co-Pilot-Avatar anzeigen", "HomeCockpitMode": "Homecockpit-Modus", "McduFocusTimeout": "MCDU Fokusdauer (Sekunden)", "McduKeyboardInput": "MCDU-Tastatureingabe", "PauseAtTod": "T/D-Pause", "PauseAtTodDistance": "Zielentfernung für T/D Pause (NM)", + "PilotAvatar": "Pilot-Avatar anzeigen", "SeparateTillerFromRudderInputs": "Trennung von Tiller und Seitenruder Eingaben", + "SyncEfis": "Synchronisieren der EFIS-Steuerelemente zwischen Captain und FO", "Title": "Realismus" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Sicherheitskegel", "DefaultBarometerUnit": "Standard Barometer-Einheit", "DynamicRegistrationDecal": "Flugzeugkennzeichen dynamisch darstellen", + "EnableSimBridge": "SimBridge Serververbindung aktivieren (Wird nach 5 Minuten automatisch deaktiviert, wenn keine Verbindung zustande kommt)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "MCDU Serververbindung aktivieren (Wird nach 5 Minuten automatisch deaktiviert, wenn keine Verbindung zustande kommt)", "ExternalMcduServerPort": "Externer MCDU-Serverport", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Aus", "Off": "Aus", "Save": "Zurückspeichern bei Änderung", + "SimBridgePort": "Externer SimBridge-Port", "SyncMsfsFlightPlan": "MSFS-Flugplan-Synchronisation", "ThrottleDetents": "Schubhebelkalibrierung", "Title": "Sim-Optionen", @@ -545,8 +597,8 @@ "Sep": "Sep", "Sun": "So", "TT": { - "ConnectedToLocalApi": "Mit Local-API Server verbunden", - "DisconnectedFromLocalApi": "Keine Verbindung zum Local-API Server", + "ConnectedToLocalApi": "Mit SimBridge verbunden", + "DisconnectedFromLocalApi": "Keine Verbindung zur SimBridge", "TurnOffOrShutdownEfb": "EFB ausschalten oder herunterfahren" }, "Thu": "Do", diff --git a/src/instruments/src/EFB/Localization/el.json b/src/instruments/src/EFB/Localization/el.json index ad40ed51c6b6..fc29833e8f33 100644 --- a/src/instruments/src/EFB/Localization/el.json +++ b/src/instruments/src/EFB/Localization/el.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Αλλαγή πηγής ATIS/ATC", "ConnectHoppieACARS": "Σύνδεση Hoppie ACARS", "NoInformationAvailableForThisFrequency": "Δεν υπάρχουν διαθέσιμες πληροφορίες για αυτή τη συχνότητα", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Αυτή η σελίδα είναι διαθέσιμη μόνο όταν έχει επιλεγεί το IVAO ή το VATSIM ως πηγή ATIS/ATC στη σελίδα ρυθμίσεων.", "SetActive": "Θέστε Ενεργό", "SetStandby": "Θέστε σε Αναμονή", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Αναμονή", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Έλεγχος Εναέριας Κυκλοφορίας" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Σύνολο Καυσίμων", "Unavailable": "Μη διαθέσιμο" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Οπισθοδρόμηση" }, @@ -162,6 +196,10 @@ "Altn": "Εναλλακτικό", "ExitFullscreenMode": "Έξοδος από Πλήρη Οθόνη", "From": "Από", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Και τα δύο", "EstablishingConnection": "Δημιουργία Σύνδεσης", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Τα αεροσκάφος πρέπει να τροφοδοτείται για να φορτώσει προκαθορισμένες ρυθμίσεις", "AircraftNeedsToBePoweredToSavePresets": "Το αεροσκάφος πρέπει να τροφοδοτείται για να εξοικονομήσει προεπιλογές", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Φόρτωση Προκαθορισμένης Ρύθμισης", "LoadingPreset": "Φόρτωση Προκαθορισμένης Ρύθμισης", "NewNameConfirmationDialogMsg": "Παρακαλώ επιβεβαιώστε το νέο όνομα για την προκαθορισμένη ρύθμιση φωτισμού", @@ -386,13 +430,15 @@ "SliderSpeed": "Move for desired speed", "SystemEnabledOff": "Click to turn the pushback system on.", "SystemEnabledOn": "Click to turn the pushback system completely off.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Zoom in", "ZoomOut": "Zoom out" }, "TugAttached": "Tug is attached", "TugDirection": "Κατεύθυνση Ρυμουλκού", "TugInTransit": "Waiting for Tug/Pin", - "TugSpeed": "Ταχύτητα Ρυμουλκού" + "TugSpeed": "Ταχύτητα Ρυμουλκού", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "Μορφή Σημείου Αναφοράς Lat/Lon FMGC", "PaxSigns": "Πινακίδες Επιβατών", "RmpVhfSpacing": "Απόσταση VHF RMP", + "Satcom": "Satcom", "ThrustReductionHeight": "Ύψος Mείωσης Ώσης (ft)", "Title": "Επιλογές Αεροσκάφους / Προγράμματα Pin", "WeightUnit": "Μονάδα Βάρους" @@ -451,12 +498,15 @@ "AutofillChecklists": "Λίστες Ελέγχου Αυτόματης Συμπλήρωσης", "BoardingTime": "Χρόνος Επιβίβασης ", "DmcSelfTestTime": "Χρόνος Αυτοδοκιμής DMC", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Λειτουργία θαλάμου πιλοτηρίου οικιακής χρήσης", "McduFocusTimeout": "Χρονικό Όριο Εστίασης MCDU (δευτερόλεπτα)", "McduKeyboardInput": "Είσαγωγή Πληκτρολογίου MCDU", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Ξεχωριστό Οιακοστροφίο απο τις Εισαγωγές Πηδαλίου", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Ρεαλισμός" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Safety Cones", "DefaultBarometerUnit": "Προκαθορισμένη Βαρομετρική Μονάδα", "DynamicRegistrationDecal": "Δυναμική Χαλκομανία Εγγραφής", + "EnableSimBridge": "Ενεργοποίηση σύνδεσης διακομιστή MCDU (Αυτόματη απενεργοποίηση μετά από 5 λεπτά εάν δεν υπάρχει επιτυχής σύνδεση)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Ενεργοποίηση σύνδεσης διακομιστή MCDU (Αυτόματη απενεργοποίηση μετά από 5 λεπτά εάν δεν υπάρχει επιτυχής σύνδεση)", "ExternalMcduServerPort": "Εξωτερική Θύρα Διακομιστή MCDU", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Κανένα", "Off": "Απενεργοποιημένο", "Save": "Αποθήκευση", + "SimBridgePort": "Εξωτερική Θύρα Διακομιστή MCDU", "SyncMsfsFlightPlan": "Συγχρονισμός του Σχεδίου Πτήσεως MSFS", "ThrottleDetents": "Αναστολέας Γκαζιού", "Title": "Επιλογές Sim", diff --git a/src/instruments/src/EFB/Localization/en.json b/src/instruments/src/EFB/Localization/en.json index a88f7051eb66..03ee9242e268 100644 --- a/src/instruments/src/EFB/Localization/en.json +++ b/src/instruments/src/EFB/Localization/en.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Change ATIS/ATC source", "ConnectHoppieACARS": "Connect Hoppie ACARS", "NoInformationAvailableForThisFrequency": "No information available for this frequency", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "This page is only available when IVAO or VATSIM is selected as the ATIS/ATC source in the settings page.", "SetActive": "Set Active", "SetStandby": "Set Standby", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Standby", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Air Traffic Control" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Total Fuel", "Unavailable": "Unavailable" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "Altn", "ExitFullscreenMode": "Exit Fullscreen Mode", "From": "From", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Both", "EstablishingConnection": "Establishing Connection", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Aircraft Needs to be Powered to Load Presets", "AircraftNeedsToBePoweredToSavePresets": "Aircraft Needs to Be Powered to Save Presets", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Load Preset", "LoadingPreset": "Loading Preset", "NewNameConfirmationDialogMsg": "Please confirm the new name for the lighting preset", @@ -386,13 +430,15 @@ "SliderSpeed": "Move for desired speed", "SystemEnabledOff": "Click to turn the pushback system on.", "SystemEnabledOn": "Click to turn the pushback system completely off.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Zoom in", "ZoomOut": "Zoom out" }, "TugAttached": "Tug is attached", "TugDirection": "Tug Direction", "TugInTransit": "Waiting for Tug/Pin", - "TugSpeed": "Tug Speed" + "TugSpeed": "Tug Speed", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC Lat/Lon Waypoint Format", "PaxSigns": "PAX Signs", "RmpVhfSpacing": "RMP VHF Spacing", + "Satcom": "Satcom", "ThrustReductionHeight": "Thrust Reduction Height (ft)", "Title": "Aircraft Options / Pin Programs", "WeightUnit": "Weight Unit" @@ -451,12 +498,15 @@ "AutofillChecklists": "Autofill Checklists", "BoardingTime": "Boarding Time", "DmcSelfTestTime": "DMC Self Test Time", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Home Cockpit Mode", "McduFocusTimeout": "MCDU Focus Timeout (seconds)", "McduKeyboardInput": "MCDU Keyboard Input", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Separate Tiller from Rudder Inputs", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Realism" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Safety Cones", "DefaultBarometerUnit": "Default Barometer Unit", "DynamicRegistrationDecal": "Dynamic Registration Decal", + "EnableSimBridge": "Enable Simbridge Connection (Auto deactivates after 5 minutes if no successful connection)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Enable MCDU Server Connection (Auto deactivates after 5 minutes if no successful connection)", "ExternalMcduServerPort": "External MCDU Server Port", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "None", "Off": "Off", "Save": "Save", + "SimBridgePort": "External SimBridge Port", "SyncMsfsFlightPlan": "Sync MSFS Flight Plan", "ThrottleDetents": "Throttle Detents", "Title": "Sim Options", @@ -545,8 +597,8 @@ "Sep": "Sep", "Sun": "Sun", "TT": { - "ConnectedToLocalApi": "Connected to Local API", - "DisconnectedFromLocalApi": "Disconnected from Local API", + "ConnectedToLocalApi": "Connected to SimBridge", + "DisconnectedFromLocalApi": "Disconnected from SimBridge", "TurnOffOrShutdownEfb": "Turn off or Shutdown EFB" }, "Thu": "Thu", diff --git a/src/instruments/src/EFB/Localization/es.json b/src/instruments/src/EFB/Localization/es.json index 727ac58ae5ed..42cf59996e97 100644 --- a/src/instruments/src/EFB/Localization/es.json +++ b/src/instruments/src/EFB/Localization/es.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Cambiar la fuente ATIS/ATC", "ConnectHoppieACARS": "Conectar Hoppie ACARS", "NoInformationAvailableForThisFrequency": "No hay información disponible para esta frecuencia", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Esta página solo está disponible cuando se selecciona IVAO o VATSIM como fuente de ATIS/ATC en la página de configuración.", "SetActive": "Establecer como activo", "SetStandby": "Establecer modo de espera", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "En espera", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Control de tráfico aéreo" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Combustible Total", "Unavailable": "No disponible" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "Altn", "ExitFullscreenMode": "Salir de modo pantalla completa", "From": "De", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Ambos", "EstablishingConnection": "Estableciendo conexión", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "El avión debe tener alimentación para cargar los preajustes", "AircraftNeedsToBePoweredToSavePresets": "El avión debe tener alimentación para guardar los preajustes", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Cargar preajuste", "LoadingPreset": "Cargando preajuste", "NewNameConfirmationDialogMsg": "Por favor, confirme el nuevo nombre del ajuste preestablecido de iluminación", @@ -386,13 +430,15 @@ "SliderSpeed": "Mover a la velocidad deseada", "SystemEnabledOff": "Haz click para encender el sistema de remolque.", "SystemEnabledOn": "Haz click para apagar completamente el sistema de remolque ", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Acercar", "ZoomOut": "Alejar" }, "TugAttached": "El remolcador está unido", "TugDirection": "Dirección del remolcador", "TugInTransit": "Esperando al Remolcador/Pin", - "TugSpeed": "Velocidad del remolcador" + "TugSpeed": "Velocidad del remolcador", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "Formato de punto Lat/Long FMGC", "PaxSigns": "Señales PAX", "RmpVhfSpacing": "Espaciado VHF RMP", + "Satcom": "Satcom", "ThrustReductionHeight": "Altura de reducción de empuje (ft)", "Title": "Opciones de aeronave/ Programas de pin", "WeightUnit": "Unidad de peso" @@ -451,12 +498,15 @@ "AutofillChecklists": "Autocompletar listas de verificación", "BoardingTime": "Tiempo de embarque", "DmcSelfTestTime": "Tiempo de autocomprobación DMC", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Modo Home Cockpit", "McduFocusTimeout": "Tiempo de espera del MCDU (segundos)", "McduKeyboardInput": "Entrada de teclado MCDU", "PauseAtTod": "Pausa T/D", "PauseAtTodDistance": "Distancia de Pausa T/D (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Separar Tiller de la entrada del Timón", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Realismo" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Conos de seguridad", "DefaultBarometerUnit": "Unidad del barómetro predeterminado", "DynamicRegistrationDecal": "Calcomanía de registro dinámica", + "EnableSimBridge": "Activar la conexión con el servidor MCDU (se desactivará automáticamente tras 5 minutos si no ha habido una conexión exitosa)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Activar la conexión con el servidor MCDU (se desactivará automáticamente tras 5 minutos si no ha habido una conexión exitosa)", "ExternalMcduServerPort": "Puerto del servidor MCDU externo", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Ninguno", "Off": "Desactivado", "Save": "Guardar", + "SimBridgePort": "Puerto del servidor MCDU externo", "SyncMsfsFlightPlan": "Sincronizar plan de vuelo de MSFS", "ThrottleDetents": "Throttle Detents", "Title": "Opciones de simulación", diff --git a/src/instruments/src/EFB/Localization/eu.json b/src/instruments/src/EFB/Localization/eu.json index 9503087c8f34..3acdfe5a1ed5 100644 --- a/src/instruments/src/EFB/Localization/eu.json +++ b/src/instruments/src/EFB/Localization/eu.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Aldatu ATIS/ATC iturria", "ConnectHoppieACARS": "Hoppie ACARS-ra konektatu", "NoInformationAvailableForThisFrequency": "Informaziorik ez dago eskuragarri frekuentzia honetan", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "This page is only available when IVAO or VATSIM is selected as the ATIS/ATC source in the settings page.", "SetActive": "Aktibo bezala ezarri", "SetStandby": "Ezarri Egonean", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Egonean", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Air Traffic Control" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Erregai totala", "Unavailable": "Ez dago eskuragarri" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "Ordezkoa", "ExitFullscreenMode": "Pantaila osoko modutik atera", "From": "Nondik", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Biak", "EstablishingConnection": "Konexioa ezartzen", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Aircraft Needs to be Powered to Load Presets", "AircraftNeedsToBePoweredToSavePresets": "Aircraft Needs to Be Powered to Save Presets", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Load Preset", "LoadingPreset": "Loading Preset", "NewNameConfirmationDialogMsg": "Please confirm the new name for the lighting preset", @@ -386,13 +430,15 @@ "SliderSpeed": "Move for desired speed", "SystemEnabledOff": "Click to turn the pushback system on.", "SystemEnabledOn": "Click to turn the pushback system completely off.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Zoom in", "ZoomOut": "Zoom out" }, "TugAttached": "Tug is attached", "TugDirection": "Tug Direction", "TugInTransit": "Waiting for Tug/Pin", - "TugSpeed": "Tug Speed" + "TugSpeed": "Tug Speed", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC Lat/Lon Waypoint Format", "PaxSigns": "PAX Signs", "RmpVhfSpacing": "RMP VHF Spacing", + "Satcom": "Satcom", "ThrustReductionHeight": "Thrust Reduction Height (ft)", "Title": "Aircraft Options / Pin Programs", "WeightUnit": "Weight Unit" @@ -451,12 +498,15 @@ "AutofillChecklists": "Autofill Checklists", "BoardingTime": "Boarding Time", "DmcSelfTestTime": "DMC Self Test Time", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Home Cockpit Mode", "McduFocusTimeout": "MCDU Focus Timeout (seconds)", "McduKeyboardInput": "MCDU Keyboard Input", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Separate Tiller from Rudder Inputs", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Realism" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Safety Cones", "DefaultBarometerUnit": "Default Barometer Unit", "DynamicRegistrationDecal": "Dynamic Registration Decal", + "EnableSimBridge": "Enable MCDU Server Connection (Auto deactivates after 5 minutes if no successful connection)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Enable MCDU Server Connection (Auto deactivates after 5 minutes if no successful connection)", "ExternalMcduServerPort": "External MCDU Server Port", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "None", "Off": "Off", "Save": "Save", + "SimBridgePort": "External MCDU Server Port", "SyncMsfsFlightPlan": "Sync MSFS Flight Plan", "ThrottleDetents": "Throttle Detents", "Title": "Sim Options", diff --git a/src/instruments/src/EFB/Localization/fi.json b/src/instruments/src/EFB/Localization/fi.json index ec1ffa0ddac9..621a0010a275 100644 --- a/src/instruments/src/EFB/Localization/fi.json +++ b/src/instruments/src/EFB/Localization/fi.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Vaihda ATIS/ATC lähde", "ConnectHoppieACARS": "Yhdistä Hoppien ACARSiin", "NoInformationAvailableForThisFrequency": "Tietoja ei ole saatavilla tälle taajuudelle", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Tämä sivu on saatavilla vain, kun IVAO tai VATSIM on valittuna ATIS/ATC lähteeksi asetuksista.", "SetActive": "Aseta aktiiviseksi", "SetStandby": "Aseta valmiustilaan", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Valmiustila", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Lennonjohto" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Kokonaispolttoaine", "Unavailable": "Ei saatavilla" }, + "Payload": { + "BoardingTime": "Koneeseen nousun kesto", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Suurin mahdollinen matkustajamäärä", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Työntö" }, @@ -162,6 +196,10 @@ "Altn": "Vaihtoehtoinen", "ExitFullscreenMode": "Poistu kokoruuduntilasta", "From": "Mistä", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Molemmat", "EstablishingConnection": "Muodostetaan yhteyttä", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Esiasetusten lataamiseksi virta tulee olla kytketty.", "AircraftNeedsToBePoweredToSavePresets": "Esiasetusten tallentamiseksi virta tulee olla kytketty.", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "Ei mitään", "LoadPreset": "Lataa esiasetus", "LoadingPreset": "Ladataan esiasetus", "NewNameConfirmationDialogMsg": "Vahvista uusi nimi valotusesiasetukselle", @@ -386,13 +430,15 @@ "SliderSpeed": "Siirrä haluttuun nopeuteen", "SystemEnabledOff": "Napsauta kytkeäksesi työntöjärjestelmä päälle.", "SystemEnabledOn": "Napsauta kytkeäksesi työntöjärjestelmän kokonaan pois päältä.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Lähennä", "ZoomOut": "Loitonna" }, "TugAttached": "Työntöauto kiinnitetty", "TugDirection": "Työntöauton suunta", "TugInTransit": "Odotetaan työntöautoa", - "TugSpeed": "Työntöauton nopeus" + "TugSpeed": "Työntöauton nopeus", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC Lat/Lon reittipisteen muoto", "PaxSigns": "Matkustajamerkinnät", "RmpVhfSpacing": "RMP VHF välitys", + "Satcom": "Satcom", "ThrustReductionHeight": "Työntövoiman vähennyskorkeus (ft)", "Title": "Lentokoneen asetukset / ohjelmistot", "WeightUnit": "Painoyksikkö" @@ -451,12 +498,15 @@ "AutofillChecklists": "Tarkistuslistan täyttö automaattisesti", "BoardingTime": "Boarding-aika", "DmcSelfTestTime": "DMC itsenäisen testauksen aika", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Kotiohjaamo-tila", "McduFocusTimeout": "Lentotietokone poissa käytöstä tarkennuksen aikana (sekunteina)", "McduKeyboardInput": "MCDU näppäimistö syöttö", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Erota nokkapyörän ohjaus peräsimestä", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Realismi" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Varoituskartiot", "DefaultBarometerUnit": "Paineen oletusyksikkö", "DynamicRegistrationDecal": "Dynaaminen rekisteröintimerkintä", + "EnableSimBridge": "Ota lentotietokoneen palvelinyhteys käyttöön (yhdistysyritykset loppuvat 5 minuutin kuluttua, jos yhdistäminen ei onnistu)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Ota lentotietokoneen palvelinyhteys käyttöön (yhdistysyritykset loppuvat 5 minuutin kuluttua, jos yhdistäminen ei onnistu)", "ExternalMcduServerPort": "Ulkoinen lentotietokoneen palvelinyhteys", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Ei mitään", "Off": "Pois päältä", "Save": "Tallenna", + "SimBridgePort": "Ulkoinen lentotietokoneen palvelinyhteys", "SyncMsfsFlightPlan": "Synkronoi MSFS lentosuunnitelma", "ThrottleDetents": "Kaasupykälät", "Title": "Sim -asetukset", @@ -507,7 +559,7 @@ "flyPad": { "AutoBrightness": "Automaattinen kirkkaus", "AutomaticallyShowOnscreenKeyboard": "Näytä näyttönäppäimistö automaattisesti", - "BatteryLifeEnabled": "Battery Life Simulated", + "BatteryLifeEnabled": "Akun kesto simuloitu", "Blue": "Sininen", "Brightness": "Kirkkaus", "Dark": "Tumma", diff --git a/src/instruments/src/EFB/Localization/fr.json b/src/instruments/src/EFB/Localization/fr.json index d1edfa5d90db..0aca042da90a 100644 --- a/src/instruments/src/EFB/Localization/fr.json +++ b/src/instruments/src/EFB/Localization/fr.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Modifier la source ATIS/ATC", "ConnectHoppieACARS": "Connecter l'ACARS Hoppie", "NoInformationAvailableForThisFrequency": "Aucune information disponible pour cette fréquence", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Cette page est uniquement disponible lorsque IVAO ou VATSIM est sélectionné comme source ATIS/ATC dans la page des paramètres.", "SetActive": "Activer", "SetStandby": "Passer en Standby", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Veille", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Contrôle du trafic aérien" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Carburant Total", "Unavailable": "Indisponible" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Repoussage" }, @@ -162,6 +196,10 @@ "Altn": "Altn", "ExitFullscreenMode": "Quitter le mode plein-écran", "From": "FROM", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Les deux", "EstablishingConnection": "Connexion en cours", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "L'avion doit être alimenté pour charger des préréglages", "AircraftNeedsToBePoweredToSavePresets": "L'avion doit être alimenté pour enregistrer les préréglages", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Charger le préréglage", "LoadingPreset": "Chargement du préréglage", "NewNameConfirmationDialogMsg": "Veuillez confirmer le nouveau nom du préréglage d'éclairage", @@ -386,13 +430,15 @@ "SliderSpeed": "Déplacer pour la vitesse souhaitée", "SystemEnabledOff": "Cliquez pour démarrer le système de repoussage.", "SystemEnabledOn": "Cliquez pour éteindre complètement le système de repoussage.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Zoom avant", "ZoomOut": "Zoom arrière" }, "TugAttached": "Le remorqueur est attaché", "TugDirection": "Direction du remorqueur", "TugInTransit": "En attente du remorqueur/de la liaison", - "TugSpeed": "Vitesse du remorqueur" + "TugSpeed": "Vitesse du remorqueur", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "Format de point de cheminement lat/long FMGC", "PaxSigns": "Voyants passagers", "RmpVhfSpacing": "Espacement VHF RMP", + "Satcom": "Satcom", "ThrustReductionHeight": "Thrust Reduction Height (pi)", "Title": "Options de l'avion/configuration matérielle", "WeightUnit": "Unité de masse" @@ -451,12 +498,15 @@ "AutofillChecklists": "Saisie automatique des check-lists", "BoardingTime": "Durée d'embarquement", "DmcSelfTestTime": "Temps d'auto-test DMC", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Mode home cockpit", "McduFocusTimeout": "Délai d'attente du focus MCDU (secondes)", "McduKeyboardInput": "Entrée clavier MCDU", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Séparer le tiller du palonnier", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Réalisme" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Cônes de sécurité", "DefaultBarometerUnit": "Unité baromètre par défaut", "DynamicRegistrationDecal": "Calque d'enregistrement dynamique", + "EnableSimBridge": "Activer la connexion au serveur MCDU (désactivation automatique après 5 minutes en cas d'échec de la connexion)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Activer la connexion au serveur MCDU (désactivation automatique après 5 minutes en cas d'échec de la connexion)", "ExternalMcduServerPort": "Port du serveur MCDU externe", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Aucun", "Off": "DÉSACTIVÉ", "Save": "Enregistrer", + "SimBridgePort": "Port du serveur MCDU externe", "SyncMsfsFlightPlan": "Synchroniser le plan de vol MSFS", "ThrottleDetents": "Encoches des manettes", "Title": "Options de simulation", diff --git a/src/instruments/src/EFB/Localization/he.json b/src/instruments/src/EFB/Localization/he.json index b1b7dfbee8cc..0a9d88bde0f0 100644 --- a/src/instruments/src/EFB/Localization/he.json +++ b/src/instruments/src/EFB/Localization/he.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "שינוי מקור ATIS/ATC", "ConnectHoppieACARS": "חבר Hoppie ACARS", "NoInformationAvailableForThisFrequency": "אין מידע זמין עבור תדר רדיו זה", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "הדף יהיה זמין רק לאחר ש-VATSIM או IVAO נבחרו כמקור ATIS/ATC בדף ההגדרות. ", "SetActive": "הגדר כתדר פעיל", "SetStandby": "הגדר כתדר בהמתנה", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "תדר בהמתנה", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "בקרת תנועה אווירית" }, "Checklists": { @@ -85,7 +98,7 @@ "YouHaveNotYetImportedAnySimBriefData": "עדיין לא ייבאת נתוני SimBrief." }, "Overview": { - "ActualGW": "Actual GW", + "ActualGW": "משקל ברוטו נוכחי", "Engines": "מנועים", "MMO": "MMO", "MRW": "MRW", @@ -134,6 +147,27 @@ "TotalFuel": "סך הדלק", "Unavailable": "לא זמין" }, + "Payload": { + "BoardingTime": "זמן עלייה למטוס", + "Cargo": "משא", + "Current": "נוכחי", + "Passengers": "נוסעים", + "Planned": "מתוכנן", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "המטוס חייב להיות על הקרקע עם מנועים כוביים בכדי לשנות את משך העליה למטוס", + "FillPayloadFromSimbrief": "מלא מידע על המטען מ-Simbrief", + "MaxCargo": "משא מקסימלי", + "MaxPassengers": "מספר נוסעים מקסימלי", + "MaxZFW": "משקל ללא דלק מקסימלי", + "MaxZFWCG": "מרכז כובד של משקל ללא דלק מקסימלי", + "PerPaxBagWeight": "משקל תיק נוסעים", + "PerPaxWeight": "משקל נוסע", + "StartBoarding": "התחל עליה למטוס" + }, + "Title": "Payload", + "ZFW": "משקל ללא דלק", + "ZFWCG": "מרכז כובד משקל ללא דלק" + }, "Pushback": { "Title": "דחיפה" }, @@ -144,7 +178,7 @@ "DoorAft": "דלת אחורית", "DoorCargo": "דלת תא מטען", "DoorFwd": "דלת קדמית", - "ExternalPower": "יחידת מתח חיצוני", + "ExternalPower": "יחידת מתח קרקע", "FuelTruck": "משאית דלק", "JetBridge": "שרוול", "Title": "שירותים", @@ -162,6 +196,10 @@ "Altn": "חלופי", "ExitFullscreenMode": "צא ממסך מלא", "From": "מוצא", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "שניהם", "EstablishingConnection": "מייצר חיבור", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "יש לספק מתח למטוס בכדי לטעון תבניות", "AircraftNeedsToBePoweredToSavePresets": "יש לספק מתח למטוס בכדי לשמור תבניות", + "AutoLoadDawnDusk": "זריחה/שקיעה:", + "AutoLoadDay": "יום:", + "AutoLoadLightingHelpTitle": "טעינת תבנית אוטומטית", + "AutoLoadLightingPreset": "הפעל טעינת תבנית תאורה אוטומטית:", + "AutoLoadNight": "לילה:", + "AutoLoadNoneSelection": "ללא", "LoadPreset": "טען תבנית", "LoadingPreset": "טוען תבנית", "NewNameConfirmationDialogMsg": "אנא אשר את שם תבנית התאורה", @@ -386,13 +430,15 @@ "SliderSpeed": "מעבר למהירות הרצויה", "SystemEnabledOff": "לחץ כדי להפעיל את מערכת הדחיפה.", "SystemEnabledOn": "לחץ כדי לכבות את מערכת הדחיפה לחלוטין.", + "UseControllerInput": "הפעל/כבה קלט מבקר הגה כיוון וגובה", "ZoomIn": "הגדל תצוגה", "ZoomOut": "הקטן תצוגה" }, "TugAttached": "נע-דחף מחובר", "TugDirection": "כיוון הגרירה", "TugInTransit": "ממתין לנע-דחף", - "TugSpeed": "מהירות הגרירה" + "TugSpeed": "מהירות הגרירה", + "UseControllerInput": "השתמש בקלט בקר" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC - פורמט נקודת ציון של קו רוחב/אורך", "PaxSigns": "שלטי נוסעים", "RmpVhfSpacing": "פאנל רדיו - מרווח תדרי VHF", + "Satcom": "הצג Satcom", "ThrustReductionHeight": "גובה הפחתת דחף (רגל)", "Title": "אפשרויות מטוס / תוכניות סיכות", "WeightUnit": "יחידת משקל" @@ -451,12 +498,15 @@ "AutofillChecklists": "מלא רשימות בדיקה אוטומטית", "BoardingTime": "זמן עלייה למטוס", "DmcSelfTestTime": "DMC - זמן בדיקה עצמית", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "מצב תא טייס ביתי", "McduFocusTimeout": "MCDU - זמן לשחרור מקלדת", "McduKeyboardInput": "MCDU - קלט מקלדת", "PauseAtTod": "השהה לפני הנמכת גובה (T/D)", "PauseAtTodDistance": "מרחק מהשהייה לפני הנמכה", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "היגוי גלגל האף נפרד מהפדלים", + "SyncEfis": "סנכרון EFIS בין הקפטן לטייס המשנה", "Title": "מציאותיות" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "קונוס בטיחות", "DefaultBarometerUnit": "יחידת ברומטר ברירת מחדל", "DynamicRegistrationDecal": "דִינָמִי מדבקת רישום", + "EnableSimBridge": "אפשר חיבור Simbridge (נכבה אוטומטית לאחר חמש דקות אם לא היה חיבור מוצלח)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "אפשר חיבור שרת MCDU (נכבה אוטומטית לאחר חמש דקות אם לא היה חיבור מוצלח)", "ExternalMcduServerPort": "פורט MCDU חיצוני", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "ללא", "Off": "כבוי", "Save": "שמור", + "SimBridgePort": "פורט SimBridge חיצוני", "SyncMsfsFlightPlan": "סנכרן את תוכנית הטיסה מ-MSFS", "ThrottleDetents": "מעצורי מצערת", "Title": "אפשרויות סימולטור", @@ -545,8 +597,8 @@ "Sep": "ספטמבר", "Sun": "ראשון", "TT": { - "ConnectedToLocalApi": "מחובר ל-API מקומי", - "DisconnectedFromLocalApi": "מנותק מ-API מקומי", + "ConnectedToLocalApi": "מחובר ל-SimBridge", + "DisconnectedFromLocalApi": "מנותק מ-SimBridge", "TurnOffOrShutdownEfb": "כיבוי EFB" }, "Thu": "חמישי", diff --git a/src/instruments/src/EFB/Localization/hi.json b/src/instruments/src/EFB/Localization/hi.json index 302b532e4faf..d486ff7cacee 100644 --- a/src/instruments/src/EFB/Localization/hi.json +++ b/src/instruments/src/EFB/Localization/hi.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "एटिस/एटीसी स्रोत बदलें", "ConnectHoppieACARS": "कनेक्ट हॉपी एकारस", "NoInformationAvailableForThisFrequency": "इस आवृत्ति के लिए कोई जानकारी उपलब्ध नहीं है", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "इस मेनू का तभी उपयोग किया जा सकता है जब IVAO या फिर VATSIM को ऐतिस / ऐटीसी में चुना जाता ह। ", "SetActive": "सक्रिय सेट करें", "SetStandby": "स्टैंडबाय सेट करें", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "स्टैंडबाय", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "एयर ट्रैफिक कंट्रोल" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "कुल ईंधन", "Unavailable": "अनुपलब्ध" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "पुशबैक" }, @@ -162,6 +196,10 @@ "Altn": "एकांतर", "ExitFullscreenMode": "फ़ुलस्क्रीन मोड से बाहर निकलें", "From": "से", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "दोनों", "EstablishingConnection": "कनेक्शन स्थापित हो रहा है...", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "प्रीसेट लोड करने के लिए विमान को संचालित करने की आवश्यकता है", "AircraftNeedsToBePoweredToSavePresets": "प्रीसेट को बचाने के लिए विमान को संचालित करने की आवश्यकता है", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "प्रीसेट लोड करें", "LoadingPreset": "प्रीसेट लोड हो रहा है", "NewNameConfirmationDialogMsg": "कृपया प्रकाश प्रीसेट के लिए नए नाम की पुष्टि करें", @@ -386,13 +430,15 @@ "SliderSpeed": "वांछित गति के लिए मूव करें", "SystemEnabledOff": "पुशबैक सिस्टम को चालू करने के लिए क्लिक करें।", "SystemEnabledOn": "पुशबैक सिस्टम को पूरी तरह से बंद करने के लिए क्लिक करें।", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "ज़ूम इन करें", "ZoomOut": "ज़ूम आउट" }, "TugAttached": "टग जुड़ा हुआ है", "TugDirection": "टग की दिशा", "TugInTransit": "टग/पिन की प्रतीक्षा कर रहा है", - "TugSpeed": "टग की स्पीड" + "TugSpeed": "टग की स्पीड", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "एफएमजीसी लैट/लोन वेपॉइंट फॉर्मेट", "PaxSigns": "यात्री संकेत", "RmpVhfSpacing": "आरएमपी वीएचएफ स्पेसिंग", + "Satcom": "Satcom", "ThrustReductionHeight": "थ्रस्ट रिडक्शन हाइट (ft)", "Title": "विमान विकल्प/पिन प्रोग्राम", "WeightUnit": "वज़न इकाई" @@ -451,12 +498,15 @@ "AutofillChecklists": "ऑटोफ़िल चेकलिस्ट", "BoardingTime": "बोर्डिंग टाइम", "DmcSelfTestTime": "डीएमसी सेल्फ टेस्ट टाइम", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "होम कॉकपिट मोड", "McduFocusTimeout": "एमसीडीयू फोकस टाइमआउट (सेकंड)", "McduKeyboardInput": "MCDU कीबोर्ड इनपुट", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "रडर इनपुट्स से अलग टिलर", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "यथार्थवाद" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "सुरक्षा कोन", "DefaultBarometerUnit": "डिफ़ॉल्ट बैरोमीटर यूनिट", "DynamicRegistrationDecal": "डायनामिक रजिस्ट्रेशन डिकल", + "EnableSimBridge": "MCDU सर्वर कनेक्शन सक्षम करें (यदि कोई सफल कनेक्शन नहीं है, तो 5 मिनट के बाद स्वतः निष्क्रिय हो जाता है)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "MCDU सर्वर कनेक्शन सक्षम करें (यदि कोई सफल कनेक्शन नहीं है, तो 5 मिनट के बाद स्वतः निष्क्रिय हो जाता है)", "ExternalMcduServerPort": "बाहरी MCDU सर्वर पोर्ट", "Hpa": "hecta पास्कल", @@ -474,6 +525,7 @@ "None": "कोई नहीं", "Off": "बंद", "Save": "सहेजें", + "SimBridgePort": "बाहरी MCDU सर्वर पोर्ट", "SyncMsfsFlightPlan": "MSFS उड़ान योजना सिंक करें", "ThrottleDetents": "थ्रॉटल डिटेंट्स", "Title": "सिम ऑप्शन्स", diff --git a/src/instruments/src/EFB/Localization/hr.json b/src/instruments/src/EFB/Localization/hr.json index dc57c05cf4c3..9c6649153437 100644 --- a/src/instruments/src/EFB/Localization/hr.json +++ b/src/instruments/src/EFB/Localization/hr.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Promijeni ATIS/ATC izvor", "ConnectHoppieACARS": "Poveži Hoppie ACARS", "NoInformationAvailableForThisFrequency": "Nema dostupnih informacija za ovu frekvenciju", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Ova stranica je dostupna samo ako je IVAO ili VATSIM odabran kao izvor ATIS/ATC-a u postavkama.", "SetActive": "Postavi kao aktivnu", "SetStandby": "Postavi stanje pripravnosti", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Stanje pripravnosti", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Kontrola zračnog prometa" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Ukupno Gorivo", "Unavailable": "Nedostupno" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "Altn", "ExitFullscreenMode": "Isključi prikaz preko cijelog zaslona", "From": "Iz", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Oboje", "EstablishingConnection": "Uspostavljanje veze", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Aircraft Needs to be Powered to Load Presets", "AircraftNeedsToBePoweredToSavePresets": "Aircraft Needs to Be Powered to Save Presets", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Load Preset", "LoadingPreset": "Loading Preset", "NewNameConfirmationDialogMsg": "Please confirm the new name for the lighting preset", @@ -386,13 +430,15 @@ "SliderSpeed": "Move for desired speed", "SystemEnabledOff": "Click to turn the pushback system on.", "SystemEnabledOn": "Click to turn the pushback system completely off.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Zoom in", "ZoomOut": "Zoom out" }, "TugAttached": "Tug is attached", "TugDirection": "Tug Direction", "TugInTransit": "Waiting for Tug/Pin", - "TugSpeed": "Tug Speed" + "TugSpeed": "Tug Speed", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC Lat/Lon Waypoint Format", "PaxSigns": "PAX znakovi", "RmpVhfSpacing": "RMP VHF razmak", + "Satcom": "Satcom", "ThrustReductionHeight": "Thrust Reduction Height (ft)", "Title": "Opcije zrakoplova / Pin Programi", "WeightUnit": "Jedinica težine" @@ -451,12 +498,15 @@ "AutofillChecklists": "Autofill Checklists", "BoardingTime": "Boarding Time", "DmcSelfTestTime": "DMC Self Test Time", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Home Cockpit Mode", "McduFocusTimeout": "MCDU Focus Timeout (seconds)", "McduKeyboardInput": "MCDU Keyboard Input", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Separate Tiller from Rudder Inputs", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Realism" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Safety Cones", "DefaultBarometerUnit": "Default Barometer Unit", "DynamicRegistrationDecal": "Dynamic Registration Decal", + "EnableSimBridge": "Enable MCDU Server Connection (Auto deactivates after 5 minutes if no successful connection)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Enable MCDU Server Connection (Auto deactivates after 5 minutes if no successful connection)", "ExternalMcduServerPort": "External MCDU Server Port", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Niti jedan", "Off": "Isključena", "Save": "Save", + "SimBridgePort": "External MCDU Server Port", "SyncMsfsFlightPlan": "Sync MSFS Flight Plan", "ThrottleDetents": "Throttle Detents", "Title": "Sim Options", diff --git a/src/instruments/src/EFB/Localization/hu.json b/src/instruments/src/EFB/Localization/hu.json index a2c9b9f5217d..d5b996cce592 100644 --- a/src/instruments/src/EFB/Localization/hu.json +++ b/src/instruments/src/EFB/Localization/hu.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "ATIS/ATC forrás módosítása", "ConnectHoppieACARS": "Csatlakozás Hoppie ACARS-hoz", "NoInformationAvailableForThisFrequency": "Nincs információ erről a frekvenciáról", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Ez az oldal csak akkor érhető el, ha az IVAO vagy a VATSIM van kiválasztva ATIS/ATC forrásként a beállítások oldalon.", "SetActive": "Beállítás aktívként", "SetStandby": "Beállítás készenléti állapotba", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Készenléti ", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Légiforgalmi irányítás" }, "Checklists": { @@ -49,7 +62,7 @@ "Loading": "Betöltés", "MetarParsingError": "A kapott METAR nem értelmezhető", "NoIcaoProvided": "Nincs ICAO megadva", - "NotAvailableShort": "N/A", + "NotAvailableShort": "Nem elérhető (N/A)", "Raw": "Kód", "TT": { "SwitchToIconView": "Váltás ikon nézetre", @@ -85,7 +98,7 @@ "YouHaveNotYetImportedAnySimBriefData": "Még nem importált SimBrief adatokat" }, "Overview": { - "ActualGW": "Actual GW", + "ActualGW": "Tényleges súly (GW)", "Engines": "Hajtóművek", "MMO": "MMO", "MRW": "MRW", @@ -127,28 +140,49 @@ "RightInnerTank": "Jobb belső tartály", "RightOuterTank": "Jobb külső tartály", "TT": { - "AircraftMustBeColdAndDarkToChangeRefuelTimes": "A repülőgépnek áramtalanított állapotban kell lennie a tankolási időtartam megváltoztatásához", + "AircraftMustBeColdAndDarkToChangeRefuelTimes": "A tankolás időtartamának megváltoztatása csak a földön, álló hajtóművek esetén lehetséges", "FillBlockFuelFromSimBrief": "(block) üzemanyag kitöltése SimBrief-ből" }, "Title": "Üzemanyag", "TotalFuel": "Összes üzemanyag", "Unavailable": "Nem elérhető" }, + "Payload": { + "BoardingTime": "Beszállási idő", + "Cargo": "Rakomány", + "Current": "Jelenlegi", + "Passengers": "Utasok", + "Planned": "Tervezett", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "A tankolás időtartamának megváltoztatása csak a földön, álló hajtóművek esetén lehetséges", + "FillPayloadFromSimbrief": "Töltse ki a rakomány információit a Simbrief-ből", + "MaxCargo": "Max rakomány", + "MaxPassengers": "Maximális utaslétszám", + "MaxZFW": "Maximális ZFW", + "MaxZFWCG": "Maximális ZFWCG", + "PerPaxBagWeight": "Egy utasra jutó poggyász súlya", + "PerPaxWeight": "Egy utas súlya", + "StartBoarding": "Beszállás megkezdése" + }, + "Title": "Rakomány", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Hátratolás" }, "Services": { "BaggageTruck": "Poggyászkocsi", "CateringTruck": "Utasellátó", - "Cones": "Safety Cones", + "Cones": "Biztonsági bója", "DoorAft": "Hátsó ajtó", - "DoorCargo": "Cargo Door", + "DoorCargo": "Raktér ajtó", "DoorFwd": "Első ajtó", - "ExternalPower": "Külső áramforrás", + "ExternalPower": "Földi áramforrás", "FuelTruck": "Tartálykocsi", "JetBridge": "Utashíd", "Title": "Szolgáltatások", - "WheelChocks": "Wheel Chocks" + "WheelChocks": "Kerékékek" }, "Title": "Földi kiszolgálás" }, @@ -162,6 +196,10 @@ "Altn": "Kitérő reptér", "ExitFullscreenMode": "Kilépés teljes képernyős üzemmódból", "From": "Induló", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Mindkettő", "EstablishingConnection": "Kapcsolat létrehozása", @@ -177,7 +215,7 @@ "AuthenticateWithNavigraph": "Hitelesítés a Navigraph segítségével", "InsufficientEnv": "Nem megfelelő .env fájl", "IntoYourBrowserAndEnterTheCodeBelow": "a böngészőbe, és írja be a kódot lentebb\n", - "LoadingMsg": "Loading", + "LoadingMsg": "Betöltés…", "NoAirportSelected": "Nincs repülőtér kiválasztva", "ResetNavigraphAuthentication": "Navigraph hitelesítés visszaállítása", "ScanTheQrCodeOrOpen": "Szkennelje be a QR-kódot vagy megnyitás", @@ -245,12 +283,12 @@ "RunwayAltitudeUnit": "ft AMSL", "RunwayCondition": "Futópálya állapota", "RunwayConditions": { - "Dry": "Dry (6)", - "Good": "Good (5)", - "GoodMedium": "Good-Medium (4)", - "Medium": "Medium (3)", - "MediumPoor": "Medium-Poor (2)", - "Poor": "Poor (1)" + "Dry": "Száraz (6)", + "Good": "Jó (5)", + "GoodMedium": "Közepesen jó", + "Medium": "Közepes", + "MediumPoor": "Gyenge közepes", + "Poor": "Rossz" }, "RunwayHeading": "Futópálya iránya", "RunwayHeadingUnit": "°", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Helyezze áram alá a repülőgépet a beállítások betöltéséhez", "AircraftNeedsToBePoweredToSavePresets": "Helyezze áram alá a repülőgépet a beállítások mentéséhez", + "AutoLoadDawnDusk": "Hajnal/szürkület:", + "AutoLoadDay": "Nap:", + "AutoLoadLightingHelpTitle": "Automatikus beállítás betöltés", + "AutoLoadLightingPreset": "Engedélyezze a világítási beállítások automatikus betöltését:", + "AutoLoadNight": "Éjszaka:", + "AutoLoadNoneSelection": "Nincs", "LoadPreset": "Beállítás betöltése", "LoadingPreset": "Beállítás betöltée folyamatban", "NewNameConfirmationDialogMsg": "Új név megerősítése", @@ -353,46 +397,48 @@ "Title": "Beállítások" }, "Pushback": { - "AvailableOnlyOnGround": "Pushback controls are only available on the ground", + "AvailableOnlyOnGround": "A hátratolás csak a földön lehetséges", "Backward": "Hátra", "CallTug": "Vontató hívása", - "DisableSystemMessageBody": "Are you sure you want to disable the Pushback System? This will also release the tug.", - "DisableSystemMessageTitle": "Disable Pushback System", - "EnableSystemMessageBody": "The new flyPadOS 3 Pushback System can cause conflicts with other pushback add-ons. We recommend not to enable the system if you plan to use another pushback add-on. ", - "EnableSystemMessageTitle": "Enable Pushback System", + "DisableSystemMessageBody": "Biztos vagy abban, hogy kikapcsolod a hátratoló rendszert? Ez lekapcsolja a vontatót is.", + "DisableSystemMessageTitle": "Hátratoló rendszer kikapcsolása\n", + "EnableSystemMessageBody": "Az új flyPadOS 3 hátratoló rendszer konfliktusokat okozhat más hátratoló kiegészítőkkel. Javasoljuk, hogy ne engedélyezze a rendszert, ha egy másik hátratoló bővítményt kíván használni. ", + "EnableSystemMessageTitle": "Hátratoló redszer bekapcsolása", "Forward": "Előre", - "Halt": "Állj", - "LeavePageMessage": "Pausing Pushback", - "Left": "Left", - "Moving": "Moving", + "Halt": "Megállítva", + "LeavePageMessage": "Hátratolás szünetelése", + "Left": "Balra", + "Moving": "Mozgásban", "ParkingBrake": { "Off": "Ki", "On": "Be", "Title": "Rögzítőfék" }, - "Right": "Right", - "SystemEnabledOff": "Pushback System Off", - "SystemEnabledOn": "Pushback System On", + "Right": "Jobbra", + "SystemEnabledOff": "Hátratoló rendszer ki", + "SystemEnabledOn": "Hátratoló rendszer be", "TT": { - "CallReleaseTug": "Call or release tug", - "CenterPlaneMode": "Center map on plane On/Off", - "DecreaseSpeed": "Backwards or slower forwards", - "IncreaseSpeed": "Forwards or slower backwards", - "Left": "Go left", - "PausePushback": "Pause or unpause pushback", - "Right": "Go right", - "SetReleaseParkingBrake": "Set or release parking brake", - "SliderDirection": "Move for desired direction", - "SliderSpeed": "Move for desired speed", - "SystemEnabledOff": "Click to turn the pushback system on.", - "SystemEnabledOn": "Click to turn the pushback system completely off.", - "ZoomIn": "Zoom in", - "ZoomOut": "Zoom out" + "CallReleaseTug": "Hátratoló hívása vagy leoldása", + "CenterPlaneMode": "Repülőgép a térképen középen Be/Ki", + "DecreaseSpeed": "Visszafelé vagy lassabban előre", + "IncreaseSpeed": "Előrefelé vagy lassan hátrafelé", + "Left": "Menj balra", + "PausePushback": "Hátratolás szüneteltetése vagy szüneteltetés feloldása", + "Right": "Menj jobbra", + "SetReleaseParkingBrake": "Rögzítőfék behúzása vagy kiengedése", + "SliderDirection": "Mozgás a kívánt irányba", + "SliderSpeed": "Mozgás a kívánt sebességgel", + "SystemEnabledOff": "Kattintson a hátratoló rendszer bekapcsolásához.", + "SystemEnabledOn": "Kattintson a hátratoló rendszer kikapcsolásához.", + "UseControllerInput": "Kapcsolja ki/be a vezérlést a oldalkormányról és a magassági kormányról", + "ZoomIn": "Nagyítás", + "ZoomOut": "Kicsinyítés" }, - "TugAttached": "Tug is attached", + "TugAttached": "A vontató csatlakoztatva van\n", "TugDirection": "Vontatás iránya", - "TugInTransit": "Waiting for Tug/Pin", - "TugSpeed": "Vontatás sebessége" + "TugInTransit": "Várakozás a vontatóra", + "TugSpeed": "Vontatás sebessége", + "UseControllerInput": "Használja a vezérlő bemenetét" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC Lat/Lon Útvonal formátum", "PaxSigns": "Övek becsatolását jelző tabló", "RmpVhfSpacing": "RMP VHF elosztás", + "Satcom": "Satcom megjelenítése", "ThrustReductionHeight": "Tolóerő csökkentési magasság (ft)", "Title": "Repülő beállításai / Pin programok", "WeightUnit": "Súly mértékegység" @@ -451,21 +498,25 @@ "AutofillChecklists": "Ellenörző listák automatikus kitöltése", "BoardingTime": "Beszállási idő", "DmcSelfTestTime": "DMC Öntesztelési idő", + "FirstOfficerAvatar": "Mutassa az elsőtisztet", "HomeCockpitMode": "Épített kabin mód", "McduFocusTimeout": "MCDU Fókusz időtúllépés (másodperc)", "McduKeyboardInput": "MCDU Billentyűzet bevitel", - "PauseAtTod": "T/D Pause", - "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PauseAtTod": "T/D Szünet", + "PauseAtTodDistance": "T/D szüneteltetési távolság (nm)", + "PilotAvatar": "Mutassa a pilótát", "SeparateTillerFromRudderInputs": "Orrkerék kormányzás leválasztása az oldalkormányzásról", + "SyncEfis": "Szinkronizálja az EFIS vezérlőket kapitányról", "Title": "Realizmus" }, "SimOptions": { "Active": "Aktív", "Auto": "Automatikus", "Calibrate": "Kalibrálás", - "ConesEnabled": "Safety Cones", + "ConesEnabled": "Biztonsági bóják", "DefaultBarometerUnit": "Alap barométer mértékegység", "DynamicRegistrationDecal": "Dinamikus regisztrációs matrica", + "EnableSimBridge": "SimBridge Csatlakozás engedélyezése (Automatikus kikapcsolás 5 perc után, ha nincs sikeres kapcsolat)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "MCDU Server Csatlakozás engedélyezése (Automatikus kikapcsolás 5 perc után, ha nincs sikeres kapcsolat)", "ExternalMcduServerPort": "Külső MCDU szerver port", "Hpa": "hPa", @@ -474,11 +525,12 @@ "None": "Nincs", "Off": "Ki", "Save": "Mentés", + "SimBridgePort": "Külső SimBridge port", "SyncMsfsFlightPlan": "MSFS Repülési terv szinkronizálása", "ThrottleDetents": "Gázkar állások", "Title": "Szimulátor Opciók", "UseCalculatedIlsSignals": "Használjon Kiszámított ILS Jeleket", - "WheelChocksEnabled": "Wheel Chocks", + "WheelChocksEnabled": "Kerékékek", "inHg": "inHg" }, "ThrottleConfig": { @@ -507,7 +559,7 @@ "flyPad": { "AutoBrightness": "Auto. fényerő", "AutomaticallyShowOnscreenKeyboard": "Képernyőn megjelenő billentyűzet automatikus megjelenítése", - "BatteryLifeEnabled": "Battery Life Simulated", + "BatteryLifeEnabled": "Az akkumulátor élettartamának szimulálása", "Blue": "Kék", "Brightness": "Fényerő", "Dark": "Sötét", @@ -545,8 +597,8 @@ "Sep": "Szept.", "Sun": "Va", "TT": { - "ConnectedToLocalApi": "Csatlakozva a helyi API-hoz", - "DisconnectedFromLocalApi": "Leválasztva a helyi API-ról", + "ConnectedToLocalApi": "Csatlakozva a SimBridge-hez", + "DisconnectedFromLocalApi": "Lecsatlakozva a SimBridge-ről", "TurnOffOrShutdownEfb": "EFB leállítása vagy kikapcsolása" }, "Thu": "Csüt", diff --git a/src/instruments/src/EFB/Localization/id.json b/src/instruments/src/EFB/Localization/id.json index aa01dc202dbd..480409c469db 100644 --- a/src/instruments/src/EFB/Localization/id.json +++ b/src/instruments/src/EFB/Localization/id.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Ubah sumber ATIS/ATC", "ConnectHoppieACARS": "Hubungkan Hoppie ACARS", "NoInformationAvailableForThisFrequency": "Tidak ada informasi yang tersedia untuk frekuensi ini", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Halaman ini hanya tersedia apabila IVAO atau VATSIM telah terpilih sebagai ATIS/ATC di bagian pengaturan.", "SetActive": "Aktifkan", "SetStandby": "Setel Standby", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Standby", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Air Traffic Control" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Total Bahan Bakar", "Unavailable": "Tidak tersedia" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "Alternatif", "ExitFullscreenMode": "Keluar dari Mode Layar Penuh", "From": "Asal", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Keduanya", "EstablishingConnection": "Menghubungkan Koneksi", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Pesawat Perlu Diberi Daya untuk Memuat Preset", "AircraftNeedsToBePoweredToSavePresets": "Pesawat Perlu Diberi Daya untuk Menyimpan Preset", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Muat Preset", "LoadingPreset": "Memuat Preset", "NewNameConfirmationDialogMsg": "Mohon konfirmasi nama baru untuk preset cahaya", @@ -386,13 +430,15 @@ "SliderSpeed": "Pindah untuk kecepatan yang diinginkan", "SystemEnabledOff": "Klik untuk menyalakan sistem pushback.", "SystemEnabledOn": "Klik untuk mematikan sistem pushback sepenuhnya.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Perbesar", "ZoomOut": "Perkecil" }, "TugAttached": "Tug terpasang", "TugDirection": "Arah Tug", "TugInTransit": "Menunggu Tug/Pin", - "TugSpeed": "Kecepatan Tug" + "TugSpeed": "Kecepatan Tug", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "Format FMGC Lat/Lon Waypoint", "PaxSigns": "PAX Signs", "RmpVhfSpacing": "Jarak VHF RMP", + "Satcom": "Satcom", "ThrustReductionHeight": "Ketinggian Pengurangan Dorongan (ft)", "Title": "Opsi Pesawat / Tandai Program", "WeightUnit": "Satuan Berat" @@ -451,12 +498,15 @@ "AutofillChecklists": "Ceklis Otomatis Terisi", "BoardingTime": "Waktu Boarding", "DmcSelfTestTime": "Waktu Pencobaan Mandiri DMC", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Mode Kokpit Rumah", "McduFocusTimeout": "Batas Waktu Fokus MCDU (detik)", "McduKeyboardInput": "Input Keyboard MCDU", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Pisahkan Tiller dari Input Rudder", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Realisme" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Safety Cones", "DefaultBarometerUnit": "Standar Satuan Barometer", "DynamicRegistrationDecal": "Nomor Registrasi Pesawat", + "EnableSimBridge": "Aktifkan Koneksi Server MCDU (Otomatis dinonaktifkan setelah 5 menit jika tidak ada koneksi yang berhasil)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Aktifkan Koneksi Server MCDU (Otomatis dinonaktifkan setelah 5 menit jika tidak ada koneksi yang berhasil)", "ExternalMcduServerPort": "Port Server MCDU Eksternal", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Tidak ada", "Off": "Mati", "Save": "Simpan", + "SimBridgePort": "Port Server MCDU Eksternal", "SyncMsfsFlightPlan": "Sinkronkan dengan Flight Plan MSFS", "ThrottleDetents": "Penahan Throttle", "Title": "Opsi Sim", diff --git a/src/instruments/src/EFB/Localization/it.json b/src/instruments/src/EFB/Localization/it.json index cf083ac879bb..4ac2763ad2fb 100644 --- a/src/instruments/src/EFB/Localization/it.json +++ b/src/instruments/src/EFB/Localization/it.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Cambia fonte ATIS/ATC", "ConnectHoppieACARS": "Connetti ACARS Hoppie", "NoInformationAvailableForThisFrequency": "Nessuna informazione disponibile per questa frequenza", + "SearchPlaceholder": "Cerca", "SelectCorrectATISATCSource": "Questa pagina è disponibile solo quando IVAO o VATSIM sono selezionati come fonte ATIS/ATC nella pagina delle impostazioni", "SetActive": "Imposta Attivo", "SetStandby": "Imposta Standby", + "ShowAll": "Tutti", + "ShowApproach": "Avvicinamento", + "ShowAtis": "ATIS", + "ShowDelivery": "Autorizzazione", + "ShowDeparture": "Partenza", + "ShowGround": "Terra", + "ShowRadar": "Radar", + "ShowTower": "Torre", "Standby": "Standby", + "TT": { + "AtcCallSignSearch": "Cerca Call-sign ATC", + "AtcTypeFilter": "Filtra per" + }, "Title": "Controllo Traffico Aereo" }, "Checklists": { @@ -20,7 +33,7 @@ "ResetAll": "Ripristina Tutto", "ResetChecklist": "Ripristina Checklist", "TheLastChecklistIsComplete": "L'ultima checklist è completa", - "ThereAreRemainingAutofillChecklistItemsThatHaveNotYetBeenCompleted": "Ci sono campi rimanenti per l'autocompilazione della checklist, che non sono ancora stati completati", + "ThereAreRemainingAutofillChecklistItemsThatHaveNotYetBeenCompleted": "Ci sono campi rimanenti per l'autocompletamento della checklist, che non sono ancora stati completati", "Title": "Checklist" }, "Dashboard": { @@ -118,14 +131,14 @@ "Defueling": "Scaricamento", "EstimatedDuration": "Durata stimata (minuti)", "EstimatedTime": "Estimated Time", - "LeftInnerTank": "Serbatoio Sinistro Interno", - "LeftOuterTank": "Serbatoio Sinistro Esterno", + "LeftInnerTank": "Serbatoio Interno Sinistro", + "LeftOuterTank": "Serbatoio Esterno Sinistro", "ReadyToStart": "Pronto a Iniziare", "Refuel": "Rifornimento", "RefuelTime": "Durata Rifornimento", "Refueling": "Caricamento", - "RightInnerTank": "Serbatoio Destro Interno", - "RightOuterTank": "Serbatoio Destro Esterno", + "RightInnerTank": "Serbatoio Interno Destro", + "RightOuterTank": "Serbatoio Esterno Destro", "TT": { "AircraftMustBeColdAndDarkToChangeRefuelTimes": "Cambiare la durata del rifornimento soltanto a terra con motori spenti", "FillBlockFuelFromSimBrief": "Inserisci Block Fuel da SimBrief" @@ -134,6 +147,27 @@ "TotalFuel": "Carburante Totale", "Unavailable": "Non Disponibile" }, + "Payload": { + "BoardingTime": "Durata Imbarco", + "Cargo": "Carico", + "Current": "Corrente", + "Passengers": "Passeggeri", + "Planned": "Pianificato", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "L'Aeromobile Deve Essere A Terra e Avere i Motori Spenti Per Modificare la Durata dell'Imbarco", + "FillPayloadFromSimbrief": "Inserisci Payload da SimBrief", + "MaxCargo": "Carico Massimo", + "MaxPassengers": "Passeggeri Massimi", + "MaxZFW": "ZFW Massimo", + "MaxZFWCG": "ZFWCG Massimo", + "PerPaxBagWeight": "Peso Bagaglio per Passeggero", + "PerPaxWeight": "Peso per Passeggero", + "StartBoarding": "Inizia Imbarco" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -142,13 +176,13 @@ "CateringTruck": "Veicolo Catering", "Cones": "Coni di Sicurezza", "DoorAft": "Porta Post.", - "DoorCargo": "Porta Cargo", + "DoorCargo": "Porta Carico", "DoorFwd": "Porta Ant.", - "ExternalPower": "Unità di Alimentazione Esterna", + "ExternalPower": "Ground Power Unit", "FuelTruck": "Veicolo Carburante", - "JetBridge": "Ponte Imbarco", + "JetBridge": "Ponte d'Imbarco", "Title": "Servizi", - "WheelChocks": "Tacchi per Ruote" + "WheelChocks": "Tacchi Fermaruote" }, "Title": "Terra" }, @@ -160,8 +194,12 @@ "NavigationAndCharts": { "All": "Tutti", "Altn": "Altn", - "ExitFullscreenMode": "Esci dalla Modalità a Schermo Intero", + "ExitFullscreenMode": "Esci dalla Modalità Schermo Intero", "From": "Da", + "LoadingImage": "Caricamento Immagine", + "LoadingImageFailed": "Caricamento Immagine fallito", + "LoadingPdf": "Caricamento PDF", + "LoadingPdfFailed": "Caricamento PDF fallito", "LocalFiles": { "Both": "Entrambi", "EstablishingConnection": "Connessione in Corso", @@ -179,7 +217,7 @@ "IntoYourBrowserAndEnterTheCodeBelow": "nel tuo browser e inserisci il codice riportato di seguito", "LoadingMsg": "Caricamento", "NoAirportSelected": "Nessun Aeroporto Selezionato", - "ResetNavigraphAuthentication": "Rimuovi Autenticazione con Navigraph", + "ResetNavigraphAuthentication": "Rimuovi Autenticazione Navigraph", "ScanTheQrCodeOrOpen": "Scansiona il Codice QR o Apri", "Title": "Navigraph" }, @@ -188,7 +226,7 @@ "Edit": "Modifica", "NoItemsFound": "Nessun Elemento Trovato", "SearchPlaceholder": "Cerca", - "ShowingChartsFromAllProviders": "Visualizzazione delle Carte da tutti i Fornitori", + "ShowingChartsFromAllProviders": "Visualizzazione Carte da Tutti i Fornitori", "SortMethods": { "AlphabeticalAZ": "Alfabetico - A -> Z", "AlphabeticalZA": "Alfabetico - Z -> A", @@ -260,7 +298,7 @@ "RunwaySlope": "Pendenza Pista", "TT": { "YouNeedToEnterAnIcaoCodeInOrderToMakeAMetarRequest": "Devi inserire un codice ICAO in modo da effettuare una richiesta METAR", - "YouNeedToLoadSimBriefDataInOrderToAutofillData": "Devi caricare dati SimBrief per l'inserimento automatico dei dati" + "YouNeedToLoadSimBriefDataInOrderToAutofillData": "Devi caricare i dati SimBrief per l'inserimento automatico dei dati" }, "Temperature": "Temperatura", "Title": "Atterraggio", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "L'Aeromobile deve essere Alimentato per Caricare i Preset", "AircraftNeedsToBePoweredToSavePresets": "L'Aeromobile deve essere Alimentato per Salvare i Preset", + "AutoLoadDawnDusk": "Alba/Crepuscolo:", + "AutoLoadDay": "Giorno:", + "AutoLoadLightingHelpTitle": "Caricamento Preset Automatico", + "AutoLoadLightingPreset": "Abilita il caricamento automatico dei preset di illuminazione:", + "AutoLoadNight": "Notte:", + "AutoLoadNoneSelection": "No", "LoadPreset": "Carica Preset", "LoadingPreset": "Caricamento Preset", "NewNameConfirmationDialogMsg": "Prego conferma nuovo nome per il preset di illuminazione", @@ -376,8 +420,8 @@ "TT": { "CallReleaseTug": "Chiama o allontana tug", "CenterPlaneMode": "Centra mappa sull'aereo On/Off", - "DecreaseSpeed": "Indietro o avanti lentamente", - "IncreaseSpeed": "Avanti o indietro lentamente", + "DecreaseSpeed": "Indietro o lentamente avanti", + "IncreaseSpeed": "Avanti o lentamente indietro", "Left": "Vai a sinistra", "PausePushback": "Interrompi o riavvia pushback", "Right": "Vai a destra", @@ -386,13 +430,15 @@ "SliderSpeed": "Sposta per velocità desiderata", "SystemEnabledOff": "Clicca per attivare il sistema pushback.", "SystemEnabledOn": "Clicca per disattivare completamente il sistema pushback.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Ingrandisci", "ZoomOut": "Rimpicciolisci" }, "TugAttached": "Tug è collegato", "TugDirection": "Direzione Tug", "TugInTransit": "In Attesa del Tug", - "TugSpeed": "Velocità Tug" + "TugSpeed": "Velocità Tug", + "UseControllerInput": "Usa Input Controller" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "Formato Waypoint Lat/Lon FMGC", "PaxSigns": "PAX Sign", "RmpVhfSpacing": "Spaziatura VHF RMP", + "Satcom": "Mostra Satcom", "ThrustReductionHeight": "Altezza Riduzione Spinta (ft)", "Title": "Opzioni Aeromobile / Definisci Comportamenti", "WeightUnit": "Unità Peso" @@ -419,7 +466,7 @@ "HoppieEnabled": "Hoppie Abilitato", "HoppieUserId": "ID Utente Hoppie", "MetarSource": "Fonte METAR", - "OptionalA32nxErrorReporting": "Segnalazione Errori A32NX Opzionale", + "OptionalA32nxErrorReporting": "Segnalazione Errori A32NX Opzionali", "PleaseCheckThatYouHaveCorrectlyEnteredYourSimbBriefUsernameOrPilotId": "Verifica di aver inserito correttamente il tuo nome utente o ID pilota SimBrief", "SimBriefUsernamePilotId": "Nome Utente/ID Pilota SimBrief", "TafSource": "Fonte TAF", @@ -448,15 +495,18 @@ "Real": "Reale", "Realism": { "AdirsAlignTime": "Durata Allineamento ADIRS", - "AutofillChecklists": "Autocompila Checklist", + "AutofillChecklists": "Autocompleta Checklist", "BoardingTime": "Durata Imbarco", "DmcSelfTestTime": "Durata Autotest DMC", + "FirstOfficerAvatar": "Mostra Avatar Primo Ufficiale", "HomeCockpitMode": "Modalità Home Cockpit", "McduFocusTimeout": "Rilascio Selezione su MCDU (secondi)", "McduKeyboardInput": "Input Tastiera su MCDU", "PauseAtTod": "Interruzione T/D", "PauseAtTodDistance": "Distanza Interruzione T/D (nm)", + "PilotAvatar": "Mostra Avatar Pilota", "SeparateTillerFromRudderInputs": "Separa gli Input del Tiller e dei Pedali", + "SyncEfis": "Sincronizza controlli EFIS tra Comandante e Primo Ufficiale", "Title": "Realismo" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Coni di Sicurezza", "DefaultBarometerUnit": "Unità Barometro Predefinita", "DynamicRegistrationDecal": "Decalco Registrazione Dinamico", + "EnableSimBridge": "Abilita Connessione SimBridge (Disattivazione automatica dopo 5 minuti in caso di connessione non riuscita)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Abilita Connessione Server MCDU (Disattivazione automatica dopo 5 minuti in caso di connessione non riuscita)", "ExternalMcduServerPort": "Porta Esterna Server MCDU", "Hpa": "hPa", @@ -474,11 +525,12 @@ "None": "No", "Off": "Off", "Save": "Sì", + "SimBridgePort": "Porta Esterna Server SimBridge", "SyncMsfsFlightPlan": "Sincronizza Piano di Volo MSFS", "ThrottleDetents": "Posizioni Manetta", "Title": "Opzioni Sim", "UseCalculatedIlsSignals": "Usa Segnali ILS Calcolati", - "WheelChocksEnabled": "Tacchi per Ruote", + "WheelChocksEnabled": "Tacchi Fermaruote", "inHg": "inHg" }, "ThrottleConfig": { @@ -488,7 +540,7 @@ "Back": "Indietro", "ConfigureEnd": "Configura Fine", "ConfigureStart": "Configura Inizio", - "CurrentValue": "Valore Attuale", + "CurrentValue": "Valore Corrente", "Deadband": "Zona Morta", "ErrorOverlapMsg": "confligge con", "IndependentAxis": "Assi Separate", @@ -545,8 +597,8 @@ "Sep": "Set", "Sun": "Dom", "TT": { - "ConnectedToLocalApi": "Connesso all'API Locale", - "DisconnectedFromLocalApi": "Sconnesso dall'API Locale", + "ConnectedToLocalApi": "Connesso a SimBridge", + "DisconnectedFromLocalApi": "Disconnesso da SimBridge", "TurnOffOrShutdownEfb": "Spegni o Arresta EFB" }, "Thu": "Gio", diff --git a/src/instruments/src/EFB/Localization/ja.json b/src/instruments/src/EFB/Localization/ja.json index 311de62f4f7b..c0c5443ac138 100644 --- a/src/instruments/src/EFB/Localization/ja.json +++ b/src/instruments/src/EFB/Localization/ja.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "ATIS/ATCの提供元を変更", "ConnectHoppieACARS": "Hoppie ACARSに接続", "NoInformationAvailableForThisFrequency": "この周波数の情報はありません", + "SearchPlaceholder": "検索…", "SelectCorrectATISATCSource": "このページは設定ページでATIS/ATCの提供元としてIVAO、またはVATSIMが選択されている場合にのみ使用できます。", "SetActive": "アクティブに設定", "SetStandby": "スタンバイに設定", + "ShowAll": "全て", + "ShowApproach": "アプローチ", + "ShowAtis": "ATIS", + "ShowDelivery": "デリバリー", + "ShowDeparture": "デパーチャー", + "ShowGround": "グランド", + "ShowRadar": "レーダー", + "ShowTower": "タワー", "Standby": "スタンバイ", + "TT": { + "AtcCallSignSearch": "ATCコールサイン検索", + "AtcTypeFilter": "フィルター対象" + }, "Title": "航空交通管制" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "合計燃料", "Unavailable": "利用不能" }, + "Payload": { + "BoardingTime": "搭乗時間", + "Cargo": "貨物", + "Current": "現在値", + "Passengers": "乗客数", + "Planned": "計画値", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "エンジン停止かつ地上にいる場合のみ、搭乗時間を変更できます", + "FillPayloadFromSimbrief": "Simbriefから積載物の情報を読み込む", + "MaxCargo": "最大貨物重量", + "MaxPassengers": "最大乗客数", + "MaxZFW": "最大ZFW", + "MaxZFWCG": "最大ZFWCG", + "PerPaxBagWeight": "乗客1人あたりの手荷物重量", + "PerPaxWeight": "乗客1人あたりの重量", + "StartBoarding": "搭乗開始" + }, + "Title": "積載物", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "プッシュバック" }, @@ -162,6 +196,10 @@ "Altn": "代替", "ExitFullscreenMode": "フルスクリーンモードを終了する", "From": "出発", + "LoadingImage": "画像を読み込み中…", + "LoadingImageFailed": "画像の読み込み失敗", + "LoadingPdf": "PDFを読み込み中…", + "LoadingPdfFailed": "PDFの読み込み失敗", "LocalFiles": { "Both": "両方", "EstablishingConnection": "接続中...", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "プリセットを読み込むには、航空機に電力を供給する必要があります", "AircraftNeedsToBePoweredToSavePresets": "プリセットを保存するには、航空機に電力を供給する必要があります", + "AutoLoadDawnDusk": "朝/夕方:", + "AutoLoadDay": "昼:", + "AutoLoadLightingHelpTitle": "プリセットの自動読み込み中", + "AutoLoadLightingPreset": "機内照明プリセットの自動読み込みを有効にする:", + "AutoLoadNight": "夜:", + "AutoLoadNoneSelection": "なし", "LoadPreset": "プリセットの読み込み", "LoadingPreset": "プリセットの読み込み中", "NewNameConfirmationDialogMsg": "機内照明プリセットの新しい名前を確認してください", @@ -386,13 +430,15 @@ "SliderSpeed": "任意の牽引速度", "SystemEnabledOff": "クリックすると、プッシュバックシステムがオンになります。", "SystemEnabledOn": "クリックすると、プッシュバックシステムが完全にオフになります。", + "UseControllerInput": "ラダーとエレベーターで旋回操作の入力 有効/無効", "ZoomIn": "拡大", "ZoomOut": "縮小" }, "TugAttached": "牽引車に接続中", "TugDirection": "牽引方向", "TugInTransit": "牽引車/ピンの取り外しを準備中", - "TugSpeed": "牽引速度" + "TugSpeed": "牽引速度", + "UseControllerInput": "操作の入力を使用" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC緯度/経度ウェイポイント形式", "PaxSigns": "乗客サイン", "RmpVhfSpacing": "RMP VHF 周波数間隔", + "Satcom": "Satcom", "ThrustReductionHeight": "推力低減高度(ft)", "Title": "機体オプション/Pin Programs", "WeightUnit": "重量単位" @@ -451,12 +498,15 @@ "AutofillChecklists": "チェックリストの自動入力", "BoardingTime": "乗客の搭乗時間", "DmcSelfTestTime": "DMCの自己診断時間", + "FirstOfficerAvatar": "副操縦士のアバターを表示", "HomeCockpitMode": "ホームコックピットモード", "McduFocusTimeout": "MCDUへのフォーカスがタイムアウトする時間(秒)", "McduKeyboardInput": "MCDUへのキーボード入力", "PauseAtTod": "降下開始地点で一時停止", "PauseAtTodDistance": "一時停止を有効にする降下開始地点からの距離(nm)", + "PilotAvatar": "機長のアバターを表示", "SeparateTillerFromRudderInputs": "ティラーをラダー入力から分離", + "SyncEfis": "機長席と副操縦士席のEFIS操作を同期", "Title": "リアリズム" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "セーフティコーン", "DefaultBarometerUnit": "デフォルトの気圧計単位", "DynamicRegistrationDecal": "登録番号デカールの表示", + "EnableSimBridge": "SimBridge接続の有効化(接続が成功しなかった場合、5分後に自動的に非有効化)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "MCDUサーバー接続の有効化(接続が成功しなかった場合、5分後に自動的に非有効化)", "ExternalMcduServerPort": "外部MCDUサーバーポート", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "なし", "Off": "オフ", "Save": "保存", + "SimBridgePort": "外部SimBridgeサーバーポート", "SyncMsfsFlightPlan": "MSFSのフライトプランと同期", "ThrottleDetents": "スロットルディテント", "Title": "シミュレーターオプション", @@ -545,8 +597,8 @@ "Sep": "9月", "Sun": "(日)", "TT": { - "ConnectedToLocalApi": "ローカルAPIに接続中", - "DisconnectedFromLocalApi": "ローカルAPIから切断されました", + "ConnectedToLocalApi": "SimBridgeに接続中", + "DisconnectedFromLocalApi": "SimBridgeから切断されました", "TurnOffOrShutdownEfb": "EFBを消灯、またはシャットダウンする" }, "Thu": "(木)", diff --git a/src/instruments/src/EFB/Localization/ko.json b/src/instruments/src/EFB/Localization/ko.json index e595fa433cd2..6dbe8e18e322 100644 --- a/src/instruments/src/EFB/Localization/ko.json +++ b/src/instruments/src/EFB/Localization/ko.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "ATIS/ATC 출처 변경", "ConnectHoppieACARS": "Hoppie ACARS 연결", "NoInformationAvailableForThisFrequency": "이 주파수에서 사용할 수 있는 정보 없음", + "SearchPlaceholder": "검색", "SelectCorrectATISATCSource": "이 페이지는 설정 페이지의 ATIS/ATC 출처를 IVAO 또는 VATSIM으로 설정했을 때만 사용할 수 있습니다.", "SetActive": "활성 주파수로 설정", "SetStandby": "대기 주파수로 설정", + "ShowAll": "모두", + "ShowApproach": "접근", + "ShowAtis": "ATIS", + "ShowDelivery": "인가", + "ShowDeparture": "출발", + "ShowGround": "지상", + "ShowRadar": "레이더", + "ShowTower": "타워", "Standby": "대기", + "TT": { + "AtcCallSignSearch": "ATC 콜사인 검색", + "AtcTypeFilter": "필터" + }, "Title": "항공 교통 관제" }, "Checklists": { @@ -85,7 +98,7 @@ "YouHaveNotYetImportedAnySimBriefData": "SimBrief 데이터를 불러오지 않았습니다." }, "Overview": { - "ActualGW": "Actual GW", + "ActualGW": "실제 GW", "Engines": "엔진", "MMO": "최대 운항속도 (MMO)", "MRW": "최대 램프 중량 (MRW)", @@ -134,6 +147,27 @@ "TotalFuel": "총 연료", "Unavailable": "사용 불가" }, + "Payload": { + "BoardingTime": "승객 탑승 시간", + "Cargo": "수화물", + "Current": "현재", + "Passengers": "승객", + "Planned": "계획", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "탑승 소요 시간을 변경하려면 항공기가 지상에 있어야 하며, 엔진이 꺼져있어야 합니다", + "FillPayloadFromSimbrief": "Simbrief에서 페이로드 정보 채우기", + "MaxCargo": "최대 수화물", + "MaxPassengers": "최대 승객 수", + "MaxZFW": "최대 ZFW", + "MaxZFWCG": "최대 ZFWCG", + "PerPaxBagWeight": "승객 화물당 무게", + "PerPaxWeight": "승객당 무게", + "StartBoarding": "탑승 시작" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "푸시백" }, @@ -162,6 +196,10 @@ "Altn": "대체", "ExitFullscreenMode": "전체화면 모드 종료", "From": "이륙 공항", + "LoadingImage": "이미지 로딩 중", + "LoadingImageFailed": "이미지 로딩 실패", + "LoadingPdf": "PDF 로딩 중", + "LoadingPdfFailed": "PDF 로딩 실패", "LocalFiles": { "Both": "모두", "EstablishingConnection": "연결 설정 중...", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "프리셋을 불러오려면 항공기에 전원이 공급되어야 합니다", "AircraftNeedsToBePoweredToSavePresets": "프리셋을 저장하려면 항공기에 전원이 공급되어야 합니다", + "AutoLoadDawnDusk": "새벽/황혼:", + "AutoLoadDay": "주간:", + "AutoLoadLightingHelpTitle": "자동 프리셋 로딩", + "AutoLoadLightingPreset": "조명 프리셋 자동으로 불러오기 활성화:", + "AutoLoadNight": "야간:", + "AutoLoadNoneSelection": "없음", "LoadPreset": "프리셋 불러오기", "LoadingPreset": "프리셋 불러오는 중...", "NewNameConfirmationDialogMsg": "조명 프리셋의 이름을 확인해주세요", @@ -386,13 +430,15 @@ "SliderSpeed": "원하는 속도로 슬라이더 이동", "SystemEnabledOff": "클릭하여 푸시백 시스템을 켭니다.", "SystemEnabledOn": "클릭하여 푸시백 시스템을 완전히 끕니다.", + "UseControllerInput": "러더 및 엘리베이터의 컨트롤러 입력 켜기/끄기", "ZoomIn": "확대", "ZoomOut": "축소" }, "TugAttached": "터그가 연결되었습니다", "TugDirection": "터그 방향", "TugInTransit": "터그/핀 대기 중", - "TugSpeed": "터그 속도" + "TugSpeed": "터그 속도", + "UseControllerInput": "컨트롤러 입력 사용" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC 위도/경도 항로 형식", "PaxSigns": "승객 사인", "RmpVhfSpacing": "RMP VHF 주파수 간격", + "Satcom": "Satcom", "ThrustReductionHeight": "추력 감소 고도 (ft)", "Title": "항공기 옵션 / 프로그램 설정", "WeightUnit": "무게 단위" @@ -451,12 +498,15 @@ "AutofillChecklists": "체크리스트 자동 완성", "BoardingTime": "승객 탑승 시간", "DmcSelfTestTime": "DMC 자체 점검 시간", + "FirstOfficerAvatar": "부기장 아바타 표시", "HomeCockpitMode": "실내 조종석 모드", "McduFocusTimeout": "MCDU 포커스 타임아웃 (초)", "McduKeyboardInput": "MCDU 키보드 입력", "PauseAtTod": "T/D 일시정지", "PauseAtTodDistance": "T/D 일시정지 거리 (nm)", + "PilotAvatar": "기장 아바타 표시", "SeparateTillerFromRudderInputs": "러더 입력과 틸러 입력 분리", + "SyncEfis": "기장과 부기장의 EFIS 컨트롤 동기화", "Title": "리얼리즘" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "안전콘", "DefaultBarometerUnit": "기본 기압 단위", "DynamicRegistrationDecal": "다이나믹 등록 데칼", + "EnableSimBridge": "SimBridge 연결 활성화 (5분동안 연결 실패 시 자동으로 비활성화)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "MCDU 서버 연결 사용 (연결 실패 시 5분 후 자동으로 비활성화)", "ExternalMcduServerPort": "외부 MCDU 서버 포트", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "없음", "Off": "끄기", "Save": "저장", + "SimBridgePort": "외부 SimBridge 포트", "SyncMsfsFlightPlan": "MSFS 비행계획 동기화", "ThrottleDetents": "쓰로틀 단계", "Title": "시뮬레이션 옵션", @@ -545,8 +597,8 @@ "Sep": "9월", "Sun": "일", "TT": { - "ConnectedToLocalApi": "로컬 API 연결됨", - "DisconnectedFromLocalApi": "로컬 API 연결 끊김", + "ConnectedToLocalApi": "SimBridge와 연결됨", + "DisconnectedFromLocalApi": "SimBridge 연결 끊김", "TurnOffOrShutdownEfb": "EFB 끄기 또는 종료" }, "Thu": "목", diff --git a/src/instruments/src/EFB/Localization/lt.json b/src/instruments/src/EFB/Localization/lt.json index f2e76b649851..0da432e8a14d 100644 --- a/src/instruments/src/EFB/Localization/lt.json +++ b/src/instruments/src/EFB/Localization/lt.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Pakeisti ATIS/ATC šaltinį", "ConnectHoppieACARS": "Prijungti Hoppie ACARS", "NoInformationAvailableForThisFrequency": "Jokia informacija nėra pasiekiama šiame dažnyje", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Šis puslapis pasiekiamas tik pasirinkus IVAO arba VATSIM kaip ATIS/ATC šaltinį nustatymų puslapyje.", "SetActive": "Aktyvus", "SetStandby": "Atsarginis", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Atsarginis", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Oro Erdvės Kontrolė" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Bendras kuras", "Unavailable": "Nepasiekiama" }, + "Payload": { + "BoardingTime": "Laipinimo trukmė", + "Cargo": "Krovinys", + "Current": "Esamas", + "Passengers": "Keleiviai", + "Planned": "Suplanuotas", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Norint pakeisti laipinimo trukmę, orlaivis turi būti ant žemės, užgesintais varikliais", + "FillPayloadFromSimbrief": "Užpildyti informaciją apie krovinį iš Simbrief", + "MaxCargo": "Maksimalus krovinys", + "MaxPassengers": "Maksimalus keleivių skaičius", + "MaxZFW": "Maksimalus ZFW", + "MaxZFWCG": "Maximalus ZFWCG", + "PerPaxBagWeight": "Keleivio bagažo svoris", + "PerPaxWeight": "Keleivio svoris", + "StartBoarding": "Pradėti laipinimą" + }, + "Title": "Krovinys", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Išstumimas" }, @@ -162,6 +196,10 @@ "Altn": "Alternatyvus", "ExitFullscreenMode": "Išeiti iš pilno ekrano režimo", "From": "Iš", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Abu", "EstablishingConnection": "Nustatomas ryšys", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Norint įkelti išankstinius nustatymus, orlaivis turi būti įjungtas", "AircraftNeedsToBePoweredToSavePresets": "Norint išsaugoti išankstinius nustatymus, orlaivis turi būti įjungtas", + "AutoLoadDawnDusk": "Aušra/Prieblanda:", + "AutoLoadDay": "Diena:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Įjunti automatinį šviesų nustatymų užkrovimą:", + "AutoLoadNight": "Naktis:", + "AutoLoadNoneSelection": "Nėra", "LoadPreset": "Įkelti išankstinį nustatymą", "LoadingPreset": "Išankstinis nustatymas įkeliamas", "NewNameConfirmationDialogMsg": "Prašome patvirtinti naują apšvietimo išankstinio nustatymo pavadinimą", @@ -386,13 +430,15 @@ "SliderSpeed": "Pasirinkite norimą greitį", "SystemEnabledOff": "Paspauskite norint įjungti išstumimo sistemą.", "SystemEnabledOn": "Paspauskite norint pilnai išjungti išstumimo sistemą.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Priartinti", "ZoomOut": "Nutolinti" }, "TugAttached": "Vilkikas prisijungęs", "TugDirection": "Vilkiko kryptis", "TugInTransit": "Laukiama vilkiko", - "TugSpeed": "Vilkiko greitis" + "TugSpeed": "Vilkiko greitis", + "UseControllerInput": "Naudoti valdiklį" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC skrydžio taškų koordinačių formatas", "PaxSigns": "Keleivių ženklai", "RmpVhfSpacing": "RMP VHF Spacing", + "Satcom": "Satcom", "ThrustReductionHeight": "Traukos mažinimo aukštis (pėdos)", "Title": "Lėktuvo parinktys", "WeightUnit": "Svorio vienetai" @@ -451,12 +498,15 @@ "AutofillChecklists": "Automatinis patikrinimų sąrašo pildymas", "BoardingTime": "Laipinimo trukmė", "DmcSelfTestTime": "DMC automatinio patikrinimo trukmė", + "FirstOfficerAvatar": "Įjungti antrojo piloto avatarą", "HomeCockpitMode": "Namų kabinos režimas", "McduFocusTimeout": "MCDU fokuso praradimas (sekundės)", "McduKeyboardInput": "MCDU duomenų įvedimas klaviatūra", "PauseAtTod": "Pauzė prieš pradedant leistis", "PauseAtTodDistance": "Pauzės atstumas nuo pradėjimo leistis taško", + "PilotAvatar": "Rodyti piloto avatarą", "SeparateTillerFromRudderInputs": "Išskirti tiller iš krypties vairo komandų", + "SyncEfis": "Sinchronizuoti EFIS kontroles tarp kapitono ir antrojo piloto", "Title": "Realizmas" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Saugumo kūgiai", "DefaultBarometerUnit": "Numatyti barometro vienetai", "DynamicRegistrationDecal": "Dinaminis registracijos lipdukas", + "EnableSimBridge": "Įjungti SimBridge prisijungimą (automatiškai išjungiama po 5 minučių, jei nepavyksta prisijungti)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Įgalinti MCDU serverio prisijungimą (automatiškai išjungiama po 5 minučių, jei nepavyksta prisijungti)", "ExternalMcduServerPort": "Išorinis MCDU serverio port", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Nėra", "Off": "Išjungta", "Save": "Išsaugoti", + "SimBridgePort": "Išorinis SimBridge serverio port", "SyncMsfsFlightPlan": "Sinchronizuoti MSFS Skrydžio Planą", "ThrottleDetents": "Droselio detentai", "Title": "Simuliatoriaus nustatymai", @@ -545,8 +597,8 @@ "Sep": "Rgs", "Sun": "Sek", "TT": { - "ConnectedToLocalApi": "Prisijungta prie vietinio API", - "DisconnectedFromLocalApi": "Atsijungta nuo vietinio API", + "ConnectedToLocalApi": "Prisijungta prie SimBridge", + "DisconnectedFromLocalApi": "Atsijungta nuo SimBridge", "TurnOffOrShutdownEfb": "Išjunkite EFB" }, "Thu": "Ket", diff --git a/src/instruments/src/EFB/Localization/ms.json b/src/instruments/src/EFB/Localization/ms.json index 0c43f50ecc88..e6cbbe4e7e8c 100644 --- a/src/instruments/src/EFB/Localization/ms.json +++ b/src/instruments/src/EFB/Localization/ms.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Change ATIS/ATC source", "ConnectHoppieACARS": "Connect Hoppie ACARS", "NoInformationAvailableForThisFrequency": "No information available for this frequency", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "This page is only available when IVAO or VATSIM is selected as the ATIS/ATC source in the settings page.", "SetActive": "Set Active", "SetStandby": "Set Standby", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Standby", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Air Traffic Control" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Total Fuel", "Unavailable": "Unavailable" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "Altn", "ExitFullscreenMode": "Exit Fullscreen Mode", "From": "From", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Both", "EstablishingConnection": "Establishing Connection", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Aircraft Needs to be Powered to Load Presets", "AircraftNeedsToBePoweredToSavePresets": "Aircraft Needs to Be Powered to Save Presets", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Load Preset", "LoadingPreset": "Loading Preset", "NewNameConfirmationDialogMsg": "Please confirm the new name for the lighting preset", @@ -386,13 +430,15 @@ "SliderSpeed": "Move for desired speed", "SystemEnabledOff": "Click to turn the pushback system on.", "SystemEnabledOn": "Click to turn the pushback system completely off.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Zoom in", "ZoomOut": "Zoom out" }, "TugAttached": "Tug is attached", "TugDirection": "Tug Direction", "TugInTransit": "Waiting for Tug/Pin", - "TugSpeed": "Tug Speed" + "TugSpeed": "Tug Speed", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC Lat/Lon Waypoint Format", "PaxSigns": "PAX Signs", "RmpVhfSpacing": "RMP VHF Spacing", + "Satcom": "Satcom", "ThrustReductionHeight": "Thrust Reduction Height (ft)", "Title": "Aircraft Options / Pin Programs", "WeightUnit": "Weight Unit" @@ -451,12 +498,15 @@ "AutofillChecklists": "Autofill Checklists", "BoardingTime": "Boarding Time", "DmcSelfTestTime": "DMC Self Test Time", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Home Cockpit Mode", "McduFocusTimeout": "MCDU Focus Timeout (seconds)", "McduKeyboardInput": "MCDU Keyboard Input", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Separate Tiller from Rudder Inputs", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Realism" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Safety Cones", "DefaultBarometerUnit": "Default Barometer Unit", "DynamicRegistrationDecal": "Dynamic Registration Decal", + "EnableSimBridge": "Enable MCDU Server Connection (Auto deactivates after 5 minutes if no successful connection)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Enable MCDU Server Connection (Auto deactivates after 5 minutes if no successful connection)", "ExternalMcduServerPort": "External MCDU Server Port", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "None", "Off": "Off", "Save": "Save", + "SimBridgePort": "External MCDU Server Port", "SyncMsfsFlightPlan": "Sync MSFS Flight Plan", "ThrottleDetents": "Throttle Detents", "Title": "Sim Options", diff --git a/src/instruments/src/EFB/Localization/nb.json b/src/instruments/src/EFB/Localization/nb.json index 8fc68131c140..d5ec7775ca07 100644 --- a/src/instruments/src/EFB/Localization/nb.json +++ b/src/instruments/src/EFB/Localization/nb.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Endre ATIS/ATC kilde", "ConnectHoppieACARS": "Koble til Hoppie ACARS", "NoInformationAvailableForThisFrequency": "Ingen informasjon er tilgjengelig for denne frekvensen", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Denne siden er bare tilgjengelig når IVAO eller VATSIM er valgt som ATIS/ATC-kilde på innstillingssiden.", "SetActive": "Sett aktiv", "SetStandby": "Sett standby", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Standby", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Flykontrolltjeneste" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Totalt drivstoff", "Unavailable": "Ikke tilgjengelig" }, + "Payload": { + "BoardingTime": "Ombordstigningstid", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maksimum passasjerer", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -144,7 +178,7 @@ "DoorAft": "Bakre dør", "DoorCargo": "Cargo Door", "DoorFwd": "Fremre dør", - "ExternalPower": "Ekstern Strøm", + "ExternalPower": "Bakken kraftenhet", "FuelTruck": "Drivstoffbil", "JetBridge": "Ombordstigningsbro", "Title": "Tjenester", @@ -162,6 +196,10 @@ "Altn": "Alternativ", "ExitFullscreenMode": "Avslutt fullskjermmodus", "From": "Fra", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Begge", "EstablishingConnection": "Oppretter tilkobling", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Flyet må ha strøm for å laste inn forhåndsinnstillinger", "AircraftNeedsToBePoweredToSavePresets": "Flyet må ha strøm for å lagre forhåndsinnstillinger", + "AutoLoadDawnDusk": "Daggry/Skumring:", + "AutoLoadDay": "Dag:", + "AutoLoadLightingHelpTitle": "Automatisk forhåndsinnstilt lasting", + "AutoLoadLightingPreset": "Aktiver automatisk lasting av forhåndsinnstillinger for belysning:", + "AutoLoadNight": "Natt:", + "AutoLoadNoneSelection": "Ingen", "LoadPreset": "Last inn forhåndsinnstilling", "LoadingPreset": "Laster inn forhåndsinnstilling", "NewNameConfirmationDialogMsg": "Bekreft det nye navnet på forhåndsinnstillingen for belysning", @@ -386,13 +430,15 @@ "SliderSpeed": "Flytt regler for ønsket hastighet", "SystemEnabledOff": "Klikk for å slå på pushback-systemet.", "SystemEnabledOn": "Klikk for å slå pushback-systemet helt av.", + "UseControllerInput": "Slå kontrollerinngangen fra ror og heiskontrollflate på/av", "ZoomIn": "Zoom inn", "ZoomOut": "Zoom ut" }, "TugAttached": "Pushbacktraktor er festet", "TugDirection": "Pushbacktraktor-retning", "TugInTransit": "Venter på pushbacktraktor/pin", - "TugSpeed": "pushbacktraktor-hastighet" + "TugSpeed": "pushbacktraktor-hastighet", + "UseControllerInput": "Bruk Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC Lat/Lon Waypoint Format", "PaxSigns": "Passasjerskilter", "RmpVhfSpacing": "RMP VHF-intervall", + "Satcom": "Vis Satcom", "ThrustReductionHeight": "Kraftreduksjonshøyde (ft)", "Title": "Flyinnstillinger / fest programmer", "WeightUnit": "Vektenhet" @@ -451,12 +498,15 @@ "AutofillChecklists": "Autoutfyll sjekklister", "BoardingTime": "Ombordstigningstid", "DmcSelfTestTime": "DMC selvtest tid", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Hjemmecockpitsmodus", "McduFocusTimeout": "Tidsavbrudd for MCDU Focus (sekunder)", "McduKeyboardInput": "MCDU tastaturinntastning", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Separer nesehjulsstyingen fra ror-aksen", + "SyncEfis": "Synkroniser EFIS-kontroller mellom kaptein og first officer", "Title": "Realisme" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Sikkerhet kjegler", "DefaultBarometerUnit": "Standard Barometerenhet", "DynamicRegistrationDecal": "Dynamisk registreringsdekal", + "EnableSimBridge": "Aktiver SimBridge servertilkobling (Deaktiveres automatisk etter 5 minutter hvis tilkoblingen ikke er vellykket)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Aktiver MCDU Servertilkobling (Deaktiveres automatisk etter 5 minutter hvis tilkoblingen ikke er vellykket)", "ExternalMcduServerPort": "Ekstern MCDU-serverport", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Ingen", "Off": "Av", "Save": "Lagre", + "SimBridgePort": "Ekstern SimBridge-port", "SyncMsfsFlightPlan": "Synkroniser MSFS-flyplan", "ThrottleDetents": "Gassperrer", "Title": "Sim-alternativer", @@ -545,8 +597,8 @@ "Sep": "sep.", "Sun": "søn.", "TT": { - "ConnectedToLocalApi": "Koblet til lokal API", - "DisconnectedFromLocalApi": "Frakoblet lokal API", + "ConnectedToLocalApi": "Koblet til SimBridge", + "DisconnectedFromLocalApi": "Frakoblet SimBridge", "TurnOffOrShutdownEfb": "Lås eller skru av EFB" }, "Thu": "tor.", diff --git a/src/instruments/src/EFB/Localization/nl.json b/src/instruments/src/EFB/Localization/nl.json index e4795198f99b..d49250a207b2 100644 --- a/src/instruments/src/EFB/Localization/nl.json +++ b/src/instruments/src/EFB/Localization/nl.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "ATIS/ATC-bron wijzigen", "ConnectHoppieACARS": "Verbindt Hoppie ACARS", "NoInformationAvailableForThisFrequency": "Geen informatie beschikbaar voor deze frequentie", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Deze pagina is alleen beschikbaar wanneer IVAO of VATSIM is geselecteerd als de ATIS/ATC-bron op de instellingenpagina.", "SetActive": "Actief instellen", "SetStandby": "Stand-by instellen", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Stand-by", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Luchtverkeersleiding" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Totale brandstof", "Unavailable": "Niet beschikbaar" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "Altn", "ExitFullscreenMode": "Volledig scherm afsluiten", "From": "Van", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Beide", "EstablishingConnection": "Verbinding tot stand brengen...", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Vliegtuig moet voeding hebben om instellingen te kunnen laden", "AircraftNeedsToBePoweredToSavePresets": "Vliegtuigen moeten worden aangedreven om presets op te slaan", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Voorinstelling laden", "LoadingPreset": "Voorinstelling laden", "NewNameConfirmationDialogMsg": "Bevestig de nieuwe naam voor de voorinstelling voor verlichting", @@ -386,13 +430,15 @@ "SliderSpeed": "Move for desired speed", "SystemEnabledOff": "Click to turn the pushback system on.", "SystemEnabledOn": "Click to turn the pushback system completely off.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Zoom in", "ZoomOut": "Zoom out" }, "TugAttached": "Tug is attached", "TugDirection": "Richting tug", "TugInTransit": "Waiting for Tug/Pin", - "TugSpeed": "Tug snelheid" + "TugSpeed": "Tug snelheid", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC Lat/Lon Waypoint-indeling", "PaxSigns": "PAX Signs", "RmpVhfSpacing": "RMP VHF Afstand", + "Satcom": "Satcom", "ThrustReductionHeight": "Hoogte stuwkrachtreductie (ft)", "Title": "Vliegtuig Opties / Pin Programma's", "WeightUnit": "Gewichtseenheid" @@ -451,12 +498,15 @@ "AutofillChecklists": "Checklists automatisch invullen", "BoardingTime": "Instaptijd", "DmcSelfTestTime": "DMC zelf testtijd", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Home Cockpit Modus", "McduFocusTimeout": "MCDU Focus Time-out (seconden)", "McduKeyboardInput": "MCDU Toetsenbordinvoer", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Scheid Tiller van Roer invoer", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Realisme" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Safety Cones", "DefaultBarometerUnit": "Standaard barometer-eenheid", "DynamicRegistrationDecal": "Dynamische Registratie Sticker", + "EnableSimBridge": "MCDU-serververbinding inschakelen (Deactiveert automatisch na 5 minuten als er geen succesvolle verbinding is)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "MCDU-serververbinding inschakelen (Deactiveert automatisch na 5 minuten als er geen succesvolle verbinding is)", "ExternalMcduServerPort": "Externe MCDU Serverpoort", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Geen", "Off": "Uit", "Save": "Opslaan", + "SimBridgePort": "Externe MCDU Serverpoort", "SyncMsfsFlightPlan": "MSFS-vluchtplan synchroniseren", "ThrottleDetents": "Throttle Detents", "Title": "Sim-opties", diff --git a/src/instruments/src/EFB/Localization/pl.json b/src/instruments/src/EFB/Localization/pl.json index 2bfd04984e9f..0dd5372dd746 100644 --- a/src/instruments/src/EFB/Localization/pl.json +++ b/src/instruments/src/EFB/Localization/pl.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Zmień źródło ATIS/ATC", "ConnectHoppieACARS": "Połącz Hoppie ACARS", "NoInformationAvailableForThisFrequency": "Brak dostępnej informacji dla tej częstotliwości", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Ta strona jest dostępna tylko wtedy, gdy na stronie ustawień jako źródło ATIS/ATC wybrano IVAO lub VATSIM.", "SetActive": "Ustaw jako aktywną", "SetStandby": "Ustaw jako Oczekującą", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Oczekująca", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Kontrola Ruchu Lotniczego" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Paliwo Łącznie", "Unavailable": "Niedostępny" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Wypychanie" }, @@ -162,6 +196,10 @@ "Altn": "Zapasowe", "ExitFullscreenMode": "Wyłącz Tryb Pełnoekranowy", "From": "Z", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Obydwa", "EstablishingConnection": "Łączenie", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Samolot musi być zasilany, by możliwe było wczytanie ustawień", "AircraftNeedsToBePoweredToSavePresets": "Samolot musi być zasilany, by możliwe było zapisanie ustawień", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Wczytaj Ustawienia", "LoadingPreset": "Wczytywanie Ustawienia", "NewNameConfirmationDialogMsg": "Proszę potwierdzić nową nazwę ustawienia oświetlenia wnętrza", @@ -386,13 +430,15 @@ "SliderSpeed": "Ustaw pożądaną prędkość", "SystemEnabledOff": "Kliknij, aby włączyć system wypychania.", "SystemEnabledOn": "Kliknij, aby całkowicie wyłączyć system wypychania.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Przybliż", "ZoomOut": "Oddal" }, "TugAttached": "Holownik jest podłączony", "TugDirection": "Kierunek Wypychania", "TugInTransit": "Oczekuję na Holownik/Kołek", - "TugSpeed": "Prędkość Wypychania" + "TugSpeed": "Prędkość Wypychania", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "Format Punktów Trasy FMGC Szer/Dł Geog.", "PaxSigns": "Znaki dla Pasażerów", "RmpVhfSpacing": "Odstępy Częstotliwości VHF na Panelu RMP", + "Satcom": "Satcom", "ThrustReductionHeight": "Wysokość Redukcji Ciągu (ft)", "Title": "Opcje Samolotu/Przypięte Programy", "WeightUnit": "Jednostka Masy" @@ -451,12 +498,15 @@ "AutofillChecklists": "Automatyczne Wypełnianie Checklist", "BoardingTime": "Czas Wejścia Pasażerów na Pokład", "DmcSelfTestTime": "Czas Autotestu DMC", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Tryb Kokpitu Domowego", "McduFocusTimeout": "Czas Aktywności MCDU (sekundy)", "McduKeyboardInput": "Wprowadzanie Danych Fizyczną Klawiaturą do MCDU", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Oddziel Sterowanie Kołem od Steru Kierunku", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Realizm" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Pachołki Bezpieczeństwa", "DefaultBarometerUnit": "Domyślna Jednostka Barometru", "DynamicRegistrationDecal": "Dynamiczny Numer Rejestracyjny", + "EnableSimBridge": "Włącz połączenie z serwerem MCDU (Automatycznie dezaktywowane po 5 minutach bez połączenia)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Włącz połączenie z serwerem MCDU (Automatycznie dezaktywowane po 5 minutach bez połączenia)", "ExternalMcduServerPort": "Port Serwera MCDU", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Nie", "Off": "Wył.", "Save": "Tak", + "SimBridgePort": "Port Serwera MCDU", "SyncMsfsFlightPlan": "Synchronizacja Planu Lotu z MSFS", "ThrottleDetents": "Kalibracja Przepustnic", "Title": "Ustawienia Symulatora", diff --git a/src/instruments/src/EFB/Localization/pt-BR.json b/src/instruments/src/EFB/Localization/pt-BR.json index 18970f6dc846..ec35a0874819 100644 --- a/src/instruments/src/EFB/Localization/pt-BR.json +++ b/src/instruments/src/EFB/Localization/pt-BR.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Alterar fonte dos dados ATIS/ATC", "ConnectHoppieACARS": "Conectar Hoppie ACARS", "NoInformationAvailableForThisFrequency": "Nenhuma informação disponível para essa frequência", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Esta página só está disponível quando a opção IVAO ou VATSIM é selecionada como fonte de dados ATIS/ATC na página de configurações.", "SetActive": "Definir ativo", "SetStandby": "Definir Standby", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Standby", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Controle de tráfego aéreo" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Combustível total", "Unavailable": "Indisponível" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "Altn", "ExitFullscreenMode": "Sair do modo tela cheia", "From": "De", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Ambos", "EstablishingConnection": "Estabelecendo conexão…", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "A aeronave precisa ser energizada para carregar predefinições", "AircraftNeedsToBePoweredToSavePresets": "A aeronave precisa ser energizada para salvar predefinições", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Carregar Predefinição", "LoadingPreset": "Carregar Predefinição", "NewNameConfirmationDialogMsg": "Por favor, confirme o novo nome da predefinição de iluminação", @@ -386,13 +430,15 @@ "SliderSpeed": "Mova-se para a velocidade desejada", "SystemEnabledOff": "Clique para ativar o sistema de pushback.", "SystemEnabledOn": "Clique para desligar completamente o sistema de pushback.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Aumentar zoom", "ZoomOut": "Diminuir zoom" }, "TugAttached": "O rebocador está acoplado", "TugDirection": "Direção do rebocador", "TugInTransit": "Esperando pelo reboque/pino", - "TugSpeed": "Velocidade do rebocador" + "TugSpeed": "Velocidade do rebocador", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "Formato de Waypont Lat/Lon do FMGC", "PaxSigns": "Aviso de Passageiros", "RmpVhfSpacing": "Espaçamento RMP VHF", + "Satcom": "Satcom", "ThrustReductionHeight": "Altura de redução de empuxo (ft)", "Title": "Opções da aeronave / Programas fixados", "WeightUnit": "Unidade de peso" @@ -451,12 +498,15 @@ "AutofillChecklists": "Preenchimento automático dos Checklists", "BoardingTime": "Tempo de embarque", "DmcSelfTestTime": "Tempo de auto teste do DMC", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Modo Home Cockpit", "McduFocusTimeout": "Tempo limite de foco da MCDU (segundos)", "McduKeyboardInput": "Entrada de teclado MCDU", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Separar controle de taxi do leme", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Realismo" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Safety Cones", "DefaultBarometerUnit": "Unidade padrão do barômetro", "DynamicRegistrationDecal": "Registro dinâmico do decalque", + "EnableSimBridge": "Ativar conexão com o servidor MCDU (desativa automaticamente após 5 minutos se não houver conexão bem-sucedida)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Ativar conexão com o servidor MCDU (desativa automaticamente após 5 minutos se não houver conexão bem-sucedida)", "ExternalMcduServerPort": "Porta externa de servidor MCDU", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Nenhum", "Off": "Desativado", "Save": "Salvar", + "SimBridgePort": "Porta externa de servidor MCDU", "SyncMsfsFlightPlan": "Sincronizar plano de voo do MSFS", "ThrottleDetents": "Manetes de potência", "Title": "Opções do Simulador", diff --git a/src/instruments/src/EFB/Localization/pt-PT.json b/src/instruments/src/EFB/Localization/pt-PT.json index 441e880245e8..8bf5db2b7a97 100644 --- a/src/instruments/src/EFB/Localization/pt-PT.json +++ b/src/instruments/src/EFB/Localization/pt-PT.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Alterar fonte ATIS/ATC", "ConnectHoppieACARS": "Conetar Hoppie ACARS", "NoInformationAvailableForThisFrequency": "Sem informação disponível para esta frequência", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Esta página só está disponível quando IVAO ou VATSIM é selecionado como a fonte ATIS/ATC na página de configurações.", "SetActive": "Definir Ativo", "SetStandby": "Definir Standby", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Standby", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Controlo de Tráfego Aéreo" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Combustível Total", "Unavailable": "Indisponível" }, + "Payload": { + "BoardingTime": "Tempo de Embarque", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Lotação Máxima de Passageiros", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "Altn", "ExitFullscreenMode": "Sair do modo de tela cheia", "From": "De", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Ambos", "EstablishingConnection": "A Estabelecer Ligação", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "O avião precisa ser alimentado para carregar predefinições", "AircraftNeedsToBePoweredToSavePresets": "O avião precisa ser alimentado para guardar predefinições", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Carregar predefinição", "LoadingPreset": "A Carregar Predefinição", "NewNameConfirmationDialogMsg": "Por favor, confirme o novo nome para a predefinição de iluminação", @@ -386,13 +430,15 @@ "SliderSpeed": "Mova para a velocidade desejada", "SystemEnabledOff": "Clique para ativar o sistema de pushback.", "SystemEnabledOn": "Clique para desligar completamente o sistema de pushback.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Ampliar", "ZoomOut": "Reduzir" }, "TugAttached": "O rebocador está conetado", "TugDirection": "Direção do Reboque", "TugInTransit": "À espera do Rebocador/Pino", - "TugSpeed": "Velocidade do Reboque" + "TugSpeed": "Velocidade do Reboque", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "Formato Waypoint FMGC Lat/Lon", "PaxSigns": "Sinal de Passageiros", "RmpVhfSpacing": "Espaçamento RMP VHF", + "Satcom": "Satcom", "ThrustReductionHeight": "Altura de Redução de Propulsão (ft)", "Title": "Opções do Avião / Programas Pin", "WeightUnit": "Unidade de Peso" @@ -451,12 +498,15 @@ "AutofillChecklists": "Preencher automaticamente as Listas de Verificação", "BoardingTime": "Tempo de Embarque", "DmcSelfTestTime": "Tempo de autoteste DMC", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Modo Cockpit em Casa", "McduFocusTimeout": "Tempo limite de foco da MCDU (segundos)", "McduKeyboardInput": "Entrada de teclado MCDU", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Separar o Tiller do controlo do Leme", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Realismo" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Cones de segurança", "DefaultBarometerUnit": "Unidade de Barómetro Padrão", "DynamicRegistrationDecal": "Decalque de Registro Dinâmico", + "EnableSimBridge": "Ativar conexão SimBridge (desativa automaticamente após 5 minutos se não houver conexão bem-sucedida)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Ativar conexão com o servidor MCDU (desativa automaticamente após 5 minutos se não houver conexão bem-sucedida)", "ExternalMcduServerPort": "Porta de servidor MCDU externa", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Nenhum", "Off": "Desativado", "Save": "Salvar", + "SimBridgePort": "Porta SimBridge externa", "SyncMsfsFlightPlan": "Sincronizar plano de voo do MSFS", "ThrottleDetents": "Retentores da Manete", "Title": "Opções de Sim", @@ -507,7 +559,7 @@ "flyPad": { "AutoBrightness": "Brilho Automático", "AutomaticallyShowOnscreenKeyboard": "Mostrar Automaticamente o Teclado na Tela", - "BatteryLifeEnabled": "Battery Life Simulated", + "BatteryLifeEnabled": "Duração da Bateria Simulada", "Blue": "Azul", "Brightness": "Brilho", "Dark": "Escuro", diff --git a/src/instruments/src/EFB/Localization/ro.json b/src/instruments/src/EFB/Localization/ro.json index 40f8cbd8205b..d8e513066452 100644 --- a/src/instruments/src/EFB/Localization/ro.json +++ b/src/instruments/src/EFB/Localization/ro.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Schimbați sursa ATIS/ATC", "ConnectHoppieACARS": "Conectare la Hoppie ACARS", "NoInformationAvailableForThisFrequency": "Nicio informație disponibilă pentru această frecvență", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Această pagină este disponibilă doar când IVAO sau VATSIM este selectată ca sursă ATIS/ATC în această pagină.", "SetActive": "Setare Activ", "SetStandby": "Setare Standby", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Standby", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Control de Trafic Aerian" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Combustibil total ", "Unavailable": "Indisponibil" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "Altn", "ExitFullscreenMode": "Ieșire din modul pe tot ecranul", "From": "De la", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Ambele", "EstablishingConnection": "Stabilire conexiune", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Aeronava trebuie alimentată pentru a încărca presetările", "AircraftNeedsToBePoweredToSavePresets": "Aeronava trebuie să fie pornită pentru a salva setările", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Încărcați presetarea", "LoadingPreset": "Încărcare presetare", "NewNameConfirmationDialogMsg": "Vă rugăm sa confirmați noua denumire pentru presetarea luminilor", @@ -386,13 +430,15 @@ "SliderSpeed": "Move for desired speed", "SystemEnabledOff": "Click to turn the pushback system on.", "SystemEnabledOn": "Click to turn the pushback system completely off.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Zoom in", "ZoomOut": "Zoom out" }, "TugAttached": "Tug is attached", "TugDirection": "Direcție remorcher", "TugInTransit": "Waiting for Tug/Pin", - "TugSpeed": "Viteză remorcher" + "TugSpeed": "Viteză remorcher", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC Lat/Lon format puncte de trecere", "PaxSigns": "Semn PAX", "RmpVhfSpacing": "Spațiere RMP VHF", + "Satcom": "Satcom", "ThrustReductionHeight": "Înălțimea de reducere a puterii (ft)", "Title": "Opțiuni aeronave/programe Pin", "WeightUnit": "Unitatea de măsurat greutatea" @@ -451,12 +498,15 @@ "AutofillChecklists": "Completare automată a listelor de verificare", "BoardingTime": "Timp de încărcare", "DmcSelfTestTime": "Timp de testare automată DMC", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Mod cabină de pilotaj de casă", "McduFocusTimeout": "MCDU Focus Timeout (secunde)", "McduKeyboardInput": "Intrare tastatură MCDU", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Separați cârma de intrările cârmei", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Realism" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Safety Cones", "DefaultBarometerUnit": "Unitate Barometrica Implicită", "DynamicRegistrationDecal": "Număr de înregistrare dinamic", + "EnableSimBridge": "Activează conexiune server MCDU (Auto dezactivează după 5 minute dacă nu s-a stabilit conexiunea)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Activează conexiune server MCDU (Auto dezactivează după 5 minute dacă nu s-a stabilit conexiunea)", "ExternalMcduServerPort": "Poartă exterioară server MCDU", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Niciunul", "Off": "Dezactivată", "Save": "Salvează", + "SimBridgePort": "Poartă exterioară server MCDU", "SyncMsfsFlightPlan": "Sincronizare plan de zbor MSFS", "ThrottleDetents": "Senzori accelerație", "Title": "Opțiuni simulator", diff --git a/src/instruments/src/EFB/Localization/ru.json b/src/instruments/src/EFB/Localization/ru.json index e84691451ef7..20ab1b5315ce 100644 --- a/src/instruments/src/EFB/Localization/ru.json +++ b/src/instruments/src/EFB/Localization/ru.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Изменить источник ATIS/ATC", "ConnectHoppieACARS": "Подключить Hoppie ACARS", "NoInformationAvailableForThisFrequency": "Нет доступной информации по этой частоте", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Эта страница доступна только в том случае, если в качестве источника ATIS/ATC выбран IVAO или VATSIM на странице настроек.", "SetActive": "Установить активной", "SetStandby": "Установить резервной", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Резервная", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Управление Воздушным Движением" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Общее количество топлива", "Unavailable": "Недоступно" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Откат" }, @@ -162,6 +196,10 @@ "Altn": "Алтн", "ExitFullscreenMode": "Выйти из полноэкранного режима", "From": "От", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Оба", "EstablishingConnection": "Установка соединения...", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Самолёт должен иметь питание для загрузки пресетов", "AircraftNeedsToBePoweredToSavePresets": "Самолёт должен иметь питание для загрузки пресетов", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Загрузить пресет", "LoadingPreset": "Загрузка пресета...", "NewNameConfirmationDialogMsg": "Подтвердите новое имя для пресета освещения", @@ -386,13 +430,15 @@ "SliderSpeed": "Перемещение на желаемую скорость", "SystemEnabledOff": "Нажмите, чтобы включить систему буксировки.", "SystemEnabledOn": "Нажмите, чтобы полностью отключить систему буксировки.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Увеличить", "ZoomOut": "Уменьшить" }, "TugAttached": "Буксир прикреплен", "TugDirection": "Направление буксира", "TugInTransit": "Ожидание буксира", - "TugSpeed": "Скорость буксира" + "TugSpeed": "Скорость буксира", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "Формат промежуточных точек FMGC для широты/долготы", "PaxSigns": "Табло салона", "RmpVhfSpacing": "Интервал RMP VHF", + "Satcom": "Satcom", "ThrustReductionHeight": "Высота уменьшения тяги (фт)", "Title": "Опции самолёта / Пин-программы", "WeightUnit": "Единицы измерения веса" @@ -451,12 +498,15 @@ "AutofillChecklists": "Автозаполнение контрольных списков", "BoardingTime": "Скорость посадки", "DmcSelfTestTime": "Время самотестирования DMC", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Режим домашней кабины", "McduFocusTimeout": "Тайм-аут фокуса MCDU (секунды)", "McduKeyboardInput": "Ввод с клавиатуры MCDU", "PauseAtTod": "Т/Д Пауза", "PauseAtTodDistance": "Расстояние паузы Т/Д (нм)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Отдельный румпель от входов руля", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Реализм" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Предохранительные конусы", "DefaultBarometerUnit": "Единица измерения барометра по умолчанию", "DynamicRegistrationDecal": "Наклейка с динамической регистрацией", + "EnableSimBridge": "Подключение к серверу MCDU (автоматически деактивируется через 5 минут, если соединение не было успешным)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Подключение к серверу MCDU (автоматически деактивируется через 5 минут, если соединение не было успешным)", "ExternalMcduServerPort": "Порт внешнего сервера MCDU", "Hpa": "гПа", @@ -474,6 +525,7 @@ "None": "Нет", "Off": "Откл.", "Save": "Сохранить", + "SimBridgePort": "Порт внешнего сервера MCDU", "SyncMsfsFlightPlan": "Синхронизировать план полета MSFS", "ThrottleDetents": "Защелки рычагов", "Title": "Параметры симулятора", diff --git a/src/instruments/src/EFB/Localization/sk.json b/src/instruments/src/EFB/Localization/sk.json index b344e4d39cbb..f9df0b7c5d46 100644 --- a/src/instruments/src/EFB/Localization/sk.json +++ b/src/instruments/src/EFB/Localization/sk.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Zmena zdroja ATIS/ATC", "ConnectHoppieACARS": "Pripojiť Hoppie ACARS", "NoInformationAvailableForThisFrequency": "Nie sú k dispozícii žiadne informácie o tejto frekvencii", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Táto stránka je dostupná len vtedy, keď je na stránke nastavení vybratá funkcia IVAO alebo VATSIM ako zdroj ATIS/ATC.", "SetActive": "Nastaviť aktívnu", "SetStandby": "Nastaviť pohotovostný režim", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Pohotovostný režim", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Riadenie letovej prevádzky" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Celkové palivo", "Unavailable": "Nedostupné" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "Altn", "ExitFullscreenMode": "Skončiť režim na celú obrazovku", "From": "Od", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Obidva", "EstablishingConnection": "Pripájam", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Lietadlo musí byť zapnuté na načítanie predvolieb", "AircraftNeedsToBePoweredToSavePresets": "Lietadlo musí byť zapnuté pre uloženie prednastavení", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Načítať prednastavenie", "LoadingPreset": "Načítanie prednastavenia", "NewNameConfirmationDialogMsg": "Please confirm the new name for the lighting preset", @@ -386,13 +430,15 @@ "SliderSpeed": "Move for desired speed", "SystemEnabledOff": "Click to turn the pushback system on.", "SystemEnabledOn": "Click to turn the pushback system completely off.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Zoom in", "ZoomOut": "Zoom out" }, "TugAttached": "Tug is attached", "TugDirection": "Smer Tagu", "TugInTransit": "Waiting for Tug/Pin", - "TugSpeed": "Rýchlosť Tugu" + "TugSpeed": "Rýchlosť Tugu", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "Formát bod cesty FMGC Lat/Lon", "PaxSigns": "PAX Signs", "RmpVhfSpacing": "RMP vzdialenosť VHF", + "Satcom": "Satcom", "ThrustReductionHeight": "Výška redukcie ťahu (ft)", "Title": "Nastavenia lietadla / Pin programy", "WeightUnit": "Jednotka hmotnosti" @@ -451,12 +498,15 @@ "AutofillChecklists": "Automatické vyplňovanie kontrolných zoznamov", "BoardingTime": "Čas nástupu", "DmcSelfTestTime": "DMC Čas testovania", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Domáci režim kokpitu", "McduFocusTimeout": "Časový limit zaostrenia MCDU (sekundy)", "McduKeyboardInput": "Vstup klávesnice MCDU", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Oddeľte Tiller od vstupov kormidla", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "Realizmus" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Safety Cones", "DefaultBarometerUnit": "Štandardná jednotka barometra", "DynamicRegistrationDecal": "Dynamický registračný kód", + "EnableSimBridge": "Povoliť pripojenie servera MCDU (automaticky sa deaktivuje po 5 minútach, ak nie je úspešné pripojenie)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Povoliť pripojenie servera MCDU (automaticky sa deaktivuje po 5 minútach, ak nie je úspešné pripojenie)", "ExternalMcduServerPort": "Externý port servera MCDU", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Žiadne", "Off": "Vypnuté", "Save": "Uložiť", + "SimBridgePort": "Externý port servera MCDU", "SyncMsfsFlightPlan": "Synchronizácia letového plánu MSFS", "ThrottleDetents": "Detenty škrtiacej klapky", "Title": "Možnosti Simulátora", diff --git a/src/instruments/src/EFB/Localization/sl.json b/src/instruments/src/EFB/Localization/sl.json index 1d158560372f..4235b634d1f1 100644 --- a/src/instruments/src/EFB/Localization/sl.json +++ b/src/instruments/src/EFB/Localization/sl.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Zamenjaj vir ATIS/ATC", "ConnectHoppieACARS": "Povežite Hoppie ACARS", "NoInformationAvailableForThisFrequency": "ZA TO FREKVENCO NI NA VOLJO NOBENIH INFORMACIJ", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "Ta stran je na voljo le, če je IVAO ali VATSIM izbran kot vir ATIS/ATC na strani z nastavitvami.", "SetActive": "Nastavi kot aktivno", "SetStandby": "Nastavi v pripravljenost", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "V pripravljenosti", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Kontrola zračnega prometa" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "Skupno gorivo", "Unavailable": "Ni na voljo" }, + "Payload": { + "BoardingTime": "Čas vkrcanja", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Največje število potnikov", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Vlačilec" }, @@ -162,6 +196,10 @@ "Altn": "Altn", "ExitFullscreenMode": "Zapri celozaslonski način", "From": "Od", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "Oboje", "EstablishingConnection": "Vzpostavljanje povezave", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Za nalaganje prednastavitev zrakoplov potrebuje napajanje", "AircraftNeedsToBePoweredToSavePresets": "Za shranjevanje prednastavitev zrakoplov potrebuje napajanje", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "Naloži prednastavitev", "LoadingPreset": "Nalaganje prednastavitve", "NewNameConfirmationDialogMsg": "Prosimo, potrdite novo ime za prednastavitev osvetlitve", @@ -386,13 +430,15 @@ "SliderSpeed": "Premakni za želeno hitrost", "SystemEnabledOff": "Kliknite, če želite vklopiti sistem vleke.", "SystemEnabledOn": "Kliknite, če želite popolnoma izklopiti sistem vleke.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Povečaj", "ZoomOut": "Pomanjšaj" }, "TugAttached": "Vlačilec je priklopljen", "TugDirection": "Smer vlačilca", "TugInTransit": "Čakam na vlačilca/pin", - "TugSpeed": "Hitrost vlačilca" + "TugSpeed": "Hitrost vlačilca", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC - format zapisa točk zemljepisne dolžine/širine", "PaxSigns": "PAX znaki", "RmpVhfSpacing": "RMP VHF razmik", + "Satcom": "Satcom", "ThrustReductionHeight": "Višina zmanjšanja potiska (ft)", "Title": "Možnosti zrakoplova / pripeti programi", "WeightUnit": "Enota teže" @@ -451,12 +498,15 @@ "AutofillChecklists": "Samodejno izpolni kontrolne sezname", "BoardingTime": "Čas vkrcanja", "DmcSelfTestTime": "DMC samopreizkusni čas", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Način \"Home Cockpit\"", "McduFocusTimeout": "MCDU časovna omejitev fokusa (sekunde)", "McduKeyboardInput": "MCDU vnos s tipkovnico", "PauseAtTod": "T/D pavza", "PauseAtTodDistance": "T/D Razdalja pavze (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Ločite delovanje krmilnika od krmila", + "SyncEfis": "Sinhronizacija EFIS-a med CP in FO", "Title": "Realizem" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Varnostni stožci", "DefaultBarometerUnit": "Privzeta barometrična enota", "DynamicRegistrationDecal": "Dinamična registrska nalepka", + "EnableSimBridge": "Omogoči povezavo s strežnikom MCDU (samodejna deaktivacija po 5 minutah, če ni uspešne povezave)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Omogoči povezavo s strežnikom MCDU (samodejna deaktivacija po 5 minutah, če ni uspešne povezave)", "ExternalMcduServerPort": "Zunanji MCDU Port", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Brez", "Off": "Izklopljeno", "Save": "Shrani", + "SimBridgePort": "Zunanji SimBridge Port", "SyncMsfsFlightPlan": "Sinhroniziraj z MSFS načrtom leta", "ThrottleDetents": "Odkloni plinske ročke", "Title": "Nastavitve simulatorja", diff --git a/src/instruments/src/EFB/Localization/sv.json b/src/instruments/src/EFB/Localization/sv.json index 4a36f072f247..3d2d725637ea 100644 --- a/src/instruments/src/EFB/Localization/sv.json +++ b/src/instruments/src/EFB/Localization/sv.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Ändra ATIS/ATC-källa", "ConnectHoppieACARS": "Anslut Hoppie ACARS", "NoInformationAvailableForThisFrequency": "INFORMATION EJ TILLGÄNGLIG FÖR DENNA FREKVENS", + "SearchPlaceholder": "Sök", "SelectCorrectATISATCSource": "Denna sida är endast tillgänglig när IVAO eller VATSIM har valts som ATIS/ATC-källa på inställningssidan.", "SetActive": "Sätt Aktiv", "SetStandby": "Sätt Standby", + "ShowAll": "Alla", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Standby", + "TT": { + "AtcCallSignSearch": "ATC-sökning efter anropssignal", + "AtcTypeFilter": "ATC-typfilter" + }, "Title": "Flygledning" }, "Checklists": { @@ -85,7 +98,7 @@ "YouHaveNotYetImportedAnySimBriefData": "Du har ännu inte importerat SimBrief-data." }, "Overview": { - "ActualGW": "Actual GW", + "ActualGW": "Bruttovikt", "Engines": "Motorer", "MMO": "MMO", "MRW": "MRW", @@ -134,6 +147,27 @@ "TotalFuel": "Totalt bränsle", "Unavailable": "Ej tillgänglig" }, + "Payload": { + "BoardingTime": "Ombordstigningstid", + "Cargo": "Frakt", + "Current": "Nuvarande", + "Passengers": "Passagerare", + "Planned": "Planerad", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Flygplan måste vara på marken och ha motorer avstängning för att ändra ombordstigningens varaktighet", + "FillPayloadFromSimbrief": "Fyll nyttolastinformation från Simbrief", + "MaxCargo": "Max frakt", + "MaxPassengers": "Max antal passagerare", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximal ZFWCG", + "PerPaxBagWeight": "Per passagerarväska vikt", + "PerPaxWeight": "Vikt per passagerare", + "StartBoarding": "Börja Boarding" + }, + "Title": "Nyttolast", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "Altn", "ExitFullscreenMode": "Lämna fullskärmsläge", "From": "Från", + "LoadingImage": "Laddar bild", + "LoadingImageFailed": "Misslyckades ladda bilden", + "LoadingPdf": "Laddar PDF", + "LoadingPdfFailed": "Misslyckades ladda PDF", "LocalFiles": { "Both": "Båda", "EstablishingConnection": "Upprättar anslutning", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Flygplan måste ha ström för att läsa in förinställningar", "AircraftNeedsToBePoweredToSavePresets": "Flygplan måste ha ström för att spara förinställningar", + "AutoLoadDawnDusk": "Gryning/skymning:", + "AutoLoadDay": "Dag:", + "AutoLoadLightingHelpTitle": "Automatisk förinställd laddning", + "AutoLoadLightingPreset": "Aktivera automatisk laddning av belysningsförinställningar:", + "AutoLoadNight": "Natt:", + "AutoLoadNoneSelection": "Ingen", "LoadPreset": "Ladda förval", "LoadingPreset": "Laddar förval …", "NewNameConfirmationDialogMsg": "Bekräfta det nya namnet på belysningsförinställningen", @@ -386,13 +430,15 @@ "SliderSpeed": "Flytta för önskad hastighet", "SystemEnabledOff": "Klicka för att slå på pushback-systemet.", "SystemEnabledOn": "Klicka för att stänga av pushback-systemet helt.", + "UseControllerInput": "Slå på/av spelkontrollen ingång från roder och höjdrodret", "ZoomIn": "Zooma in", "ZoomOut": "Zooma ut" }, "TugAttached": "Dragare är fäst", "TugDirection": "Dragarriktning", "TugInTransit": "Väntar på dragare/pin", - "TugSpeed": "Dragarhastighet" + "TugSpeed": "Dragarhastighet", + "UseControllerInput": "Använda spelkontrollen som indata" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC Lat/Lon Waypoint-format", "PaxSigns": "Passagerarskyltar", "RmpVhfSpacing": "RMP VHF-avstånd", + "Satcom": "Visa Satcom", "ThrustReductionHeight": "Höjd för dragkraftsreduktion (ft)", "Title": "Flygplan Inställningar / Pin-Program", "WeightUnit": "Viktenhet" @@ -451,12 +498,15 @@ "AutofillChecklists": "Auto-fyll checklistor", "BoardingTime": "Ombordstigningstid", "DmcSelfTestTime": "DMC självtesttid", + "FirstOfficerAvatar": "Visa första officer avatar", "HomeCockpitMode": "Hem Cockpit-läge", "McduFocusTimeout": "Tidsgräns för MCDU fokus (sekunder)", "McduKeyboardInput": "MCDU Tangentbordsinput", "PauseAtTod": "T/D Paus", "PauseAtTodDistance": "Avstånd till målet för T/D-paus (nm)", + "PilotAvatar": "Visa pilot avatar", "SeparateTillerFromRudderInputs": "Separera Tiller från roder input", + "SyncEfis": "Synkronisera EFIS-kontroller mellan Captain och FO", "Title": "Realism" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Säkerhetskoner", "DefaultBarometerUnit": "Standardbarometerenhet", "DynamicRegistrationDecal": "Dynamisk registreringsdekal", + "EnableSimBridge": "Aktivera SimBridge-serveranslutning (inaktiveras automatiskt efter 5 minuter om anslutningen inte lyckas).", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Aktivera MCDU-serveranslutning (inaktiveras automatiskt efter 5 minuter om anslutningen inte lyckas).", "ExternalMcduServerPort": "Extern MCDU-serverport", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Ingen", "Off": "Av", "Save": "Spara", + "SimBridgePort": "Extern SimBridge-port", "SyncMsfsFlightPlan": "Synkronisera MSFS-färdplan", "ThrottleDetents": "Kalibrering av tryckspaken", "Title": "Sim-inställningar", @@ -545,8 +597,8 @@ "Sep": "Sep", "Sun": "Sön", "TT": { - "ConnectedToLocalApi": "Ansluten till Local-API", - "DisconnectedFromLocalApi": "Avklopplad från Local-API", + "ConnectedToLocalApi": "Ansluten till SimBridge", + "DisconnectedFromLocalApi": "Avklopplad från SimBridge", "TurnOffOrShutdownEfb": "Stäng av EFB" }, "Thu": "Tor", diff --git a/src/instruments/src/EFB/Localization/th.json b/src/instruments/src/EFB/Localization/th.json index 67f2de185b57..a24572ec559d 100644 --- a/src/instruments/src/EFB/Localization/th.json +++ b/src/instruments/src/EFB/Localization/th.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "เปลี่ยนแหล่งที่มา ATIS/ATC", "ConnectHoppieACARS": "เชื่อมต่อ Hoppie ACARS", "NoInformationAvailableForThisFrequency": "ไม่มีข้อมูลให้สำหรับความถี่นี้", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "หน้านี้จะใช้ได้เฉพาะเมื่อเลือก IVAO หรือ VATSIM เป็นแหล่งข้อมูล ATIS/ATC ในหน้าการตั้งค่า", "SetActive": "ตั้งค่าเป็นใช้งานอยู่", "SetStandby": "ตั้งค่าเป็นสแตนด์บาย", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "สแตนด์บาย", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "การควบคุมการจราจรทางอากาศ" }, "Checklists": { @@ -64,7 +77,7 @@ "YourFlight": { "Alternate": "สำรอง", "AverageWind": "ลมเฉลี่ย", - "CompanyRoute": "Company Route", + "CompanyRoute": "เส้นทางบริษัท", "CostIndex": "Cost Index", "CruiseAlt": "ความสูงที่บิน", "ImportSimBriefData": "นำเข้าข้อมูล SimBrief", @@ -134,6 +147,27 @@ "TotalFuel": "น้ำมันรวม", "Unavailable": "ไม่พร้อมใช้งาน" }, + "Payload": { + "BoardingTime": "เวลาเรียกขึ้นเครื่อง", + "Cargo": "คาร์โก้", + "Current": "ปัจจุบัน", + "Passengers": "ผู้โดยสาร", + "Planned": "ที่วางแผนไว้", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "เครื่องบินต้องอยู่บนพื้นดิน และต้องปิดเครื่องยนต์เพื่อที่จะเปลี่ยนเวลาเรียกขึ้นเครื่อง", + "FillPayloadFromSimbrief": "กรอกข้อมูลน้ำหนักบรรทุกจาก Simbrief", + "MaxCargo": "จำนวนคาร์โก้สูงสุด", + "MaxPassengers": "จำนวนผู้โดยสารสูงสุด", + "MaxZFW": "จำนวน ZFW สูงสุด", + "MaxZFWCG": "จำนวน ZFWCG สูงสุด", + "PerPaxBagWeight": "น้ำหนักสัมภาระของผู้โดยสารต่อคน", + "PerPaxWeight": "น้ำหนักผู้โดยสารต่อคน", + "StartBoarding": "เริ่มการเรียกขึ้นเครื่อง" + }, + "Title": "น้ำหนักบรรทุก", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Pushback" }, @@ -162,6 +196,10 @@ "Altn": "สำรอง", "ExitFullscreenMode": "ออกจากโหมดเต็มหน้าจอ", "From": "จาก", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "ทั้งสอง", "EstablishingConnection": "กำลังสร้างการเชื่อมต่อ", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "เครื่องบินจำเป็นต้องใช้พลังงานสำหรับการโหลดค่าที่ตั้งไว้", "AircraftNeedsToBePoweredToSavePresets": "เครื่องบินจำเป็นต้องใช้พลังงานสำหรับการบันทึกค่าที่ตั้งไว้", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "โหลดค่าที่ตั้งไว้ล่วงหน้า", "LoadingPreset": "กำลังโหลดค่าที่ตั้งไว้", "NewNameConfirmationDialogMsg": "กรุณายืนยันชื่อใหม่ของค่าแสงที่ตั้งไว้", @@ -386,13 +430,15 @@ "SliderSpeed": "เคลื่อนที่สำหรับความเร็วที่ต้องการ", "SystemEnabledOff": "คลิกเพื่อเปิดระบบ Pushback", "SystemEnabledOn": "คลิกเพื่อปิดระบบ Pushback ให้ปิดอย่างสมบูรณ์", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "ซูมเข้า", "ZoomOut": "ซูมออก" }, "TugAttached": "รถลากจูงติดอยู่แล้ว", "TugDirection": "ทิศทางการลากจูง", "TugInTransit": "กำลังรอ รถลากจูง/หมุด", - "TugSpeed": "ความเร็วลากจูง" + "TugSpeed": "ความเร็วลากจูง", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "รูปแบบเวย์พอยท์ FMGC Lat/Lon", "PaxSigns": "ป้ายห้องโดยสาร", "RmpVhfSpacing": "ระยะห่าง RMP VHF", + "Satcom": "Satcom", "ThrustReductionHeight": "Thrust Reduction Height (ft)", "Title": "ตัวเลือกเครื่องบิน/โปรแกรมปักหมุด", "WeightUnit": "หน่วยน้ำหนัก" @@ -451,12 +498,15 @@ "AutofillChecklists": "Checklists ป้อนอัตโนมัติ", "BoardingTime": "เวลาขึ้นเครื่อง", "DmcSelfTestTime": "เวลา DMC Self Test", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "โหมด Home Cockpit", "McduFocusTimeout": "หมดเวลาโฟกัส MCDU (วินาที)", "McduKeyboardInput": "ป้อนข้อมูลคีย์บอร์ด MCDU", "PauseAtTod": "หยุดชั่วคราวที่ T/D", "PauseAtTodDistance": "ระยะทางหยุดชั่วคราว T/D (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "แยก Tiller ออกจากอินพุต Rudder", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "ความสมจริง" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "กรวยนิรภัย", "DefaultBarometerUnit": "หน่วย Barometer เริ่มต้น", "DynamicRegistrationDecal": "ทะเบียนเครื่องบินแบบไดนามิก", + "EnableSimBridge": "เปิดใช้งานการเชื่อมต่อ SimBridge (ปิดการทำงานหลังจาก 5 นาทีอัตโนมัติ หากไม่มีการเชื่อมต่อที่สำเร็จ)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "เปิดใช้งานการเชื่อมต่อเซิร์ฟเวอร์ MCDU (ปิดการทำงานหลังจาก 5 นาทีอัตโนมัติ หากไม่มีการเชื่อมต่อที่สำเร็จ)", "ExternalMcduServerPort": "พอร์ตเซิร์ฟเวอร์ MCDU ภายนอก", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "ไม่มี", "Off": "ปิด", "Save": "บันทึก", + "SimBridgePort": "พอร์ต SimBridge ภายนอก", "SyncMsfsFlightPlan": "ซิงค์แผนการบิน MSFS", "ThrottleDetents": "Throttle Detents", "Title": "ตัวเลือกซิม", diff --git a/src/instruments/src/EFB/Localization/tr.json b/src/instruments/src/EFB/Localization/tr.json index 03e92e575fa5..1bb02bf608dd 100644 --- a/src/instruments/src/EFB/Localization/tr.json +++ b/src/instruments/src/EFB/Localization/tr.json @@ -1,13 +1,26 @@ { "AirTrafficControl": { "Active": "Etkin", - "ChangeATISATCSourceButton": "Change ATIS/ATC source", + "ChangeATISATCSourceButton": "ATIS/ATC Kaynağını Değiştir", "ConnectHoppieACARS": "Hoppie ACARS'a bağlan", "NoInformationAvailableForThisFrequency": "BU FREKANS İÇİN MEVCUT BİLGİ YOK", - "SelectCorrectATISATCSource": "This page is only available when IVAO or VATSIM is selected as the ATIS/ATC source in the settings page.", + "SearchPlaceholder": "Search", + "SelectCorrectATISATCSource": "Bu sayfa yalnızca ayarlar sayfasında ATIS/ATC kaynağı olarak IVAO veya VATSIM seçildiğinde kullanılabilir.", "SetActive": "Etkinleştir", "SetStandby": "Beklemede Ayarla", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Beklemede", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Hava Trafik Kontrol" }, "Checklists": { @@ -127,28 +140,49 @@ "RightInnerTank": "Sağ İç Tank", "RightOuterTank": "Sağ Dış Tank", "TT": { - "AircraftMustBeColdAndDarkToChangeRefuelTimes": "Yakıt sürelerini değiştirmek için uçak Cold and Dark durumda olmalı", + "AircraftMustBeColdAndDarkToChangeRefuelTimes": "Yakıt İkmal Süresini Değiştirmek İçin Uçak Yerde ve Motorları Kapalı Olmalıdır", "FillBlockFuelFromSimBrief": "SimBrief'ten Blok Yakıtı Doldur" }, "Title": "Yakıt", "TotalFuel": "Toplam Yakıt", "Unavailable": "Kullanım Dışı" }, + "Payload": { + "BoardingTime": "Biniş Süresi", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maksimum Yolcu", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Geri itme" }, "Services": { "BaggageTruck": "Bagaj Kamyonu", "CateringTruck": "Yemek Servis Aracı", - "Cones": "Safety Cones", + "Cones": "Emniyet Konileri", "DoorAft": "Arka Giriş Kapısı", - "DoorCargo": "Cargo Door", + "DoorCargo": "Yükleme Kapısı", "DoorFwd": "Ön Giriş Kapısı", - "ExternalPower": "Harici Güç", + "ExternalPower": "Yer Güç Ünitesi", "FuelTruck": "Yakıt Kamyonu", "JetBridge": "Körük", "Title": "Hizmetler", - "WheelChocks": "Wheel Chocks" + "WheelChocks": "Tekerlek Takozları" }, "Title": "Yer" }, @@ -162,6 +196,10 @@ "Altn": "Alternatif", "ExitFullscreenMode": "Tam Ekran Modundan Çık", "From": "Kalkış", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "PDF Yükleniyor", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "İkisi de", "EstablishingConnection": "Bağlantı Kuruluyor", @@ -177,7 +215,7 @@ "AuthenticateWithNavigraph": "Navigraph ile Kimlik Doğrulama", "InsufficientEnv": "Yetersiz .env dosyası", "IntoYourBrowserAndEnterTheCodeBelow": "tarayıcınıza girin ve aşağıdaki kodu girin\n", - "LoadingMsg": "Loading", + "LoadingMsg": "Yükleniyor", "NoAirportSelected": "Havaalanı Seçilmedi", "ResetNavigraphAuthentication": "Navigraph Kimlik Doğrulamasını Sıfırla", "ScanTheQrCodeOrOpen": "QR Kodunu tarayın veya Açın", @@ -199,7 +237,7 @@ "TT": { "ChangeChartProvider": "Harita Sağlayıcısını Değiştir", "ChangeChartSortMethod": "Harita Sıralama Yöntemini Değiştir", - "RemoveAllPinnedCharts": "Remove All Pinned Charts" + "RemoveAllPinnedCharts": "Tüm Sabitlenmiş Haritaları Kaldır" }, "Title": "Sabitlenmiş Haritalar", "View": "Görüntüle" @@ -236,8 +274,8 @@ "Low": "Alçak", "MaximumManual": "Maksimum Manuel", "Medium": "Orta", - "MetarErrorDialogMessage": "The METAR from your OFP could not be successfully parsed. Would you like to autofill data using a fetched METAR instead?", - "MetarErrorDialogTitle": "Error Parsing OFP METAR", + "MetarErrorDialogMessage": "OFP'den okunan METAR ayrıştırılamadı. Bunun yerine mevcut METAR'ı kullanarak verileri otomatik doldurmak ister misiniz?", + "MetarErrorDialogTitle": "OFP METAR Ayrıştırma Hatası", "OverweightProcedure": "Overweight Prosedürleri", "Qnh": "QNH", "ReverseThrust": "Reverse Thrust", @@ -245,12 +283,12 @@ "RunwayAltitudeUnit": "ft ASL", "RunwayCondition": "Pist Durumu", "RunwayConditions": { - "Dry": "Dry (6)", - "Good": "Good (5)", - "GoodMedium": "Good-Medium (4)", - "Medium": "Medium (3)", - "MediumPoor": "Medium-Poor (2)", - "Poor": "Poor (1)" + "Dry": "Kuru (6)", + "Good": "İyi (5)", + "GoodMedium": "İyi-Orta (4)", + "Medium": "Orta (3)", + "MediumPoor": "Orta-Kötü (2)", + "Poor": "Kötü (1)" }, "RunwayHeading": "Pist Başı", "RunwayHeadingUnit": "°", @@ -333,9 +371,15 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "Ön Ayarları Yüklemek için Uçağa Güç Verilmesi Gerekiyor", "AircraftNeedsToBePoweredToSavePresets": "Ön Ayarları Kaydetmek için Uçağa Güç Verilmesi Gerekiyor", + "AutoLoadDawnDusk": "Gün batımı:", + "AutoLoadDay": "Gündüz:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Aydınlatma ön ayarlarının otomatik yüklenmesini etkinleştirin:", + "AutoLoadNight": "Akşam:", + "AutoLoadNoneSelection": "Boş", "LoadPreset": "Ön Ayarı Yükle", "LoadingPreset": "Ön Ayar Yükleniyor", - "NewNameConfirmationDialogMsg": "Please confirm the new name for the lighting preset", + "NewNameConfirmationDialogMsg": "Lütfen aydınlatma ön ayarının yeni adını onaylayın", "NoName": "İsimsiz", "PleaseConfirmSavingPreset": "Lütfen Ön Ayarı Kaydetmeyi Onaylayın", "SavePreset": "Ön ayarı kaydet", @@ -353,46 +397,48 @@ "Title": "Ön Ayarlar" }, "Pushback": { - "AvailableOnlyOnGround": "Pushback controls are only available on the ground", + "AvailableOnlyOnGround": "Pushback kontrolleri sadece yerde kullanılabilir", "Backward": "Geri", - "CallTug": "Çekiciye Haber Ver", - "DisableSystemMessageBody": "Are you sure you want to disable the Pushback System? This will also release the tug.", - "DisableSystemMessageTitle": "Disable Pushback System", - "EnableSystemMessageBody": "The new flyPadOS 3 Pushback System can cause conflicts with other pushback add-ons. We recommend not to enable the system if you plan to use another pushback add-on. ", - "EnableSystemMessageTitle": "Enable Pushback System", + "CallTug": "Römorköre Haber Ver", + "DisableSystemMessageBody": "Pushback Sistemini devre dışı bırakmak istediğinizden emin misiniz? Bu aynı zamanda römorkörü serbest bırakacaktır.", + "DisableSystemMessageTitle": "Pushback sistemini devre dışı bırak", + "EnableSystemMessageBody": "Yeni flyPadOS 3 Pushback Sistemi henüz yapım aşamasında. Bu sistem diğer Pushback eklentileriyle uyumsuzluklara ve başka sorunlara neden olabilir.", + "EnableSystemMessageTitle": "Pushback Sistemini Etkinleştir", "Forward": "İleri", - "Halt": "Durdur", - "LeavePageMessage": "Pausing Pushback", - "Left": "Left", - "Moving": "Moving", + "Halt": "Duraklatıldı", + "LeavePageMessage": "Pushback Duraklatılıyor", + "Left": "Sol", + "Moving": "Hareket halinde", "ParkingBrake": { "Off": "Kapalı", "On": "Açık", "Title": "Park Freni" }, - "Right": "Right", - "SystemEnabledOff": "Pushback System Off", - "SystemEnabledOn": "Pushback System On", + "Right": "Sağ", + "SystemEnabledOff": "Pushback Sistemi Kapalı", + "SystemEnabledOn": "Pushback Sistemi Açık", "TT": { - "CallReleaseTug": "Call or release tug", - "CenterPlaneMode": "Center map on plane On/Off", - "DecreaseSpeed": "Backwards or slower forwards", - "IncreaseSpeed": "Forwards or slower backwards", - "Left": "Go left", - "PausePushback": "Pause or unpause pushback", - "Right": "Go right", - "SetReleaseParkingBrake": "Set or release parking brake", - "SliderDirection": "Move for desired direction", - "SliderSpeed": "Move for desired speed", - "SystemEnabledOff": "Click to turn the pushback system on.", - "SystemEnabledOn": "Click to turn the pushback system completely off.", - "ZoomIn": "Zoom in", - "ZoomOut": "Zoom out" + "CallReleaseTug": "Römorkörü çağırın veya serbest bırakın", + "CenterPlaneMode": "Uçağı harita üzerinde ortalama özelliği açık/kapalı", + "DecreaseSpeed": "Geri veya daha yavaş ileri", + "IncreaseSpeed": "İleri veya daha yavaş geri", + "Left": "Sola git", + "PausePushback": "Pushback'i duraklat veya devam ettir", + "Right": "Sağa git", + "SetReleaseParkingBrake": "Park Frenini Aç veya Kapat", + "SliderDirection": "İstediğiniz yöne hareket ettirin", + "SliderSpeed": "İstediğiniz hıza hareket ettirin", + "SystemEnabledOff": "Pushback sistemini etkinleştirmek için tıklayın.", + "SystemEnabledOn": "Pushback sistemini tamamen devre dışı bırakmak için tıklayın.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", + "ZoomIn": "Büyüt", + "ZoomOut": "Küçült" }, - "TugAttached": "Tug is attached", - "TugDirection": "Çekici Yönü", - "TugInTransit": "Waiting for Tug/Pin", - "TugSpeed": "Çekici Hızı" + "TugAttached": "Römorkör bağlı", + "TugDirection": "Römorkör Yönü", + "TugInTransit": "Römorkör/Pin Bekleniyor", + "TugSpeed": "Römorkör Hızı", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -400,12 +446,13 @@ }, "AircraftOptionsPinPrograms": { "AccelerationHeight": "İvme Yüksekliği (ft)", - "EngineOutAccelerationHeight": "Engine-Out Acceleration Height (ft)", + "EngineOutAccelerationHeight": "Motor Arıza İvmelenme Yüksekliği (ft)", "IsisBaroUnit": "ISIS Baro Birimi", "IsisMetricAltitude": "ISIS Metrik İrtifası", - "LatLonExtendedFormat": "FMGC Lat/Lon Waypoint Format", + "LatLonExtendedFormat": "FMGC Enlem/Boylam Yol-noktası Biçimi", "PaxSigns": "Yolcu İşaretleri", "RmpVhfSpacing": "RMP VHF Aralığı", + "Satcom": "Satcom", "ThrustReductionHeight": "İtki Azaltma İrtifası (ft)", "Title": "Uçak Seçenekleri / Pin Programları", "WeightUnit": "Ağırlık Birimi" @@ -416,7 +463,7 @@ "Enable": "Etkinleştir", "EnableTelex": "TELEX'i Etkinleştir", "ErrorReporting": "Hata Raporlama", - "HoppieEnabled": "Hoppie enabled", + "HoppieEnabled": "Hoppie etkin", "HoppieUserId": "Hoppie Kullanıcı Kimliği", "MetarSource": "METAR Kaynağı", "OptionalA32nxErrorReporting": "İsteğe Bağlı A32NX Hata Bildirimi", @@ -451,21 +498,25 @@ "AutofillChecklists": "Kontrol listelerini Otomatik Doldur", "BoardingTime": "Biniş Süresi", "DmcSelfTestTime": "DMC Self Test Süresi", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "Ev Kokpit Modu", "McduFocusTimeout": "MCDU Focus Zaman Aşımı (saniye)", "McduKeyboardInput": "MCDU Klavye Girişi", - "PauseAtTod": "T/D Pause", - "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PauseAtTod": "T/D Noktasında Duraklat", + "PauseAtTodDistance": "T/D Noktasında Duraklatma Mesafesi (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "Tiller'ı Rudder Girişlerinden Ayır", + "SyncEfis": "EFIS kontrollerini Kaptan'dan FO'ya senkronize edin", "Title": "Gerçekçilik" }, "SimOptions": { "Active": "Etkin", "Auto": "Otomatik", "Calibrate": "Kalibrasyon", - "ConesEnabled": "Safety Cones", + "ConesEnabled": "Emniyet Konileri", "DefaultBarometerUnit": "Varsayılan Barometre Birimi", "DynamicRegistrationDecal": "Dinamik Kayıt Çıkartması", + "EnableSimBridge": "SimBridge Bağlantısını Etkinleştir (Başarılı bir bağlantı yoksa 5 dakika sonra otomatik olarak devre dışı bırakılır)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "MCDU Sunucu Bağlantısını Etkinleştir (Başarılı bağlantı olmazsa 5 dakika sonra otomatik olarak devre dışı bırakılır)", "ExternalMcduServerPort": "Harici MCDU Sunucu Bağlantı Noktası", "Hpa": "hPa", @@ -474,11 +525,12 @@ "None": "Hiçbiri", "Off": "Kapalı", "Save": "Kaydet", + "SimBridgePort": "Harici SimBridge Bağlantı Noktası", "SyncMsfsFlightPlan": "MSFS Uçuş Planını Eşitle", "ThrottleDetents": "Gaz Mandalları", "Title": "Simülasyon Seçenekleri", "UseCalculatedIlsSignals": "Hesaplanan ILS Sinyallerini Kullan", - "WheelChocksEnabled": "Wheel Chocks", + "WheelChocksEnabled": "Tekerlek Takozları", "inHg": "inHg" }, "ThrottleConfig": { @@ -487,7 +539,7 @@ "Axis": "Eksen", "Back": "Geri", "ConfigureEnd": "Sonu Yapılandır", - "ConfigureStart": "Configure Start", + "ConfigureStart": "Başlat", "CurrentValue": "Geçerli Değer", "Deadband": "Ölü bant", "ErrorOverlapMsg": "ile örtüşen", @@ -507,7 +559,7 @@ "flyPad": { "AutoBrightness": "Otomatik Parlaklık", "AutomaticallyShowOnscreenKeyboard": "Ekran Klavyesini Otomatik Olarak Göster", - "BatteryLifeEnabled": "Battery Life Simulated", + "BatteryLifeEnabled": "Pil Ömrü Simülasyonu Etkin", "Blue": "Mavi", "Brightness": "Parlaklık", "Dark": "Koyu", @@ -534,7 +586,7 @@ "Feb": "Şub", "Fri": "Cum", "Jan": "Oca", - "Jul": "Jul", + "Jul": "Tem", "Jun": "Haz", "Mar": "Mar", "May": "Mayıs", @@ -545,8 +597,8 @@ "Sep": "Eyl", "Sun": "Paz", "TT": { - "ConnectedToLocalApi": "Yerel API'ye Bağlandı", - "DisconnectedFromLocalApi": "Yerel API bağlantısı kesildi", + "ConnectedToLocalApi": "SimBridge’e Bağlandı", + "DisconnectedFromLocalApi": "SimBridge Bağlantısı Kesildi", "TurnOffOrShutdownEfb": "EFB'yi Kapatın" }, "Thu": "Per", diff --git a/src/instruments/src/EFB/Localization/vi.json b/src/instruments/src/EFB/Localization/vi.json index 8efb920b182d..2781afb6c9e6 100644 --- a/src/instruments/src/EFB/Localization/vi.json +++ b/src/instruments/src/EFB/Localization/vi.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Thay đổi nguồn ATIS/ATC", "ConnectHoppieACARS": "Kết nối Hoppie ACARS", "NoInformationAvailableForThisFrequency": "Không có thông tin có sẵn cho tần số này", + "SearchPlaceholder": "Tìm kiếm", "SelectCorrectATISATCSource": "Trang này chỉ khả dụng khi IVAO hoặc VATSIM được sử dụng làm nguồn ATIS/ATC trong trang Cài đặt.", "SetActive": "Đặt hoạt động", "SetStandby": "Đặt chế độ chờ", + "ShowAll": "Tất cả", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Mặt đất", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "Chế độ chờ", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "Điều khiển không lưu" }, "Checklists": { @@ -39,7 +52,7 @@ "Title": "Biểu đồ đã ghim" }, "TT": { - "RearrangeWidgets": "Sắp xếp lại widgets" + "RearrangeWidgets": "Sắp xếp lại các widgets" }, "Title": "Thông tin quan trọng", "Weather": { @@ -85,7 +98,7 @@ "YouHaveNotYetImportedAnySimBriefData": "Bạn chưa nhập bất kỳ dữ liệu SimBrief nào." }, "Overview": { - "ActualGW": "Actual GW", + "ActualGW": "Tổng trọng lượng thật", "Engines": "Động cơ", "MMO": "Tốc độ vận hành tối đa theo số Mach", "MRW": "Trong lượng bãi đậu tối đa", @@ -127,13 +140,34 @@ "RightInnerTank": "Bình phía phải trong cùng", "RightOuterTank": "Bình phía phải ngoài cùng", "TT": { - "AircraftMustBeColdAndDarkToChangeRefuelTimes": "Máy bay phải ở trong chế độ Cold and Dark để thay đổi thời gian tiếp nhiên liệu", + "AircraftMustBeColdAndDarkToChangeRefuelTimes": "Máy bay phải ở trên mặt đất và động cơ phải được tắt để thay đổi thời gian tiếp nhiên liệu", "FillBlockFuelFromSimBrief": "Nạp nhiên liệu từ SimBrief" }, "Title": "Nhiên liệu", "TotalFuel": "Tổng nhiên liệu", "Unavailable": "Không khả dụng" }, + "Payload": { + "BoardingTime": "Thời gian lên máy bay", + "Cargo": "Hàng hóa", + "Current": "Hiện tại", + "Passengers": "Hành khách", + "Planned": "Đã lên kế hoạch", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Máy bay phải ở trên mặt đất và phải có động cơ tắt để thay đổi thời gian lên máy bay", + "FillPayloadFromSimbrief": "Điền thông tin tải trọng từ Simbrief", + "MaxCargo": "Số lượng hàng hóa tối đa", + "MaxPassengers": "Số hành khách tối đa", + "MaxZFW": "ZFW tối đa", + "MaxZFWCG": "ZFWCG tối đa", + "PerPaxBagWeight": "Trọng lượng mỗi túi hành khách", + "PerPaxWeight": "Trọng lượng mỗi hành khách", + "StartBoarding": "Bắt đầu lên máy bay" + }, + "Title": "Tải trọng", + "ZFW": "Trọng lượng máy bay không kể nhiên liệu", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "Đẩy lùi" }, @@ -144,7 +178,7 @@ "DoorAft": "Cửa phía sau", "DoorCargo": "Cửa hàng hóa vận chuyển", "DoorFwd": "Cửa phía trước", - "ExternalPower": "Nguồn điện phía ngoài", + "ExternalPower": "Nguồn điện từ mặt đất", "FuelTruck": "Xe chở nhiên liệu", "JetBridge": "Ống lồng", "Title": "Các dịch vụ", @@ -162,6 +196,10 @@ "Altn": "Thay thế", "ExitFullscreenMode": "Thoát chế độ toàn màn hình", "From": "Từ", + "LoadingImage": "Đang tải hình ảnh", + "LoadingImageFailed": "Tải ảnh thất bại", + "LoadingPdf": "Đang tải PDF", + "LoadingPdfFailed": "Tải PDF thất bại", "LocalFiles": { "Both": "Cả hai", "EstablishingConnection": "Đang thiết lập kết nối", @@ -177,7 +215,7 @@ "AuthenticateWithNavigraph": "Xác thực với Navigraph", "InsufficientEnv": "Tệp .env không đủ", "IntoYourBrowserAndEnterTheCodeBelow": "vào trình duyệt của bạn và nhập mã dưới đây\n", - "LoadingMsg": "Đang tải…", + "LoadingMsg": "Đang tải", "NoAirportSelected": "Không có sân bay nào được chọn", "ResetNavigraphAuthentication": "Đặt lại xác thực Navigraph", "ScanTheQrCodeOrOpen": "Quét mã QR hoặc Mở", @@ -236,7 +274,7 @@ "Low": "Thấp", "MaximumManual": "Mức độ tối đa khi lái tay", "Medium": "Trung bình", - "MetarErrorDialogMessage": "METAR từ OFP của bạn không thể được phân tích thành công. Bạn có muốn tự động điền dữ liệu bằng METAR đã được lấy từ nguồn khác không?", + "MetarErrorDialogMessage": "METAR từ OFP của bạn không được phân tích thành công. Bạn có muốn tự động điền dữ liệu bằng METAR đã được lấy từ nguồn khác không?", "MetarErrorDialogTitle": "Lỗi phân tích OFP METAR", "OverweightProcedure": "Thủ tục quá trọng lượng", "Qnh": "QNH", @@ -324,18 +362,24 @@ "ReadyPushback": "Sẵn sàng để đẩy lùi", "ReadyTakeoff": "Sẵn sàng để cất cánh", "ReadyTaxi": "Sẵn sàng để lăn", - "SelectAPresetToLoad": "Chọn cấu hình sẵn để tải", - "TheAircraftMustBeOnTheGroundToLoadAPreset": "Máy bay cần phải ở trên mặt đất để tải cấu hình sẵn.", - "TheAircraftMustBeOntheGroundToLoadAPreset": "Máy bay cần phải ở trên mặt đất để tải cấu hình sẵn.", + "SelectAPresetToLoad": "Chọn cấu hình đặt sẵn để tải", + "TheAircraftMustBeOnTheGroundToLoadAPreset": "Máy bay cần phải ở trên mặt đất để tải cấu hình đặt sẵn.", + "TheAircraftMustBeOntheGroundToLoadAPreset": "Máy bay cần phải ở trên mặt đất để tải cấu hình đặt sẵn.", "Title": "Trạng thái máy bay", "Turnaround": "Quay đầu" }, "InteriorLighting": { - "AircraftNeedsToBePoweredToLoadPresets": "Máy bay cần được cung cấp năng lượng để tải cấu hình sẵn", - "AircraftNeedsToBePoweredToSavePresets": "Máy bay cần được cung cấp năng lượng để lưu cấu hình sẵn", + "AircraftNeedsToBePoweredToLoadPresets": "Máy bay cần được cung cấp năng lượng để tải cấu hình đặt sẵn", + "AircraftNeedsToBePoweredToSavePresets": "Máy bay cần được cung cấp năng lượng để lưu cấu hình đặt sẵn", + "AutoLoadDawnDusk": "Bình minh/Hoàng hôn:", + "AutoLoadDay": "Ban ngày:", + "AutoLoadLightingHelpTitle": "Tự động nạp cấu hình sẵn", + "AutoLoadLightingPreset": "Bật tự động tải cài đặt đèn:", + "AutoLoadNight": "Ban đêm:", + "AutoLoadNoneSelection": "Không", "LoadPreset": "Tải cấu hình sẵn", "LoadingPreset": "Đang tải cấu hình sẵn", - "NewNameConfirmationDialogMsg": "Vui lòng xác nhận tên mới của Cấu hình bố trí đèn đặt sẵn", + "NewNameConfirmationDialogMsg": "Vui lòng xác nhận tên mới của cấu hình đèn đặt sẵn", "NoName": "Không tên", "PleaseConfirmSavingPreset": "Vui lòng xác nhận lưu cấu hình sẵn.", "SavePreset": "Lưu cấu hình sẵn", @@ -358,7 +402,7 @@ "CallTug": "Gọi xe kéo", "DisableSystemMessageBody": "Bạn có chắc muốn vô hiệu hóa hệ thống đẩy lùi không? Điều này sẽ làm ngắt kết nối với xe lai dắt.", "DisableSystemMessageTitle": "Vô hiệu hóa hệ thống đẩy lùi", - "EnableSystemMessageBody": "Hệ thống đẩy lùi của flyPadOS 3 chưa được hoàn thiện. Nó không tương thích với các tiện ích đẩy lùi khác và có thể gây ra lỗi. Chúng tôi khuyên bạn không nên dùng hệ thống này nếu bạn định dùng một tiện ích đẩy lùi khác.", + "EnableSystemMessageBody": "Hệ thống đẩy lùi mới của flyPadOS 3 có thể gây xung đột với các tiện ích đẩy lùi khác. Chúng tôi khuyến khích bạn không nên kích hoạt hệ thống này nếu bạn định dùng tiện ích đẩy lùi khác.", "EnableSystemMessageTitle": "Kích hoạt hệ thống đẩy lùi", "Forward": "Tiến", "Halt": "Tạm dừng", @@ -386,13 +430,15 @@ "SliderSpeed": "Di chuyển thanh trượt cho tốc độ mong muốn", "SystemEnabledOff": "Nhấp để bật hệ thống đẩy lùi.", "SystemEnabledOn": "Nhấp để tắt hoàn toàn hệ thống đẩy lùi.", + "UseControllerInput": "Bật/tắt đầu vào bộ điều khiển cho bàn đạp và bánh lái độ cao", "ZoomIn": "Phóng to", "ZoomOut": "Thu nhỏ" }, "TugAttached": "Xe lai dắt được kết nối", "TugDirection": "Hướng của xe kéo", "TugInTransit": "Đang chờ xe đẩy", - "TugSpeed": "Tốc độ xe kéo" + "TugSpeed": "Tốc độ xe kéo", + "UseControllerInput": "Sử dụng đầu vào từ bộ điều khiển" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "Định dạng điểm trên FMGC dạng vĩ/kinh", "PaxSigns": "Đèn báo hiệu hành khách", "RmpVhfSpacing": "Khoảng cách RMP VHF", + "Satcom": "Satcom", "ThrustReductionHeight": "Độ cao giảm lực đẩy (ft)", "Title": "Tùy chọn máy bay / Pin Programs", "WeightUnit": "Đơn vị trọng lượng" @@ -424,7 +471,7 @@ "SimBriefUsernamePilotId": "Tên người dùng SimBrief/ID Pilot", "TafSource": "Nguồn TAF", "Telex": "TELEX", - "TelexEnablesFreeTextAndLiveMap": "TELEX cho phép nhắn tin miễn phí và bản đồ trực tiếp. Nếu được kích hoạt, dữ liệu vị trí máy bay được công bố trong suốt thời gian của chuyến bay. Tin nhắn được công khai và không được kiểm duyệt. BẠN PHẢI TỰ CHỊU TRÁCH NGHIỆM KHI DÙNG TÍNH NĂNG NÀY. Để tìm hiểu thêm về TELEX và các tính năng mà nó cho phép, vui lòng truy cập https://docs.flybywiresim.com/telex.", + "TelexEnablesFreeTextAndLiveMap": "TELEX cho phép nhắn tin miễn phí và đồng thời bật bản đồ trực tiếp. Nếu được kích hoạt, dữ liệu vị trí máy bay được công bố trong suốt thời gian của chuyến bay. Tin nhắn được công khai và không được kiểm duyệt. BẠN TỰ CHỊU MỌI RỦI RO KHI SỬ DỤNG TÍNH NĂNG NÀY. Để tìm hiểu thêm về TELEX và các tính năng mà nó cho phép, vui lòng truy cập https://docs.flybywiresim.com/telex.", "TelexWarning": "Cảnh báo TELEX", "ThereWasAnErrorEncounteredWhenValidatingYourHoppieID": "Có lỗi xảy ra trong khi xác nhận ID Hoppie của bạn", "Title": "ATSU / AOC", @@ -451,12 +498,15 @@ "AutofillChecklists": "Tự đồng điền danh sách kiểm tra", "BoardingTime": "Thời gian lên máy bay", "DmcSelfTestTime": "Thời gian tự kiểm tra của DMC", + "FirstOfficerAvatar": "Hiển thị Avatar Cơ Phó", "HomeCockpitMode": "Chế độ buồng lái tại nhà", "McduFocusTimeout": "Thời gian chờ MCDU chỉnh (giây)", "McduKeyboardInput": "Dùng bàn phím cho MCDU", "PauseAtTod": "Dừng tại điểm hạ độ cao", "PauseAtTodDistance": "Khoảng cách dừng từ điểm hạ độ cao (nm)", + "PilotAvatar": "Hiện thị Avatar Phi công", "SeparateTillerFromRudderInputs": "Tách rời Điều Khiển Bánh Mũi với Bàn Đạp", + "SyncEfis": "Đồng bộ hóa các điều khiển EFIS giữa cơ trưởng và cơ phó", "Title": "Mức thực tế" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Chóp nón An toàn", "DefaultBarometerUnit": "Đơn vị áp kế mặc định", "DynamicRegistrationDecal": "Đề-can đăng ký động", + "EnableSimBridge": "Bật kết nối Simbridge (Tự động ngừng kích hoạt sau 5 phút nếu không có kết nối thành công)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "Bật kết nối máy chủ MCDU (Tự động ngừng kích hoạt sau 5 phút nếu không có kết nối thành công)", "ExternalMcduServerPort": "Cổng máy chủ MCDU bên ngoài", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "Không", "Off": "Tắt", "Save": "Lưu", + "SimBridgePort": "Cổng SimBridge bên ngoài", "SyncMsfsFlightPlan": "Đồng bộ hóa Kế hoạch chuyến bay của MSFS", "ThrottleDetents": "Chốt cần ga", "Title": "Tùy chọn giả lập", @@ -545,8 +597,8 @@ "Sep": "Thg 9", "Sun": "CN", "TT": { - "ConnectedToLocalApi": "Kết nối tới API cục bộ", - "DisconnectedFromLocalApi": "Ngắt kết nối khỏi API cục bộ", + "ConnectedToLocalApi": "Đã được kết nối tới SimBridge", + "DisconnectedFromLocalApi": "Đã ngắt kết nối khỏi SimBridge", "TurnOffOrShutdownEfb": "Tắt hoặc tắt máy EFB" }, "Thu": "Th 5", diff --git a/src/instruments/src/EFB/Localization/zh-Hans-CN.json b/src/instruments/src/EFB/Localization/zh-Hans-CN.json index 2d29004c1a15..ea4496db7b44 100644 --- a/src/instruments/src/EFB/Localization/zh-Hans-CN.json +++ b/src/instruments/src/EFB/Localization/zh-Hans-CN.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "更改 ATIS/ATC 来源", "ConnectHoppieACARS": "连接 Hoppie ACARS", "NoInformationAvailableForThisFrequency": "当前频率无可用信息", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "只有在设置页面中选择 IVAO 或 VATSIM 作为 ATIS/ATC 来源时,此页面才可用。", "SetActive": "设置为启用", "SetStandby": "设置为待命", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "待命", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "空中交通管制" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "总燃油量", "Unavailable": "不可用" }, + "Payload": { + "BoardingTime": "登机需时", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "最大载客人数", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "零油重量", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "后推" }, @@ -162,6 +196,10 @@ "Altn": "备降机场", "ExitFullscreenMode": "退出全屏模式", "From": "从", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "全部", "EstablishingConnection": "正在建立连接", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "飞机必须接通电源才能加载预设", "AircraftNeedsToBePoweredToSavePresets": "飞机必须接通电源才能保存预设", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "加载预设", "LoadingPreset": "正在加载预设", "NewNameConfirmationDialogMsg": "请确认灯光预设配置的新名称", @@ -386,13 +430,15 @@ "SliderSpeed": "以所需的速度移动", "SystemEnabledOff": "点击以打开退出系统", "SystemEnabledOn": "点击以完全关闭推出系统", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "放大", "ZoomOut": "缩小" }, "TugAttached": "拖杆已连接", "TugDirection": "后推方向", "TugInTransit": "等待拖车/销", - "TugSpeed": "后推速度" + "TugSpeed": "后推速度", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC 纬度/经度 航点格式", "PaxSigns": "客舱标志", "RmpVhfSpacing": "RMP 甚高频间隔", + "Satcom": "Satcom", "ThrustReductionHeight": "减推力高度 (英尺/ft)", "Title": "飞机选项/置顶程序", "WeightUnit": "重量单位" @@ -451,12 +498,15 @@ "AutofillChecklists": "自动填充检查单", "BoardingTime": "登机需时", "DmcSelfTestTime": "DMC 自检需时", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "家用驾驶舱模式", "McduFocusTimeout": "MCDU 对焦超时 (秒)", "McduKeyboardInput": "MCDU 键盘输入", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "将舵柄与脚舵输入分开", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "仿真度" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "安全锥", "DefaultBarometerUnit": "默认气压单位", "DynamicRegistrationDecal": "动态注册号喷涂", + "EnableSimBridge": "启用 MCDU 服务器 (5 分钟之内无连接将自动停用)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "启用 MCDU 服务器 (5 分钟之内无连接将自动停用)", "ExternalMcduServerPort": "MCDU 服务器端口", "Hpa": "百帕 (hPa)", @@ -474,6 +525,7 @@ "None": "无", "Off": "关", "Save": "保存", + "SimBridgePort": "MCDU 服务器端口", "SyncMsfsFlightPlan": "同步 MSFS 内飞行计划", "ThrottleDetents": "油门卡位", "Title": "模拟器选项", diff --git a/src/instruments/src/EFB/Localization/zh-Hant-HK.json b/src/instruments/src/EFB/Localization/zh-Hant-HK.json index 029d733106bb..68f56891b1dc 100644 --- a/src/instruments/src/EFB/Localization/zh-Hant-HK.json +++ b/src/instruments/src/EFB/Localization/zh-Hant-HK.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "更改 ATIS/ATC 來源", "ConnectHoppieACARS": "連接 Hoppie ACARS", "NoInformationAvailableForThisFrequency": "此頻率沒有資料", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "您需要在設置中選擇 IVAO 或 VATSIM 為 ATC 來源才可使用此頁面。", "SetActive": "設定為使用中", "SetStandby": "設定為待命", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "待命", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "空中管制" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "總計油量", "Unavailable": "唔用得" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "推機" }, @@ -162,6 +196,10 @@ "Altn": "在備機場", "ExitFullscreenMode": "退出全屏模式", "From": "由", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "兩者", "EstablishingConnection": "正在連接", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "飛機必須通電才能加載預設", "AircraftNeedsToBePoweredToSavePresets": "飛機必須通電才能儲存預設", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "加載預設", "LoadingPreset": "正在加載預設", "NewNameConfirmationDialogMsg": "請確認新照明預設的名稱", @@ -386,13 +430,15 @@ "SliderSpeed": "移動到所需速度", "SystemEnabledOff": "Click to turn the pushback system on.", "SystemEnabledOn": "Click to turn the pushback system completely off.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "放大", "ZoomOut": "縮小" }, "TugAttached": "Tug is attached", "TugDirection": "推機方向", "TugInTransit": "Waiting for Tug/Pin", - "TugSpeed": "推機速度" + "TugSpeed": "推機速度", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC 緯度/經度 (Lat/Lon) 航點格式", "PaxSigns": "客倉標誌", "RmpVhfSpacing": "RMP 高頻間距", + "Satcom": "Satcom", "ThrustReductionHeight": "減油高度 (英尺,ft)", "Title": "飛機選項/電針程式", "WeightUnit": "重量單位" @@ -451,12 +498,15 @@ "AutofillChecklists": "Checklist 自動填充", "BoardingTime": "登機需時", "DmcSelfTestTime": "DMC 自檢需時", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "砌機駕駛艙模式", "McduFocusTimeout": "MCDU 對焦超時 (秒)", "McduKeyboardInput": "MCDU 鍵盤輸入", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "將舵柄與腳踏輸入分開", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "仿真度" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Safety Cones", "DefaultBarometerUnit": "氣壓單位", "DynamicRegistrationDecal": "註冊印花隨機", + "EnableSimBridge": "啟用 MCDU 伺服器 (5 分鐘之內無連接下會自動停用)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "啟用 MCDU 伺服器 (5 分鐘之內無連接下會自動停用)", "ExternalMcduServerPort": "MCDU 伺服器端口 (Port)", "Hpa": "毫巴 (hPa)", @@ -474,6 +525,7 @@ "None": "非同步", "Off": "關", "Save": "儲存", + "SimBridgePort": "MCDU 伺服器端口 (Port)", "SyncMsfsFlightPlan": "同步處理 MSFS 航線", "ThrottleDetents": "油門停點", "Title": "模擬選項", diff --git a/src/instruments/src/EFB/Localization/zh-Hant-TW.json b/src/instruments/src/EFB/Localization/zh-Hant-TW.json index 274d6131ea91..fe01a45c4211 100644 --- a/src/instruments/src/EFB/Localization/zh-Hant-TW.json +++ b/src/instruments/src/EFB/Localization/zh-Hant-TW.json @@ -4,10 +4,23 @@ "ChangeATISATCSourceButton": "Change ATIS/ATC source", "ConnectHoppieACARS": "連接 Hoppie ACARS", "NoInformationAvailableForThisFrequency": "此頻率無可用信息", + "SearchPlaceholder": "Search", "SelectCorrectATISATCSource": "This page is only available when IVAO or VATSIM is selected as the ATIS/ATC source in the settings page.", "SetActive": "設定為啟用", "SetStandby": "設定為待機", + "ShowAll": "All", + "ShowApproach": "Approach", + "ShowAtis": "ATIS", + "ShowDelivery": "Delivery", + "ShowDeparture": "Departure", + "ShowGround": "Ground", + "ShowRadar": "Radar", + "ShowTower": "Tower", "Standby": "待機", + "TT": { + "AtcCallSignSearch": "ATC Call-sign Search", + "AtcTypeFilter": "Filter for" + }, "Title": "航空交通管制" }, "Checklists": { @@ -134,6 +147,27 @@ "TotalFuel": "總燃油量", "Unavailable": "不可用" }, + "Payload": { + "BoardingTime": "Boarding Time", + "Cargo": "Cargo", + "Current": "Current", + "Passengers": "Passengers", + "Planned": "Planned", + "TT": { + "AircraftMustBeColdAndDarkToChangeBoardingTimes": "Aircraft Must Be On the Ground and Have Engines Shutdown to Change Boarding Duration", + "FillPayloadFromSimbrief": "Fill Payload Information from Simbrief", + "MaxCargo": "Max Cargo", + "MaxPassengers": "Maximum Passengers", + "MaxZFW": "Maximum ZFW", + "MaxZFWCG": "Maximum ZFWCG", + "PerPaxBagWeight": "Per Passenger Bag Weight", + "PerPaxWeight": "Per Passenger Weight", + "StartBoarding": "Begin Boarding" + }, + "Title": "Payload", + "ZFW": "ZFW", + "ZFWCG": "ZFWCG" + }, "Pushback": { "Title": "後推" }, @@ -162,6 +196,10 @@ "Altn": "備降機場", "ExitFullscreenMode": "離開全螢幕模式", "From": "起飛機場", + "LoadingImage": "Loading Image", + "LoadingImageFailed": "Loading Image failed", + "LoadingPdf": "Loading PDF", + "LoadingPdfFailed": "Loading PDF failed", "LocalFiles": { "Both": "全部", "EstablishingConnection": "正在建立連線...", @@ -333,6 +371,12 @@ "InteriorLighting": { "AircraftNeedsToBePoweredToLoadPresets": "飛機需要通電才能加載預設", "AircraftNeedsToBePoweredToSavePresets": "飛機需要通電以保存預設", + "AutoLoadDawnDusk": "Dawn/Dusk:", + "AutoLoadDay": "Day:", + "AutoLoadLightingHelpTitle": "Automatic Preset Loading", + "AutoLoadLightingPreset": "Enable automatic loading of lighting presets:", + "AutoLoadNight": "Night:", + "AutoLoadNoneSelection": "None", "LoadPreset": "加載預設", "LoadingPreset": "加載預設", "NewNameConfirmationDialogMsg": "Please confirm the new name for the lighting preset", @@ -386,13 +430,15 @@ "SliderSpeed": "Move for desired speed", "SystemEnabledOff": "Click to turn the pushback system on.", "SystemEnabledOn": "Click to turn the pushback system completely off.", + "UseControllerInput": "Turn controller input from rudder and elevator on/off", "ZoomIn": "Zoom in", "ZoomOut": "Zoom out" }, "TugAttached": "Tug is attached", "TugDirection": "推機方向", "TugInTransit": "Waiting for Tug/Pin", - "TugSpeed": "推機速度" + "TugSpeed": "推機速度", + "UseControllerInput": "Use Controller Input" }, "Settings": { "About": { @@ -406,6 +452,7 @@ "LatLonExtendedFormat": "FMGC Lat/Lon Waypoint Format", "PaxSigns": "客艙標誌", "RmpVhfSpacing": "RMP 甚高頻(VHF)間隔", + "Satcom": "Satcom", "ThrustReductionHeight": "減推高度(英呎/ft)", "Title": "飛機選項/置頂程式", "WeightUnit": "重量單位" @@ -451,12 +498,15 @@ "AutofillChecklists": "自動填充檢查單", "BoardingTime": "登機需時", "DmcSelfTestTime": "DMC 自檢時間", + "FirstOfficerAvatar": "Show First Officer Avatar", "HomeCockpitMode": "家用駕駛艙模式", "McduFocusTimeout": "MCDU 對焦超時 (秒)", "McduKeyboardInput": "MCDU 鍵盤輸入", "PauseAtTod": "T/D Pause", "PauseAtTodDistance": "T/D Pause Distance (nm)", + "PilotAvatar": "Show Pilot Avatar", "SeparateTillerFromRudderInputs": "使用方向舵控制鼻輪轉向", + "SyncEfis": "Sync EFIS controls between Captain and FO", "Title": "現實" }, "SimOptions": { @@ -466,6 +516,7 @@ "ConesEnabled": "Safety Cones", "DefaultBarometerUnit": "默認氣壓單位", "DynamicRegistrationDecal": "動態註冊號噴塗", + "EnableSimBridge": "啟用 MCDU 伺服器 (5 分鐘之內無連接將自動停用)", "EnabledMcduServerConnectionAutoDeactivatesAfter5MinutesIfNoSuccessfulConnection": "啟用 MCDU 伺服器 (5 分鐘之內無連接將自動停用)", "ExternalMcduServerPort": "外部 MCDU 服務器端口", "Hpa": "hPa", @@ -474,6 +525,7 @@ "None": "無", "Off": "關", "Save": "儲存", + "SimBridgePort": "外部 MCDU 服務器端口", "SyncMsfsFlightPlan": "同步 MSFS 飛行計劃", "ThrottleDetents": "油門卡位", "Title": "模擬選項", diff --git a/src/instruments/src/EFB/Performance/Widgets/LandingWidget.tsx b/src/instruments/src/EFB/Performance/Widgets/LandingWidget.tsx index 923aaf913a7a..fb2c70b21256 100644 --- a/src/instruments/src/EFB/Performance/Widgets/LandingWidget.tsx +++ b/src/instruments/src/EFB/Performance/Widgets/LandingWidget.tsx @@ -672,7 +672,7 @@ export const LandingWidget = () => { { }, []); return ( -
+
{isPowered ? t('Presets.InteriorLighting.SelectAnInteriorLightingPresetToLoadOrSave') @@ -82,11 +84,82 @@ export const LightPresets = () => { /> ))}
+
); }; +type AutoLoadConfigurationProps = { + namesMap: Map, + storedNames: string +} + +const AutoLoadConfiguration = (props: AutoLoadConfigurationProps) => { + const [autoLoadPreset, setAutoLoadPreset] = usePersistentNumberProperty('LIGHT_PRESET_AUTOLOAD', 0); + + // State for persistent copy of autoload preset IDs + const [autoLoadDayPresetID, setAutoLoadDayPresetID] = usePersistentNumberProperty('LIGHT_PRESET_AUTOLOAD_DAY', 0); + const [autoLoadDawnDuskPresetID, setAutoLoadDawnDuskPresetID] = usePersistentNumberProperty('LIGHT_PRESET_AUTOLOAD_DAWNDUSK', 0); + const [autoLoadNightPresetID, setAutoLoadNightPresetID] = usePersistentNumberProperty('LIGHT_PRESET_AUTOLOAD_NIGHT', 0); + + const [presetSelectionOptions, setPresetSelectionOptions] = useState([{ value: 0, displayValue: t('Presets.InteriorLighting.AutoLoadNoneSelection') }]); + + // Creates the option list for the selections, ignoring any that are the default NoName title + const generatePresetSelectionOptions = () => { + const options: Array<{value: number, displayValue: string}> = [{ value: 0, displayValue: t('Presets.InteriorLighting.AutoLoadNoneSelection') }]; + props.namesMap.forEach((value, key) => { + options.push({ value: key, displayValue: value }); + }); + return options; + }; + + useEffect(() => { + setPresetSelectionOptions(generatePresetSelectionOptions()); + }, []); + + useEffect(() => { + setPresetSelectionOptions(generatePresetSelectionOptions()); + }, [props.namesMap, props.storedNames]); + + return ( +
+
+
+ {t('Presets.InteriorLighting.AutoLoadLightingPreset')} +
+ (setAutoLoadPreset(value ? 1 : 0))} /> +
+
+
{t('Presets.InteriorLighting.AutoLoadDay')}
+ (setAutoLoadDayPresetID(newPreset as number))} + /> +
{t('Presets.InteriorLighting.AutoLoadDawnDusk')}
+ (setAutoLoadDawnDuskPresetID(newPreset as number))} + /> +
{t('Presets.InteriorLighting.AutoLoadNight')}
+ (setAutoLoadNightPresetID(newPreset as number))} + /> +
+
+ ); +}; + type SinglePresetParams = { presetID: number, getPresetName: (presetID: number) => string | undefined, @@ -196,7 +269,7 @@ const SinglePreset = (props: SinglePresetParams) => { {props.presetID}
-
+
{
handleLoad()} > {t('Presets.InteriorLighting.LoadPreset')} @@ -221,7 +294,7 @@ const SinglePreset = (props: SinglePresetParams) => {
handleSave()} > {t('Presets.InteriorLighting.SavePreset')} diff --git a/src/instruments/src/EFB/Settings/Migration.tsx b/src/instruments/src/EFB/Settings/Migration.tsx new file mode 100644 index 000000000000..8ed0f4c8012e --- /dev/null +++ b/src/instruments/src/EFB/Settings/Migration.tsx @@ -0,0 +1,27 @@ +import { NXDataStore } from '@shared/persistence'; + +type SimVarProp = {name: string, defaultValue: string} +type migrationSet = [oldSimvar: SimVarProp, newSimvar: string] + +const migrateSetting = (oldSimvar: SimVarProp, newSimvar: string) => { + NXDataStore.set(newSimvar, NXDataStore.get(oldSimvar.name, oldSimvar.defaultValue)); +}; + +// Object is set so that a list of simvars will be migrated when the migrated flag is false. +const settingsToMigrate: Map = new Map([ + ['SIMBRIDGE_MIGRATED', [ + [{ name: 'CONFIG_EXTERNAL_MCDU_PORT', defaultValue: '8380' }, 'CONFIG_SIMBRIDGE_PORT'], + [{ name: 'CONFIG_EXTERNAL_MCDU_SERVER_ENABLED', defaultValue: 'AUTO ON' }, 'CONFIG_SIMBRIDGE_ENABLED'], + ]], +]); + +export function migrateSettings() { + settingsToMigrate.forEach((migrations, migrationCheck) => { + if (NXDataStore.get(migrationCheck, 'false') === 'false') { + migrations.forEach((value) => { + migrateSetting(value[0], value[1]); + }); + NXDataStore.set(migrationCheck, 'true'); + } + }); +} diff --git a/src/instruments/src/EFB/Settings/Pages/AircraftOptionsPinProgramsPage.tsx b/src/instruments/src/EFB/Settings/Pages/AircraftOptionsPinProgramsPage.tsx index 37ce4a1c0397..fd39fe4b5200 100644 --- a/src/instruments/src/EFB/Settings/Pages/AircraftOptionsPinProgramsPage.tsx +++ b/src/instruments/src/EFB/Settings/Pages/AircraftOptionsPinProgramsPage.tsx @@ -20,6 +20,7 @@ export const AircraftOptionsPinProgramsPage = () => { const [isisMetricAltitude, setIsisMetricAltitude] = usePersistentNumberProperty('ISIS_METRIC_ALTITUDE', 0); const [vhfSpacing, setVhfSpacing] = usePersistentProperty('RMP_VHF_SPACING_25KHZ', '0'); const [latLonExtended, setLatLonExtended] = usePersistentProperty('LATLON_EXT_FMT', '0'); + const [satcomEnabled, setsatcomEnabled] = usePersistentNumberProperty('MODEL_SATCOM_ENABLED', 0); const handleSetThrustReductionAlt = (value: string) => { setThrustReductionHeightSetting(value); @@ -178,6 +179,10 @@ export const AircraftOptionsPinProgramsPage = () => { + + setsatcomEnabled(value ? 1 : 0)} /> + + ); }; diff --git a/src/instruments/src/EFB/Settings/Pages/RealismPage.tsx b/src/instruments/src/EFB/Settings/Pages/RealismPage.tsx index 2d2493e4771d..5529880845b1 100644 --- a/src/instruments/src/EFB/Settings/Pages/RealismPage.tsx +++ b/src/instruments/src/EFB/Settings/Pages/RealismPage.tsx @@ -23,6 +23,7 @@ export const RealismPage = () => { const [realisticTiller, setRealisticTiller] = usePersistentNumberProperty('REALISTIC_TILLER_ENABLED', 0); const [homeCockpit, setHomeCockpit] = usePersistentProperty('HOME_COCKPIT_ENABLED', '0'); const [autoFillChecklists, setAutoFillChecklists] = usePersistentNumberProperty('EFB_AUTOFILL_CHECKLISTS', 0); + const [syncEfis, setFoEfis] = usePersistentNumberProperty('FO_SYNC_EFIS_ENABLED', 0); const adirsAlignTimeButtons: (ButtonType & SimVarButton)[] = [ { name: t('Settings.Instant'), setting: 'INSTANT', simVarValue: 1 }, @@ -121,6 +122,10 @@ export const RealismPage = () => { )} + + setFoEfis(value ? 1 : 0)} /> + + ); }; diff --git a/src/instruments/src/EFB/Settings/Pages/SimOptionsPage.tsx b/src/instruments/src/EFB/Settings/Pages/SimOptionsPage.tsx index 231f055b8801..439d53038958 100644 --- a/src/instruments/src/EFB/Settings/Pages/SimOptionsPage.tsx +++ b/src/instruments/src/EFB/Settings/Pages/SimOptionsPage.tsx @@ -16,10 +16,10 @@ export const SimOptionsPage = () => { const [showThrottleSettings, setShowThrottleSettings] = useState(false); const [defaultBaro, setDefaultBaro] = usePersistentProperty('CONFIG_INIT_BARO_UNIT', 'AUTO'); - const [dynamicRegistration, setDynamicRegistration] = usePersistentProperty('DYNAMIC_REGISTRATION_DECAL', 'DISABLED'); + const [dynamicRegistration, setDynamicRegistration] = usePersistentProperty('DYNAMIC_REGISTRATION_DECAL', '0'); const [fpSync, setFpSync] = usePersistentProperty('FP_SYNC', 'LOAD'); - const [mcduServerPort, setMcduServerPort] = usePersistentProperty('CONFIG_EXTERNAL_MCDU_PORT', '8380'); - const [mcduServerEnabled, setMcduServerEnabled] = usePersistentProperty('CONFIG_EXTERNAL_MCDU_SERVER_ENABLED', 'AUTO ON'); + const [simbridgePort, setSimbridgePort] = usePersistentProperty('CONFIG_SIMBRIDGE_PORT', '8380'); + const [simbridgeEnabled, setSimbridgeEnabled] = usePersistentProperty('CONFIG_SIMBRIDGE_ENABLED', 'AUTO ON'); const [radioReceiverUsage, setRadioReceiverUsage] = usePersistentProperty('RADIO_RECEIVER_USAGE_ENABLED', '0'); const [, setRadioReceiverUsageSimVar] = useSimVar('L:A32NX_RADIO_RECEIVER_USAGE_ENABLED', 'number', 0); const [wheelChocksEnabled, setWheelChocksEnabled] = usePersistentNumberProperty('MODEL_WHEELCHOCKS_ENABLED', 1); @@ -68,40 +68,40 @@ export const SimOptionsPage = () => { - + setMcduServerEnabled('AUTO ON')} - selected={mcduServerEnabled === 'AUTO ON' || mcduServerEnabled === 'AUTO OFF'} + onSelect={() => setSimbridgeEnabled('AUTO ON')} + selected={simbridgeEnabled === 'AUTO ON' || simbridgeEnabled === 'AUTO OFF'} > {t('Settings.SimOptions.Auto')} setMcduServerEnabled('PERM OFF')} - selected={mcduServerEnabled === 'PERM OFF'} + onSelect={() => setSimbridgeEnabled('PERM OFF')} + selected={simbridgeEnabled === 'PERM OFF'} > {t('Settings.SimOptions.Off')}
- {mcduServerEnabled === 'AUTO ON' ? t('Settings.SimOptions.Active') : t('Settings.SimOptions.Inactive')} + {simbridgeEnabled === 'AUTO ON' ? t('Settings.SimOptions.Active') : t('Settings.SimOptions.Inactive')}
- + { - setMcduServerPort(event); + setSimbridgePort(event.replace(/[^0-9]+/g, '')); }} /> - setDynamicRegistration(value ? 'ENABLED' : 'DISABLED')} /> + setDynamicRegistration(value ? '1' : '0')} /> @@ -135,7 +135,7 @@ export const SimOptionsPage = () => {
)} - {/* */} - {/* {localApiConnected ? ( */} - {/* */} - {/* ) : ( */} - {/* */} - {/* )} */} - {/* */} + + {simBridgeConnected ? ( + + ) : ( + + )} + diff --git a/src/instruments/src/EFB/Store/features/simBrief.ts b/src/instruments/src/EFB/Store/features/simBrief.ts index 1c50b66c9ec0..ac9fe98e9a65 100644 --- a/src/instruments/src/EFB/Store/features/simBrief.ts +++ b/src/instruments/src/EFB/Store/features/simBrief.ts @@ -70,16 +70,19 @@ export const initialState: {data: SimbriefData} = { flightETAInSeconds: '', cruiseAltitude: 0, weights: { - cargo: 0, - estLandingWeight: 0, - estTakeOffWeight: 0, - estZeroFuelWeight: 0, - maxLandingWeight: 0, - maxTakeOffWeight: 0, - maxZeroFuelWeight: 0, - passengerCount: 0, - passengerWeight: 0, - payload: 0, + cargo: '0', + estLandingWeight: '0', + estTakeOffWeight: '0', + estZeroFuelWeight: '0', + maxLandingWeight: '0', + maxTakeOffWeight: '0', + maxZeroFuelWeight: '0', + bagCount: '0', + passengerCount: '0', + passengerWeight: '0', + bagWeight: '0', + payload: '0', + freight: '0', }, fuels: { avgFuelFlow: 0, @@ -159,8 +162,11 @@ export async function fetchSimbriefDataAction(simbriefUserId: string): Promise

{ setValue(option.displayValue); }, [props.value]); + // This useEffect is to support dynamically generated option lists. + useEffect(() => { + const option = props.options.find((option) => option.value === props.value) ?? defaultOption; + setValue(option.displayValue); + }, [props.options]); + const onOptionClicked = (option: Option) => { if (props.onChange) { props.onChange(option.value); diff --git a/src/instruments/src/EFB/UtilComponents/Form/SimpleInput/SimpleInput.tsx b/src/instruments/src/EFB/UtilComponents/Form/SimpleInput/SimpleInput.tsx index 70e376d36880..52b40bdf5d26 100644 --- a/src/instruments/src/EFB/UtilComponents/Form/SimpleInput/SimpleInput.tsx +++ b/src/instruments/src/EFB/UtilComponents/Form/SimpleInput/SimpleInput.tsx @@ -15,6 +15,7 @@ interface SimpleInputProps { number?: boolean; padding?: number; decimalPrecision?: number; + fontSizeClassName?: string; reverse?: boolean; // Flip label/input order; className?: string; maxLength?: number; @@ -87,7 +88,6 @@ export const SimpleInput = (props: PropsWithChildren) => { if (inputRef.current.getBoundingClientRect().bottom > spaceBeforeKeyboard) { const offset = inputRef.current.getBoundingClientRect().bottom - spaceBeforeKeyboard; - console.log('offset', offset); dispatch(setOffsetY(offset)); } @@ -164,7 +164,7 @@ export const SimpleInput = (props: PropsWithChildren) => { return ( <> state.keyboard); + const [offsetX, setOffsetX] = useState(0); + const [verticalDeviation, setVerticalDeviation] = useState(0); + const keyboardWrapperRef = useRef(null); const onKeyPress = (button: string) => { if (button === '{shift}' || button === '{lock}') { @@ -55,10 +58,21 @@ export const KeyboardWrapper = ({ onChangeAll, keyboardRef, setOpen, blurInput, onKeyDown?.(button); }; + useEffect(() => { + if (!keyboardWrapperRef.current) { + return; + } + + const { left, bottom } = keyboardWrapperRef.current.getBoundingClientRect(); + setVerticalDeviation(window.innerHeight - bottom); + setOffsetX(-left); + }, []); + return (

{ diff --git a/src/instruments/src/EFB/UtilComponents/TooltipWrapper.tsx b/src/instruments/src/EFB/UtilComponents/TooltipWrapper.tsx index c05f0d0cd02c..b913d1cad9b8 100644 --- a/src/instruments/src/EFB/UtilComponents/TooltipWrapper.tsx +++ b/src/instruments/src/EFB/UtilComponents/TooltipWrapper.tsx @@ -1,6 +1,6 @@ /* eslint-disable max-len */ import React, { useRef, useState, useEffect, forwardRef, RefObject } from 'react'; -import { useAppDispatch } from '../Store/store'; +import { useAppDispatch, useAppSelector } from '../Store/store'; import { setPosX, setPosY, setShown, setText } from '../Store/features/tooltip'; interface TooltipProps { @@ -10,15 +10,19 @@ interface TooltipProps { shown: boolean; } -export const Tooltip = forwardRef(({ text, posX, posY, shown }: TooltipProps, ref: RefObject) => ( -
- {text} -
-)); +export const Tooltip = forwardRef(({ text, posX, posY, shown }: TooltipProps, ref: RefObject) => { + const { offsetY } = useAppSelector((state) => state.keyboard); + + return ( +
+ {text} +
+ ); +}); interface TooltipWrapperProps { text?: string; diff --git a/src/instruments/src/EFB/failures-orchestrator-provider.tsx b/src/instruments/src/EFB/failures-orchestrator-provider.tsx index c2a9a5ff04cb..46bdf27514b5 100644 --- a/src/instruments/src/EFB/failures-orchestrator-provider.tsx +++ b/src/instruments/src/EFB/failures-orchestrator-provider.tsx @@ -11,10 +11,21 @@ interface FailuresOrchestratorContext { } const createOrchestrator = () => new FailuresOrchestrator('A32NX', [ + [22, A320Failure.Fac1Failure, 'FAC 1'], + [22, A320Failure.Fac2Failure, 'FAC 2'], + [24, A320Failure.TransformerRectifier1, 'TR 1'], [24, A320Failure.TransformerRectifier2, 'TR 2'], [24, A320Failure.TransformerRectifierEssential, 'ESS TR'], + [27, A320Failure.Elac1Failure, 'ELAC 1'], + [27, A320Failure.Elac2Failure, 'ELAC 2'], + [27, A320Failure.Sec1Failure, 'SEC 1'], + [27, A320Failure.Sec2Failure, 'SEC 2'], + [27, A320Failure.Sec3Failure, 'SEC 3'], + [27, A320Failure.Fcdc1Failure, 'FCDC 1'], + [27, A320Failure.Fcdc2Failure, 'FCDC 2'], + [29, A320Failure.GreenReservoirLeak, 'Green reservoir leak'], [29, A320Failure.BlueReservoirLeak, 'Blue reservoir leak'], [29, A320Failure.YellowReservoirLeak, 'Yellow reservoir leak'], diff --git a/src/instruments/src/EFB/index.tsx b/src/instruments/src/EFB/index.tsx index e559b588be14..66e885868eb1 100644 --- a/src/instruments/src/EFB/index.tsx +++ b/src/instruments/src/EFB/index.tsx @@ -16,6 +16,7 @@ import './Assets/Efb.scss'; import './Assets/Theme.css'; import './Assets/Slider.scss'; import { readSettingsFromPersistentStorage } from './Settings/sync'; +import { migrateSettings } from './Settings/Migration'; import { store } from './Store/store'; import { Error } from './Assets/Error'; @@ -65,7 +66,7 @@ export const ErrorFallback = ({ resetErrorBoundary }: ErrorFallbackProps) => { )} -
+

Reset Display

@@ -85,6 +86,7 @@ const setSessionId = () => { const setup = () => { readSettingsFromPersistentStorage(); + migrateSettings(); setSessionId(); }; diff --git a/src/instruments/src/EWD/elements/PseudoFWC.tsx b/src/instruments/src/EWD/elements/PseudoFWC.tsx index 8f03d03ef1f1..79069ff519ea 100644 --- a/src/instruments/src/EWD/elements/PseudoFWC.tsx +++ b/src/instruments/src/EWD/elements/PseudoFWC.tsx @@ -96,6 +96,18 @@ const PseudoFWC: React.FC = () => { const [packOffBleedAvailable2] = useState(() => new NXLogicConfirmNode(5, false)); const [cabAltSetReset1] = useState(() => new NXLogicMemoryNode()); const [cabAltSetReset2] = useState(() => new NXLogicMemoryNode()); + const [elac1HydConfirmNode] = useState(() => new NXLogicConfirmNode(3, false)); + const [elac1FaultConfirmNode] = useState(() => new NXLogicConfirmNode(0.6, true)); + const [elac1HydConfirmNodeOutput, setElac1HydConfirmNodeOutput] = useState(false); + const [elac1FaultConfirmNodeOutput, setElac1FaultConfirmNodeOutput] = useState(false); + const [elac2HydConfirmNode] = useState(() => new NXLogicConfirmNode(3, false)); + const [elac2FaultConfirmNode] = useState(() => new NXLogicConfirmNode(0.6, true)); + const [elac2HydConfirmNodeOutput, setElac2HydConfirmNodeOutput] = useState(false); + const [elac2FaultConfirmNodeOutput, setElac2FaultConfirmNodeOutput] = useState(false); + const [altn1LawConfirmNode] = useState(() => new NXLogicConfirmNode(0.3, true)); + const [altn1LawConfirmNodeOutput, setAltn1LawConfirmNodeOutput] = useState(false); + const [altn2LawConfirmNode] = useState(() => new NXLogicConfirmNode(0.3, true)); + const [altn2LawConfirmNodeOutput, setAltn2LawConfirmNodeOutput] = useState(false); const [memoMessageLeft, setMemoMessageLeft] = useState([]); const [memoMessageRight, setMemoMessageRight] = useState([]); @@ -176,16 +188,106 @@ const PseudoFWC: React.FC = () => { /* HYDRAULICS */ const [greenLP] = useSimVar('L:A32NX_HYD_GREEN_EDPUMP_LOW_PRESS', 'bool', 500); + const [greenSysPressurised] = useSimVar('L:A32NX_HYD_GREEN_SYSTEM_1_SECTION_PRESSURE_SWITCH', 'bool', 500); const [greenHydEng1PBAuto] = useSimVar('L:A32NX_OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO', 'bool', 500); const [blueLP] = useSimVar('L:A32NX_HYD_BLUE_EDPUMP_LOW_PRESS', 'bool', 500); + const [blueSysPressurised] = useSimVar('L:A32NX_HYD_BLUE_SYSTEM_1_SECTION_PRESSURE_SWITCH', 'bool', 500); const [blueRvrLow] = useSimVar('L:A32NX_HYD_BLUE_RESERVOIR_LEVEL_IS_LOW', 'bool', 500); const [blueElecPumpPBAuto] = useSimVar('L:A32NX_OVHD_HYD_EPUMPB_PB_IS_AUTO', 'bool', 500); const [yellowLP] = useSimVar('L:A32NX_HYD_YELLOW_EDPUMP_LOW_PRESS', 'bool', 500); + const [yellowSysPressurised] = useSimVar('L:A32NX_HYD_YELLOW_SYSTEM_1_SECTION_PRESSURE_SWITCH', 'bool', 500); const [eng1pumpPBisAuto] = useSimVar('L:A32NX_OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO', 'bool', 500); const [eng2pumpPBisAuto] = useSimVar('L:A32NX_OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO', 'bool', 500); const [hydPTU] = useSimVar('L:A32NX_HYD_PTU_ON_ECAM_MEMO', 'bool', 500); const [ratDeployed] = useSimVar('L:A32NX_HYD_RAT_STOW_POSITION', 'percent over 100', 500); + /* F/CTL */ + const fcdc1DiscreteWord1 = useArinc429Var('L:A32NX_FCDC_1_DISCRETE_WORD_1'); + const fcdc2DiscreteWord1 = useArinc429Var('L:A32NX_FCDC_2_DISCRETE_WORD_1'); + const fcdc1DiscreteWord2 = useArinc429Var('L:A32NX_FCDC_1_DISCRETE_WORD_2'); + const fcdc2DiscreteWord2 = useArinc429Var('L:A32NX_FCDC_2_DISCRETE_WORD_2'); + const fcdc1DiscreteWord3 = useArinc429Var('L:A32NX_FCDC_1_DISCRETE_WORD_3'); + const fcdc2DiscreteWord3 = useArinc429Var('L:A32NX_FCDC_2_DISCRETE_WORD_3'); + const fcdc1DiscreteWord4 = useArinc429Var('L:A32NX_FCDC_1_DISCRETE_WORD_4'); + const fcdc2DiscreteWord4 = useArinc429Var('L:A32NX_FCDC_2_DISCRETE_WORD_4'); + + const spoilersArmed = fcdc1DiscreteWord4.getBitValueOr(27, false) || fcdc2DiscreteWord4.getBitValueOr(27, false); + const speedBrakeCommand = fcdc1DiscreteWord4.getBitValueOr(28, false) || fcdc2DiscreteWord4.getBitValueOr(28, false); + + // ELAC 1 FAULT computation + const se1f = (fcdc1DiscreteWord1.getBitValueOr(19, false) || fcdc2DiscreteWord1.getBitValueOr(19, false)) + && (fcdc1DiscreteWord1.getBitValueOr(20, false) || fcdc2DiscreteWord1.getBitValueOr(20, false)); + const elac1FaultCondition = !([1, 10].includes(flightPhase) && (fcdc1DiscreteWord3.getBitValueOr(19, false) || fcdc2DiscreteWord3.getBitValueOr(19, false))) + && dcESSBusPowered + && ((fcdc1DiscreteWord1.getBitValueOr(23, false) || fcdc2DiscreteWord1.getBitValueOr(23, false)) || (!elac1HydConfirmNodeOutput && se1f)); + const elac1FaultLine123Display = !(fcdc1DiscreteWord3.getBitValueOr(19, false) || fcdc2DiscreteWord3.getBitValueOr(19, false)) + && (fcdc1DiscreteWord1.getBitValueOr(23, false) || fcdc2DiscreteWord1.getBitValueOr(23, false)); + const elac1FaultLine45Display = false; + + // ELAC 2 FAULT computation + const se2f = (fcdc1DiscreteWord1.getBitValueOr(21, false) || fcdc2DiscreteWord1.getBitValueOr(21, false)) + && (fcdc1DiscreteWord1.getBitValueOr(22, false) || fcdc2DiscreteWord1.getBitValueOr(22, false)); + const elac2FaultCondition = !([1, 10].includes(flightPhase) && (fcdc1DiscreteWord3.getBitValueOr(20, false) || fcdc2DiscreteWord3.getBitValueOr(20, false))) + && dc2BusPowered + && ((fcdc1DiscreteWord1.getBitValueOr(24, false) || fcdc2DiscreteWord1.getBitValueOr(24, false)) + || (!elac2HydConfirmNodeOutput && se2f)); + const elac2FaultLine123Display = !(fcdc1DiscreteWord3.getBitValueOr(20, false) || fcdc2DiscreteWord3.getBitValueOr(20, false)) + && (fcdc1DiscreteWord1.getBitValueOr(24, false) || fcdc2DiscreteWord1.getBitValueOr(24, false)); + const elac2FaultLine45Display = false; + + // SEC 1 FAULT computation + const ss1f = fcdc1DiscreteWord1.getBitValueOr(25, false) || fcdc2DiscreteWord1.getBitValueOr(25, false); + const sec1FaultCondition = !([1, 10].includes(flightPhase) && (fcdc1DiscreteWord3.getBitValueOr(27, false) || fcdc2DiscreteWord3.getBitValueOr(27, false))) + && dcESSBusPowered + && ss1f; + const sec1FaultLine123Display = !(fcdc1DiscreteWord3.getBitValueOr(27, false) || fcdc2DiscreteWord3.getBitValueOr(27, false)); + const sec1FaultLine45Display = false; + + // SEC 2 FAULT computation + const ss2f = fcdc1DiscreteWord1.getBitValueOr(26, false) || fcdc2DiscreteWord1.getBitValueOr(26, false); + const sec2FaultCondition = !([1, 10].includes(flightPhase) && (fcdc1DiscreteWord3.getBitValueOr(28, false) || fcdc2DiscreteWord3.getBitValueOr(28, false))) + && dc2BusPowered + && ss2f; + const sec2FaultLine123Display = !(fcdc1DiscreteWord3.getBitValueOr(28, false) || fcdc2DiscreteWord3.getBitValueOr(28, false)); + + // SEC 3 FAULT computation + const ss3f = fcdc1DiscreteWord1.getBitValueOr(29, false) || fcdc2DiscreteWord1.getBitValueOr(29, false); + const sec3FaultCondition = !([1, 10].includes(flightPhase) && (fcdc1DiscreteWord3.getBitValueOr(29, false) || fcdc2DiscreteWord3.getBitValueOr(29, false))) + && dc2BusPowered + && ss3f; + const sec3FaultLine123Display = !(fcdc1DiscreteWord3.getBitValueOr(29, false) || fcdc2DiscreteWord3.getBitValueOr(29, false)); + + // FCDC 1+2 FAULT computation + const SFCDC1FT = fcdc1DiscreteWord1.isFailureWarning() && fcdc1DiscreteWord2.isFailureWarning() && fcdc1DiscreteWord3.isFailureWarning(); + const SFCDC2FT = fcdc2DiscreteWord1.isFailureWarning() && fcdc2DiscreteWord2.isFailureWarning() && fcdc2DiscreteWord3.isFailureWarning(); + const SFCDC12FT = SFCDC1FT && SFCDC2FT; + const fcdc12FaultCondition = SFCDC12FT && dc2BusPowered; + const fcdc1FaultCondition = SFCDC1FT && !SFCDC12FT; + const fcdc2FaultCondition = SFCDC2FT && !(SFCDC12FT || !dc2BusPowered); + + // ALTN LAW 2 computation + const SPA2 = fcdc1DiscreteWord1.getBitValueOr(13, false) || fcdc2DiscreteWord1.getBitValueOr(13, false); + const altn2Condition = SPA2 && ![1, 10].includes(flightPhase); + + // ALTN LAW 1 computation + const SPA1 = fcdc1DiscreteWord1.getBitValueOr(12, false) || fcdc2DiscreteWord1.getBitValueOr(12, false); + const altn1Condition = SPA1 && ![1, 10].includes(flightPhase); + + // DIRECT LAW computation + const SPBUL = (false && SFCDC12FT) || (fcdc1DiscreteWord1.getBitValueOr(15, false) || fcdc2DiscreteWord1.getBitValueOr(15, false)); + const directLawCondition = SPBUL && ![1, 10].includes(flightPhase); + + // L+R ELEV FAULT computation + const lhElevBlueFail = (fcdc1DiscreteWord3.isNormalOperation() && !fcdc1DiscreteWord3.getBitValueOr(15, false)) + || (fcdc2DiscreteWord3.isNormalOperation() && !fcdc2DiscreteWord3.getBitValueOr(15, false)); + const lhElevGreenFail = (fcdc1DiscreteWord3.isNormalOperation() && !fcdc1DiscreteWord3.getBitValueOr(16, false)) + || (fcdc2DiscreteWord3.isNormalOperation() && !fcdc2DiscreteWord3.getBitValueOr(16, false)); + const rhElevBlueFail = (fcdc1DiscreteWord3.isNormalOperation() && !fcdc1DiscreteWord3.getBitValueOr(17, false)) + || (fcdc2DiscreteWord3.isNormalOperation() && !fcdc2DiscreteWord3.getBitValueOr(17, false)); + const rhElevGreenFail = (fcdc1DiscreteWord3.isNormalOperation() && !fcdc1DiscreteWord3.getBitValueOr(18, false)) + || (fcdc2DiscreteWord3.isNormalOperation() && !fcdc2DiscreteWord3.getBitValueOr(18, false)); + const lrElevFaultCondition = lhElevBlueFail && lhElevGreenFail && rhElevBlueFail && rhElevGreenFail && ![1, 10].includes(flightPhase); + /* LANDING GEAR AND LIGHTS */ // const [left1LandingGear] = useSimVar('L:A32NX_LGCIU_1_LEFT_GEAR_COMPRESSED', 'bool', 500); // const [right1LandingGear] = useSimVar('L:A32NX_LGCIU_1_RIGHT_GEAR_COMPRESSED', 'bool', 500); @@ -202,7 +304,6 @@ const PseudoFWC: React.FC = () => { /* OTHER STUFF */ - const [spoilersArmed] = useSimVar('L:A32NX_SPOILERS_ARMED', 'bool', 500); const [seatBelt] = useSimVar('A:CABIN SEATBELTS ALERT SWITCH', 'bool', 500); const [noSmoking] = useSimVar('L:A32NX_NO_SMOKING_MEMO', 'bool', 500); const [noSmokingSwitchPosition] = useSimVar('L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_Position', 'enum', 500); @@ -240,7 +341,6 @@ const PseudoFWC: React.FC = () => { const [toconfigBtn] = useSimVar('L:A32NX_BTN_TOCONFIG', 'bool'); const [flapsMcdu] = useSimVar('L:A32NX_TO_CONFIG_FLAPS', 'number', 500); const [flapsMcduEntered] = useSimVar('L:A32NX_TO_CONFIG_FLAPS_ENTERED', 'bool', 500); - const [speedBrake] = useSimVar('L:A32NX_SPOILERS_HANDLE_POSITION', 'number', 500); const [parkBrake] = useSimVar('L:A32NX_PARK_BRAKE_LEVER_POS', 'bool', 500); const [brakesHot] = useSimVar('L:A32NX_BRAKES_HOT', 'bool', 500); const [v1Speed] = useSimVar('L:AIRLINER_V1_SPEED', 'knots', 500); @@ -297,6 +397,8 @@ const PseudoFWC: React.FC = () => { const [pack2On] = useSimVar('L:A32NX_OVHD_COND_PACK_2_PB_IS_ON', 'bool'); const [excessPressure] = useSimVar('L:A32NX_PRESS_EXCESS_CAB_ALT', 'bool', 500); + const [voiceVHF3] = useSimVar('A:COM ACTIVE FREQUENCY:3', 'number', 500); + /* WARNINGS AND FAILURES */ const landASAPRed: boolean = !!(!aircraftOnGround && ( @@ -473,6 +575,36 @@ const PseudoFWC: React.FC = () => { if (cabAltSetResetState2 !== cabAltSetReset2Node) { setCabAltSetResetState2(cabAltSetReset2Node); } + + const elac1HydraulicResult = elac1HydConfirmNode.write(!greenSysPressurised && !blueSysPressurised, deltaTime); + if (elac1HydConfirmNodeOutput !== elac1HydraulicResult) { + setElac1HydConfirmNodeOutput(elac1HydraulicResult); + } + + const elac1FaultResult = elac1FaultConfirmNode.write(elac1FaultCondition, deltaTime); + if (elac1FaultConfirmNodeOutput !== elac1FaultResult) { + setElac1FaultConfirmNodeOutput(elac1FaultResult); + } + + const elac2HydraulicResult = elac2HydConfirmNode.write((!greenSysPressurised || !yellowSysPressurised) && !blueSysPressurised, deltaTime); + if (elac2HydConfirmNodeOutput !== elac2HydraulicResult) { + setElac2HydConfirmNodeOutput(elac2HydraulicResult); + } + + const elac2FaultResult = elac2FaultConfirmNode.write(elac2FaultCondition, deltaTime); + if (elac2FaultConfirmNodeOutput !== elac2FaultResult) { + setElac2FaultConfirmNodeOutput(elac2FaultResult); + } + + const altn1Result = altn1LawConfirmNode.write(altn1Condition, deltaTime); + if (altn1LawConfirmNodeOutput !== altn1Result) { + setAltn1LawConfirmNodeOutput(altn1Result); + } + + const altn2Result = altn2LawConfirmNode.write(altn2Condition, deltaTime); + if (altn2LawConfirmNodeOutput !== altn2Result) { + setAltn2LawConfirmNodeOutput(altn2Result); + } }); /* FAILURES, MEMOS AND SPECIAL LINES */ @@ -673,6 +805,156 @@ const PseudoFWC: React.FC = () => { sysPage: -1, side: 'LEFT', }, + 2700110: { // ELAC 1 FAULT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: elac1FaultConfirmNodeOutput, + whichCodeToReturn: [ + 0, + elac1FaultLine123Display ? 1 : null, + elac1FaultLine123Display ? 2 : null, + elac1FaultLine123Display ? 3 : null, + elac1FaultLine45Display ? 4 : null, + elac1FaultLine45Display ? 5 : null, + ], + codesToReturn: ['270011001', '270011002', '270011003', '270011004', '270011005', '270011006'], + memoInhibit: false, + failure: 2, + sysPage: 10, + side: 'LEFT', + }, + 2700120: { // ELAC 2 FAULT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: elac2FaultConfirmNodeOutput, + whichCodeToReturn: [ + 0, + elac2FaultLine123Display ? 1 : null, + elac2FaultLine123Display ? 2 : null, + elac2FaultLine123Display ? 3 : null, + elac2FaultLine45Display ? 4 : null, + elac2FaultLine45Display ? 5 : null, + ], + codesToReturn: ['270012001', '270012002', '270012003', '270012004', '270012005', '270012006'], + memoInhibit: false, + failure: 2, + sysPage: 10, + side: 'LEFT', + }, + 2700210: { // SEC 1 FAULT + flightPhaseInhib: [3, 4, 5], + simVarIsActive: sec1FaultCondition, + whichCodeToReturn: [ + 0, + sec1FaultLine123Display ? 1 : null, + sec1FaultLine123Display ? 2 : null, + sec1FaultLine123Display ? 3 : null, + sec1FaultLine45Display ? 4 : null, + ], + codesToReturn: ['270021001', '270021002', '270021003', '270021004', '270021005'], + memoInhibit: false, + failure: 2, + sysPage: 10, + side: 'LEFT', + }, + 2700220: { // SEC 2 FAULT + flightPhaseInhib: [3, 4, 5], + simVarIsActive: sec2FaultCondition, + whichCodeToReturn: [ + 0, + sec2FaultLine123Display ? 1 : null, + sec2FaultLine123Display ? 2 : null, + sec2FaultLine123Display ? 3 : null, + ], + codesToReturn: ['270022001', '270022002', '270022003', '270022004'], + memoInhibit: false, + failure: 2, + sysPage: 10, + side: 'LEFT', + }, + 2700230: { // SEC 3 FAULT + flightPhaseInhib: [3, 4, 5], + simVarIsActive: sec3FaultCondition, + whichCodeToReturn: [ + 0, + sec3FaultLine123Display ? 1 : null, + sec3FaultLine123Display ? 2 : null, + sec3FaultLine123Display ? 3 : null, + ], + codesToReturn: ['270023001', '270023002', '270023003', '270023004'], + memoInhibit: false, + failure: 2, + sysPage: 10, + side: 'LEFT', + }, + 2700360: { // FCDC 1+2 FAULT + flightPhaseInhib: [3, 4, 5, 7], + simVarIsActive: fcdc12FaultCondition, + whichCodeToReturn: [0, 1], + codesToReturn: ['270036001', '270036002'], + memoInhibit: false, + failure: 2, + sysPage: 10, + side: 'LEFT', + }, + 2700365: { // DIRECT LAW + flightPhaseInhib: [4, 5, 7, 8], + simVarIsActive: directLawCondition, + whichCodeToReturn: [0, 1, 2, 3, 4, null, 6, 7], + codesToReturn: ['270036501', '270036502', '270036503', '270036504', '270036505', '270036506', '270036507', '270036508'], + memoInhibit: false, + failure: 2, + sysPage: 10, + side: 'LEFT', + }, + 2700375: { // ALTN 2 + flightPhaseInhib: [4, 5, 7, 8], + simVarIsActive: altn2LawConfirmNodeOutput, + whichCodeToReturn: [0, 1, null, 3, 4, null, 6], + codesToReturn: ['270037501', '270037502', '270037503', '270037504', '270037505', '270037506', '270037507'], + memoInhibit: false, + failure: 2, + sysPage: 10, + side: 'LEFT', + }, + 2700390: { // ALTN 1 + flightPhaseInhib: [4, 5, 7, 8], + simVarIsActive: altn1LawConfirmNodeOutput, + whichCodeToReturn: [0, 1, null, 3, 4, null, 6], + codesToReturn: ['270039001', '270039002', '270039003', '270039004', '270039005', '270039006', '270039007'], + memoInhibit: false, + failure: 2, + sysPage: 10, + side: 'LEFT', + }, + 2700400: { // L+R ELEV FAULT + flightPhaseInhib: [], + simVarIsActive: lrElevFaultCondition, + whichCodeToReturn: [0, 1, 2, null, null, 5], + codesToReturn: ['270040001', '270040002', '270040003', '270040004', '270040005', '270040006'], + memoInhibit: false, + failure: 3, + sysPage: 10, + side: 'LEFT', + }, + 2700555: { // FCDC 1 FAULT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: fcdc1FaultCondition, + whichCodeToReturn: [0], + codesToReturn: ['270055501'], + memoInhibit: false, + failure: 1, + sysPage: -1, + side: 'LEFT', + }, + 2700557: { // FCDC 2 FAULT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: fcdc2FaultCondition, + whichCodeToReturn: [0], + codesToReturn: ['270055701'], + memoInhibit: false, + failure: 1, + sysPage: -1, + side: 'LEFT', + }, 3200050: { // PK BRK ON flightPhaseInhib: [1, 4, 5, 6, 7, 8, 9, 10], simVarIsActive: !!(flightPhase === 3 && parkBrake === 1), @@ -695,7 +977,7 @@ const PseudoFWC: React.FC = () => { cabAltSetResetState2 ? 3 : null, cabAltSetResetState1 ? 4 : null, cabAltSetResetState2 && (throttle1Position !== 0 || throttle2Position !== 0) && autoThrustStatus !== 2 ? 5 : null, - cabAltSetResetState2 && speedBrake !== 1 ? 6 : null, + cabAltSetResetState2 && !speedBrakeCommand ? 6 : null, cabAltSetResetState2 ? 7 : null, cabAltSetResetState2 && engSelectorPosition !== 2 ? 8 : null, cabAltSetResetState2 ? 9 : null, @@ -1129,7 +1411,7 @@ const PseudoFWC: React.FC = () => { '0000060': // SPEED BRK { flightPhaseInhib: [], - simVarIsActive: speedBrake > 0 && ![1, 8, 9, 10].includes(flightPhase), + simVarIsActive: speedBrakeCommand && ![1, 8, 9, 10].includes(flightPhase), whichCodeToReturn: [![6, 7].includes(flightPhase) ? 1 : 0], codesToReturn: ['000006001', '000006002'], memoInhibit: false, @@ -1390,6 +1672,17 @@ const PseudoFWC: React.FC = () => { sysPage: -1, side: 'RIGHT', }, + '0000567': // VHF3 VOICE + { + flightPhaseInhib: [], + simVarIsActive: voiceVHF3 !== 0 && [1, 2, 6, 9, 10].includes(flightPhase), + whichCodeToReturn: [0], + codesToReturn: ['000056701'], + memoInhibit: false, + failure: 0, + sysPage: -1, + side: 'RIGHT', + }, }; /* TO CONFIG */ @@ -1404,7 +1697,7 @@ const PseudoFWC: React.FC = () => { const speeds = !!(v1Speed <= vrSpeed && vrSpeed <= v2Speed); const doors = !!(cabin === 0 && catering === 0 && cargoaftLocked && cargofwdLocked); const flapsAgree = !flapsMcduEntered || flapsHandle === flapsMcdu; - const sb = speedBrake === 0; + const sb = !speedBrakeCommand; if (systemStatus && speeds && !brakesHot && doors && flapsAgree && sb) { SimVar.SetSimVarValue('L:A32NX_TO_CONFIG_NORMAL', 'bool', 1); @@ -1416,7 +1709,7 @@ const PseudoFWC: React.FC = () => { } }, [ engine1Generator, engine2Generator, blueLP, greenLP, yellowLP, eng1pumpPBisAuto, eng2pumpPBisAuto, - flapsMcdu, flapsMcduEntered, speedBrake, parkBrake, v1Speed, vrSpeed, v2Speed, cabin, + flapsMcdu, flapsMcduEntered, speedBrakeCommand, parkBrake, v1Speed, vrSpeed, v2Speed, cabin, catering, cargoaftLocked, cargofwdLocked, toconfigBtn, tomemo, flapsHandle, brakesHot, ]); @@ -1590,6 +1883,8 @@ const PseudoFWC: React.FC = () => { agent2Eng2Discharge, agentAPUDischarge, AIRKnob, + altn1LawConfirmNodeOutput, + altn2LawConfirmNodeOutput, antiskidActive, apuAgentPB, apuAvail, @@ -1615,6 +1910,21 @@ const PseudoFWC: React.FC = () => { dc2BusPowered, dcESSBusPowered, dmcSwitchingKnob, + directLawCondition, + elac1FaultConfirmNodeOutput, + elac1FaultLine123Display, + elac2FaultConfirmNodeOutput, + elac2FaultLine123Display, + sec1FaultCondition, + sec1FaultLine123Display, + sec2FaultCondition, + sec2FaultLine123Display, + sec3FaultCondition, + sec3FaultLine123Display, + lrElevFaultCondition, + fcdc12FaultCondition, + fcdc1FaultCondition, + fcdc2FaultCondition, emergencyGeneratorOn, engine1ValueSwitch, engine2ValueSwitch, @@ -1682,7 +1992,7 @@ const PseudoFWC: React.FC = () => { showLandingInhibit, slatsInfD, slatsSupG, - speedBrake, + speedBrakeCommand, spoilersArmed, strobeLightsOn, tcasFault, @@ -1697,6 +2007,7 @@ const PseudoFWC: React.FC = () => { unit, usrStartRefueling, wingAntiIce, + voiceVHF3, ]); useEffect(() => { diff --git a/src/instruments/src/EWD/index.tsx b/src/instruments/src/EWD/index.tsx index 1c9830678f47..39a011fa4e7f 100644 --- a/src/instruments/src/EWD/index.tsx +++ b/src/instruments/src/EWD/index.tsx @@ -19,7 +19,7 @@ export const EWD: React.FC = () => { return ( diff --git a/src/instruments/src/PFD/AltitudeIndicator.tsx b/src/instruments/src/PFD/AltitudeIndicator.tsx index 1335cbc8b09c..78e8e46dd2dc 100644 --- a/src/instruments/src/PFD/AltitudeIndicator.tsx +++ b/src/instruments/src/PFD/AltitudeIndicator.tsx @@ -7,7 +7,7 @@ import { SimplaneValues } from './shared/SimplaneValueProvider'; import { VerticalTape } from './VerticalTape'; import { Arinc429Values } from './shared/ArincValueProvider'; -const DisplayRange = 600; +const DisplayRange = 570; const ValueSpacing = 100; const DistanceSpacing = 7.5; @@ -16,17 +16,18 @@ class LandingElevationIndicator extends DisplayComponent<{bus: EventBus}> { private altitude = 0; - private landingElevation = 0; + private landingElevation = new Arinc429Word(0); private flightPhase = 0; private delta = 0; private handleLandingElevation() { - const delta = this.altitude - this.landingElevation; + const landingElevationValid = !this.landingElevation.isFailureWarning() && !this.landingElevation.isNoComputedData(); + const delta = this.altitude - this.landingElevation.value; const offset = (delta - DisplayRange) * DistanceSpacing / ValueSpacing; this.delta = delta; - if (delta > DisplayRange || (this.flightPhase !== 7 && this.flightPhase !== 8)) { + if (delta > DisplayRange || (this.flightPhase !== 7 && this.flightPhase !== 8) || !landingElevationValid) { this.landingElevationIndicator.instance.classList.add('HiddenElement'); } else { this.landingElevationIndicator.instance.classList.remove('HiddenElement'); @@ -106,6 +107,99 @@ class RadioAltIndicator extends DisplayComponent<{ bus: EventBus, filteredRadioA } } +class MinimumDescentAltitudeIndicator extends DisplayComponent<{ bus: EventBus }> { + private visibility = Subject.create('hidden'); + + private path = Subject.create(''); + + private altitude = 0; + + private radioAltitudeValid = false; + + private qnhLandingAltValid = false; + + private qfeLandingAltValid = false; + + private inLandingPhases = false; + + private altMode: 'STD' | 'QNH' | 'QFE' = 'STD'; + + private mda = new Arinc429Word(0); + + private landingElevation = new Arinc429Word(0); + + private updateIndication(): void { + this.qnhLandingAltValid = !this.landingElevation.isFailureWarning() + && !this.landingElevation.isNoComputedData() + && this.inLandingPhases + && this.altMode === 'QNH'; + + this.qfeLandingAltValid = this.inLandingPhases + && this.altMode === 'QFE'; + + const altDelta = this.mda.value - this.altitude; + + const showMda = (this.radioAltitudeValid || this.qnhLandingAltValid || this.qfeLandingAltValid) + && Math.abs(altDelta) <= 570 + && !this.mda.isFailureWarning() + && !this.mda.isNoComputedData(); + + if (!showMda) { + this.visibility.set('hidden'); + return; + } + + const offset = altDelta * DistanceSpacing / ValueSpacing; + this.path.set(`m 127.9276,${80.249604 - offset} h 5.80948 v 1.124908 h -5.80948 z`); + this.visibility.set('visible'); + } + + onAfterRender(node: VNode): void { + super.onAfterRender(node); + + const sub = this.props.bus.getSubscriber(); + + sub.on('chosenRa').whenArinc429SsmChanged().handle((ra) => { + this.radioAltitudeValid = !ra.isFailureWarning() && !ra.isNoComputedData(); + this.updateIndication(); + }); + + sub.on('landingElevation').withArinc429Precision(0).handle((landingElevation) => { + this.landingElevation = landingElevation; + this.updateIndication(); + }); + + sub.on('baroMode').whenChanged().handle((m) => { + this.altMode = m; + this.updateIndication(); + }); + + sub.on('altitudeAr').withArinc429Precision(0).handle((a) => { + // TODO filtered alt + this.altitude = a.value; + this.updateIndication(); + }); + + sub.on('mda').whenChanged().handle((mda) => { + // TODO get a real word + this.mda.value = mda; + this.mda.ssm = mda > 0 ? Arinc429Word.SignStatusMatrix.NormalOperation : Arinc429Word.SignStatusMatrix.NoComputedData; + this.updateIndication(); + }); + + sub.on('fwcFlightPhase').whenChanged().handle((fp) => { + this.inLandingPhases = fp === 7 || fp === 8; + this.updateIndication(); + }); + } + + render(): VNode { + return ( + + ); + } +} + interface AltitudeIndicatorProps { bus: EventBus; @@ -138,7 +232,7 @@ export class AltitudeIndicator extends DisplayComponent tapeValue={this.subscribable} type="altitude" /> - @@ -209,13 +302,14 @@ export class AltitudeIndicatorOfftape extends DisplayComponentALT - T - C - A - S + T + C + A + S + diff --git a/src/instruments/src/PFD/AttitudeIndicatorFixed.tsx b/src/instruments/src/PFD/AttitudeIndicatorFixed.tsx index 16602b6f744c..0202e41d7584 100644 --- a/src/instruments/src/PFD/AttitudeIndicatorFixed.tsx +++ b/src/instruments/src/PFD/AttitudeIndicatorFixed.tsx @@ -17,6 +17,10 @@ export class AttitudeIndicatorFixedUpper extends DisplayComponent(); + + private rollProtLostSymbol = FSComponent.createRef(); + onAfterRender(node: VNode): void { super.onAfterRender(node); @@ -39,16 +43,24 @@ export class AttitudeIndicatorFixedUpper extends DisplayComponent { + const isNormalLawActive = fcdcWord1.getBitValue(11) && !fcdcWord1.isFailureWarning(); + + this.rollProtSymbol.instance.style.display = isNormalLawActive ? 'block' : 'none'; + + this.rollProtLostSymbol.instance.style.display = !isNormalLawActive ? 'block' : 'none'; + }); } render(): VNode { return ( - + - - + - PFD.dxf - scale = 1.000000, origin = (0.000000, 0.000000), method = file diff --git a/src/instruments/src/PFD/SpeedIndicator.tsx b/src/instruments/src/PFD/SpeedIndicator.tsx index 4ac5d1d544b3..71f230e9e67f 100644 --- a/src/instruments/src/PFD/SpeedIndicator.tsx +++ b/src/instruments/src/PFD/SpeedIndicator.tsx @@ -131,30 +131,20 @@ export class AirspeedIndicator extends DisplayComponent private failedGroup: NodeReference = FSComponent.createRef(); - private alphaProtRef: NodeReference[] = []; - - private vMaxRef: NodeReference[] = []; - private showBarsRef = FSComponent.createRef(); - private barberPoleRef = FSComponent.createRef(); - private vfeNext = FSComponent.createRef(); private altitude = new Arinc429Word(0); private flapHandleIndex = 0; - private lastAlphaProtSub = Subject.create(0); - private barTimeout= 0; private onGround = Subject.create(true); private airSpeed = new Arinc429Word(0); - private vMax = 0; - private leftMainGearCompressed: boolean; private rightMainGearCompressed: boolean; @@ -218,38 +208,6 @@ export class AirspeedIndicator extends DisplayComponent pf.on('speedAr').handle((airSpeed) => { this.airSpeed = airSpeed; this.setOutline(); - this.vMaxRef.forEach((el, index) => { - const isInRange = this.vMax <= this.speedSub.get() + DisplayRange; - if (isInRange) { - let elementValue = this.vMax + 5.040 * index; - - let offset = -elementValue * DistanceSpacing / ValueSpacing; - // if the lowest bug is below the speedtape place it on top again - if (-offset < this.speedSub.get() - 45) { - elementValue = (this.vMax + 5.040 * (index + 30)); - - offset = -elementValue * DistanceSpacing / ValueSpacing; - } - el.instance.style.transform = `translate3d(0px, ${offset}px, 0px)`; - el.instance.style.visibility = 'visible'; - } else { - el.instance.style.visibility = 'hidden'; - } - }); - }); - - pf.on('alphaProt').withPrecision(2).handle((a) => { - this.alphaProtRef.forEach((el, index) => { - const elementValue = a + -1 * 2.923 * index; - const offset = -elementValue * DistanceSpacing / ValueSpacing; - el.instance.style.transform = `translate3d(0px, ${offset}px, 0px)`; - }); - - this.lastAlphaProtSub.set(a); - }); - - pf.on('vMax').whenChanged().handle((vMax) => { - this.vMax = vMax; }); pf.on('leftMainGearCompressed').whenChanged().handle((g) => { @@ -268,45 +226,16 @@ export class AirspeedIndicator extends DisplayComponent this.onGround.sub((g) => { if (g) { this.showBarsRef.instance.style.display = 'none'; - this.barberPoleRef.instance.style.display = 'none'; clearTimeout(this.barTimeout); } else { this.barTimeout = setTimeout(() => { this.showBarsRef.instance.style.display = 'block'; - this.barberPoleRef.instance.style.display = 'block'; }, 10000) as unknown as number; } this.setOutline(); }); } - private createAlphaProtBarberPole() { - const group: SVGGElement[] = []; - for (let i = 0; i < 10; i++) { - const apref = FSComponent.createRef(); - group.push( - - - ); - , - ); - this.alphaProtRef.push(apref); - } - return group; - } - - private createVMaxBarberPole() { - const path: SVGGElement[] = []; - for (let i = 0; i < 30; i++) { - const vMaxRef = FSComponent.createRef(); - path.push( - , - ); - this.vMaxRef.push(vMaxRef); - } - return path; - } - render(): VNode { const length = 42.9 + Math.max(Math.max(Math.min(100, 72.1), 30) - 30, 0); return ( @@ -334,10 +263,6 @@ export class AirspeedIndicator extends DisplayComponent type="speed" > - - {this.createVMaxBarberPole()} - {this.createAlphaProtBarberPole()} - @@ -346,11 +271,13 @@ export class AirspeedIndicator extends DisplayComponent + + + - - + @@ -438,6 +365,8 @@ export class AirspeedIndicatorOfftape extends DisplayComponent<{ bus: EventBus } private decelRef = FSComponent.createRef(); + private spdLimFlagRef = FSComponent.createRef(); + private onGround = true; private leftMainGearCompressed = true; @@ -493,6 +422,14 @@ export class AirspeedIndicatorOfftape extends DisplayComponent<{ bus: EventBus } this.decelRef.instance.style.visibility = 'hidden'; } }); + + sub.on('facToUse').whenChanged().handle((a) => { + if (a === 0) { + this.spdLimFlagRef.instance.style.visibility = 'visible'; + } else { + this.spdLimFlagRef.instance.style.visibility = 'hidden'; + } + }); } render(): VNode { @@ -509,6 +446,10 @@ export class AirspeedIndicatorOfftape extends DisplayComponent<{ bus: EventBus } + + SPD + LIM + @@ -581,37 +522,38 @@ class SpeedTrendArrow extends DisplayComponent<{ airspeed: Subscribable, } } -interface VLSState { - alphaProtSpeed: number; - airSpeed: number; - vls: number; -} class VLsBar extends DisplayComponent<{ bus: EventBus }> { private previousTime = (new Date() as any).appTime(); private vlsPath = Subject.create(''); - private vlsState: VLSState = { - alphaProtSpeed: 0, - airSpeed: 0, - vls: 0, - } + private vAlphaProt = new Arinc429Word(0); + + private vStallWarn = new Arinc429Word(0); + + private airSpeed= new Arinc429Word(0); + + private vls= 0; + + private fcdc1DiscreteWord1 = new Arinc429Word(0); + + private fcdc2DiscreteWord1 = new Arinc429Word(0); private smoothSpeeds = (vlsDestination: number) => { const currentTime = (new Date() as any).appTime(); const deltaTime = currentTime - this.previousTime; const seconds = deltaTime / 1000; - const vls = SmoothSin(this.vlsState.vls, vlsDestination, 0.5, seconds); + const vls = SmoothSin(this.vls, vlsDestination, 0.5, seconds); this.previousTime = currentTime; return vls; }; - private setVlsPath(vls: number) { - const airSpeed = this.vlsState.airSpeed; + private setVlsPath() { + const normalLawActive = this.fcdc1DiscreteWord1.getBitValueOr(11, false) || this.fcdc2DiscreteWord1.getBitValueOr(11, false); - const VLsPos = (airSpeed - vls) * DistanceSpacing / ValueSpacing + 80.818; - const offset = (vls - this.vlsState.alphaProtSpeed) * DistanceSpacing / ValueSpacing; + const VLsPos = (this.airSpeed.value - this.vls) * DistanceSpacing / ValueSpacing + 80.818; + const offset = (this.vls - (normalLawActive ? this.vAlphaProt.valueOr(0) : this.vStallWarn.valueOr(0))) * DistanceSpacing / ValueSpacing; this.vlsPath.set(`m19.031 ${VLsPos}h 1.9748v${offset}`); } @@ -621,20 +563,34 @@ class VLsBar extends DisplayComponent<{ bus: EventBus }> { const sub = this.props.bus.getSubscriber(); - sub.on('alphaProt').handle((a) => { - this.vlsState.alphaProtSpeed = a; - this.setVlsPath(this.vlsState.vls); + sub.on('vAlphaProt').withArinc429Precision(2).handle((a) => { + this.vAlphaProt = a; + this.setVlsPath(); + }); + + sub.on('vStallWarn').withArinc429Precision(2).handle((a) => { + this.vStallWarn = a; + this.setVlsPath(); }); sub.on('speedAr').withArinc429Precision(2).handle((s) => { - this.vlsState.airSpeed = s.value; - this.setVlsPath(this.vlsState.vls); + this.airSpeed = s; + this.setVlsPath(); }); sub.on('vls').handle((vls) => { - const smoothedVls = this.smoothSpeeds(vls); - this.setVlsPath(smoothedVls); - this.vlsState.vls = smoothedVls; + this.vls = this.smoothSpeeds(vls); + this.setVlsPath(); + }); + + sub.on('fcdc1DiscreteWord1').handle((word) => { + this.fcdc1DiscreteWord1 = word; + this.setVlsPath(); + }); + + sub.on('fcdc2DiscreteWord1').handle((word) => { + this.fcdc2DiscreteWord1 = word; + this.setVlsPath(); }); } @@ -648,15 +604,20 @@ class VAlphaLimBar extends DisplayComponent<{ bus: EventBus }> { private airSpeed = new Arinc429Word(0); - private vAlphaLim = 0; + private vAlphaLim = new Arinc429Word(0); + + private fcdc1DiscreteWord1 = new Arinc429Word(0); + + private fcdc2DiscreteWord1 = new Arinc429Word(0); private setAlphaLimBarPath() { - if (this.vAlphaLim - this.airSpeed.value < -DisplayRange) { + const normalLawActive = this.fcdc1DiscreteWord1.getBitValueOr(11, false) || this.fcdc2DiscreteWord1.getBitValueOr(11, false); + if (this.vAlphaLim.value - this.airSpeed.value < -DisplayRange || this.vAlphaLim.isFailureWarning() || this.vAlphaLim.isNoComputedData() || !normalLawActive) { this.VAlimIndicator.instance.style.visibility = 'hidden'; } else { this.VAlimIndicator.instance.style.visibility = 'visible'; - const delta = this.airSpeed.value - DisplayRange - this.vAlphaLim; + const delta = this.airSpeed.value - DisplayRange - this.vAlphaLim.value; const offset = delta * DistanceSpacing / ValueSpacing; this.VAlimIndicator.instance.setAttribute('d', `m19.031 123.56h3.425v${offset}h-3.425z`); @@ -673,10 +634,20 @@ class VAlphaLimBar extends DisplayComponent<{ bus: EventBus }> { this.setAlphaLimBarPath(); }); - sub.on('alphaLim').withPrecision(2).handle((al) => { + sub.on('vAlphaMax').handle((al) => { this.vAlphaLim = al; this.setAlphaLimBarPath(); }); + + sub.on('fcdc1DiscreteWord1').handle((word) => { + this.fcdc1DiscreteWord1 = word; + this.setAlphaLimBarPath(); + }); + + sub.on('fcdc2DiscreteWord1').handle((word) => { + this.fcdc2DiscreteWord1 = word; + this.setAlphaLimBarPath(); + }); } render(): VNode { @@ -684,6 +655,183 @@ class VAlphaLimBar extends DisplayComponent<{ bus: EventBus }> { } } +class VAlphaProtBar extends DisplayComponent<{ bus: EventBus }> { + private VAprotIndicator = FSComponent.createRef(); + + private airSpeed = new Arinc429Word(0); + + private vAlphaProt = new Arinc429Word(0); + + private fcdc1DiscreteWord1 = new Arinc429Word(0); + + private fcdc2DiscreteWord1 = new Arinc429Word(0); + + private setAlphaProtBarPath() { + const normalLawActive = this.fcdc1DiscreteWord1.getBitValueOr(11, false) || this.fcdc2DiscreteWord1.getBitValueOr(11, false); + if (this.airSpeed.value - this.vAlphaProt.value > DisplayRange || this.vAlphaProt.isFailureWarning() || this.vAlphaProt.isNoComputedData() || !normalLawActive) { + this.VAprotIndicator.instance.style.visibility = 'hidden'; + } else { + this.VAprotIndicator.instance.style.visibility = 'visible'; + + const delta = Math.max(this.airSpeed.value - this.vAlphaProt.value, -DisplayRange); + const offset = delta * DistanceSpacing / ValueSpacing; + + this.VAprotIndicator.instance.style.transform = `translate3d(0px, ${offset}px, 0px)`; + } + } + + onAfterRender(node: VNode): void { + super.onAfterRender(node); + + const sub = this.props.bus.getSubscriber(); + + sub.on('speedAr').withArinc429Precision(2).handle((s) => { + this.airSpeed = s; + this.setAlphaProtBarPath(); + }); + + sub.on('vAlphaProt').withArinc429Precision(2).handle((word) => { + this.vAlphaProt = word; + this.setAlphaProtBarPath(); + }); + + sub.on('fcdc1DiscreteWord1').handle((word) => { + this.fcdc1DiscreteWord1 = word; + this.setAlphaProtBarPath(); + }); + + sub.on('fcdc2DiscreteWord1').handle((word) => { + this.fcdc2DiscreteWord1 = word; + this.setAlphaProtBarPath(); + }); + } + + render(): VNode { + return ( + + ); + } +} + +class VMaxBar extends DisplayComponent<{ bus: EventBus }> { + private VMaxIndicator = FSComponent.createRef(); + + private airSpeed = new Arinc429Word(0); + + private vMax = 0; + + private setVMaxBarPath() { + if (this.airSpeed.value - this.vMax < -DisplayRange) { + this.VMaxIndicator.instance.style.visibility = 'hidden'; + } else { + this.VMaxIndicator.instance.style.visibility = 'visible'; + + const delta = Math.min(this.airSpeed.value - this.vMax, DisplayRange); + const offset = delta * DistanceSpacing / ValueSpacing; + + this.VMaxIndicator.instance.style.transform = `translate3d(0px, ${offset}px, 0px)`; + } + } + + onAfterRender(node: VNode): void { + super.onAfterRender(node); + + const sub = this.props.bus.getSubscriber(); + + sub.on('speedAr').withArinc429Precision(2).handle((s) => { + this.airSpeed = s; + this.setVMaxBarPath(); + }); + + sub.on('vMax').withPrecision(2).handle((v) => { + this.vMax = v; + this.setVMaxBarPath(); + }); + } + + render(): VNode { + return ( + + ); + } +} + +class VStallWarnBar extends DisplayComponent<{ bus: EventBus }> { + private VStallWarnIndicator = FSComponent.createRef(); + + private airSpeed = new Arinc429Word(0); + + private vStallWarn = new Arinc429Word(0); + + private fcdc1DiscreteWord1 = new Arinc429Word(0); + + private fcdc2DiscreteWord1 = new Arinc429Word(0); + + private setVStallWarnBarPath() { + const normalLawActive = this.fcdc1DiscreteWord1.getBitValueOr(11, false) || this.fcdc2DiscreteWord1.getBitValueOr(11, false); + if (this.airSpeed.value - this.vStallWarn.value > DisplayRange || this.vStallWarn.isFailureWarning() || this.vStallWarn.isNoComputedData() || normalLawActive) { + this.VStallWarnIndicator.instance.style.visibility = 'hidden'; + } else { + this.VStallWarnIndicator.instance.style.visibility = 'visible'; + + const delta = Math.max(this.airSpeed.value - this.vStallWarn.value, -DisplayRange); + const offset = delta * DistanceSpacing / ValueSpacing; + + this.VStallWarnIndicator.instance.style.transform = `translate3d(0px, ${offset}px, 0px)`; + } + } + + onAfterRender(node: VNode): void { + super.onAfterRender(node); + + const sub = this.props.bus.getSubscriber(); + + sub.on('speedAr').withArinc429Precision(2).handle((s) => { + this.airSpeed = s; + this.setVStallWarnBarPath(); + }); + + sub.on('vStallWarn').withArinc429Precision(2).handle((v) => { + this.vStallWarn = v; + this.setVStallWarnBarPath(); + }); + + sub.on('fcdc1DiscreteWord1').handle((word) => { + this.fcdc1DiscreteWord1 = word; + this.setVStallWarnBarPath(); + }); + + sub.on('fcdc2DiscreteWord1').handle((word) => { + this.fcdc2DiscreteWord1 = word; + this.setVStallWarnBarPath(); + }); + } + + render(): VNode { + return ( + + ); + } +} + class V1Offtape extends DisplayComponent<{ bus: EventBus }> { private v1TextRef = FSComponent.createRef(); @@ -939,31 +1087,48 @@ export class MachNumber extends DisplayComponent<{bus: EventBus}> { } class VProtBug extends DisplayComponent<{bus: EventBus}> { - private vMaxBug = FSComponent.createRef(); + private vProtBug = FSComponent.createRef(); + + private fcdcWord1 = new Arinc429Word(0); + + private Vmax = 0; + + private handleVProtBugDisplay() { + const showVProt = this.Vmax > 240; + const offset = -(this.Vmax + 6) * DistanceSpacing / ValueSpacing; + + const isNormalLawActive = this.fcdcWord1.getBitValue(11) && !this.fcdcWord1.isFailureWarning(); + + if (showVProt && isNormalLawActive) { + this.vProtBug.instance.style.display = 'block'; + this.vProtBug.instance.style.transform = `translate3d(0px, ${offset}px, 0px)`; + } else { + this.vProtBug.instance.style.display = 'none'; + } + } onAfterRender(node: VNode): void { super.onAfterRender(node); - const sub = this.props.bus.getSubscriber(); + const sub = this.props.bus.getSubscriber(); sub.on('vMax').whenChanged().handle((vm) => { - const showVProt = vm > 240; - const offset = -(vm + 6) * DistanceSpacing / ValueSpacing; + this.Vmax = vm; - if (showVProt) { - this.vMaxBug.instance.classList.remove('HiddenElement'); - this.vMaxBug.instance.style.transform = `translate3d(0px, ${offset}px, 0px)`; - } else { - this.vMaxBug.instance.classList.add('HiddenElement'); - } + this.handleVProtBugDisplay(); + }); + + sub.on('fcdcDiscreteWord1').whenChanged().handle((word) => { + this.fcdcWord1 = word; + + this.handleVProtBugDisplay(); }); } render(): VNode { return ( - + ); } diff --git a/src/instruments/src/PFD/instrument.tsx b/src/instruments/src/PFD/instrument.tsx index 6d6cee57a4d3..c5bef8f42054 100644 --- a/src/instruments/src/PFD/instrument.tsx +++ b/src/instruments/src/PFD/instrument.tsx @@ -70,7 +70,6 @@ class A32NX_PFD extends BaseInstrument { this.simVarPublisher.subscribe('heading'); this.simVarPublisher.subscribe('altitude'); this.simVarPublisher.subscribe('speed'); - this.simVarPublisher.subscribe('alphaProt'); this.simVarPublisher.subscribe('noseGearCompressed'); this.simVarPublisher.subscribe('leftMainGearCompressed'); this.simVarPublisher.subscribe('rightMainGearCompressed'); @@ -97,8 +96,6 @@ class A32NX_PFD extends BaseInstrument { this.simVarPublisher.subscribe('airKnob'); this.simVarPublisher.subscribe('vsBaro'); this.simVarPublisher.subscribe('vsInert'); - this.simVarPublisher.subscribe('sideStickY'); - this.simVarPublisher.subscribe('sideStickX'); this.simVarPublisher.subscribe('fdYawCommand'); this.simVarPublisher.subscribe('fdBank'); this.simVarPublisher.subscribe('fdPitch'); @@ -161,17 +158,12 @@ class A32NX_PFD extends BaseInstrument { this.simVarPublisher.subscribe('expediteMode'); this.simVarPublisher.subscribe('setHoldSpeed'); this.simVarPublisher.subscribe('vls'); - this.simVarPublisher.subscribe('alphaLim'); this.simVarPublisher.subscribe('trkFpaDeselectedTCAS'); this.simVarPublisher.subscribe('tcasRaInhibited'); this.simVarPublisher.subscribe('groundSpeed'); this.simVarPublisher.subscribe('radioAltitude1'); this.simVarPublisher.subscribe('radioAltitude2'); - this.simVarPublisher.subscribe('beta'); - this.simVarPublisher.subscribe('betaTargetActive'); - this.simVarPublisher.subscribe('betaTarget'); - this.simVarPublisher.subscribe('latAcc'); this.simVarPublisher.subscribe('crzAltMode'); this.simVarPublisher.subscribe('tcasModeDisarmed'); this.simVarPublisher.subscribe('flexTemp'); @@ -180,11 +172,43 @@ class A32NX_PFD extends BaseInstrument { this.simVarPublisher.subscribe('autoBrakeDecel'); this.simVarPublisher.subscribe('fpaRaw'); this.simVarPublisher.subscribe('daRaw'); + this.simVarPublisher.subscribe('latAccRaw'); this.simVarPublisher.subscribe('ls1Button'); this.simVarPublisher.subscribe('ls2Button'); this.simVarPublisher.subscribe('xtk'); this.simVarPublisher.subscribe('ldevRequestLeft'); this.simVarPublisher.subscribe('ldevRequestRight'); + this.simVarPublisher.subscribe('landingElevation1'); + this.simVarPublisher.subscribe('landingElevation1Ssm'); + this.simVarPublisher.subscribe('landingElevation2'); + this.simVarPublisher.subscribe('landingElevation2Ssm'); + + this.simVarPublisher.subscribe('fcdc1DiscreteWord1Raw'); + this.simVarPublisher.subscribe('fcdc2DiscreteWord1Raw'); + this.simVarPublisher.subscribe('fcdc1DiscreteWord2Raw'); + this.simVarPublisher.subscribe('fcdc2DiscreteWord2Raw'); + + this.simVarPublisher.subscribe('fcdc1CaptPitchCommandRaw'); + this.simVarPublisher.subscribe('fcdc2CaptPitchCommandRaw'); + this.simVarPublisher.subscribe('fcdc1FoPitchCommandRaw'); + this.simVarPublisher.subscribe('fcdc2FoPitchCommandRaw'); + this.simVarPublisher.subscribe('fcdc1CaptRollCommandRaw'); + this.simVarPublisher.subscribe('fcdc2CaptRollCommandRaw'); + this.simVarPublisher.subscribe('fcdc1FoRollCommandRaw'); + this.simVarPublisher.subscribe('fcdc2FoRollCommandRaw'); + + this.simVarPublisher.subscribe('fac1Healthy'); + this.simVarPublisher.subscribe('fac2Healthy'); + this.simVarPublisher.subscribe('fac1VAlphaProtRaw'); + this.simVarPublisher.subscribe('fac2VAlphaProtRaw'); + this.simVarPublisher.subscribe('fac1VAlphaMaxRaw'); + this.simVarPublisher.subscribe('fac2VAlphaMaxRaw'); + this.simVarPublisher.subscribe('fac1VStallWarnRaw'); + this.simVarPublisher.subscribe('fac2VStallWarnRaw'); + this.simVarPublisher.subscribe('fac1EstimatedBetaRaw'); + this.simVarPublisher.subscribe('fac2EstimatedBetaRaw'); + this.simVarPublisher.subscribe('fac1BetaTargetRaw'); + this.simVarPublisher.subscribe('fac2BetaTargetRaw'); FSComponent.render(, document.getElementById('PFD_CONTENT')); } diff --git a/src/instruments/src/PFD/shared/AdirsValueProvider.tsx b/src/instruments/src/PFD/shared/AdirsValueProvider.tsx index 490bfc8ffadf..a8cde60aaa0d 100644 --- a/src/instruments/src/PFD/shared/AdirsValueProvider.tsx +++ b/src/instruments/src/PFD/shared/AdirsValueProvider.tsx @@ -20,6 +20,7 @@ export class AdirsValueProvider { this.pfdSimvar.updateSimVarSource('groundTrack', { name: `L:A32NX_ADIRS_IR_${inertialSource}_TRACK`, type: SimVarValueType.Number }); this.pfdSimvar.updateSimVarSource('fpaRaw', { name: `L:A32NX_ADIRS_IR_${inertialSource}_FLIGHT_PATH_ANGLE`, type: SimVarValueType.Number }); this.pfdSimvar.updateSimVarSource('daRaw', { name: `L:A32NX_ADIRS_IR_${inertialSource}_DRIFT_ANGLE`, type: SimVarValueType.Number }); + this.pfdSimvar.updateSimVarSource('latAccRaw', { name: `L:A32NX_ADIRS_IR_${inertialSource}_BODY_LATERAL_ACC`, type: SimVarValueType.Number }); }); sub.on('airKnob').whenChanged().handle((a) => { diff --git a/src/instruments/src/PFD/shared/ArincValueProvider.ts b/src/instruments/src/PFD/shared/ArincValueProvider.ts index e30732179f93..513354aef2c1 100644 --- a/src/instruments/src/PFD/shared/ArincValueProvider.ts +++ b/src/instruments/src/PFD/shared/ArincValueProvider.ts @@ -16,7 +16,23 @@ export interface Arinc429Values { chosenRa: Arinc429Word; fpa: Arinc429Word; da: Arinc429Word; - + landingElevation: Arinc429Word; + latAcc: Arinc429Word; + fcdcDiscreteWord1: Arinc429Word; + fcdc1DiscreteWord1: Arinc429Word; + fcdc2DiscreteWord1: Arinc429Word; + fcdc1DiscreteWord2: Arinc429Word; + fcdc2DiscreteWord2: Arinc429Word; + fcdcCaptPitchCommand: Arinc429Word; + fcdcFoPitchCommand: Arinc429Word; + fcdcCaptRollCommand: Arinc429Word; + fcdcFoRollCommand: Arinc429Word; + facToUse: number; + vAlphaMax: Arinc429Word; + vAlphaProt: Arinc429Word; + vStallWarn: Arinc429Word; + estimatedBeta: Arinc429Word; + betaTarget: Arinc429Word; } export class ArincValueProvider { private roll = new Arinc429Word(0); @@ -47,6 +63,32 @@ export class ArincValueProvider { private da = new Arinc429Word(0); + private ownLandingElevation = new Arinc429Word(0); + + private oppLandingElevation = new Arinc429Word(0); + + private latAcc = new Arinc429Word(0); + + private fcdc1DiscreteWord1 = new Arinc429Word(0); + + private fcdc2DiscreteWord1 = new Arinc429Word(0); + + private fcdc1DiscreteWord2 = new Arinc429Word(0); + + private fcdc2DiscreteWord2 = new Arinc429Word(0); + + private fcdcToUse = 0; + + private fac1Healthy = false; + + private fac2Healthy = false; + + private fac1VAlphaMax = new Arinc429Word(0); + + private fac2VAlphaMax = new Arinc429Word(0); + + private facToUse = 0; + constructor(private readonly bus: EventBus) { } @@ -134,6 +176,207 @@ export class ArincValueProvider { this.da = new Arinc429Word(da); publisher.pub('da', this.da); }); + + subscriber.on('landingElevation1').handle((elevation) => { + if (getDisplayIndex() === 1) { + this.ownLandingElevation.value = elevation; + } else { + this.oppLandingElevation.value = elevation; + } + this.determineAndPublishChosenLandingElevation(publisher); + }); + + subscriber.on('landingElevation1Ssm').handle((ssm) => { + if (getDisplayIndex() === 1) { + this.ownLandingElevation.ssm = ssm as any; + } else { + this.oppLandingElevation.ssm = ssm as any; + } + this.determineAndPublishChosenLandingElevation(publisher); + }); + + subscriber.on('landingElevation1').handle((elevation) => { + if (getDisplayIndex() === 1) { + this.oppLandingElevation.value = elevation; + } else { + this.ownLandingElevation.value = elevation; + } + this.determineAndPublishChosenLandingElevation(publisher); + }); + + subscriber.on('landingElevation1Ssm').handle((ssm) => { + if (getDisplayIndex() === 1) { + this.oppLandingElevation.ssm = ssm as any; + } else { + this.ownLandingElevation.ssm = ssm as any; + } + this.determineAndPublishChosenLandingElevation(publisher); + }); + + subscriber.on('latAccRaw').handle((latAcc) => { + this.latAcc = new Arinc429Word(latAcc); + publisher.pub('latAcc', this.latAcc); + }); + + subscriber.on('fcdc1DiscreteWord1Raw').handle((discreteWord1) => { + this.fcdc1DiscreteWord1 = new Arinc429Word(discreteWord1); + this.fcdcToUse = this.determineFcdcToUse(); + publisher.pub('fcdc1DiscreteWord1', this.fcdc1DiscreteWord1); + if (this.fcdcToUse === 1) { + publisher.pub('fcdcDiscreteWord1', this.fcdc1DiscreteWord1); + } + }); + + subscriber.on('fcdc2DiscreteWord1Raw').handle((discreteWord1) => { + this.fcdc2DiscreteWord1 = new Arinc429Word(discreteWord1); + this.fcdcToUse = this.determineFcdcToUse(); + publisher.pub('fcdc2DiscreteWord1', this.fcdc2DiscreteWord1); + if (this.fcdcToUse === 2) { + publisher.pub('fcdcDiscreteWord1', this.fcdc2DiscreteWord1); + } + }); + + subscriber.on('fcdc1DiscreteWord2Raw').handle((discreteWord2) => { + this.fcdc1DiscreteWord2 = new Arinc429Word(discreteWord2); + publisher.pub('fcdc1DiscreteWord2', this.fcdc1DiscreteWord2); + }); + + subscriber.on('fcdc2DiscreteWord2Raw').handle((discreteWord2) => { + this.fcdc2DiscreteWord2 = new Arinc429Word(discreteWord2); + publisher.pub('fcdc2DiscreteWord2', this.fcdc2DiscreteWord2); + }); + + subscriber.on('fcdc1CaptPitchCommandRaw').handle((word) => { + if (this.fcdcToUse === 1) { + publisher.pub('fcdcCaptPitchCommand', new Arinc429Word(word)); + } + }); + + subscriber.on('fcdc2CaptPitchCommandRaw').handle((word) => { + if (this.fcdcToUse === 2) { + publisher.pub('fcdcCaptPitchCommand', new Arinc429Word(word)); + } + }); + + subscriber.on('fcdc1FoPitchCommandRaw').handle((word) => { + if (this.fcdcToUse === 1) { + publisher.pub('fcdcFoPitchCommand', new Arinc429Word(word)); + } + }); + + subscriber.on('fcdc2FoPitchCommandRaw').handle((word) => { + if (this.fcdcToUse === 2) { + publisher.pub('fcdcFoPitchCommand', new Arinc429Word(word)); + } + }); + + subscriber.on('fcdc1CaptRollCommandRaw').handle((word) => { + if (this.fcdcToUse === 1) { + publisher.pub('fcdcCaptRollCommand', new Arinc429Word(word)); + } + }); + + subscriber.on('fcdc2CaptRollCommandRaw').handle((word) => { + if (this.fcdcToUse === 2) { + publisher.pub('fcdcCaptRollCommand', new Arinc429Word(word)); + } + }); + + subscriber.on('fcdc1FoRollCommandRaw').handle((word) => { + if (this.fcdcToUse === 1) { + publisher.pub('fcdcFoRollCommand', new Arinc429Word(word)); + } + }); + + subscriber.on('fcdc2FoRollCommandRaw').handle((word) => { + if (this.fcdcToUse === 2) { + publisher.pub('fcdcFoRollCommand', new Arinc429Word(word)); + } + }); + + subscriber.on('fac1Healthy').handle((val) => { + this.fac1Healthy = val; + this.determineFacToUse(publisher); + }); + + subscriber.on('fac2Healthy').handle((val) => { + this.fac2Healthy = val; + this.determineFacToUse(publisher); + }); + + subscriber.on('fac1VAlphaMaxRaw').handle((word) => { + this.fac1VAlphaMax = new Arinc429Word(word); + this.determineFacToUse(publisher); + if (this.facToUse === 1) { + publisher.pub('vAlphaMax', this.fac1VAlphaMax); + } else if (this.facToUse === 0) { + publisher.pub('vAlphaMax', new Arinc429Word(0)); + } + }); + + subscriber.on('fac2VAlphaMaxRaw').handle((word) => { + this.fac2VAlphaMax = new Arinc429Word(word); + this.determineFacToUse(publisher); + if (this.facToUse === 2) { + publisher.pub('vAlphaMax', this.fac2VAlphaMax); + } + }); + + subscriber.on('fac1VAlphaProtRaw').handle((word) => { + if (this.facToUse === 1) { + publisher.pub('vAlphaProt', new Arinc429Word(word)); + } else if (this.facToUse === 0) { + publisher.pub('vAlphaProt', new Arinc429Word(0)); + } + }); + + subscriber.on('fac2VAlphaProtRaw').handle((word) => { + if (this.facToUse === 2) { + publisher.pub('vAlphaProt', new Arinc429Word(word)); + } + }); + + subscriber.on('fac1VStallWarnRaw').handle((word) => { + if (this.facToUse === 1) { + publisher.pub('vStallWarn', new Arinc429Word(word)); + } else if (this.facToUse === 0) { + publisher.pub('vStallWarn', new Arinc429Word(0)); + } + }); + + subscriber.on('fac2VStallWarnRaw').handle((word) => { + if (this.facToUse === 2) { + publisher.pub('vStallWarn', new Arinc429Word(word)); + } + }); + + subscriber.on('fac1EstimatedBetaRaw').handle((word) => { + if (this.facToUse === 1) { + publisher.pub('estimatedBeta', new Arinc429Word(word)); + } else if (this.facToUse === 0) { + publisher.pub('estimatedBeta', new Arinc429Word(0)); + } + }); + + subscriber.on('fac2EstimatedBetaRaw').handle((word) => { + if (this.facToUse === 2) { + publisher.pub('estimatedBeta', new Arinc429Word(word)); + } + }); + + subscriber.on('fac1BetaTargetRaw').handle((word) => { + if (this.facToUse === 1) { + publisher.pub('betaTarget', new Arinc429Word(word)); + } else if (this.facToUse === 0) { + publisher.pub('betaTarget', new Arinc429Word(0)); + } + }); + + subscriber.on('fac2BetaTargetRaw').handle((word) => { + if (this.facToUse === 2) { + publisher.pub('betaTarget', new Arinc429Word(word)); + } + }); } private determineAndPublishChosenRadioAltitude(publisher: Publisher) { @@ -148,4 +391,56 @@ export class ArincValueProvider { publisher.pub('chosenRa', chosenRadioAltitude); } + + private determineAndPublishChosenLandingElevation(publisher: Publisher) { + const useOpposite = (this.ownLandingElevation.isFailureWarning() + || this.ownLandingElevation.isNoComputedData()) + && !this.oppLandingElevation.isFailureWarning() + && !this.oppLandingElevation.isNoComputedData(); + + if (useOpposite) { + publisher.pub('landingElevation', this.oppLandingElevation); + } else { + publisher.pub('landingElevation', this.ownLandingElevation); + } + } + + private determineFcdcToUse() { + if (getDisplayIndex() === 1) { + if ( + (this.fcdc1DiscreteWord1.isFailureWarning() && !this.fcdc2DiscreteWord1.isFailureWarning()) + || (!this.fcdc1DiscreteWord1.getBitValueOr(24, false) && this.fcdc2DiscreteWord1.getBitValueOr(24, false))) { + return 2; + } + return 1; + } + if (!((!this.fcdc1DiscreteWord1.isFailureWarning() && this.fcdc2DiscreteWord1.isFailureWarning()) + || (this.fcdc1DiscreteWord1.getBitValueOr(24, false) && !this.fcdc2DiscreteWord1.getBitValueOr(24, false)))) { + return 2; + } + return 1; + } + + // Determine which FAC bus to use for FE function. If FAC HEALTHY discrete is low or any word is coded FW, + // declare FAC as invalid. For simplicty reasons, only check SSM of words that use the same data, so all failure cases are + // handled while minimizing the words that have to be checked. + // Left PFD uses FAC 1 when both are valid, the right PFD uses FAC 2. In case of invalidity, switchover is performed. + // If no FAC is valid, set facToUse to 0. This causes the SPD LIM flag to be displayed. + private determineFacToUse(publisher: Publisher) { + const fac1Valid = this.fac1Healthy && !this.fac1VAlphaMax.isFailureWarning(); + const fac2Valid = this.fac2Healthy && !this.fac2VAlphaMax.isFailureWarning(); + if (getDisplayIndex() === 1 && fac1Valid) { + this.facToUse = 1; + } else if (getDisplayIndex() === 2 && fac2Valid) { + this.facToUse = 1; + } else if (fac1Valid) { + this.facToUse = 1; + } else if (fac2Valid) { + this.facToUse = 2; + } else { + this.facToUse = 0; + } + + publisher.pub('facToUse', this.facToUse); + } } diff --git a/src/instruments/src/PFD/shared/PFDSimvarPublisher.tsx b/src/instruments/src/PFD/shared/PFDSimvarPublisher.tsx index 9d8d30462c7f..1dfb878714d6 100644 --- a/src/instruments/src/PFD/shared/PFDSimvarPublisher.tsx +++ b/src/instruments/src/PFD/shared/PFDSimvarPublisher.tsx @@ -11,7 +11,6 @@ export interface PFDSimvars { heading: number; altitude: number; speed: number; - alphaProt: number; noseGearCompressed: boolean; leftMainGearCompressed: boolean; rightMainGearCompressed: boolean; @@ -38,8 +37,6 @@ export interface PFDSimvars { airKnob: number; vsBaro: number; vsInert: number; - sideStickX: number; - sideStickY: number; fdYawCommand: number; fdBank: number; fdPitch: number; @@ -92,16 +89,11 @@ export interface PFDSimvars { expediteMode: boolean; setHoldSpeed: boolean; vls: number; - alphaLim: number; trkFpaDeselectedTCAS: boolean; tcasRaInhibited: boolean; groundSpeed: number; radioAltitude1: number; radioAltitude2: number; - beta: number; - betaTargetActive: number; - betaTarget: number; - latAcc: number; crzAltMode: boolean; tcasModeDisarmed: boolean; flexTemp: number; @@ -110,11 +102,40 @@ export interface PFDSimvars { autoBrakeDecel: boolean; fpaRaw: number; daRaw: number; + latAccRaw: number; ls1Button: boolean; ls2Button: boolean; + fcdc1DiscreteWord1Raw: number; + fcdc2DiscreteWord1Raw: number; + fcdc1DiscreteWord2Raw: number; + fcdc2DiscreteWord2Raw: number; + fcdc1CaptPitchCommandRaw: number; + fcdc2CaptPitchCommandRaw: number; + fcdc1FoPitchCommandRaw: number; + fcdc2FoPitchCommandRaw: number; + fcdc1CaptRollCommandRaw: number; + fcdc2CaptRollCommandRaw: number; + fcdc1FoRollCommandRaw: number; + fcdc2FoRollCommandRaw: number; xtk: number; ldevRequestLeft: boolean; ldevRequestRight: boolean; + landingElevation1: number; + landingElevation1Ssm: number; + landingElevation2: number; + landingElevation2Ssm: number; + fac1Healthy: boolean; + fac2Healthy: boolean; + fac1VAlphaProtRaw: number; + fac2VAlphaProtRaw: number; + fac1VAlphaMaxRaw: number; + fac2VAlphaMaxRaw: number; + fac1VStallWarnRaw: number; + fac2VStallWarnRaw: number; + fac1EstimatedBetaRaw: number; + fac2EstimatedBetaRaw: number; + fac1BetaTargetRaw: number; + fac2BetaTargetRaw: number; } export enum PFDVars { @@ -128,7 +149,6 @@ export enum PFDVars { heading = 'L:A32NX_ADIRS_IR_1_HEADING', altitude = 'L:A32NX_ADIRS_ADR_1_ALTITUDE', speed = 'L:A32NX_ADIRS_ADR_1_COMPUTED_AIRSPEED', - alphaProt = 'L:A32NX_SPEEDS_ALPHA_PROTECTION', noseGearCompressed = 'L:A32NX_LGCIU_1_NOSE_GEAR_COMPRESSED', leftMainGearCompressed = 'L:A32NX_LGCIU_1_LEFT_GEAR_COMPRESSED', rightMainGearCompressed = 'L:A32NX_LGCIU_1_RIGHT_GEAR_COMPRESSED', @@ -155,8 +175,6 @@ export enum PFDVars { airKnob = 'L:A32NX_AIR_DATA_SWITCHING_KNOB', vsBaro = 'L:A32NX_ADIRS_ADR_1_BAROMETRIC_VERTICAL_SPEED', vsInert = 'L:A32NX_ADIRS_IR_1_VERTICAL_SPEED', - sideStickX = 'L:A32NX_SIDESTICK_POSITION_X', - sideStickY = 'L:A32NX_SIDESTICK_POSITION_Y', fdYawCommand = 'L:A32NX_FLIGHT_DIRECTOR_YAW', fdBank = 'L:A32NX_FLIGHT_DIRECTOR_BANK', fdPitch = 'L:A32NX_FLIGHT_DIRECTOR_PITCH', @@ -196,7 +214,6 @@ export enum PFDVars { metricAltToggle = 'L:A32NX_METRIC_ALT_TOGGLE', tla1='L:A32NX_AUTOTHRUST_TLA:1', tla2='L:A32NX_AUTOTHRUST_TLA:2', - landingElevation = 'L:A32NX_PRESS_AUTO_LANDING_ELEVATION', tcasState = 'L:A32NX_TCAS_STATE', tcasCorrective = 'L:A32NX_TCAS_RA_CORRECTIVE', tcasRedZoneL = 'L:A32NX_TCAS_VSPEED_RED:1', @@ -209,16 +226,11 @@ export enum PFDVars { expediteMode = 'L:A32NX_FMA_EXPEDITE_MODE', setHoldSpeed = 'L:A32NX_PFD_MSG_SET_HOLD_SPEED', vls = 'L:A32NX_SPEEDS_VLS', - alphaLim = 'L:A32NX_SPEEDS_ALPHA_MAX', trkFpaDeselectedTCAS= 'L:A32NX_AUTOPILOT_TCAS_MESSAGE_TRK_FPA_DESELECTION', tcasRaInhibited = 'L:A32NX_AUTOPILOT_TCAS_MESSAGE_RA_INHIBITED', groundSpeed = 'L:A32NX_ADIRS_IR_1_GROUND_SPEED', radioAltitude1 = 'L:A32NX_RA_1_RADIO_ALTITUDE', radioAltitude2 = 'L:A32NX_RA_2_RADIO_ALTITUDE', - beta = 'INCIDENCE BETA', - betaTargetActive = 'L:A32NX_BETA_TARGET_ACTIVE', - betaTarget = 'L:A32NX_BETA_TARGET', - latAcc = 'ACCELERATION BODY X', crzAltMode = 'L:A32NX_FMA_CRUISE_ALT_MODE', tcasModeDisarmed = 'L:A32NX_AUTOPILOT_TCAS_MESSAGE_DISARM', flexTemp = 'L:AIRLINER_TO_FLEX_TEMP', @@ -227,11 +239,40 @@ export enum PFDVars { autoBrakeDecel = 'L:A32NX_AUTOBRAKES_DECEL_LIGHT', fpaRaw = 'L:A32NX_ADIRS_IR_1_FLIGHT_PATH_ANGLE', daRaw = 'L:A32NX_ADIRS_IR_1_DRIFT_ANGLE', + latAccRaw = 'L:A32NX_ADIRS_IR_1_BODY_LATERAL_ACC', ls1Button = 'L:BTN_LS_1_FILTER_ACTIVE', ls2Button = 'L:BTN_LS_2_FILTER_ACTIVE', + fcdc1DiscreteWord1Raw = 'L:A32NX_FCDC_1_DISCRETE_WORD_1', + fcdc2DiscreteWord1Raw = 'L:A32NX_FCDC_2_DISCRETE_WORD_1', + fcdc1DiscreteWord2Raw = 'L:A32NX_FCDC_1_DISCRETE_WORD_2', + fcdc2DiscreteWord2Raw = 'L:A32NX_FCDC_2_DISCRETE_WORD_2', + fcdc1CaptPitchCommandRaw = 'L:A32NX_FCDC_1_CAPT_PITCH_COMMAND', + fcdc2CaptPitchCommandRaw = 'L:A32NX_FCDC_2_CAPT_PITCH_COMMAND', + fcdc1FoPitchCommandRaw = 'L:A32NX_FCDC_1_FO_PITCH_COMMAND', + fcdc2FoPitchCommandRaw = 'L:A32NX_FCDC_2_FO_PITCH_COMMAND', + fcdc1CaptRollCommandRaw = 'L:A32NX_FCDC_1_CAPT_ROLL_COMMAND', + fcdc2CaptRollCommandRaw = 'L:A32NX_FCDC_2_CAPT_ROLL_COMMAND', + fcdc1FoRollCommandRaw = 'L:A32NX_FCDC_1_FO_ROLL_COMMAND', + fcdc2FoRollCommandRaw = 'L:A32NX_FCDC_2_FO_ROLL_COMMAND', xtk = 'L:A32NX_FG_CROSS_TRACK_ERROR', ldevLeft = 'L:A32NX_FMGC_L_LDEV_REQUEST', ldevRight = 'L:A32NX_FMGC_R_LDEV_REQUEST', + landingElevation1 = 'L:A32NX_FM1_LANDING_ELEVATION', + landingElevation1Ssm = 'L:A32NX_FM1_LANDING_ELEVATION_SSM', + landingElevation2 = 'L:A32NX_FM2_LANDING_ELEVATION', + landingElevation2Ssm = 'L:A32NX_FM2_LANDING_ELEVATION_SSM', + fac1Healthy = 'L:A32NX_FAC_1_HEALTHY', + fac2Healthy = 'L:A32NX_FAC_2_HEALTHY', + fac1VAlphaProtRaw = 'L:A32NX_FAC_1_V_ALPHA_PROT', + fac2VAlphaProtRaw = 'L:A32NX_FAC_2_V_ALPHA_PROT', + fac1VAlphaMaxRaw = 'L:A32NX_FAC_1_V_ALPHA_LIM', + fac2VAlphaMaxRaw = 'L:A32NX_FAC_2_V_ALPHA_LIM', + fac1VStallWarnRaw = 'L:A32NX_FAC_1_V_STALL_WARN', + fac2VStallWarnRaw = 'L:A32NX_FAC_2_V_STALL_WARN', + fac1EstimatedBetaRaw = 'L:A32NX_FAC_1_ESTIMATED_SIDESLIP', + fac2EstimatedBetaRaw = 'L:A32NX_FAC_2_ESTIMATED_SIDESLIP', + fac1BetaTargetRaw = 'L:A32NX_FAC_1_SIDESLIP_TARGET', + fac2BetaTargetRaw = 'L:A32NX_FAC_2_SIDESLIP_TARGET', } /** A publisher to poll and publish nav/com simvars. */ @@ -247,7 +288,6 @@ export class PFDSimvarPublisher extends SimVarPublisher { ['heading', { name: PFDVars.heading, type: SimVarValueType.Number }], ['altitude', { name: PFDVars.altitude, type: SimVarValueType.Number }], ['speed', { name: PFDVars.speed, type: SimVarValueType.Number }], - ['alphaProt', { name: PFDVars.alphaProt, type: SimVarValueType.Number }], ['noseGearCompressed', { name: PFDVars.noseGearCompressed, type: SimVarValueType.Bool }], ['leftMainGearCompressed', { name: PFDVars.leftMainGearCompressed, type: SimVarValueType.Bool }], ['rightMainGearCompressed', { name: PFDVars.rightMainGearCompressed, type: SimVarValueType.Bool }], @@ -274,8 +314,6 @@ export class PFDSimvarPublisher extends SimVarPublisher { ['airKnob', { name: PFDVars.airKnob, type: SimVarValueType.Enum }], ['vsBaro', { name: PFDVars.vsBaro, type: SimVarValueType.Number }], ['vsInert', { name: PFDVars.vsInert, type: SimVarValueType.Number }], - ['sideStickX', { name: PFDVars.sideStickX, type: SimVarValueType.Number }], - ['sideStickY', { name: PFDVars.sideStickY, type: SimVarValueType.Number }], ['fdYawCommand', { name: PFDVars.fdYawCommand, type: SimVarValueType.Number }], ['fdBank', { name: PFDVars.fdBank, type: SimVarValueType.Number }], ['fdPitch', { name: PFDVars.fdPitch, type: SimVarValueType.Number }], @@ -315,7 +353,6 @@ export class PFDSimvarPublisher extends SimVarPublisher { ['metricAltToggle', { name: PFDVars.metricAltToggle, type: SimVarValueType.Bool }], ['tla1', { name: PFDVars.tla1, type: SimVarValueType.Number }], ['tla2', { name: PFDVars.tla2, type: SimVarValueType.Number }], - ['landingElevation', { name: PFDVars.landingElevation, type: SimVarValueType.Feet }], ['tcasState', { name: PFDVars.tcasState, type: SimVarValueType.Enum }], ['tcasCorrective', { name: PFDVars.tcasCorrective, type: SimVarValueType.Bool }], ['tcasRedZoneL', { name: PFDVars.tcasRedZoneL, type: SimVarValueType.Number }], @@ -328,16 +365,11 @@ export class PFDSimvarPublisher extends SimVarPublisher { ['expediteMode', { name: PFDVars.expediteMode, type: SimVarValueType.Bool }], ['setHoldSpeed', { name: PFDVars.setHoldSpeed, type: SimVarValueType.Bool }], ['vls', { name: PFDVars.vls, type: SimVarValueType.Number }], - ['alphaLim', { name: PFDVars.alphaLim, type: SimVarValueType.Number }], ['trkFpaDeselectedTCAS', { name: PFDVars.trkFpaDeselectedTCAS, type: SimVarValueType.Bool }], ['tcasRaInhibited', { name: PFDVars.tcasRaInhibited, type: SimVarValueType.Bool }], ['groundSpeed', { name: PFDVars.groundSpeed, type: SimVarValueType.Number }], ['radioAltitude1', { name: PFDVars.radioAltitude1, type: SimVarValueType.Number }], ['radioAltitude2', { name: PFDVars.radioAltitude2, type: SimVarValueType.Number }], - ['beta', { name: PFDVars.beta, type: SimVarValueType.Degree }], - ['betaTargetActive', { name: PFDVars.betaTargetActive, type: SimVarValueType.Number }], - ['betaTarget', { name: PFDVars.betaTarget, type: SimVarValueType.Number }], - ['latAcc', { name: PFDVars.latAcc, type: SimVarValueType.GForce }], ['crzAltMode', { name: PFDVars.crzAltMode, type: SimVarValueType.Bool }], ['tcasModeDisarmed', { name: PFDVars.tcasModeDisarmed, type: SimVarValueType.Bool }], ['flexTemp', { name: PFDVars.flexTemp, type: SimVarValueType.Number }], @@ -346,11 +378,40 @@ export class PFDSimvarPublisher extends SimVarPublisher { ['autoBrakeDecel', { name: PFDVars.autoBrakeDecel, type: SimVarValueType.Bool }], ['fpaRaw', { name: PFDVars.fpaRaw, type: SimVarValueType.Number }], ['daRaw', { name: PFDVars.daRaw, type: SimVarValueType.Number }], + ['latAccRaw', { name: PFDVars.latAccRaw, type: SimVarValueType.Number }], ['ls1Button', { name: PFDVars.ls1Button, type: SimVarValueType.Bool }], ['ls2Button', { name: PFDVars.ls2Button, type: SimVarValueType.Bool }], + ['fcdc1DiscreteWord1Raw', { name: PFDVars.fcdc1DiscreteWord1Raw, type: SimVarValueType.Number }], + ['fcdc2DiscreteWord1Raw', { name: PFDVars.fcdc2DiscreteWord1Raw, type: SimVarValueType.Number }], + ['fcdc1DiscreteWord2Raw', { name: PFDVars.fcdc1DiscreteWord2Raw, type: SimVarValueType.Number }], + ['fcdc2DiscreteWord2Raw', { name: PFDVars.fcdc2DiscreteWord2Raw, type: SimVarValueType.Number }], + ['fcdc1CaptPitchCommandRaw', { name: PFDVars.fcdc1CaptPitchCommandRaw, type: SimVarValueType.Number }], + ['fcdc2CaptPitchCommandRaw', { name: PFDVars.fcdc2CaptPitchCommandRaw, type: SimVarValueType.Number }], + ['fcdc1FoPitchCommandRaw', { name: PFDVars.fcdc1FoPitchCommandRaw, type: SimVarValueType.Number }], + ['fcdc2FoPitchCommandRaw', { name: PFDVars.fcdc2FoPitchCommandRaw, type: SimVarValueType.Number }], + ['fcdc1CaptRollCommandRaw', { name: PFDVars.fcdc1CaptRollCommandRaw, type: SimVarValueType.Number }], + ['fcdc2CaptRollCommandRaw', { name: PFDVars.fcdc2CaptRollCommandRaw, type: SimVarValueType.Number }], + ['fcdc1FoRollCommandRaw', { name: PFDVars.fcdc1FoRollCommandRaw, type: SimVarValueType.Number }], + ['fcdc2FoRollCommandRaw', { name: PFDVars.fcdc2FoRollCommandRaw, type: SimVarValueType.Number }], ['xtk', { name: PFDVars.xtk, type: SimVarValueType.NM }], ['ldevRequestLeft', { name: PFDVars.ldevLeft, type: SimVarValueType.Bool }], ['ldevRequestRight', { name: PFDVars.ldevRight, type: SimVarValueType.Bool }], + ['landingElevation1', { name: PFDVars.landingElevation1, type: SimVarValueType.Number }], + ['landingElevation1Ssm', { name: PFDVars.landingElevation1Ssm, type: SimVarValueType.Number }], + ['landingElevation2', { name: PFDVars.landingElevation2, type: SimVarValueType.Number }], + ['landingElevation2Ssm', { name: PFDVars.landingElevation2Ssm, type: SimVarValueType.Number }], + ['fac1Healthy', { name: PFDVars.fac1Healthy, type: SimVarValueType.Bool }], + ['fac2Healthy', { name: PFDVars.fac2Healthy, type: SimVarValueType.Bool }], + ['fac1VAlphaProtRaw', { name: PFDVars.fac1VAlphaProtRaw, type: SimVarValueType.Number }], + ['fac2VAlphaProtRaw', { name: PFDVars.fac2VAlphaProtRaw, type: SimVarValueType.Number }], + ['fac1VAlphaMaxRaw', { name: PFDVars.fac1VAlphaMaxRaw, type: SimVarValueType.Number }], + ['fac2VAlphaMaxRaw', { name: PFDVars.fac2VAlphaMaxRaw, type: SimVarValueType.Number }], + ['fac1VStallWarnRaw', { name: PFDVars.fac1VStallWarnRaw, type: SimVarValueType.Number }], + ['fac2VStallWarnRaw', { name: PFDVars.fac2VStallWarnRaw, type: SimVarValueType.Number }], + ['fac1EstimatedBetaRaw', { name: PFDVars.fac1EstimatedBetaRaw, type: SimVarValueType.Number }], + ['fac2EstimatedBetaRaw', { name: PFDVars.fac2EstimatedBetaRaw, type: SimVarValueType.Number }], + ['fac1BetaTargetRaw', { name: PFDVars.fac1BetaTargetRaw, type: SimVarValueType.Number }], + ['fac2BetaTargetRaw', { name: PFDVars.fac2BetaTargetRaw, type: SimVarValueType.Number }], ]) public constructor(bus: EventBus) { diff --git a/src/instruments/src/PFD/shared/displayUnit.tsx b/src/instruments/src/PFD/shared/displayUnit.tsx index d59f15485ea4..4bec3fe2f7cc 100644 --- a/src/instruments/src/PFD/shared/displayUnit.tsx +++ b/src/instruments/src/PFD/shared/displayUnit.tsx @@ -1,6 +1,7 @@ import { ClockEvents, DisplayComponent, EventBus, FSComponent, Subscribable, VNode } from 'msfssdk'; import './common.scss'; +import './pixels.scss'; import { NXDataStore } from '@shared/persistence'; import { PFDSimvars } from './PFDSimvarPublisher'; diff --git a/src/instruments/src/PFD/shared/pixels.scss b/src/instruments/src/PFD/shared/pixels.scss index b0e404ecf88c..c586e9f9101b 100644 --- a/src/instruments/src/PFD/shared/pixels.scss +++ b/src/instruments/src/PFD/shared/pixels.scss @@ -4,7 +4,7 @@ height: 100%; position: absolute; z-index: 998; - opacity: 0.75; - background-color: rgba(0, 0, 255, 0.025); - box-shadow: inset 0px 0px 30px 10px rgba(0, 75, 255, 0.04); + opacity: 1; + background-color: rgba(0, 0, 255, 0.035); + box-shadow: inset 0px 0px 30px 10px rgba(0, 76, 255, 0.08); } diff --git a/src/instruments/src/PFD/style.scss b/src/instruments/src/PFD/style.scss index fd0ad0ae2c02..9934bf34f271 100644 --- a/src/instruments/src/PFD/style.scss +++ b/src/instruments/src/PFD/style.scss @@ -191,6 +191,10 @@ text.Green { stroke: $display-amber; fill: none; } +.Amber.Fill { + fill: $display-amber; + stroke: none; +} text.Amber { fill: $display-amber; stroke: none; @@ -254,13 +258,3 @@ text.Red { .HiddenElement { display: none; } - -.BacklightBleed { - width: 100%; - height: 100%; - position: absolute; - z-index: 998; - opacity: 0.75; - background-color: rgba(0, 0, 255, 0.025); - box-shadow: inset 0px 0px 30px 10px rgba(0, 75, 255, 0.04); -} diff --git a/src/instruments/src/RMP/Components/RadioPanelDisplay.tsx b/src/instruments/src/RMP/Components/RadioPanelDisplay.tsx index c338eeae0e14..a2ada050a576 100644 --- a/src/instruments/src/RMP/Components/RadioPanelDisplay.tsx +++ b/src/instruments/src/RMP/Components/RadioPanelDisplay.tsx @@ -8,6 +8,8 @@ interface Props { value: number | string, } +const TEXT_DATA_MODE_VHF3 = 'DATA'; + /** * Format the given frequency to be displayed. * @param frequency The given frequency number in Hz. @@ -23,14 +25,39 @@ const formatFrequency = (frequency: number): string => (frequency / 1000000).toF export function RadioPanelDisplay(props: Props) { const [lightsTest] = useSimVar('L:A32NX_OVHD_INTLT_ANN', 'Boolean', 1000); - // If the passed value prop is a number, we'll use formatFrequency to get string format. - const value = typeof props.value === 'number' ? formatFrequency(props.value) : props.value; + let content: JSX.Element; - return ( - + if (lightsTest === 0) { + content = ( + + 8.8.8.8.8.8 + + ); + } else if (props.value > 0) { + let value = ''; + // If the passed value prop is a number, we'll use formatFrequency to get string format. + if (typeof props.value === 'number') { + value = formatFrequency(props.value); + } else { + value = props.value; + } + + content = ( - {lightsTest === 0 ? '8.8.8.8.8.8' : value} + {value} + + ); + } else { + content = ( + + {TEXT_DATA_MODE_VHF3} + ); + } + + return ( + + {content} ); } diff --git a/src/instruments/src/RMP/Components/StandbyFrequency.tsx b/src/instruments/src/RMP/Components/StandbyFrequency.tsx index 46f6ada5b40b..6b2e46fc21e8 100644 --- a/src/instruments/src/RMP/Components/StandbyFrequency.tsx +++ b/src/instruments/src/RMP/Components/StandbyFrequency.tsx @@ -103,29 +103,36 @@ export const StandbyFrequency = (props: Props) => { const spacing = usePersistentProperty('RMP_VHF_SPACING_25KHZ', '0')[0] === '0' ? 8.33 : 25; // Handle outer knob turned. const outerKnobUpdateCallback: UpdateValueCallback = useCallback((offset) => { - const frequency = Math.round(props.value / 1000); - const integer = Math.floor(frequency / 1000); - const decimal = frequency % 1000; - // @todo determine min/max depending on mode. - const maxInteger = decimal > 975 ? 135 : 136; - const newInteger = Utils.Clamp(integer + offset, 118, maxInteger); - props.setValue((newInteger * 1000 + decimal) * 1000); + if (props.value !== 0) { + const frequency = Math.round(props.value / 1000); + const integer = Math.floor(frequency / 1000); + const decimal = frequency % 1000; + // @todo determine min/max depending on mode. + const maxInteger = decimal > 975 ? 135 : 136; + const newInteger = Utils.Clamp(integer + offset, 118, maxInteger); + props.setValue((newInteger * 1000 + decimal) * 1000); + } else { + props.setValue(0); + } }, [props.value]); // Handle inner knob turned. const innerKnobUpdateCallback: UpdateValueCallback = useCallback((offset) => { const frequency = Math.round(props.value / 1000); - if (Math.sign(offset) === 1 && frequency === 136975) { - return; + if (props.value !== 0) { + const integer = Math.floor(frequency / 1000); + // @todo determine correct frequency spacing depending on mode. + const decimal = offsetFrequencyChannel(spacing, frequency % 1000, offset); + // @todo determine min/max depending on mode. + // Tested in real life: + // Integer cannot return to 118 from 136 to the right + // Decimal can return to 0 from 975 to the right + // const maxDecimal = integer === 136 ? 975 : 1000; + const newDecimal = Utils.Clamp(decimal, 0, 1000); + props.setValue((integer * 1000 + newDecimal) * 1000); + } else { + props.setValue(0); } - - const integer = Math.floor(frequency / 1000); - // @todo determine correct frequency spacing depending on mode. - const decimal = offsetFrequencyChannel(spacing, frequency % 1000, offset); - // @todo determine min/max depending on mode. - const maxDecimal = integer === 136 ? 975 : 1000; - const newDecimal = Utils.Clamp(decimal, 0, maxDecimal); - props.setValue((integer * 1000 + newDecimal) * 1000); }, [props.value]); // Used to change integer value of freq. diff --git a/src/instruments/src/RMP/Components/VhfRadioPanel.tsx b/src/instruments/src/RMP/Components/VhfRadioPanel.tsx index 9e2dcd4eeb13..8e358d309b61 100644 --- a/src/instruments/src/RMP/Components/VhfRadioPanel.tsx +++ b/src/instruments/src/RMP/Components/VhfRadioPanel.tsx @@ -58,9 +58,15 @@ const useStandbyVhfFrequency = (side: string, transceiver: number) => { export const VhfRadioPanel = (props: Props) => { const [active, setActive] = useActiveVhfFrequency(props.transceiver); const [standby, setStandby] = useStandbyVhfFrequency(props.side, props.transceiver); + const [, setValueOppositePanelStandby] = props.side === 'L' ? useStandbyVhfFrequency('R', 3) : useStandbyVhfFrequency('L', 3); // Handle Transfer Button Pressed. useInteractionEvent(`A32NX_RMP_${props.side}_TRANSFER_BUTTON_PRESSED`, () => { + // Force the standby opposite side otherwise we would lose the frequency/data format + // Otherwise it would become frequency/frequency + if (props.transceiver === 3) { + setValueOppositePanelStandby(active); + } setActive(standby); setStandby(active); }); diff --git a/src/instruments/src/SD/Common/CommonStyles.scss b/src/instruments/src/SD/Common/CommonStyles.scss index 4e5e66cac764..90c35becb724 100644 --- a/src/instruments/src/SD/Common/CommonStyles.scss +++ b/src/instruments/src/SD/Common/CommonStyles.scss @@ -1,6 +1,12 @@ @import "../style.scss"; @import "../../Common/definitions.scss"; +.ecam-common-styles { + +.Hide { + display: none; +} + .ThickCyanLine { stroke: $display-cyan !important; stroke-width: 6; @@ -14,7 +20,12 @@ stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; - fill: none; + &:not(.FillAmber) { + fill: none; + } + &.FillAmber { + fill: $display-amber; + } } .RedLine { @@ -30,7 +41,12 @@ stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; - fill: none; + &:not(.FillGreen) { + fill: none; + } + &.FillGreen { + fill: $display-green; + } } .WhiteLine { @@ -124,3 +140,5 @@ tspan { text-decoration-color: $display-white; } } + +} diff --git a/src/instruments/src/SD/Common/Spoilers.tsx b/src/instruments/src/SD/Common/Spoilers.tsx index 1af138900eed..843ba650ee88 100644 --- a/src/instruments/src/SD/Common/Spoilers.tsx +++ b/src/instruments/src/SD/Common/Spoilers.tsx @@ -1,76 +1,73 @@ -import { useSimVar } from '@instruments/common/simVars'; import React from 'react'; +import { useArinc429Var } from '@instruments/common/arinc429'; +import { Arinc429Word } from '@shared/arinc429'; import { ComponentPositionProps } from './ComponentPositionProps'; import { ComponentSidePositionProps } from './ComponentSidePositionProps'; -import { useHydraulics } from './HydraulicsProvider'; -import { HydraulicSystem } from './HydraulicSystem'; import { SvgGroup } from './SvgGroup'; export const Spoilers = ({ x, y }: ComponentPositionProps) => { - const [aileronLeftDeflectionState] = useSimVar('L:A32NX_HYD_AILERON_LEFT_DEFLECTION', 'number', 50); - const [aileronRightDeflectionState] = useSimVar('L:A32NX_HYD_AILERON_RIGHT_DEFLECTION', 'number', 50); + const fcdc1DiscreteWord3 = useArinc429Var('L:A32NX_FCDC_1_DISCRETE_WORD_3'); + const fcdc2DiscreteWord3 = useArinc429Var('L:A32NX_FCDC_2_DISCRETE_WORD_3'); + const fcdc1DiscreteWord4 = useArinc429Var('L:A32NX_FCDC_1_DISCRETE_WORD_4'); + const fcdc2DiscreteWord4 = useArinc429Var('L:A32NX_FCDC_2_DISCRETE_WORD_4'); - const [leftSpoilerState] = useSimVar('L:A32NX_HYD_SPOILERS_LEFT_DEFLECTION', 'number', 50); - const [rightSpoilerState] = useSimVar('L:A32NX_HYD_SPOILERS_RIGHT_DEFLECTION', 'number', 50); - const [speedBrakeHandlePosition] = useSimVar('SPOILERS HANDLE POSITION', 'percent over 100', 100); - - const [spoilersArmed] = useSimVar('L:A32NX_SPOILERS_ARMED', 'boolean', 500); - - const leftSpoilerUp = leftSpoilerState > 0.1; - const leftAileronUp = aileronLeftDeflectionState < -0.5; - const rightSpoilerUp = rightSpoilerState > 0.1; - const rightAileronUp = aileronRightDeflectionState > 0.5; - - const speedBrakeUp = speedBrakeHandlePosition > 0.1; + const fcdcWord3ToUse = !fcdc1DiscreteWord3.isFailureWarning() ? fcdc1DiscreteWord3 : fcdc2DiscreteWord3; + const fcdcWord4ToUse = !fcdc1DiscreteWord4.isFailureWarning() ? fcdc1DiscreteWord4 : fcdc2DiscreteWord4; return ( - - - - - + + + + + - - - - - + + + + + ); }; interface SpoilerProps extends ComponentPositionProps, ComponentSidePositionProps { number: number, - actuatedBy: HydraulicSystem, - upWhenActuated: boolean, + fcdcWord3: Arinc429Word, + fcdcWord4: Arinc429Word } -const Spoiler = ({ x, y, number, side, actuatedBy, upWhenActuated }: SpoilerProps) => { - const hydraulics = useHydraulics(); +const Spoiler = ({ x, y, number, side, fcdcWord3, fcdcWord4 }: SpoilerProps) => { + const availAndValidBit = 20 + number; + const isAvail = fcdcWord3.getBitValueOr(availAndValidBit, false); + const isPosValid = fcdcWord4.getBitValueOr(availAndValidBit, false); + + const spoilerOutIndex = 9 + number * 2 + (side === 'left' ? 0 : 1); + const isSpoilerOut = fcdcWord4.getBitValueOr(spoilerOutIndex, false); return ( - {number} + {isPosValid ? number : 'X'} ); diff --git a/src/instruments/src/SD/Pages/Bleed/elements/BleedGauge.tsx b/src/instruments/src/SD/Pages/Bleed/elements/BleedGauge.tsx index 0d1824fc90c1..78c46021030e 100644 --- a/src/instruments/src/SD/Pages/Bleed/elements/BleedGauge.tsx +++ b/src/instruments/src/SD/Pages/Bleed/elements/BleedGauge.tsx @@ -18,7 +18,7 @@ const BleedGauge: FC = ({ x, y, engine, sdacDatum, packFlowValv const [precoolerOutletTemp] = useSimVar(`L:A32NX_PNEU_ENG_${engine}_PRECOOLER_OUTLET_TEMPERATURE`, 'celsius', 500); const compressorOutletTemp = Math.round(precoolerOutletTemp / 5) * 5; - const [packInletFlowPercentage] = useSimVar('L:A32NX_COND_PACK_FLOW', 'percent', 500); + const [packInletFlowPercentage] = useSimVar(`L:A32NX_COND_PACK_FLOW_${engine}`, 'percent', 500); const [fwdCondSelectorKnob] = useSimVar('L:A32NX_OVHD_COND_FWD_SELECTOR_KNOB', 'number', 1000); // 0 to 300 const packBypassValve = Math.round(fwdCondSelectorKnob / 300 * 100); diff --git a/src/instruments/src/SD/Pages/Cond/Cond.scss b/src/instruments/src/SD/Pages/Cond/Cond.scss deleted file mode 100644 index 8f1881237291..000000000000 --- a/src/instruments/src/SD/Pages/Cond/Cond.scss +++ /dev/null @@ -1,95 +0,0 @@ -@import "../../style"; -@import "../../Common/definitions"; - -#cond-page { - - .st5 { - fill: none; - stroke: $display-green; - stroke-width: 2; - } - - .pump-off { - fill: none; - stroke: $display-amber; - stroke-width: 2; - } - - #titleAndWarnings { - - text { - font-size: $font-size-xlarge; - fill: $display-white; - } - - .PageTitle { - font-size: $font-size-title; - text-decoration: underline; - fill: $display-white; - } - - .TempText { - font-size: $font-size-xlarge; - fill: $display-white; - } - - .UnitText { - font-size: $font-size-large; - fill: $display-cyan; - } - } - - - .CondPlane { - stroke: $display-light-grey; - stroke-width: 1; - fill: none; - } - - .Cond { - - text-anchor: middle !important; - - .title { - font-size: $font-size-larger; - fill: $display-white; - } - - .valueText { - font-size: $font-size-medium; - fill: $display-green; - } - - .DuctStatus { - fill: $display-white; - font-weight: bold; - font-size: $font-size-huge; - } - - .Gauge { - fill: none; - stroke: $display-white; - stroke-width: 2; - stroke-miterlimit: 10; - } - - } - - #ValveAndTubes { - - .valve-moving > .st5 { - stroke: $display-amber; - } - - .DuctStatus { - fill: $display-white; - font-weight: bold; - font-size: $font-size-huge; - } - - .Hide { - display:none; - } - - } -} diff --git a/src/instruments/src/SD/Pages/Cond/Cond.tsx b/src/instruments/src/SD/Pages/Cond/Cond.tsx index cc2057b9b02c..d31c41d7fc33 100644 --- a/src/instruments/src/SD/Pages/Cond/Cond.tsx +++ b/src/instruments/src/SD/Pages/Cond/Cond.tsx @@ -1,14 +1,16 @@ import React from 'react'; +import { SvgGroup } from '../../Common/SvgGroup'; import { render } from '../../../Common'; import { useSimVar } from '../../../Common/simVars'; +import Valve from './Valve'; -import './Cond.scss'; +import '../../Common/CommonStyles.scss'; // setIsEcamPage('cond_page'); export const CondPage = () => { // Disaply trim valve position for each zone - const gaugeOffset = -50; // Gauges range is from -50 degree to +50 degree + const gaugeOffset = -43; // Gauges range is from -43 degree to +43 degree const [cockpitSelectedTemp] = useSimVar('L:A32NX_OVHD_COND_CKPT_SELECTOR_KNOB', 'number', 1000); const [cockpitTrimTemp] = useSimVar('L:A32NX_COND_CKPT_DUCT_TEMP', 'celsius', 1000); @@ -27,50 +29,45 @@ export const CondPage = () => { const [hotAir] = useSimVar('L:A32NX_AIRCOND_HOTAIR_TOGGLE', 'bool', 1000); return ( - <> - - {/* Title and unit */} - - COND - TEMP - : - °C - { /* Not yet implemented - FAN - FAN - ALTN MODE - */} - - - {/* Plane shape */} - {/* eslint-disable-next-line max-len */} - - - {/* Cockpit */} - - - {/* Fwd */} - - - {/* Aft */} - - - {/* Valve and tubes */} - - - HOT - AIR - - - - - - - - - - - + + {/* Title and unit */} + + COND + TEMP + : + °C + { /* Not yet implemented + FAN + FAN + ALTN MODE + */} + + + {/* Plane shape */} + + + + + {/* Cockpit */} + + + {/* Fwd */} + + + {/* Aft */} + + + {/* Valve and tubes */} + + + HOT + AIR + + + + + + ); }; @@ -80,40 +77,31 @@ type CondUnitProps = { cabinTemp: number, trimTemp: number, x: number, + y: number, offset: number, hotAir: number } -const CondUnit = ({ title, selectedTemp, cabinTemp, trimTemp, x, offset, hotAir } : CondUnitProps) => { - const rotateTemp = offset + (selectedTemp / 3); - const polyPoints = `${x + 4},206 ${x},199 ${x - 4},206`; - const gaugeD = `m ${x - 26} 208 Q ${x} 186 ${x + 26} 208`; - const ductC = `${x - 40}`; - const ductH = `${x + 40}`; - const transformStyle = { - transform: `rotate(${rotateTemp.toFixed(0)}deg)`, - transformOrigin: `${x}px 230px`, - }; +const CondUnit = ({ title, selectedTemp, cabinTemp, trimTemp, x, y, offset, hotAir } : CondUnitProps) => { + const rotateTemp = offset + (selectedTemp * 86 / 300); return ( - <> - - {title} - {cabinTemp.toFixed(0)} - {trimTemp.toFixed(0)} - C - H - - - - - - - - - + + {title} + {cabinTemp.toFixed(0)} + {trimTemp.toFixed(0)} + C + H + + + + + + + + - + ); }; diff --git a/src/instruments/src/SD/Pages/Cond/Valve.tsx b/src/instruments/src/SD/Pages/Cond/Valve.tsx new file mode 100644 index 000000000000..aee3cd041bc5 --- /dev/null +++ b/src/instruments/src/SD/Pages/Cond/Valve.tsx @@ -0,0 +1,21 @@ +import React, { FC } from 'react'; + +interface ValveProps { + x: number, + y: number, + radius: number, + position: 'V' |'H', + css: string, + sdacDatum: boolean +} + +const Valve: FC = ({ x, y, radius, position, css, sdacDatum }) => ( + + + {!sdacDatum ? XX : null} + {sdacDatum && position === 'V' ? : null} + {sdacDatum && position === 'H' ? : null} + +); + +export default Valve; diff --git a/src/instruments/src/SD/Pages/Crz/Crz.tsx b/src/instruments/src/SD/Pages/Crz/Crz.tsx index af8594c1cc33..0921d98ec6a1 100644 --- a/src/instruments/src/SD/Pages/Crz/Crz.tsx +++ b/src/instruments/src/SD/Pages/Crz/Crz.tsx @@ -119,6 +119,7 @@ export const OilComponent = () => { export const PressureComponent = () => { const [landingElevDialPosition] = useSimVar('L:XMLVAR_KNOB_OVHD_CABINPRESS_LDGELEV', 'Number', 100); + // FIXME Use CPC landing elev ARINC vars when made and get them via SDACs when made const [landingRunwayElevation] = useSimVar('L:A32NX_PRESS_AUTO_LANDING_ELEVATION', 'feet', 1000); const [autoMode] = useSimVar('L:A32NX_OVHD_PRESS_MODE_SEL_PB_IS_AUTO', 'Bool', 1000); const [ldgElevMode, setLdgElevMode] = useState('AUTO'); diff --git a/src/instruments/src/SD/Pages/Fctl/Fctl.tsx b/src/instruments/src/SD/Pages/Fctl/Fctl.tsx index d62cdc76302b..fa47b356578d 100644 --- a/src/instruments/src/SD/Pages/Fctl/Fctl.tsx +++ b/src/instruments/src/SD/Pages/Fctl/Fctl.tsx @@ -1,5 +1,7 @@ import React from 'react'; import { render } from '@instruments/common/index'; +import { useArinc429Var } from '@instruments/common/arinc429'; +import { Arinc429Word } from '@shared/arinc429'; import { useSimVar } from '../../../Common/simVars'; import { SvgGroup } from '../../Common/SvgGroup'; import { HydraulicsProvider, useHydraulics } from '../../Common/HydraulicsProvider'; @@ -16,36 +18,49 @@ interface HydraulicSystemPairProps { rightHydraulicSystem: HydraulicSystem, } -export const FctlPage = () => ( - - F/CTL +export const FctlPage = () => { + const fcdc1DiscreteWord1 = useArinc429Var('L:A32NX_FCDC_1_DISCRETE_WORD_1'); + const fcdc2DiscreteWord1 = useArinc429Var('L:A32NX_FCDC_2_DISCRETE_WORD_1'); + const fcdc1DiscreteWord2 = useArinc429Var('L:A32NX_FCDC_1_DISCRETE_WORD_2'); + const fcdc2DiscreteWord2 = useArinc429Var('L:A32NX_FCDC_2_DISCRETE_WORD_2'); + const fcdc1DiscreteWord3 = useArinc429Var('L:A32NX_FCDC_1_DISCRETE_WORD_3'); + const fcdc2DiscreteWord3 = useArinc429Var('L:A32NX_FCDC_2_DISCRETE_WORD_3'); - - + const fcdcDiscreteWord1ToUse = !fcdc1DiscreteWord1.isFailureWarning() ? fcdc1DiscreteWord1 : fcdc2DiscreteWord1; + const fcdcDiscreteWord2ToUse = !fcdc1DiscreteWord2.isFailureWarning() ? fcdc1DiscreteWord2 : fcdc2DiscreteWord2; + const fcdcDiscreteWord3ToUse = !fcdc1DiscreteWord3.isFailureWarning() ? fcdc1DiscreteWord3 : fcdc2DiscreteWord3; - - + return ( + + F/CTL - ELAC - - + + - SEC - - - + + - - + ELAC + + - + SEC + + + - + + - - - -); + + + + + + + + ); +}; const Wings = ({ x = 0, y = 0 }: ComponentPositionProps) => ( @@ -67,29 +82,54 @@ const Wings = ({ x = 0, y = 0 }: ComponentPositionProps) => ( ); -const PitchTrim = ({ x, y }: ComponentPositionProps) => { - const [rawPitchTrim] = useSimVar('ELEVATOR TRIM INDICATOR', 'Position 16k', 50); - - const adjustedPitchTrim = rawPitchTrim / 1213.6296; - const [pitchIntegral, pitchFractional] = Math.abs(adjustedPitchTrim).toFixed(1).split('.'); +interface PitchTrimProps extends ComponentPositionProps { + fcdcDiscreteWord2: Arinc429Word, +} +const PitchTrim = ({ x, y, fcdcDiscreteWord2 }: PitchTrimProps) => { + const fcdc1ThsPosition = useArinc429Var('L:A32NX_FCDC_1_ELEVATOR_TRIM_POS'); + const fcdc2ThsPosition = useArinc429Var('L:A32NX_FCDC_2_ELEVATOR_TRIM_POS'); + const thsPositionToUse = !fcdc1ThsPosition.isFailureWarning() ? fcdc1ThsPosition : fcdc2ThsPosition; + const posValid = thsPositionToUse.isNormalOperation(); + + let pitchIntegral: string; + let pitchFractional: string; + if (thsPositionToUse.isNormalOperation()) { + [pitchIntegral, pitchFractional] = Math.abs(thsPositionToUse.value).toFixed(1).split('.'); + } else { + pitchIntegral = 'XX'; + pitchFractional = 'X'; + } const hydraulics = useHydraulics(); - const hydraulicAvailableClass = hydraulics.G.available || hydraulics.Y.available ? 'Green' : 'Amber'; + const hydraulicAvailableClass = thsPositionToUse.isNormalOperation() && (hydraulics.G.available || hydraulics.Y.available) ? 'Green' : 'Amber'; + + const thsJam = fcdcDiscreteWord2.getBitValueOr(27, false); return ( - PITCH TRIM - {pitchIntegral} - . - {pitchFractional} - ° + PITCH TRIM + + {pitchIntegral} + . + {pitchFractional} + ° + 0.05 ? 'visible' : 'hidden'} + className={`${hydraulicAvailableClass} Standard Center`} + > + {Math.sign(thsPositionToUse.valueOr(0)) === 1 ? 'DN' : 'UP'} + + + 0.05 ? 'visible' : 'hidden'} - className={`${hydraulicAvailableClass} Standard Center`} + x={28} + y={50} + visibility={!posValid ? 'visible' : 'hidden'} + className="Amber Large Center" > - {Math.sign(adjustedPitchTrim) === -1 ? 'DN' : 'UP'} + XX @@ -102,18 +142,8 @@ const Rudder = ({ x, y }: ComponentPositionProps) => { const [rudderDeflectionState] = useSimVar('L:A32NX_HYD_RUDDER_DEFLECTION', 'Percent', 50); const rudderAngle = -rudderDeflectionState * 25 / 100; - // Rudder limits - const [indicatedAirspeedState] = useSimVar('AIRSPEED INDICATED', 'knots', 500); - let maxAngleNorm = 1; - if (indicatedAirspeedState > 380) { - maxAngleNorm = 3.4 / 25; - } else if (indicatedAirspeedState > 160) { - maxAngleNorm = (69.2667 - 0.351818 * indicatedAirspeedState - + 0.00047 * indicatedAirspeedState ** 2) / 25; - } - // Rudder trim - const [rudderTrimState] = useSimVar('RUDDER TRIM PCT', 'percent over 100', 500); + const [rudderTrimState] = useSimVar('RUDDER TRIM PCT', 'percent over 100', 100); const rudderTrimAngle = -rudderTrimState * 20; const hydraulics = useHydraulics(); @@ -133,26 +163,78 @@ const Rudder = ({ x, y }: ComponentPositionProps) => { - - - + - - - - - + - - - + ); }; +const RudderTrim = () => { + // Should use data from FAC through FWC, but since that is not implemented yet it is read directly + + const fac1DiscreteWord2 = useArinc429Var('L:A32NX_FAC_1_DISCRETE_WORD_2'); + const fac2DiscreteWord2 = useArinc429Var('L:A32NX_FAC_2_DISCRETE_WORD_2'); + + const anyTrimEngaged = fac1DiscreteWord2.getBitValueOr(13, false) || fac1DiscreteWord2.getBitValueOr(14, false) + || fac2DiscreteWord2.getBitValueOr(13, false) || fac2DiscreteWord2.getBitValueOr(14, false); + + const facSourceForTrim = fac2DiscreteWord2.getBitValueOr(13, false) ? 2 : 1; + const trimPosWord = useArinc429Var(`L:A32NX_FAC_${facSourceForTrim}_RUDDER_TRIM_POS`); + + return ( + <> + + + + + XX + + + ); +}; + +const RudderTravelLimit = () => { + const fac1DiscreteWord2 = useArinc429Var('L:A32NX_FAC_1_DISCRETE_WORD_2'); + const fac2DiscreteWord2 = useArinc429Var('L:A32NX_FAC_2_DISCRETE_WORD_2'); + + const anyTluEngaged = fac1DiscreteWord2.getBitValueOr(15, false) || fac1DiscreteWord2.getBitValueOr(16, false) + || fac2DiscreteWord2.getBitValueOr(15, false) || fac2DiscreteWord2.getBitValueOr(16, false); + + const facSourceForTlu = fac2DiscreteWord2.getBitValueOr(15, false) ? 2 : 1; + const rtluPosWord = useArinc429Var(`L:A32NX_FAC_${facSourceForTlu}_RUDDER_TRAVEL_LIMIT_COMMAND`); + const rtluDisplayAngle = rtluPosWord.value + 2; + + return ( + <> + + + + + + + + + + + TLU + TLU + + + ); +}; + const Stabilizer = ({ x, y }: ComponentPositionProps) => ( @@ -160,15 +242,36 @@ const Stabilizer = ({ x, y }: ComponentPositionProps) => ( ); -const Aileron = ({ x, y, side, leftHydraulicSystem, rightHydraulicSystem }: ComponentPositionProps & ComponentSidePositionProps & HydraulicSystemPairProps) => { +interface AileronElevatorProps { + fcdcDiscreteWord2: Arinc429Word, + fcdcDiscreteWord3: Arinc429Word, +} + +const Aileron = ({ + x, + y, + side, + leftHydraulicSystem, + rightHydraulicSystem, + fcdcDiscreteWord2, + fcdcDiscreteWord3, +}: ComponentPositionProps & ComponentSidePositionProps & HydraulicSystemPairProps & AileronElevatorProps) => { const textPositionX = side === 'left' ? -53 : 54; - const [aileronDeflection] = useSimVar(`L:A32NX_HYD_AILERON_${side.toUpperCase()}_DEFLECTION`, 'number', 50); - const aileronDeflectPctNormalized = aileronDeflection * 68.5; - const cursorPath = `M0 ${side === 'left' ? 57 + aileronDeflectPctNormalized - : 57 - aileronDeflectPctNormalized} l${side === 'right' ? '-' : ''}15 -9 l0 18Z`; + const fcdc1AileronDeflection = useArinc429Var(`L:A32NX_FCDC_1_AILERON_${side.toUpperCase()}_POS`); + const fcdc2AileronDeflection = useArinc429Var(`L:A32NX_FCDC_2_AILERON_${side.toUpperCase()}_POS`); + const aileronDeflection = !fcdc1AileronDeflection.isFailureWarning() ? fcdc1AileronDeflection : fcdc2AileronDeflection; - const hydraulics = useHydraulics(); + const cursorPath = `M0 57 l${side === 'right' ? '-' : ''}15 -9 l0 18Z`; + + const aileronDeflectPctNormalized = aileronDeflection.valueOr(0) * 68.5 / 25; + const servcontrol1Avail = fcdcDiscreteWord3.getBitValue(side === 'left' ? 11 : 13); + const servcontrol2Avail = fcdcDiscreteWord3.getBitValue(side === 'left' ? 12 : 14); + const cursorClassName = servcontrol1Avail || servcontrol2Avail ? 'GreenLine' : 'AmberLine'; + const aileronPositionValid = aileronDeflection.isNormalOperation(); + + const servcontrol1Fault = fcdcDiscreteWord2.getBitValueOr(side === 'left' ? 11 : 13, false); + const servcontrol2Fault = fcdcDiscreteWord2.getBitValueOr(side === 'left' ? 12 : 14, false); return ( @@ -177,43 +280,61 @@ const Aileron = ({ x, y, side, leftHydraulicSystem, rightHydraulicSystem }: Comp - + + + + + + XX + + + ); }; interface ElacSecProps extends ComponentPositionProps { - number: number, + num: number, + fcdcDiscreteWord1: Arinc429Word, } -const Elac = ({ x, y, number }: ElacSecProps) => ( - -); +const Elac = ({ x, y, num, fcdcDiscreteWord1 }: ElacSecProps) => { + const infoAvailable = !fcdcDiscreteWord1.isFailureWarning(); + const computerFailed = fcdcDiscreteWord1.getBitValueOr(22 + num, false); + return ; +}; -const Sec = ({ x, y, number }: ElacSecProps) => ( - -); +const Sec = ({ x, y, num, fcdcDiscreteWord1 }: ElacSecProps) => { + const infoAvailable = !fcdcDiscreteWord1.isFailureWarning(); + const computerFailed = fcdcDiscreteWord1.getBitValueOr(num === 3 ? 29 : 24 + num, false); -interface ElacSecShapeProps extends ElacSecProps { - type: 'ELAC' | 'SEC', -} + return ; +}; -const ElacSecShape = ({ x, y, number, type }: ElacSecShapeProps) => { - const [on] = useSimVar(`L:A32NX_FBW_${type}_SWITCH:${number}`, 'boolean', 1000); - const [failed] = useSimVar(`L:A32NX_FBW_${type}_FAILED:${number}`, 'boolean', 1000); +interface ElacSecShapeProps { + x: number, + y: number, + num: number, + infoAvailable: boolean, + computerFailed: boolean, +} - return ( - - - - {number} - - - ); -}; +const ElacSecShape = ({ x, y, num, infoAvailable, computerFailed }: ElacSecShapeProps) => ( + + + + {infoAvailable ? num : 'X'} + + +); const AileronAxis = ({ x, y, side }: ComponentPositionProps & ComponentSidePositionProps) => { const d1 = `M0 0 l${ @@ -233,15 +354,34 @@ const AileronAxis = ({ x, y, side }: ComponentPositionProps & ComponentSidePosit ); }; -const Elevator = ({ x, y, side, leftHydraulicSystem, rightHydraulicSystem }: ComponentPositionProps & ComponentSidePositionProps & HydraulicSystemPairProps) => { +const Elevator = ({ + x, + y, + side, + leftHydraulicSystem, + rightHydraulicSystem, + fcdcDiscreteWord2, + fcdcDiscreteWord3, +}: ComponentPositionProps & ComponentSidePositionProps & HydraulicSystemPairProps & AileronElevatorProps) => { const textPositionX = side === 'left' ? -59 : 62; const textLetter = side === 'left' ? 'L' : 'R'; - const [elevatorDeflection] = useSimVar(`L:A32NX_HYD_ELEVATOR_${side.toUpperCase()}_DEFLECTION`, 'percent over 100', 50); - const elevatorDeflectPctNormalized = elevatorDeflection * (elevatorDeflection > 0 ? 1 * 91 : 16 / 11.5 * 45); - const cursorPath = `M0,${77 - elevatorDeflectPctNormalized} l${side === 'right' ? '-' : ''}15,-9 l0,18Z`; + const fcdc1ElevatorDeflection = useArinc429Var(`L:A32NX_FCDC_1_ELEVATOR_${side.toUpperCase()}_POS`); + const fcdc2ElevatorDeflection = useArinc429Var(`L:A32NX_FCDC_2_ELEVATOR_${side.toUpperCase()}_POS`); + const elevatorDeflection = !fcdc1ElevatorDeflection.isFailureWarning() ? fcdc1ElevatorDeflection : fcdc2ElevatorDeflection; + const elevatorPositionValid = elevatorDeflection.isNormalOperation(); - const hydraulics = useHydraulics(); + const cursorPath = `M0,77 l${side === 'right' ? '-' : ''}15,-9 l0,18Z`; + + // Need to scale the "nose down" elevator position up, since the current elevator limits are wrong. + const elevatorDeflectPctNormalized = elevatorDeflection.value * 90 / 30; + + const servcontrolLeftAvail = fcdcDiscreteWord3.getBitValue(side === 'left' ? 15 : 18); + const servcontrolRightAvail = fcdcDiscreteWord3.getBitValue(side === 'left' ? 16 : 17); + const cursorClassName = servcontrolLeftAvail || servcontrolRightAvail ? 'GreenLine' : 'AmberLine'; + + const servcontrolLeftFault = fcdcDiscreteWord2.getBitValueOr(side === 'left' ? 15 : 18, false); + const servcontrolRightFault = fcdcDiscreteWord2.getBitValueOr(side === 'left' ? 16 : 17, false); return ( @@ -250,14 +390,28 @@ const Elevator = ({ x, y, side, leftHydraulicSystem, rightHydraulicSystem }: Com - + + + + + + XX + + + ); }; @@ -276,4 +430,13 @@ const ElevatorAxis = ({ x, y, side }: ComponentPositionProps & ComponentSidePosi ); }; +interface ServoControlIndicatorProps extends ComponentPositionProps { + servoFailed: boolean, +} +const ServoControlIndicator = ({ x, y, servoFailed }: ServoControlIndicatorProps) => ( + + + +); + render(); diff --git a/src/instruments/src/SD/Pages/Hyd/Hyd.scss b/src/instruments/src/SD/Pages/Hyd/Hyd.scss deleted file mode 100644 index 71c24809aa2e..000000000000 --- a/src/instruments/src/SD/Pages/Hyd/Hyd.scss +++ /dev/null @@ -1,80 +0,0 @@ -@import "../../style"; -@import "../../../Common/definitions"; - -#hyd-page { - - .PageTitle { - font-size: 24px; - text-decoration: underline; - fill: $display-white; - text-anchor: middle !important; - } - - .EngineNumber { - font-size: 24px; - text-anchor: middle !important; - }; - - text { - &.Title { - font-size: 22px; - fill: $display-white; - text-anchor: middle !important; - } - - &.Pressure { - font-size: 22px; - text-anchor: middle !important; - } - - &.Psi { - font-size: 16px; - fill: $display-cyan; - text-anchor: middle !important; - } - - &.RatPtuElec { - font-size: 18px; - text-anchor: middle !important; - } - } - - .NoFill { - fill: none; - } - - .FillGreen { - fill: $display-green; - } - - .FillAmber { - fill: $display-amber; - } - - .FillWhite { - fill: $display-white; - } - - .FillBlack { - fill: $display-background; - } - - .GreenLine { - stroke: $display-green; - stroke-width: 2; - } - - .AmberLine { - stroke: $display-amber; - stroke-width: 2; - } - - .WhiteLine { - stroke: $display-white; - stroke-width: 2; - } - - .Hide { - display: none; - } -} diff --git a/src/instruments/src/SD/Pages/Hyd/Hyd.tsx b/src/instruments/src/SD/Pages/Hyd/Hyd.tsx index e8cb8e70fe78..2e9835b00a7b 100644 --- a/src/instruments/src/SD/Pages/Hyd/Hyd.tsx +++ b/src/instruments/src/SD/Pages/Hyd/Hyd.tsx @@ -2,13 +2,21 @@ import React, { useEffect, useState } from 'react'; import { render } from '@instruments/common/index'; import { useSimVar } from '@instruments/common/simVars'; import { setIsEcamPage } from '@instruments/common/defaults'; -import { ptuArray, levels } from './common'; +import { SvgGroup } from '../../Common/SvgGroup'; import { Triangle } from '../../Common/Shapes'; -import './Hyd.scss'; +import '../../Common/CommonStyles.scss'; /* setIsEcamPage('hyd_page'); */ +const litersPerGallon = 3.79; + +enum HydSystem { + GREEN = 'GREEN', + BLUE = 'BLUE', + YELLOW = 'YELLOW', +} + export const HydPage = () => { // The FADEC SimVars include a test for the fire button. const [Eng1N2] = useSimVar('TURB ENG N2:1', 'Percent', 1000); @@ -18,24 +26,26 @@ export const HydPage = () => { const [yellowPressure] = useSimVar('L:A32NX_HYD_YELLOW_SYSTEM_1_SECTION_PRESSURE', 'psi', 500); const [bluePressure] = useSimVar('L:A32NX_HYD_BLUE_SYSTEM_1_SECTION_PRESSURE', 'psi', 500); - const [greenPumpPBStatus] = useSimVar('L:A32NX_OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO', 'boolean', 500); - const [yellowPumpPBStatus] = useSimVar('L:A32NX_OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO', 'boolean', 500); - const [bluePumpPBStatus] = useSimVar('L:A32NX_OVHD_HYD_EPUMPB_PB_IS_AUTO', 'boolean', 500); + const [yellowPumpPressurisedSwitch] = useSimVar('L:A32NX_HYD_YELLOW_PUMP_1_SECTION_PRESSURE_SWITCH', 'boolean', 500); + const [greenPumpPressurisedSwitch] = useSimVar('L:A32NX_HYD_GREEN_PUMP_1_SECTION_PRESSURE_SWITCH', 'boolean', 500); - const [yellowElectricPumpStatus] = useSimVar('L:A32NX_HYD_YELLOW_EPUMP_ACTIVE', 'boolean', 500); + const [yellowFluidLevel] = useSimVar('L:A32NX_HYD_YELLOW_RESERVOIR_LEVEL', 'gallon', 1000); + const [greenFluidLevel] = useSimVar('L:A32NX_HYD_GREEN_RESERVOIR_LEVEL', 'gallon', 1000); - const [greenHydLevel] = useSimVar('L:A32NX_HYD_GREEN_RESERVOIR_LEVEL', 'gallon', 1000); - const [yellowHydLevel] = useSimVar('L:A32NX_HYD_YELLOW_RESERVOIR_LEVEL', 'gallon', 1000); - const [blueHydLevel] = useSimVar('L:A32NX_HYD_BLUE_RESERVOIR_LEVEL', 'gallon', 1000); + const [greenPumpPBOn] = useSimVar('L:A32NX_OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO', 'boolean', 500); + const [yellowPumpPBOn] = useSimVar('L:A32NX_OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO', 'boolean', 500); + const [bluePumpPBAuto] = useSimVar('L:A32NX_OVHD_HYD_EPUMPB_PB_IS_AUTO', 'boolean', 500); + const [bluePumpActive] = useSimVar('L:A32NX_HYD_BLUE_EPUMP_ACTIVE', 'boolean', 500); + + const [yellowElectricPumpStatus] = useSimVar('L:A32NX_HYD_YELLOW_EPUMP_ACTIVE', 'boolean', 500); const [greenFireValve] = useSimVar('L:A32NX_HYD_GREEN_PUMP_1_FIRE_VALVE_OPENED', 'boolean', 500); const [yellowFireValve] = useSimVar('L:A32NX_HYD_YELLOW_PUMP_1_FIRE_VALVE_OPENED', 'boolean', 500); const [ACBus1IsPowered] = useSimVar('L:A32NX_ELEC_AC_1_BUS_IS_POWERED', 'bool', 1000); - const [ACBus2IsPowered] = useSimVar('L:A32NX_ELEC_AC_2_BUS_IS_POWERED', 'bool', 1000); - const [engine1Running, setEngine1Running] = useState(0); - const [engine2Running, setEngine2Running] = useState(0); + const [engine1Running, setEngine1Running] = useState(false); + const [engine2Running, setEngine2Running] = useState(false); useEffect(() => { setEngine1Running(Eng1N2 > 15 && greenFireValve); @@ -43,157 +53,69 @@ export const HydPage = () => { }, [Eng1N2, Eng2N2]); // PTU variables - const [ptuAvailable] = useSimVar('L:A32NX_HYD_PTU_VALVE_OPENED', 'boolean', 500); - const [ptuScenario, setPtuScenario] = useState('normal'); - - type PressureChartType = { - high: string, - low: string, - highValue: number, - lowValue: number, - ptuScenario: string - } - - const [pressureChart, setPressureChart] = useState({ high: '', low: '', highValue: -1, lowValue: -1, ptuScenario: 'normal' }); - const [ptuActive, setPtuActive] = useState(0); - - const [elecRightFormat, setElecRightFormat] = useState('hide'); - const [elecTriangleFill, setElecTriangleFill] = useState(0); - const [elecTriangleColour, setElecTriangleColour] = useState('white'); - - function setPressures(clearState = false) { - if (clearState) { - setPressureChart({ high: '', low: '', highValue: -1, lowValue: -1, ptuScenario: 'normal' }); - } else if (yellowPressure > greenPressure) { - setPressureChart({ - high: 'YELLOW', - low: 'GREEN', - highValue: yellowPressure, - lowValue: greenPressure, - ptuScenario: 'right-to-left', - }); - } else { - setPressureChart({ - high: 'GREEN', - low: 'YELLOW', - highValue: greenPressure, - lowValue: yellowPressure, - ptuScenario: 'left-to-right', - }); - } - } - - useEffect(() => { - setPtuScenario(pressureChart.ptuScenario); - }, [pressureChart]); - - // PTU logic - useEffect(() => { - if (yellowElectricPumpStatus) { - setElecTriangleFill(1); - setElecTriangleColour(yellowPressure <= 1450 ? 'Amber' : 'Green'); - setElecRightFormat(yellowPressure <= 1450 ? 'AmberLine' : 'GreenLine'); - } else { - setElecTriangleFill(0); - setElecTriangleColour('White'); - setElecRightFormat('Hide'); - } - - if (ptuAvailable && !yellowElectricPumpStatus) { - // The PTU valve has to be open and the yellow electric pump should not be on - const pressureDifferential = Math.abs(greenPressure - yellowPressure); - const maxPressure = Math.max(yellowPressure, greenPressure); - // const minPressure = Math.min(yellowPressure, greenPressure); - const negativePressureDifferential = pressureChart.low === 'GREEN' ? pressureChart.lowValue - yellowPressure : pressureChart.lowValue - greenPressure; - if (maxPressure < 1450 || (greenPressure > 2990 && yellowPressure > 2990)) { - setPressures(true); - setPtuActive(0); - } else if (pressureDifferential > 200 && maxPressure > 1450 && !ptuActive) { - setPtuActive(1); - setPressures(); - } else if (negativePressureDifferential <= -500 && ptuActive) { - setPressures(true); - setPtuActive(0); - } - } else if (ptuAvailable && yellowElectricPumpStatus && greenPressure <= 2990) { - setPtuScenario('right-to-left'); - setPtuActive(1); - } else { - setPtuScenario(ptuAvailable ? 'normal' : 'PTU-off'); - } - }, [greenPressure, yellowPressure, yellowElectricPumpStatus, ptuAvailable]); - - const y = 45; + const [ptuControlValveOpen] = useSimVar('L:A32NX_HYD_PTU_VALVE_OPENED', 'boolean', 500); return ( <> {/* This is already in an svg so we should remove the containing one - TODO remove style once we are not in the Asobo ECAM */} - - HYD - 1 - 2 + + HYD + 1 + 2 - + - + ELEC - - - ELEC + - - - - - PSI - PSI + PSI + PSI @@ -209,243 +131,325 @@ const RAT = ({ x, y }: RATProps) => { const [RatStowed] = useSimVar('L:A32NX_HYD_RAT_STOW_POSITION', 'percent over 100', 500); return ( - <> - RAT - 0.1 ? '' : 'Hide'}`} x1={x} y1={y + 180} x2={x + 10} y2={y + 180} /> - 0.1 ? 'Green' : 'White'} fill={RatStowed > 0.1 ? 1 : 0} orientation={90} /> - + + RAT + 0.1 ? '' : 'Hide'}`} x1={0} y1={0} x2={10} y2={0} /> + 0.1 ? 'Green' : 'White'} fill={RatStowed > 0.1 ? 1 : 0} orientation={90} /> + ); }; type HydSysProps = { - title: string, + system: HydSystem, pressure: number, - hydLevel: number, x: number, y: number, - fireValve: number, - pumpPBStatus: number, - yellowElectricPumpStatus: number + fireValve: boolean, + pumpPBOn: boolean, } -const HydSys = ({ title, pressure, hydLevel, x, y, fireValve, pumpPBStatus, yellowElectricPumpStatus } : HydSysProps) => { - const [hydLevelLow, setHydLevelLow] = useState(false); +const HydSys = ({ system, pressure, x, y, fireValve, pumpPBOn } : HydSysProps) => { const lowPressure = 1450; const pressureNearest50 = Math.round(pressure / 50) * 50 >= 100 ? Math.round(pressure / 50) * 50 : 0; - const [greenPumpActive] = useSimVar('L:A32NX_HYD_GREEN_EDPUMP_ACTIVE', 'boolean', 500); - const [yellowPumpActive] = useSimVar('L:A32NX_HYD_YELLOW_EDPUMP_ACTIVE', 'boolean', 500); - const [bluePumpActive] = useSimVar('L:A32NX_HYD_BLUE_EPUMP_ACTIVE', 'boolean', 500); - - const [greenPumpLowPressure] = useSimVar('L:A32NX_HYD_GREEN_EDPUMP_LOW_PRESS', 'boolean', 500); - const [yellowPumpLowPressure] = useSimVar('L:A32NX_HYD_YELLOW_EDPUMP_LOW_PRESS', 'boolean', 500); - const [bluePumpLowPressure] = useSimVar('L:A32NX_HYD_BLUE_EPUMP_LOW_PRESS', 'boolean', 500); - - function checkPumpLowPressure(pump) { - switch (pump) { - case 'GREEN': - return greenPumpLowPressure || !greenPumpActive; - case 'BLUE': - return bluePumpLowPressure || !bluePumpActive; - case 'YELLOW': - return yellowPumpLowPressure || !yellowPumpActive; - default: - return 1; - } - } + const [pumpPressurisedSwitch] = useSimVar(`L:A32NX_HYD_${system}_PUMP_1_SECTION_PRESSURE_SWITCH`, 'boolean', 500); + const [systemPressurisedSwitch] = useSimVar(`L:A32NX_HYD_${system}_SYSTEM_1_SECTION_PRESSURE_SWITCH`, 'boolean', 500); - const pumpDetectLowPressure = checkPumpLowPressure(title); + const [reservoirLowQuantitySwitch] = useSimVar(`L:A32NX_HYD_${system}_RESERVOIR_LEVEL_IS_LOW`, 'boolean', 500); - const hydLevelBoolean = (value: boolean) => { - setHydLevelLow(value); - }; + let hydTitleXPos: number; + if (system === HydSystem.GREEN) { + hydTitleXPos = -2; + } else if (system === HydSystem.BLUE) { + hydTitleXPos = 1; + } else { + hydTitleXPos = 3; + } return ( - <> - - {title} - {pressureNearest50} - - {/* The colour of these lines will be affected by the yellow electric pump */} - - - + + + {system} + {pressureNearest50} + + - + { + system !== HydSystem.BLUE && + } {/* Reservoir */} - - + + ); }; type HydEngPumpProps = { - system: string, - pumpOn: number, + system: HydSystem, + pumpPBOn: boolean, x: number, y: number, - hydLevelLow: boolean, - fireValve: number, - pumpDetectLowPressure: number - pressure: number + pumpSwitchLowPressure: boolean } -const HydEngPump = ({ system, pumpOn, x, y, hydLevelLow, fireValve, pumpDetectLowPressure, pressure } : HydEngPumpProps) => { - const lowPressure = 1450; - if (system === 'BLUE') { - return ( - <> - - - - - LO - - - ); +const HydEngPump = ({ system, pumpPBOn, x, y, pumpSwitchLowPressure } : HydEngPumpProps) => { + let pumpLineYUpper: number; + if (system === HydSystem.GREEN) { + pumpLineYUpper = -151; + } else if (system === HydSystem.BLUE) { + pumpLineYUpper = -153; + } else { + pumpLineYUpper = -84; } return ( - <> - - - - - - LO - - - - + + + + + + LO + ); }; type HydEngValveProps = { - system: string, x: number, y: number, - fireValve: number, - hydLevelLow: boolean + fireValve: boolean, + lowLevel: boolean } -const HydEngValve = ({ system, x, y, fireValve, hydLevelLow } : HydEngValveProps) => { - if (system === 'BLUE') { - return ( - - ); - } - - return ( - <> - - - - - ); +const HydEngValve = ({ x, y, fireValve, lowLevel } : HydEngValveProps) => ( + + + + + + +); + +type Levels = {max: number, low: number, norm: number}; + +const levels = { + GREEN: { max: 14.5, low: 3.5, norm: 2.6 } as Levels, + BLUE: { max: 6.5, low: 2.4, norm: 1.6 } as Levels, + YELLOW: { max: 12.5, low: 3.5, norm: 2.6 } as Levels, }; type HydReservoirProps = { - system: string, + system: HydSystem, x: number, y: number, - fluidLevel: number, - setHydLevel: React.RefCallback< - Boolean> + lowLevel: boolean, } -const HydReservoir = ({ system, x, y, fluidLevel, setHydLevel } : HydReservoirProps) => { - const fluidLevelInLitres = fluidLevel * 3.79; +const HydReservoir = ({ system, x, y, lowLevel } : HydReservoirProps) => { + const [fluidLevel] = useSimVar(`L:A32NX_HYD_${system}_RESERVOIR_LEVEL`, 'gallon', 1000); - const values = levels.filter((item) => item.system === system); - const litersPerPixel = 96 / values[0].max; - const reserveHeight = (litersPerPixel * values[0].low); - const upperReserve = y - reserveHeight; - const lowerNorm = y - 96 + (litersPerPixel * values[0].norm); - const fluidLevelPerPixel = 96 / values[0].max; - const fluidHeight = y - (fluidLevelPerPixel * fluidLevelInLitres); + const [lowAirPress] = useSimVar(`L:A32NX_HYD_${system}_RESERVOIR_AIR_PRESSURE_IS_LOW`, 'boolean', 1000); - useEffect(() => { - if (fluidLevelInLitres < values[0].low) { - setHydLevel(true); - } else { - setHydLevel(false); - } - }, [fluidLevelInLitres]); + const fluidLevelInLitres = fluidLevel * litersPerGallon; + + const values = levels[system]; + const litersPerPixel = 121 / values.max; + const reserveHeight = (litersPerPixel * values.low); + const upperReserve = -reserveHeight; + const lowerNorm = -121 + (litersPerPixel * values.norm); + const fluidLevelPerPixel = 121 / values.max; + const fluidHeight = -(fluidLevelPerPixel * fluidLevelInLitres); return ( - <> - - - - - - + + + + + + + {/* Hydraulic level */} - - - - + + + + + + LO AIR + PRESS + + ); +}; + +type YellowElecPumpProps = { + pumpPushbuttonOn: boolean, + pressure: number, + enginePumpPressureLowSwitch: boolean, +}; + +const YellowElecPump = ({ pumpPushbuttonOn, pressure, enginePumpPressureLowSwitch }: YellowElecPumpProps) => { + const [ACBus2IsPowered] = useSimVar('L:A32NX_ELEC_AC_2_BUS_IS_POWERED', 'bool', 1000); + + let elecHorizontalLineFormat: string; + let verticalLineFormat: string; + let elecTriangleFill: number; + let elecTriangleColour: string; + + if (!pumpPushbuttonOn) { + elecTriangleFill = 0; + elecHorizontalLineFormat = 'Hide'; + elecTriangleColour = 'White'; + } else { + elecTriangleFill = 1; + elecHorizontalLineFormat = pressure <= 1450 ? 'AmberLine' : 'GreenLine'; + elecTriangleColour = pressure <= 1450 ? 'Amber' : 'Green'; + } + + if ((enginePumpPressureLowSwitch && !pumpPushbuttonOn) || pressure <= 1450) { + verticalLineFormat = 'AmberLine'; + } else { + verticalLineFormat = 'GreenLine'; + } + + return ( + <> + + ELEC + + + + ); }; +enum TransferColor { + Green = 'Green', + Amber = 'Amber', +} + +enum TransferState { + GreenToYellow, + YellowToGreen, + None +} + type PTUProps = { x: number, y: number, - ptuScenario: string + yellowPressure: number, + greenPressure: number, + yellowPumpLowPressure: boolean, + greenPumpLowPressure: boolean, + yellowQuantity: number, + greenQuantity: number, + ptuControlValveOff: boolean + yellowElecPumpOn: boolean, } -const PTU = ({ x, y, ptuScenario } : PTUProps) => { - const semiCircleD = `M${x - 16},${y} C${x - 16},${y + 24} ${x + 16},${y + 24} ${x + 16},${y}`; +const shouldTransferActivate = ( + lowerPressureSystemQuantity: number, + lowerPressureSystemPressure: number, + highPressureSystemPressure: number, + lowerPressureSystemPumpLowPress: boolean, + yellowElecPumpOn: boolean, + transferDirection: TransferState, +) => { + if (transferDirection === TransferState.None) { + return false; + } + + return lowerPressureSystemPumpLowPress + && (transferDirection === TransferState.GreenToYellow && !yellowElecPumpOn || transferDirection === TransferState.YellowToGreen) + && ((highPressureSystemPressure > 1450 && lowerPressureSystemQuantity * litersPerGallon < 2.5) + || (lowerPressureSystemPressure > 1500 && lowerPressureSystemQuantity * litersPerGallon > 2.5)); +}; - const result: any = ptuArray.find(({ scenario }) => scenario === ptuScenario); - const ptu1 = result.format.find(({ id }) => id === 'ptu1'); - const ptu2 = result.format.find(({ id }) => id === 'ptu2'); - const ptu3 = result.format.find(({ id }) => id === 'ptu3'); - const ptu4 = result.format.find(({ id }) => id === 'ptu4'); - const ptu5 = result.format.find(({ id }) => id === 'ptu5'); - const triangle1 = result.format.find(({ id }) => id === 'triangle1'); - const triangle2 = result.format.find(({ id }) => id === 'triangle2'); - const triangle3 = result.format.find(({ id }) => id === 'triangle3'); +const PTU = ({ x, y, yellowPressure, greenPressure, yellowPumpLowPressure, greenPumpLowPressure, yellowQuantity, greenQuantity, ptuControlValveOff, yellowElecPumpOn } : PTUProps) => { + const [transferState, setTransferState] = useState(TransferState.None); + + useEffect(() => { + let newTransferState; + + if (ptuControlValveOff) { + newTransferState = TransferState.None; + } else if (transferState === TransferState.None) { + if (yellowPressure - greenPressure > 200) { + newTransferState = TransferState.YellowToGreen; + } else if (greenPressure - yellowPressure > 200) { + newTransferState = TransferState.GreenToYellow; + } + } else if (transferState === TransferState.GreenToYellow && greenPressure - yellowPressure < -300) { + newTransferState = TransferState.YellowToGreen; + } else if (transferState === TransferState.YellowToGreen && yellowPressure - greenPressure < -300) { + newTransferState = TransferState.GreenToYellow; + } else { + newTransferState = transferState; + } + + let lowPressureSystemPressure: number; + let highPressureSystemPressure: number; + let lowPressureSystemQuantity: number; + let lowerPressureSystemPumpLowPress: boolean; + if (newTransferState === TransferState.GreenToYellow) { + highPressureSystemPressure = greenPressure; + lowPressureSystemPressure = yellowPressure; + lowPressureSystemQuantity = yellowQuantity; + lowerPressureSystemPumpLowPress = yellowPumpLowPressure; + } else if (newTransferState === TransferState.YellowToGreen) { + highPressureSystemPressure = yellowPressure; + lowPressureSystemPressure = greenPressure; + lowPressureSystemQuantity = greenQuantity; + lowerPressureSystemPumpLowPress = greenPumpLowPressure; + } else { + highPressureSystemPressure = 0; + lowPressureSystemPressure = 0; + lowPressureSystemQuantity = 0; + lowerPressureSystemPumpLowPress = false; + } + + if (shouldTransferActivate(lowPressureSystemQuantity, lowPressureSystemPressure, highPressureSystemPressure, lowerPressureSystemPumpLowPress, yellowElecPumpOn, newTransferState)) { + setTransferState(newTransferState); + } else { + setTransferState(TransferState.None); + } + }, [yellowPressure, greenPressure, yellowPumpLowPressure, greenPumpLowPressure, yellowQuantity, greenQuantity, ptuControlValveOff, yellowElecPumpOn]); + + // Should also be amber if PTU fault + let transferColor: TransferColor; + if (ptuControlValveOff) { + transferColor = TransferColor.Amber; + } else { + transferColor = TransferColor.Green; + } + + const triangleFill = transferState !== TransferState.None ? 1 : 0; + const triangle1Orientation = transferState !== TransferState.GreenToYellow ? -90 : 90; + const triangle2Orientation = transferState !== TransferState.GreenToYellow ? -90 : 90; + const triangle3Orientation = transferState !== TransferState.YellowToGreen ? 90 : -90; return ( - <> - - - - - - PTU - - 0 ? x + 70 : x + 50} y={y} colour={triangle2.colour} fill={triangle2.fill} orientation={triangle2.orientation} /> - 0 ? x + 135 : x + 117} y={y} colour={triangle3.colour} fill={triangle3.fill} orientation={triangle3.orientation} /> - + + + + + + + PTU + + 0 ? 80 : 56} y={0} colour={transferColor} fill={triangleFill} orientation={triangle2Orientation} /> + 0 ? 177 : 153} y={0} colour={transferColor} fill={triangleFill} orientation={triangle3Orientation} /> + ); }; diff --git a/src/instruments/src/SD/Pages/Hyd/common.tsx b/src/instruments/src/SD/Pages/Hyd/common.tsx deleted file mode 100644 index 2acd61181d4c..000000000000 --- a/src/instruments/src/SD/Pages/Hyd/common.tsx +++ /dev/null @@ -1,252 +0,0 @@ -export const ptuArray = [ - { - scenario: 'PTU-off', - format: [ - { - id: 'ptu1', - className: 'Hide', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu2', - className: 'AmberLine', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu3', - className: 'AmberLine NoFill', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu4', - className: 'AmberLine', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu5', - className: 'Hide', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'triangle1', - className: '', - colour: 'Amber', - orientation: -90, - fill: 0, - }, - { - id: 'triangle2', - className: '', - colour: 'Amber', - orientation: -90, - fill: 0, - }, - { - id: 'triangle3', - className: '', - colour: 'Amber', - orientation: 90, - fill: 0, - }, - ], - }, - { - scenario: 'normal', - format: [ - { - id: 'ptu1', - className: 'Hide', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu2', - className: 'GreenLine', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu3', - className: 'GreenLine NoFill', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu4', - className: 'GreenLine', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu5', - className: 'GreenLine Hide', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'triangle1', - className: '', - colour: 'Green', - orientation: -90, - fill: 0, - }, - { - id: 'triangle2', - className: '', - colour: 'Green', - orientation: -90, - fill: 0, - }, - { - id: 'triangle3', - className: '', - colour: 'Green', - orientation: 90, - fill: 0, - }, - ], - }, - { - scenario: 'right-to-left', - format: [ - { - id: 'ptu1', - className: 'GreenLine', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu2', - className: 'GreenLine', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu3', - className: 'GreenLine NoFill', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu4', - className: 'GreenLine', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu5', - className: 'GreenLine', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'triangle1', - className: 'FillGreen', - colour: 'Green', - orientation: -90, - fill: 1, - }, - { - id: 'triangle2', - className: 'FillGreen', - colour: 'Green', - orientation: -90, - fill: 1, - }, - { - id: 'triangle3', - className: 'FillGreen', - colour: 'Green', - orientation: -90, - fill: 1, - }, - ], - }, - { - scenario: 'left-to-right', - format: [ - { - id: 'ptu1', - className: 'GreenLine', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu2', - className: 'GreenLine', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu3', - className: 'GreenLine NoFill', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu4', - className: 'GreenLine', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'ptu5', - className: 'GreenLine', - colour: '', - orientation: 0, - fill: 0, - }, - { - id: 'triangle1', - className: 'FillGreen', - colour: 'Green', - orientation: 90, - fill: 1, - }, - { - id: 'triangle2', - className: 'FillGreen', - colour: 'Green', - orientation: 90, - fill: 1, - }, - { - id: 'triangle3', - className: 'FillGreen', - colour: 'Green', - orientation: 90, - fill: 1, - }, - ], - }, -]; - -export const levels = [ - { system: 'GREEN', max: 14.5, low: 3.5, norm: 2.6 }, - { system: 'BLUE', max: 6.5, low: 2.4, norm: 1.6 }, - { system: 'YELLOW', max: 12.5, low: 3.5, norm: 2.6 }, -]; diff --git a/src/instruments/src/SD/Pages/Press/Press.tsx b/src/instruments/src/SD/Pages/Press/Press.tsx index 2fe9e4abe918..52d11f982f1c 100644 --- a/src/instruments/src/SD/Pages/Press/Press.tsx +++ b/src/instruments/src/SD/Pages/Press/Press.tsx @@ -279,6 +279,7 @@ const CabinVerticalSpeedComponent: FC = ({ vsx, const PressureComponent = () => { const [landingElevDialPosition] = useSimVar('L:XMLVAR_KNOB_OVHD_CABINPRESS_LDGELEV', 'number', 100); + // FIXME Use CPC landing elev ARINC vars when made and get them via SDACs when made const [landingRunwayElevation] = useSimVar('L:A32NX_PRESS_AUTO_LANDING_ELEVATION', 'feet', 1000); const [autoMode] = useSimVar('L:A32NX_OVHD_PRESS_MODE_SEL_PB_IS_AUTO', 'Bool', 1000); const [ldgElevMode, setLdgElevMode] = useState('AUTO'); diff --git a/src/instruments/src/SD/Pages/Wheel/Wheel.tsx b/src/instruments/src/SD/Pages/Wheel/Wheel.tsx index ea943de59af9..59f58f8086d0 100644 --- a/src/instruments/src/SD/Pages/Wheel/Wheel.tsx +++ b/src/instruments/src/SD/Pages/Wheel/Wheel.tsx @@ -28,7 +28,7 @@ export const WheelPage = () => { const lgciu2DiscreteWord3 = useArinc429Var('L:A32NX_LGCIU_2_DISCRETE_WORD_3'); return ( - + WHEEL diff --git a/src/mcdu-server/client/LICENSE-ROBOTO.txt b/src/mcdu-server/client/LICENSE-ROBOTO.txt deleted file mode 100644 index d64569567334..000000000000 --- a/src/mcdu-server/client/LICENSE-ROBOTO.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/src/mcdu-server/client/public/RobotoMono-Bold.ttf b/src/mcdu-server/client/public/RobotoMono-Bold.ttf deleted file mode 100644 index 900fce684821..000000000000 Binary files a/src/mcdu-server/client/public/RobotoMono-Bold.ttf and /dev/null differ diff --git a/src/mcdu-server/client/public/android-chrome-192x192.png b/src/mcdu-server/client/public/android-chrome-192x192.png deleted file mode 100644 index 35847c1df325..000000000000 Binary files a/src/mcdu-server/client/public/android-chrome-192x192.png and /dev/null differ diff --git a/src/mcdu-server/client/public/android-chrome-512x512.png b/src/mcdu-server/client/public/android-chrome-512x512.png deleted file mode 100644 index be25dc5640c9..000000000000 Binary files a/src/mcdu-server/client/public/android-chrome-512x512.png and /dev/null differ diff --git a/src/mcdu-server/client/public/apple-touch-icon-180x180.png b/src/mcdu-server/client/public/apple-touch-icon-180x180.png deleted file mode 100644 index 291dd0350a49..000000000000 Binary files a/src/mcdu-server/client/public/apple-touch-icon-180x180.png and /dev/null differ diff --git a/src/mcdu-server/client/public/button-click.mp3 b/src/mcdu-server/client/public/button-click.mp3 deleted file mode 100644 index e4e405edfbfb..000000000000 Binary files a/src/mcdu-server/client/public/button-click.mp3 and /dev/null differ diff --git a/src/mcdu-server/client/public/favicon.ico b/src/mcdu-server/client/public/favicon.ico deleted file mode 100644 index 469a0f2946e5..000000000000 Binary files a/src/mcdu-server/client/public/favicon.ico and /dev/null differ diff --git a/src/mcdu-server/client/public/index.html b/src/mcdu-server/client/public/index.html deleted file mode 100644 index 1fa10f654c4c..000000000000 --- a/src/mcdu-server/client/public/index.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - A32NX MCDU - - - -
- - -
- - diff --git a/src/mcdu-server/client/public/manifest.json b/src/mcdu-server/client/public/manifest.json deleted file mode 100644 index bbd2e824900b..000000000000 --- a/src/mcdu-server/client/public/manifest.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "A32NX MCDU", - "short_name": "A32NX MCDU", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#000000", - "background_color": "#000000", - "display": "standalone" -} \ No newline at end of file diff --git a/src/mcdu-server/client/public/mcdu-a32nx-dark.png b/src/mcdu-server/client/public/mcdu-a32nx-dark.png deleted file mode 100644 index b755a8b507fa..000000000000 Binary files a/src/mcdu-server/client/public/mcdu-a32nx-dark.png and /dev/null differ diff --git a/src/mcdu-server/client/public/mcdu-a32nx.png b/src/mcdu-server/client/public/mcdu-a32nx.png deleted file mode 100644 index 13ea8cfc1011..000000000000 Binary files a/src/mcdu-server/client/public/mcdu-a32nx.png and /dev/null differ diff --git a/src/mcdu-server/client/public/mcdu-r2-c.png b/src/mcdu-server/client/public/mcdu-r2-c.png deleted file mode 100644 index 76d7cbad0d8b..000000000000 Binary files a/src/mcdu-server/client/public/mcdu-r2-c.png and /dev/null differ diff --git a/src/mcdu-server/client/rollup.config.js b/src/mcdu-server/client/rollup.config.js deleted file mode 100644 index a6c2212c9528..000000000000 --- a/src/mcdu-server/client/rollup.config.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -const copy = require('rollup-plugin-copy'); -const babel = require('@rollup/plugin-babel').default; -const nodeResolve = require('@rollup/plugin-node-resolve').default; -const commonjs = require('@rollup/plugin-commonjs'); -const postcss = require('rollup-plugin-postcss'); -const replace = require('@rollup/plugin-replace'); -const path = require('path'); - -export default { - input: path.join(__dirname, 'src/index.jsx'), - output: { - file: path.join(__dirname, './build/bundle.js'), - format: 'iife', - sourcemap: false, - }, - plugins: [ - replace({ 'process.env.NODE_ENV': JSON.stringify('production') }), - postcss(), - copy({ - targets: [ - { - src: `${path.join(__dirname, './public/')}*`, - dest: path.join(__dirname, './build/'), - }, - { - src: [ - path.join(__dirname, '../../../flybywire-aircraft-a320-neo/html_ui/Fonts/HoneywellMCDU.ttf'), - path.join(__dirname, '../../../flybywire-aircraft-a320-neo/html_ui/Fonts/HoneywellMCDUSmall.ttf'), - ], - dest: path.join(__dirname, './build/'), - }, - ], - }), - nodeResolve({ extensions: ['.js', '.jsx', '.ts', '.tsx'] }), - babel({ presets: ['@babel/preset-react'] }), - commonjs(), - ], -}; diff --git a/src/mcdu-server/client/src/App.css b/src/mcdu-server/client/src/App.css deleted file mode 100644 index 1d707d77ef5f..000000000000 --- a/src/mcdu-server/client/src/App.css +++ /dev/null @@ -1,31 +0,0 @@ -.App { - --app-width: min(100vw, calc(var(--vh) * var(--aspect-ratio))); - --app-height: calc(min(var(--vh), calc(100vw * calc(1 / var(--aspect-ratio)))) - calc(var(--app-width) * var(--top-padding))); - height: var(--app-height); - padding-top: calc(var(--app-width) * var(--top-padding)); - left: calc(50vw - calc(var(--app-width) / 2)); - width: var(--app-width); - position: relative; -} - -.normal { - --aspect-ratio: calc(1290/2000); - --top-padding: .146; - --screen-scale: .69; -} - -.normal .App { - background-size: contain; - background-repeat: no-repeat; -} - -.fullscreen { - --aspect-ratio: 1.21234964; - --top-padding: 0; - --screen-scale: 1; - cursor: pointer; -} - -.aspect43 { - --aspect-ratio: 1.3; -} diff --git a/src/mcdu-server/client/src/App.jsx b/src/mcdu-server/client/src/App.jsx deleted file mode 100644 index 6ee52d135081..000000000000 --- a/src/mcdu-server/client/src/App.jsx +++ /dev/null @@ -1,106 +0,0 @@ -import './App.css'; -import React, { useState, useEffect } from 'react'; -import useWebSocket, { ReadyState } from 'react-use-websocket'; -import { McduScreen } from './McduScreen'; -import { McduButtons } from './McduButtons'; -import { WebsocketContext } from './WebsocketContext'; - -function App() { - const [fullscreen, setFullscreen] = useState(window.location.href.endsWith('fullscreen') || window.location.href.endsWith('43')); - const [aspect43] = useState(window.location.href.endsWith('43')); - const [dark, setDark] = useState(false); - const [sound] = useState(window.location.href.endsWith('sound')); - const socketUrl = `ws://${window.location.hostname}:__WEBSOCKET_PORT__`; - - const [content, setContent] = useState( - { - lines: [ - ['', '', ''], - ['', '', ''], - ['', '', ''], - ['', '', ''], - ['', '', ''], - ['', '', ''], - ['', '', ''], - ['', '', ''], - ['', '', ''], - ['', '', ''], - ['', '', ''], - ['', '', ''], - ], - scratchpad: '', - title: '', - titleLeft: '', - page: '', - arrows: [false, false, false, false], - }, - ); - - const { - sendMessage, - lastMessage, - readyState, - } = useWebSocket(socketUrl, { - shouldReconnect: () => true, - reconnectAttempts: Infinity, - reconnectInterval: 500, - }); - - useEffect(() => { - if (readyState === ReadyState.OPEN) { - sendMessage('requestUpdate'); - } - }, [readyState]); - - useEffect(() => { - if (lastMessage != null) { - const messageType = lastMessage.data.split(':')[0]; - if (messageType === 'update') { - setContent(JSON.parse(lastMessage.data.substring(lastMessage.data.indexOf(':') + 1)).left); - } - } - }, [lastMessage]); - - let backgroundImageUrl = 'mcdu-a32nx.png'; - if (dark) { - backgroundImageUrl = 'mcdu-a32nx-dark.png'; - } - - return ( - <> - {!fullscreen && ( -
-
- - - -
-
-
setFullscreen(!fullscreen)} /> -
-
-
-
-
setDark(!dark)} /> -
-
- -
-
- )} - {fullscreen && ( -
-
- -
setFullscreen(false)}> - -
-
-
-
- )} - - ); -} - -export default App; diff --git a/src/mcdu-server/client/src/McduButtons.css b/src/mcdu-server/client/src/McduButtons.css deleted file mode 100644 index 1429e731445a..000000000000 --- a/src/mcdu-server/client/src/McduButtons.css +++ /dev/null @@ -1,20 +0,0 @@ -.button-grid { - position: absolute; - display: flex; - flex-direction: column; -} - -.button-row { - flex-grow: 1; - display: flex; -} - -.button { - flex-grow: 1; - cursor: pointer; - -webkit-tap-highlight-color: transparent; -} - -.dummy { - flex-grow: 1; -} diff --git a/src/mcdu-server/client/src/McduButtons.jsx b/src/mcdu-server/client/src/McduButtons.jsx deleted file mode 100644 index 00fdb52436e8..000000000000 --- a/src/mcdu-server/client/src/McduButtons.jsx +++ /dev/null @@ -1,183 +0,0 @@ -import React, { useRef, useContext } from 'react'; -import './McduButtons.css'; -import { WebsocketContext } from './WebsocketContext'; - -const ButtonGrid = ({ children, x, y, width, height }) => ( -
- {children} -
-); - -const ButtonRow = ({ children }) => ( -
- {children} -
-); - -const Button = ({ sound, name }) => { - const socket = useContext(WebsocketContext); - const timeout = useRef(); - const buttonHeldTime = 1500; - - function pressButton(event) { - if (event.defaultPrevented) { - event.preventDefault(); - } - if (sound) { - new Audio('button-click.mp3').play(); - } - socket.sendMessage(`event:${name}`); - timeout.current = setTimeout(() => { - socket.sendMessage(`event:${name}_Held`); - }, buttonHeldTime); - } - - function releaseButton(event) { - event.preventDefault(); - if (timeout.current) { - clearTimeout(timeout.current); - } - } - - if (name.length) { - return ( -
pressButton(e)} - onMouseUp={(e) => releaseButton(e)} - onTouchStart={(e) => pressButton(e)} - onTouchEnd={(e) => releaseButton(e)} - /> - ); - } - return
; -}; - -export const McduButtons = ({ sound }) => ( -
- - -
-); diff --git a/src/mcdu-server/client/src/McduScreen.css b/src/mcdu-server/client/src/McduScreen.css deleted file mode 100644 index 69229f4b0dfb..000000000000 --- a/src/mcdu-server/client/src/McduScreen.css +++ /dev/null @@ -1,184 +0,0 @@ -@font-face { - font-family: "HoneywellMCDU"; - src: url("HoneywellMCDU.ttf"); -} - -@font-face { - font-family: "HoneywellMCDUSmall"; - src: url("HoneywellMCDUSmall.ttf"); -} - -.screen { - --displayBackground: rgba(13, 20, 35, 1); - --displaySeparator: #3c3c3c; - --displaySeparatorLight: #aaaaaa; - - --displayWhite: #ffffff; - --displayGrey: #787878; - --displayDarkGrey: #b3b3b3; - --displayLightGrey: lightgray; - --displayAmber: #e68000; - --displayCyan: #00ffff; - --displayGreen: #00ff00; - --displayMagenta: #ff94ff; - --displayRed: #ff0000; - --displayYellow: #ffff00; - - --mcduWhite: #ffffff; - --mcduLightGrey: #666666; - --mcduGrey: #787878; - --mcduGreen: #00ff00; - --mcduAmber: #ff9a00; - --mcduCyan: #00ffff; - --mcduYellow: #ffff00; - --mcduRed: #ff0000; - --mcduMagenta: #ff94ff; - - --mcdu-font-size: calc(calc(0.053 * var(--screen-scale)) * var(--app-width)); - --mcdu-line-height: calc(calc(0.058 * var(--screen-scale)) * var(--app-width)); - - font-family: "HoneywellMCDU", monospace; - - margin: 0 !important; - font-size: var(--mcdu-font-size); - color: var(--displayWhite); - line-height: var(--mcdu-line-height); - - - position: relative; - width: calc(100% * var(--screen-scale)); - left: 16.4%; - height: calc(var(--app-width) * var(--screen-scale)); -} - -.fullscreen .screen { - left: 0; - width: 100%; -} - -.label { - margin: 0; - white-space: pre; - position: relative; -} - -.label-left { - position: absolute; - left: 4.2%; - bottom: 0; - width: 100%; - text-align: left; -} - -.label-right { - position: absolute; - right: 4.2%; - bottom: 0; - width: 100%; - text-align: right; -} - -.label-center { - position: absolute; - left: 0; - bottom: 0; - width: 100%; - text-align: center; -} - -.line { - height: 5.5%; - margin: 0; - white-space: pre; -} - - -/* Fixes cut-off text in Safari */ -/* https://stackoverflow.com/questions/36294234/safari-cuts-off-fonts-with-characters-that-go-outside-of-font-file-bounding-box */ - -.fmc-block > span { - padding: 20px; - margin: -20px; -} - -.line-left { - position: absolute; - left: 4.2%; - width: 100%; - text-align: left; -} - -.line-right { - position: absolute; - right: 4.2%; - width: 100%; - text-align: right; -} - -.line-center { - position: absolute; - left: 0; - width: 100%; - text-align: center; -} - -.white { - color: var(--mcduWhite); -} - -.inop { - color: var(--mcduLightGrey); -} - -.cyan { - color: var(--mcduCyan); -} - -.yellow { - color: var(--mcduYellow); -} - -.green { - color: var(--mcduGreen); -} - -.amber { - color: var(--mcduAmber); -} - -.red { - color: var(--mcduRed); -} - -.magenta { - color: var(--mcduMagenta); -} - -.small, .label, .s-text { - font-family: "HoneywellMCDUSmall", "HoneywellMCDU", monospace; -} - -.b-text { - font-family: "HoneywellMCDU", monospace; -} - -.arrow-vertical { - float: right; -} - -.arrow-horizontal { - position: absolute; - right: 1%; - width: 100%; - text-align: right; -} - -.left { - margin-left: 4%; - float: left; -} - -.right { - margin-right: 6%; - float: right; -} diff --git a/src/mcdu-server/client/src/McduScreen.jsx b/src/mcdu-server/client/src/McduScreen.jsx deleted file mode 100644 index 3bbac65cf589..000000000000 --- a/src/mcdu-server/client/src/McduScreen.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import './McduScreen.css'; - -function escapeHTML(unsafeText) { - const div = document.createElement('div'); - div.innerText = unsafeText; - return div.innerHTML; -} - -function formatCell(str) { - return escapeHTML(str) - .replace(/{big}/g, "") - .replace(/{small}/g, "") - .replace(/{big}/g, "") - .replace(/{amber}/g, "") - .replace(/{red}/g, "") - .replace(/{green}/g, "") - .replace(/{cyan}/g, "") - .replace(/{white}/g, "") - .replace(/{magenta}/g, "") - .replace(/{yellow}/g, "") - .replace(/{inop}/g, "") - .replace(/{sp}/g, ' ') - .replace(/{left}/g, "") - .replace(/{right}/g, "") - .replace(/{end}/g, ''); -} - -const Line = ({ label, cols }) => ( -
- - - -
-); - -export const McduScreen = ({ content, aspect43 }) => { - const lines = []; - for (let i = 0; i < content.lines.length; i++) { - lines.push(); - } - return ( -
- {!aspect43 && ( - - )} - - - {lines} - -
- ); -}; diff --git a/src/mcdu-server/client/src/WebsocketContext.jsx b/src/mcdu-server/client/src/WebsocketContext.jsx deleted file mode 100644 index c0f80871f18d..000000000000 --- a/src/mcdu-server/client/src/WebsocketContext.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import { createContext } from 'react'; - -export const WebsocketContext = createContext(() => {}); diff --git a/src/mcdu-server/client/src/index.css b/src/mcdu-server/client/src/index.css deleted file mode 100644 index f87950d4164f..000000000000 --- a/src/mcdu-server/client/src/index.css +++ /dev/null @@ -1,19 +0,0 @@ -html { - position: fixed; - user-select:none; -} - -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - background-color: black; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/src/mcdu-server/client/src/index.jsx b/src/mcdu-server/client/src/index.jsx deleted file mode 100644 index c5a508068e3d..000000000000 --- a/src/mcdu-server/client/src/index.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import App from './App'; - -ReactDOM.render( - - - , - document.getElementById('root'), -); diff --git a/src/mcdu-server/index.js b/src/mcdu-server/index.js deleted file mode 100644 index 4f335503f946..000000000000 --- a/src/mcdu-server/index.js +++ /dev/null @@ -1,420 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -/* eslint-disable no-console */ - -'use strict'; - -const WebSocket = require('ws'); -const http = require('http'); -const path = require('path'); -const fs = require('fs'); -const network = require('network'); -const print = require('pdf-to-printer'); -const os = require('os'); -const PDFDocument = require('pdfkit'); -const childProcess = require('child_process'); -require('./standalone-patch'); - -// This tells pkg to include these files in the binary -path.join(__dirname, 'client/build/android-chrome-192x192.png'); -path.join(__dirname, 'client/build/android-chrome-512x512.png'); -path.join(__dirname, 'client/build/apple-touch-icon-180x180.png'); -path.join(__dirname, 'client/build/bundle.js'); -path.join(__dirname, 'client/build/favicon.ico'); -path.join(__dirname, 'client/build/HoneywellMCDU.ttf'); -path.join(__dirname, 'client/build/HoneywellMCDUSmall.ttf'); -path.join(__dirname, 'client/build/index.html'); -path.join(__dirname, 'client/build/mcdu-r2-c.png'); -path.join(__dirname, 'client/build/mcdu-a32nx.png'); -path.join(__dirname, 'client/build/mcdu-a32nx-dark.png'); -path.join(__dirname, 'client/build/button-click.mp3'); -path.join(__dirname, '../../node_modules/pdf-to-printer/dist/SumatraPDF.exe'); -path.join(__dirname, '../../node_modules/linebreak/src/classes.trie'); -path.join(__dirname, '../../node_modules/pdfkit/js/data/Helvetica.afm'); - -const sumatraPdfPath = path.join(os.tmpdir(), 'SumatraPDF.exe'); -fs.copyFileSync(path.join(__dirname, '../../node_modules/pdf-to-printer/dist/SumatraPDF.exe'), sumatraPdfPath); - -const readline = require('readline').createInterface({ - input: process.stdin, - output: process.stdout, -}); - -const readNumber = (prompt, min, max, callback) => { - readline.question(`${prompt} (${min}-${max}):`, (response) => { - const number = parseInt(response, 10); - if (number != null && number >= min && number <= max) { - callback(number); - } else { - readNumber(prompt, min, max, callback); - } - }); -}; - -let selectedPrinter = null; - -// Parse command line args -let printerName = null; -let skipPrinter = false; -let httpPort = 8125; -let websocketPort = 8380; -let debug = false; -let fontSize = 19; -let paperSize = 'A4'; -let margin; - -const args = [...process.argv]; -args.splice(0, 2); - -for (const arg of args) { - if (arg === '--no-printer') { - skipPrinter = true; - continue; - } - if (arg.startsWith('--printer=')) { - printerName = arg.split('=')[1]; - continue; - } - if (arg.startsWith('--http-port=')) { - httpPort = parseInt(arg.split('=')[1], 10); - if (Number.isNaN(httpPort) || httpPort < 0 || httpPort > 65353) { - console.error(`Invalid http port: ${arg.split('=')[1]}`); - pressAnyKey(1); - } - continue; - } - if (arg.startsWith('--websocket-port=')) { - websocketPort = parseInt(arg.split('=')[1], 10); - if (Number.isNaN(websocketPort) || websocketPort < 0 || websocketPort > 65353) { - console.error(`Invalid websocket port: ${arg.split('=')[1]}`); - pressAnyKey(1); - } - continue; - } - if (arg.startsWith('--font-size=')) { - fontSize = parseInt(arg.split('=')[1], 10); - if (Number.isNaN(fontSize) || fontSize < 1) { - console.error(`Invalid font size: ${arg.split('=')[1]}`); - pressAnyKey(1); - } - continue; - } - if (arg.startsWith('--paper-size=')) { - paperSize = arg.split('=')[1].toUpperCase(); - if (!validPaperSize(paperSize)) { - console.error(`Invalid paper size: ${arg.split('=')[1]}`); - pressAnyKey(1); - } - continue; - } - if (arg.startsWith('--margin=')) { - margin = parseInt(arg.split('=')[1], 10); - if (Number.isNaN(margin)) { - console.error(`Invalid margin: ${arg.split('=')[1]}`); - pressAnyKey(1); - } - continue; - } - if (arg === '--debug') { - debug = true; - continue; - } - if (arg === '-h' || arg === '--help') { - printUsage(); - process.exit(0); - } - console.error(`Unknown argument: ${arg}`); - printUsage(); - pressAnyKey(1); -} - -if (margin === undefined) { - margin = paperSize === 'A4' ? 30 : 10; -} - -if (httpPort === websocketPort) { - console.error(`Error: HTTP port (${httpPort}) and Websocket port (${websocketPort}) cannot be identical`); - pressAnyKey(1); -} - -if (printerName != null) { - print.getPrinters().then((printers) => { - selectedPrinter = printers.find((printer) => printer.name === printerName); - if (!selectedPrinter) { - console.error(`Unknown printer: ${printerName}`); - pressAnyKey(1); - return; - } - start(); - }).catch((error) => { - showError('Failed to load printers.\nMake sure the "Print Spooler" Windows service is running.', error); - console.error('\nSee the documentation for more information:\n\x1b[47m\x1b[30mhttps://docs.flybywiresim.com/fbw-a32nx/feature-guides/web-mcdu/#troubleshooting\x1b[0m'); - pressAnyKey(1); - }); -} else if (!skipPrinter) { - readline.question('Would you like to enable printing to a real printer? (y/N): ', (response) => { - if (response.toLowerCase() === 'y') { - print.getPrinters().then((printers) => { - if (printers) { - console.log('The following printers are available:'); - for (let i = 0; i < printers.length; i++) { - console.log(` ${i + 1}: ${printers[i].name}`); - } - readNumber('Which printer would you like to use?', 1, printers.length, (printerIndex) => { - selectedPrinter = printers[printerIndex - 1]; - start(); - }); - } else { - console.error('Error: No printers detected'); - pressAnyKey(1); - } - }).catch((error) => { - showError('Failed to load printers.\nMake sure the "Print Spooler" Windows service is running.', error); - console.error('\nSee the documentation for more information:\n\x1b[47m\x1b[30mhttps://docs.flybywiresim.com/fbw-a32nx/feature-guides/web-mcdu/#troubleshooting\x1b[0m'); - pressAnyKey(1); - }); - } else { - start(); - } - }); -} else { - start(); -} - -/** - * Starts the HTTP and Websocket servers - */ -function start() { - console.log('Starting server...'); - - // Simple HTTP server for the web-based client - const httpServer = http.createServer((request, response) => { - let filePath = `.${request.url}`; - if (filePath === './') filePath = './index.html'; - - const extname = path.extname(filePath); - let contentType = 'text/html'; - switch (extname) { - case '.js': - contentType = 'text/javascript'; - break; - case '.css': - contentType = 'text/css'; - break; - case '.json': - contentType = 'application/json'; - break; - case '.png': - contentType = 'image/png'; - break; - case '.jpg': - contentType = 'image/jpg'; - break; - case '.mp3': - contentType = 'audio/mpeg'; - break; - default: - break; - } - - fs.readFile(path.join(__dirname, './client/build/', filePath), (error, content) => { - if (error) { - if (error.code === 'ENOENT') { - fs.readFile(path.join(__dirname, './client/build/index.html'), (error, content) => { - response.writeHead(200, { 'Content-Type': 'text/html' }); - if (contentType === 'text/javascript') { - content = content.toString('utf8').replace(/__WEBSOCKET_PORT__/g, websocketPort); - } - response.end(content, 'utf-8'); - }); - } else { - response.writeHead(500); - response.end(`Error: ${error.code}`); - response.end(); - } - } else { - response.writeHead(200, { 'Content-Type': contentType }); - if (contentType === 'text/javascript') { - content = content.toString('utf8').replace(/__WEBSOCKET_PORT__/g, websocketPort); - } - response.end(content, 'utf-8'); - } - }); - }); - let httpServerStarted = false; - - httpServer.listen(httpPort); - - httpServer.on('error', (error) => { - if (error.code === 'EADDRINUSE') { - console.error(`Error: Port ${httpPort} is already in use`); - console.error('\nSee the documentation for more information:\n\x1b[47m\x1b[30mhttps://docs.flybywiresim.com/fbw-a32nx/feature-guides/web-mcdu/#occupied-port\x1b[0m'); - pressAnyKey(1); - } else if (!httpServerStarted) { - showError('Failed to start HTTP server', error); - pressAnyKey(1); - } else if (debug) { - console.error(`${error}`); - } - }); - - httpServer.on('listening', () => { - httpServerStarted = true; - network.get_private_ip((err, ip) => { - // Create websocket server - let wss = null; - let serverRunning = false; - - wss = new WebSocket.Server({ port: websocketPort }, () => { - serverRunning = true; - console.clear(); - console.log('External MCDU server started.\n'); - console.log(`Listening for websocket connections on port ${websocketPort}... this should match the EFB settings page.\n`); - console.log('Waiting for simulator...'); - }); - - wss.on('error', (error) => { - if (error.code === 'EADDRINUSE') { - console.error(`Error: Port ${websocketPort} is already in use`); - console.error('\nSee the documentation for more information:\n\x1b[47m\x1b[30mhttps://docs.flybywiresim.com/fbw-a32nx/feature-guides/web-mcdu/#occupied-port\x1b[0m'); - pressAnyKey(1); - } else if (!serverRunning) { - showError('Failed to start websocket server', error); - pressAnyKey(1); - } else if (debug) { - console.error(`${error}`); - } - }); - - wss.on('connection', (ws) => { - let isMcdu = false; - ws.on('message', (message) => { - if (message === 'mcduConnected') { - console.clear(); - console.log('\x1b[32mSimulator connected!\x1b[0m\n'); - if (err) { - console.log(`To control the MCDU from this device, open \x1b[47m\x1b[30mhttp://localhost:${httpPort}\x1b[0m in your browser.`); - console.log('\nTo control the MCDU from another device on your network, replace localhost with your local IP address.'); - // eslint-disable-next-line max-len - console.log('To find your local IP address, see here: \x1b[47m\x1b[30mhttps://support.microsoft.com/en-us/windows/find-your-ip-address-in-windows-f21a9bbc-c582-55cd-35e0-73431160a1b9\x1b[0m'); - } else { - console.log(`To control the MCDU from another device on your network, open \x1b[47m\x1b[30mhttp://${ip}:${httpPort}\x1b[0m in your browser.`); - console.log(`To control the MCDU from this device, open \x1b[47m\x1b[30mhttp://localhost:${httpPort}\x1b[0m in your browser.`); - } - console.log(`\nCan't connect? You may need to open TCP ports ${httpPort} and ${websocketPort} on your firewall.`); - console.log('See the documentation for more information:'); - console.log('\x1b[47m\x1b[30mhttps://docs.flybywiresim.com/fbw-a32nx/feature-guides/web-mcdu/#firewall-configuration\x1b[0m\n'); - console.log('Add "/sound" to your browser´s URL to get click sounds.'); - - if (selectedPrinter) { - console.log(`Printer: ${selectedPrinter.name}`); - console.log(`Font size: ${fontSize}`); - console.log(`Paper size: ${paperSize}`); - console.log(`Margin: ${margin}`); - } - - isMcdu = true; - return; - } - wss.clients.forEach((client) => { - if (client.readyState === WebSocket.OPEN) { - client.send(message); - } - }); - if (debug) { - console.log(message); - } - if (message.startsWith('print:')) { - const { lines } = JSON.parse(message.substring(6)); - if (selectedPrinter) { - const doc = new PDFDocument({ size: paperSize, margin }); - const pdfPath = path.join(os.tmpdir(), 'a32nxPrint.pdf'); - doc.pipe(fs.createWriteStream(pdfPath)); - doc.font(path.join(__dirname, 'client/build/RobotoMono-Bold.ttf')); - doc.fontSize(fontSize); - for (let i = 0; i < lines.length; i++) { - doc.text(lines[i], { align: 'left' }); - doc.moveDown(); - } - doc.end(); - print.print(pdfPath, { printer: selectedPrinter.name, sumatraPdfPath }); - } - } - }); - ws.on('close', () => { - if (isMcdu) { - console.clear(); - console.log('\x1b[31mLost connection to simulator.\x1b[0m\n'); - console.log(`Listening for websocket connections on port ${websocketPort}... this should match the EFB settings page.\n`); - console.log('Waiting for simulator...'); - } - }); - }); - }); - }); -} - -/** - * Prints usage information - */ -function printUsage() { - console.log('\nUsage:'); - console.log('server [options]'); - console.log('\nOptions:'); - console.log('--debug shows full error details and logs websocket traffic'); - console.log('--font-size=... sets font size for printing (default: 19)'); - console.log('-h, --help print command line options'); - console.log('--http-port=... sets port for http server (default: 8125)'); - console.log('--margin=... sets margin for printing in points'); - console.log('--no-printer skips prompt to select printer'); - console.log('--paper-size=... sets paper size for printing (default: A4)'); - console.log('--printer=... enables printing to the specified printer'); - console.log('--websocket-port=... sets port for websocket server (default: 8380)'); - - console.log('\nSee the documentation for more information:\n\x1b[47m\x1b[30mhttps://docs.flybywiresim.com/fbw-a32nx/feature-guides/web-mcdu/\x1b[0m'); -} - -/** - * Shows a short error message. Also shows full error details if debug mode is enabled. - * @param {string} message The short error message that is always displayed - * @param {any[]} error The full error details that is only displayed in debug mode - */ -function showError(message, error) { - console.error(`Error: ${message}\n`); - if (debug) { - console.error('Full error details:\n'); - console.error(error); - } else { - console.error('Run with the --debug option to see full error details.'); - } -} - -/** - * Shows "Press any key to continue..." prompt and exits the process if no args were passed or debug mode is enabled. - * Otherwise, it just exits the process. - * @param {number} exitCode - */ -function pressAnyKey(exitCode) { - // Hack to really pause and wait for a key press in Node.js. Other solutions - // execute code after this line and start the server anyway. - childProcess.spawnSync('pause', { shell: true, stdio: [0, 1, 2] }); - process.exit(exitCode); -} - -/** - * Checks the paper size against valid paper sizes of the pdfkit library. - * @param {string} size - * @returns {boolean} - */ -function validPaperSize(size) { - return !!(size.match(/^[ABC]\d\d?$/) - || size.match(/^S?RA\d\d?$/) - || size === 'EXECUTIVE' - || size === 'LEGAL' - || size === 'LETTER' - || size === 'TABLOID' - || size === '4A0' - || size === '2A0' - || size === 'FOLIO'); -} diff --git a/src/mcdu-server/standalone-patch.js b/src/mcdu-server/standalone-patch.js deleted file mode 100644 index 3899d359b072..000000000000 --- a/src/mcdu-server/standalone-patch.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - Copyright (c) 2019 Serverless, Inc. http://www.serverless.com - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - */ - -'use strict'; - -// Workaround 'pkg' bug: https://github.com/zeit/pkg/issues/420 -// Copying files from snapshot via `fs.copyFileSync` crashes with ENOENT -// Overriding copyFileSync with primitive alternative - -const fs = require('fs'); - -if (!fs.copyFile) return; - -const path = require('path'); - -const originalCopyFile = fs.copyFile; -const originalCopyFileSync = fs.copyFileSync; - -const isBundled = RegExp.prototype.test.bind(/^(?:\/snapshot\/|[A-Z]+:\\snapshot\\)/); - -fs.copyFile = function copyFile(src, dest, flags, callback) { - if (!isBundled(path.resolve(src))) { - originalCopyFile(src, dest, flags, callback); - return; - } - if (typeof flags === 'function') { - callback = flags; - flags = 0; - } else if (typeof callback !== 'function') { - throw new TypeError('Callback must be a function'); - } - - fs.readFile(src, (readError, content) => { - if (readError) { - callback(readError); - return; - } - if (flags & fs.constants.COPYFILE_EXCL) { - fs.stat(dest, (statError) => { - if (!statError) { - callback(Object.assign(new Error('File already exists'), { code: 'EEXIST' })); - return; - } - if (statError.code !== 'ENOENT') { - callback(statError); - return; - } - fs.writeFile(dest, content, callback); - }); - } else { - fs.writeFile(dest, content, callback); - } - }); -}; - -fs.copyFileSync = function copyFileSync(src, dest, flags) { - if (!isBundled(path.resolve(src))) { - originalCopyFileSync(src, dest, flags); - return; - } - const content = fs.readFileSync(src); - if (flags & fs.constants.COPYFILE_EXCL) { - try { - fs.statSync(dest); - } catch (statError) { - if (statError.code !== 'ENOENT') throw statError; - fs.writeFileSync(dest, content); - return; - } - throw Object.assign(new Error('File already exists'), { code: 'EEXIST' }); - } - fs.writeFileSync(dest, content); -}; - -if (!fs.promises) return; - -const { promisify } = require('util'); - -fs.promises.copyFile = promisify(fs.copyFile); diff --git a/src/model/a320-exterior/A320_NEO_LOD00.gltf b/src/model/a320-exterior/A320_NEO_LOD00.gltf index bef5297b4411..0015b329159d 100644 --- a/src/model/a320-exterior/A320_NEO_LOD00.gltf +++ b/src/model/a320-exterior/A320_NEO_LOD00.gltf @@ -25809,30 +25809,6 @@ { "name": "c_gear", "channels": [ - { - "sampler": 0, - "target": { "node": 19, "path": "translation" } - }, - { "sampler": 1, "target": { "node": 19, "path": "rotation" } }, - { "sampler": 2, "target": { "node": 19, "path": "scale" } }, - { - "sampler": 3, - "target": { "node": 23, "path": "translation" } - }, - { "sampler": 4, "target": { "node": 23, "path": "rotation" } }, - { "sampler": 5, "target": { "node": 23, "path": "scale" } }, - { - "sampler": 6, - "target": { "node": 21, "path": "translation" } - }, - { "sampler": 7, "target": { "node": 21, "path": "rotation" } }, - { "sampler": 8, "target": { "node": 21, "path": "scale" } }, - { - "sampler": 9, - "target": { "node": 25, "path": "translation" } - }, - { "sampler": 10, "target": { "node": 25, "path": "rotation" } }, - { "sampler": 11, "target": { "node": 25, "path": "scale" } }, { "sampler": 12, "target": { "node": 128, "path": "translation" } @@ -25909,6 +25885,56 @@ { "input": 147, "output": 148, "interpolation": "LINEAR" } ] }, + { + "name": "c_gear_door1", + "channels": [ + { + "sampler": 0, + "target": { "node": 19, "path": "translation" } + }, + { "sampler": 1, "target": { "node": 19, "path": "rotation" } }, + { "sampler": 2, "target": { "node": 19, "path": "scale" } }, + { + "sampler": 3, + "target": { "node": 23, "path": "translation" } + }, + { "sampler": 4, "target": { "node": 23, "path": "rotation" } }, + { "sampler": 5, "target": { "node": 23, "path": "scale" } } + ], + "samplers": [ + { "input": 95, "output": 96, "interpolation": "LINEAR" }, + { "input": 97, "output": 98, "interpolation": "LINEAR" }, + { "input": 99, "output": 100, "interpolation": "LINEAR" }, + { "input": 101, "output": 102, "interpolation": "LINEAR" }, + { "input": 103, "output": 104, "interpolation": "LINEAR" }, + { "input": 105, "output": 106, "interpolation": "LINEAR" } + ] + }, + { + "name": "c_gear_door2", + "channels": [ + { + "sampler": 0, + "target": { "node": 21, "path": "translation" } + }, + { "sampler": 1, "target": { "node": 21, "path": "rotation" } }, + { "sampler": 2, "target": { "node": 21, "path": "scale" } }, + { + "sampler": 3, + "target": { "node": 25, "path": "translation" } + }, + { "sampler": 4, "target": { "node": 25, "path": "rotation" } }, + { "sampler": 5, "target": { "node": 25, "path": "scale" } } + ], + "samplers": [ + { "input": 107, "output": 108, "interpolation": "LINEAR" }, + { "input": 109, "output": 110, "interpolation": "LINEAR" }, + { "input": 111, "output": 112, "interpolation": "LINEAR" }, + { "input": 113, "output": 114, "interpolation": "LINEAR" }, + { "input": 115, "output": 116, "interpolation": "LINEAR" }, + { "input": 117, "output": 118, "interpolation": "LINEAR" } + ] + }, { "name": "c_tire_anim", "channels": [ @@ -26148,24 +26174,6 @@ "target": { "node": 109, "path": "rotation" } }, { "sampler": 35, "target": { "node": 109, "path": "scale" } }, - { - "sampler": 36, - "target": { "node": 12, "path": "translation" } - }, - { "sampler": 37, "target": { "node": 12, "path": "rotation" } }, - { "sampler": 38, "target": { "node": 12, "path": "scale" } }, - { - "sampler": 39, - "target": { "node": 13, "path": "translation" } - }, - { "sampler": 40, "target": { "node": 13, "path": "rotation" } }, - { "sampler": 41, "target": { "node": 13, "path": "scale" } }, - { - "sampler": 42, - "target": { "node": 14, "path": "translation" } - }, - { "sampler": 43, "target": { "node": 14, "path": "rotation" } }, - { "sampler": 44, "target": { "node": 14, "path": "scale" } }, { "sampler": 45, "target": { "node": 118, "path": "translation" } @@ -26299,6 +26307,40 @@ { "input": 353, "output": 354, "interpolation": "LINEAR" } ] }, + { + "name": "l_gear_door", + "channels": [ + { + "sampler": 0, + "target": { "node": 12, "path": "translation" } + }, + { "sampler": 1, "target": { "node": 12, "path": "rotation" } }, + { "sampler": 2, "target": { "node": 12, "path": "scale" } }, + { + "sampler": 3, + "target": { "node": 13, "path": "translation" } + }, + { "sampler": 4, "target": { "node": 13, "path": "rotation" } }, + { "sampler": 5, "target": { "node": 13, "path": "scale" } }, + { + "sampler": 6, + "target": { "node": 14, "path": "translation" } + }, + { "sampler": 7, "target": { "node": 14, "path": "rotation" } }, + { "sampler": 8, "target": { "node": 14, "path": "scale" } } + ], + "samplers": [ + { "input": 299, "output": 300, "interpolation": "LINEAR" }, + { "input": 301, "output": 302, "interpolation": "LINEAR" }, + { "input": 303, "output": 304, "interpolation": "LINEAR" }, + { "input": 305, "output": 306, "interpolation": "LINEAR" }, + { "input": 307, "output": 308, "interpolation": "LINEAR" }, + { "input": 309, "output": 310, "interpolation": "LINEAR" }, + { "input": 311, "output": 312, "interpolation": "LINEAR" }, + { "input": 313, "output": 314, "interpolation": "LINEAR" }, + { "input": 315, "output": 316, "interpolation": "LINEAR" } + ] + }, { "name": "l_spoiler_key", "channels": [ @@ -26558,24 +26600,6 @@ "target": { "node": 193, "path": "rotation" } }, { "sampler": 29, "target": { "node": 193, "path": "scale" } }, - { - "sampler": 30, - "target": { "node": 5, "path": "translation" } - }, - { "sampler": 31, "target": { "node": 5, "path": "rotation" } }, - { "sampler": 32, "target": { "node": 5, "path": "scale" } }, - { - "sampler": 33, - "target": { "node": 6, "path": "translation" } - }, - { "sampler": 34, "target": { "node": 6, "path": "rotation" } }, - { "sampler": 35, "target": { "node": 6, "path": "scale" } }, - { - "sampler": 36, - "target": { "node": 7, "path": "translation" } - }, - { "sampler": 37, "target": { "node": 7, "path": "rotation" } }, - { "sampler": 38, "target": { "node": 7, "path": "scale" } }, { "sampler": 39, "target": { "node": 176, "path": "translation" } @@ -26727,6 +26751,40 @@ { "input": 571, "output": 572, "interpolation": "LINEAR" } ] }, + { + "name": "r_gear_door", + "channels": [ + { + "sampler": 0, + "target": { "node": 5, "path": "translation" } + }, + { "sampler": 1, "target": { "node": 5, "path": "rotation" } }, + { "sampler": 2, "target": { "node": 5, "path": "scale" } }, + { + "sampler": 3, + "target": { "node": 6, "path": "translation" } + }, + { "sampler": 4, "target": { "node": 6, "path": "rotation" } }, + { "sampler": 5, "target": { "node": 6, "path": "scale" } }, + { + "sampler": 6, + "target": { "node": 7, "path": "translation" } + }, + { "sampler": 7, "target": { "node": 7, "path": "rotation" } }, + { "sampler": 8, "target": { "node": 7, "path": "scale" } } + ], + "samplers": [ + { "input": 505, "output": 506, "interpolation": "LINEAR" }, + { "input": 507, "output": 508, "interpolation": "LINEAR" }, + { "input": 509, "output": 510, "interpolation": "LINEAR" }, + { "input": 511, "output": 512, "interpolation": "LINEAR" }, + { "input": 513, "output": 514, "interpolation": "LINEAR" }, + { "input": 515, "output": 516, "interpolation": "LINEAR" }, + { "input": 517, "output": 518, "interpolation": "LINEAR" }, + { "input": 519, "output": 520, "interpolation": "LINEAR" }, + { "input": 521, "output": 522, "interpolation": "LINEAR" } + ] + }, { "name": "r_spoiler_key", "channels": [ diff --git a/src/model/a320-interior/A320_NEO_INTERIOR_LOD00.gltf b/src/model/a320-interior/A320_NEO_INTERIOR_LOD00.gltf index d46af141cbd1..93c529182747 100644 --- a/src/model/a320-interior/A320_NEO_INTERIOR_LOD00.gltf +++ b/src/model/a320-interior/A320_NEO_INTERIOR_LOD00.gltf @@ -187840,7 +187840,6 @@ 1329, 1330, 1331, - 1334, 1335, 1336, 1337, @@ -187878,8 +187877,7 @@ 1378, 1379, 1382, - 1383, - 1384 + 1383 ] } ], diff --git a/src/model/build.js b/src/model/build.js index 4edda75c75ec..b3ab8dfe06da 100644 --- a/src/model/build.js +++ b/src/model/build.js @@ -176,19 +176,21 @@ function combineGltf(pathA, pathB, outputPath) { }); mesh.primitives[0].indices += accessorsCount; // workaround to allow added meshes to use existing materials - if (!Number.isFinite(mesh.primitives[0].material)) { - for (let i = 0; i < gltfA.materials.length; i += 1) { - if (gltfA.materials[i].name === mesh.primitives[0].material) { - mesh.primitives[0].material = i; - break; + for (const primitive of mesh.primitives) { + if (!Number.isFinite(primitive.material)) { + for (let i = 0; i < gltfA.materials.length; i += 1) { + if (gltfA.materials[i].name === primitive.material) { + primitive.material = i; + break; + } } + // If the material is not found, use material 0 + if (!Number.isFinite(primitive.material)) { + primitive.material = 0; + } + } else { + primitive.material += materialsCount; } - // If the material is not found, use material 0 - if (!Number.isFinite(mesh.primitives[0].material)) { - mesh.primitives[0].material = 0; - } - } else { - mesh.primitives[0].material += materialsCount; } gltfA.meshes.push(mesh); } @@ -430,13 +432,16 @@ for (const model of models) { } } } else { - combineGltf(p(model.output.gltf[i]), p(addition.gltf), p(model.output.gltf[i])); + const maxLod = addition.maxLod != null ? addition.maxLod : Infinity; + if (i <= maxLod) { + combineGltf(p(model.output.gltf[i]), p(addition.gltf), p(model.output.gltf[i])); - // add some zeroes to the end of the bin file to make sure its length is divisible by 4 - fs.appendFileSync(p(model.output.bin[i]), Buffer.alloc((4 - (fs.statSync(p(model.output.bin[i])).size % 4)) % 4)); + // add some zeroes to the end of the bin file to make sure its length is divisible by 4 + fs.appendFileSync(p(model.output.bin[i]), Buffer.alloc((4 - (fs.statSync(p(model.output.bin[i])).size % 4)) % 4)); - // add the second bin file to the end of the first one - fs.appendFileSync(p(model.output.bin[i]), fs.readFileSync(p(addition.bin))); + // add the second bin file to the end of the first one + fs.appendFileSync(p(model.output.bin[i]), fs.readFileSync(p(addition.bin))); + } } } if (model.splitAnimations) { diff --git a/src/model/cockpit/cockpit.bin b/src/model/cockpit/cockpit.bin new file mode 100644 index 000000000000..6dc9044f1e40 Binary files /dev/null and b/src/model/cockpit/cockpit.bin differ diff --git a/src/model/cockpit/cockpit.gltf b/src/model/cockpit/cockpit.gltf new file mode 100644 index 000000000000..53298d93092f --- /dev/null +++ b/src/model/cockpit/cockpit.gltf @@ -0,0 +1,1992 @@ +{ + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "count": 2728, + "min": [ + -1.7160303592681885, + 0.420204222202301, + 0.06134706735610962 + ], + "max": [ + 1.326724648475647, + 1.9099429845809937, + 0.9249697923660278 + ], + "type": "VEC3", + "name": "x0_COCKPIT_BACK01_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 12, + "componentType": 5120, + "count": 2728, + "type": "VEC4", + "name": "x0_COCKPIT_BACK01_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 16, + "componentType": 5120, + "count": 2728, + "type": "VEC4", + "name": "x0_COCKPIT_BACK01_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 20, + "componentType": 5122, + "count": 2728, + "type": "VEC2", + "name": "x0_COCKPIT_BACK01_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 24, + "componentType": 5122, + "count": 2728, + "type": "VEC2", + "name": "x0_COCKPIT_BACK01_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 28, + "componentType": 5123, + "count": 2728, + "type": "VEC4", + "name": "x0_COCKPIT_BACK01_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "componentType": 5123, + "count": 9156, + "type": "SCALAR", + "name": "x0_COCKPIT_BACK01_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 98208, + "componentType": 5126, + "count": 4154, + "min": [ + -1.7529988288879395, + -0.06217685341835022, + 0.27546700835227966 + ], + "max": [ + 1.0812934637069702, + 2.0699973106384277, + 1.6066025495529175 + ], + "type": "VEC3", + "name": "x0_COCKPIT_BACK02_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 98220, + "componentType": 5120, + "count": 4154, + "type": "VEC4", + "name": "x0_COCKPIT_BACK02_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 98224, + "componentType": 5120, + "count": 4154, + "type": "VEC4", + "name": "x0_COCKPIT_BACK02_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 98228, + "componentType": 5122, + "count": 4154, + "type": "VEC2", + "name": "x0_COCKPIT_BACK02_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 98232, + "componentType": 5122, + "count": 4154, + "type": "VEC2", + "name": "x0_COCKPIT_BACK02_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 98236, + "componentType": 5123, + "count": 4154, + "type": "VEC4", + "name": "x0_COCKPIT_BACK02_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 18312, + "componentType": 5123, + "count": 11061, + "type": "SCALAR", + "name": "x0_COCKPIT_BACK02_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 247752, + "componentType": 5126, + "count": 1984, + "min": [ + -1.6846565008163452, + -0.06534881889820099, + -1.5508090257644653 + ], + "max": [ + 1.299214482307434, + 2.0375022888183594, + 1.5945782661437988 + ], + "type": "VEC3", + "name": "x0_COCKPIT_DECALSALPHA_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 247764, + "componentType": 5120, + "count": 1984, + "type": "VEC4", + "name": "x0_COCKPIT_DECALSALPHA_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 247768, + "componentType": 5120, + "count": 1984, + "type": "VEC4", + "name": "x0_COCKPIT_DECALSALPHA_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 247772, + "componentType": 5122, + "count": 1984, + "type": "VEC2", + "name": "x0_COCKPIT_DECALSALPHA_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 247776, + "componentType": 5122, + "count": 1984, + "type": "VEC2", + "name": "x0_COCKPIT_DECALSALPHA_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 247780, + "componentType": 5123, + "count": 1984, + "type": "VEC4", + "name": "x0_COCKPIT_DECALSALPHA_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 40434, + "componentType": 5123, + "count": 3684, + "type": "SCALAR", + "name": "x0_COCKPIT_DECALSALPHA_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 319176, + "componentType": 5126, + "count": 3478, + "min": [ + -1.315739393234253, + 1.0269562005996704, + -1.8179785013198853 + ], + "max": [ + 0.9477484822273254, + 2.067660331726074, + 0.7846047878265381 + ], + "type": "VEC3", + "name": "x0_COCKPIT_FLOOR_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 319188, + "componentType": 5120, + "count": 3478, + "type": "VEC4", + "name": "x0_COCKPIT_FLOOR_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 319192, + "componentType": 5120, + "count": 3478, + "type": "VEC4", + "name": "x0_COCKPIT_FLOOR_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 319196, + "componentType": 5122, + "count": 3478, + "type": "VEC2", + "name": "x0_COCKPIT_FLOOR_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 319200, + "componentType": 5122, + "count": 3478, + "type": "VEC2", + "name": "x0_COCKPIT_FLOOR_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 319204, + "componentType": 5123, + "count": 3478, + "type": "VEC4", + "name": "x0_COCKPIT_FLOOR_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 47802, + "componentType": 5123, + "count": 12294, + "type": "SCALAR", + "name": "x0_COCKPIT_FLOOR_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 444384, + "componentType": 5126, + "count": 120, + "min": [ + -1.0467132329940796, + 1.0016862154006958, + -0.3815113604068756 + ], + "max": [ + 0.6788986325263977, + 1.018474817276001, + -0.3444666564464569 + ], + "type": "VEC3", + "name": "x0_COCKPIT_INPUTS01_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 444396, + "componentType": 5120, + "count": 120, + "type": "VEC4", + "name": "x0_COCKPIT_INPUTS01_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 444400, + "componentType": 5120, + "count": 120, + "type": "VEC4", + "name": "x0_COCKPIT_INPUTS01_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 444404, + "componentType": 5122, + "count": 120, + "type": "VEC2", + "name": "x0_COCKPIT_INPUTS01_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 444408, + "componentType": 5122, + "count": 120, + "type": "VEC2", + "name": "x0_COCKPIT_INPUTS01_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 444412, + "componentType": 5123, + "count": 120, + "type": "VEC4", + "name": "x0_COCKPIT_INPUTS01_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 72390, + "componentType": 5123, + "count": 264, + "type": "SCALAR", + "name": "x0_COCKPIT_INPUTS01_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 448704, + "componentType": 5126, + "count": 32868, + "min": [ + -0.5957156419754028, + -0.038016363978385925, + -0.43538352847099304 + ], + "max": [ + 0.22822688519954681, + 0.032261695712804794, + 0.863524317741394 + ], + "type": "VEC3", + "name": "x0_COCKPIT_OVERHEAD_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 448716, + "componentType": 5120, + "count": 32868, + "type": "VEC4", + "name": "x0_COCKPIT_OVERHEAD_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 448720, + "componentType": 5120, + "count": 32868, + "type": "VEC4", + "name": "x0_COCKPIT_OVERHEAD_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 448724, + "componentType": 5122, + "count": 32868, + "type": "VEC2", + "name": "x0_COCKPIT_OVERHEAD_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 448728, + "componentType": 5122, + "count": 32868, + "type": "VEC2", + "name": "x0_COCKPIT_OVERHEAD_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 448732, + "componentType": 5123, + "count": 32868, + "type": "VEC4", + "name": "x0_COCKPIT_OVERHEAD_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 72918, + "componentType": 5123, + "count": 115020, + "type": "SCALAR", + "name": "x0_COCKPIT_OVERHEAD_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 1631952, + "componentType": 5126, + "count": 4448, + "min": [ + -1.0900219678878784, + 0.6707103848457336, + -1.6970192193984985 + ], + "max": [ + 0.7220311164855957, + 1.2384802103042603, + -1.2057383060455322 + ], + "type": "VEC3", + "name": "x0_COCKPIT_PEDALS_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 1631964, + "componentType": 5120, + "count": 4448, + "type": "VEC4", + "name": "x0_COCKPIT_PEDALS_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 1631968, + "componentType": 5120, + "count": 4448, + "type": "VEC4", + "name": "x0_COCKPIT_PEDALS_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 1631972, + "componentType": 5122, + "count": 4448, + "type": "VEC2", + "name": "x0_COCKPIT_PEDALS_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 1631976, + "componentType": 5122, + "count": 4448, + "type": "VEC2", + "name": "x0_COCKPIT_PEDALS_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 1631980, + "componentType": 5123, + "count": 4448, + "type": "VEC4", + "name": "x0_COCKPIT_PEDALS_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 302958, + "componentType": 5123, + "count": 17316, + "type": "SCALAR", + "name": "x0_COCKPIT_PEDALS_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 1792080, + "componentType": 5126, + "count": 535, + "min": [ + -1.2770382165908813, + 0.30345475673675537, + -1.187746286392212 + ], + "max": [ + 0.6767993569374084, + 1.4600638151168823, + 1.0253498554229736 + ], + "type": "VEC3", + "name": "x0_COCKPIT_PLASTICGLASS_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 1792092, + "componentType": 5120, + "count": 535, + "type": "VEC4", + "name": "x0_COCKPIT_PLASTICGLASS_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 1792096, + "componentType": 5120, + "count": 535, + "type": "VEC4", + "name": "x0_COCKPIT_PLASTICGLASS_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 1792100, + "componentType": 5122, + "count": 535, + "type": "VEC2", + "name": "x0_COCKPIT_PLASTICGLASS_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 1792104, + "componentType": 5122, + "count": 535, + "type": "VEC2", + "name": "x0_COCKPIT_PLASTICGLASS_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 1792108, + "componentType": 5123, + "count": 535, + "type": "VEC4", + "name": "x0_COCKPIT_PLASTICGLASS_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 337590, + "componentType": 5123, + "count": 1881, + "type": "SCALAR", + "name": "x0_COCKPIT_PLASTICGLASS_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 1811340, + "componentType": 5126, + "count": 7740, + "min": [ + -1.1497234106063843, + 0.5126140713691711, + -0.7016947269439697 + ], + "max": [ + 0.7817324995994568, + 1.6532886028289795, + 0.38779494166374207 + ], + "type": "VEC3", + "name": "x0_COCKPIT_SEATS_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 1811352, + "componentType": 5120, + "count": 7740, + "type": "VEC4", + "name": "x0_COCKPIT_SEATS_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 1811356, + "componentType": 5120, + "count": 7740, + "type": "VEC4", + "name": "x0_COCKPIT_SEATS_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 1811360, + "componentType": 5122, + "count": 7740, + "type": "VEC2", + "name": "x0_COCKPIT_SEATS_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 1811364, + "componentType": 5122, + "count": 7740, + "type": "VEC2", + "name": "x0_COCKPIT_SEATS_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 1811368, + "componentType": 5123, + "count": 7740, + "type": "VEC4", + "name": "x0_COCKPIT_SEATS_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 341352, + "componentType": 5123, + "count": 28914, + "type": "SCALAR", + "name": "x0_COCKPIT_SEATS_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 2089980, + "componentType": 5126, + "count": 10329, + "min": [ + -1.7220906019210815, + 0.5720301270484924, + -1.811937689781189 + ], + "max": [ + 1.3319990634918213, + 1.9113353490829468, + 0.5925940275192261 + ], + "type": "VEC3", + "name": "x0_COCKPIT_SIDEPANELS_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 2089992, + "componentType": 5120, + "count": 10329, + "type": "VEC4", + "name": "x0_COCKPIT_SIDEPANELS_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 2089996, + "componentType": 5120, + "count": 10329, + "type": "VEC4", + "name": "x0_COCKPIT_SIDEPANELS_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 2090000, + "componentType": 5122, + "count": 10329, + "type": "VEC2", + "name": "x0_COCKPIT_SIDEPANELS_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 2090004, + "componentType": 5122, + "count": 10329, + "type": "VEC2", + "name": "x0_COCKPIT_SIDEPANELS_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 2090008, + "componentType": 5123, + "count": 10329, + "type": "VEC4", + "name": "x0_COCKPIT_SIDEPANELS_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 399180, + "componentType": 5123, + "count": 38946, + "type": "SCALAR", + "name": "x0_COCKPIT_SIDEPANELS_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 2461824, + "componentType": 5126, + "count": 9901, + "min": [ + -1.7248035669326782, + 0.09337909519672394, + -1.0417966842651367 + ], + "max": [ + 1.3417145013809204, + 1.2637892961502075, + 0.6432894468307495 + ], + "type": "VEC3", + "name": "x0_COCKPIT_SIDETOP_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 2461836, + "componentType": 5120, + "count": 9901, + "type": "VEC4", + "name": "x0_COCKPIT_SIDETOP_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 2461840, + "componentType": 5120, + "count": 9901, + "type": "VEC4", + "name": "x0_COCKPIT_SIDETOP_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 2461844, + "componentType": 5122, + "count": 9901, + "type": "VEC2", + "name": "x0_COCKPIT_SIDETOP_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 2461848, + "componentType": 5122, + "count": 9901, + "type": "VEC2", + "name": "x0_COCKPIT_SIDETOP_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 2461852, + "componentType": 5123, + "count": 9901, + "type": "VEC4", + "name": "x0_COCKPIT_SIDETOP_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 477072, + "componentType": 5123, + "count": 38154, + "type": "SCALAR", + "name": "x0_COCKPIT_SIDETOP_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 2818260, + "componentType": 5126, + "count": 104, + "min": [ + -0.7777349352836609, + 0.7477385997772217, + -1.3386439085006714 + ], + "max": [ + 0.40925663709640503, + 1.2983571290969849, + -0.05757951736450195 + ], + "type": "VEC3", + "name": "x0_COCKPIT_TEXT_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 2818272, + "componentType": 5120, + "count": 104, + "type": "VEC4", + "name": "x0_COCKPIT_TEXT_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 2818276, + "componentType": 5120, + "count": 104, + "type": "VEC4", + "name": "x0_COCKPIT_TEXT_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 2818280, + "componentType": 5122, + "count": 104, + "type": "VEC2", + "name": "x0_COCKPIT_TEXT_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 2818284, + "componentType": 5122, + "count": 104, + "type": "VEC2", + "name": "x0_COCKPIT_TEXT_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 2818288, + "componentType": 5123, + "count": 104, + "type": "VEC4", + "name": "x0_COCKPIT_TEXT_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 553380, + "componentType": 5123, + "count": 168, + "type": "SCALAR", + "name": "x0_COCKPIT_TEXT_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 2822004, + "componentType": 5126, + "count": 165, + "min": [ + -1.0592583417892456, + -0.03582889586687088, + -0.3984667956829071 + ], + "max": [ + 0.691586434841156, + 1.0205332040786743, + 0.20397257804870605 + ], + "type": "VEC3", + "name": "x0_COCKPIT_TOPGLASS_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 2822016, + "componentType": 5120, + "count": 165, + "type": "VEC4", + "name": "x0_COCKPIT_TOPGLASS_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 2822020, + "componentType": 5120, + "count": 165, + "type": "VEC4", + "name": "x0_COCKPIT_TOPGLASS_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 2822024, + "componentType": 5122, + "count": 165, + "type": "VEC2", + "name": "x0_COCKPIT_TOPGLASS_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 2822028, + "componentType": 5122, + "count": 165, + "type": "VEC2", + "name": "x0_COCKPIT_TOPGLASS_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 2822032, + "componentType": 5123, + "count": 165, + "type": "VEC4", + "name": "x0_COCKPIT_TOPGLASS_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 553716, + "componentType": 5123, + "count": 504, + "type": "SCALAR", + "name": "x0_COCKPIT_TOPGLASS_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 2827944, + "componentType": 5126, + "count": 7930, + "min": [ + -1.442156434059143, + -0.09175551682710648, + -0.7729623317718506 + ], + "max": [ + 1.07416570186615, + 0.5964341163635254, + 1.6307673454284668 + ], + "type": "VEC3", + "name": "x0_COCKPIT_TOPPANEL_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 2827956, + "componentType": 5120, + "count": 7930, + "type": "VEC4", + "name": "x0_COCKPIT_TOPPANEL_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 2827960, + "componentType": 5120, + "count": 7930, + "type": "VEC4", + "name": "x0_COCKPIT_TOPPANEL_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 2827964, + "componentType": 5122, + "count": 7930, + "type": "VEC2", + "name": "x0_COCKPIT_TOPPANEL_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 2827968, + "componentType": 5122, + "count": 7930, + "type": "VEC2", + "name": "x0_COCKPIT_TOPPANEL_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 2827972, + "componentType": 5123, + "count": 7930, + "type": "VEC4", + "name": "x0_COCKPIT_TOPPANEL_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 554724, + "componentType": 5123, + "count": 29727, + "type": "SCALAR", + "name": "x0_COCKPIT_TOPPANEL_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 3113424, + "componentType": 5126, + "count": 5242, + "min": [ + -1.0312665700912476, + -0.03509041666984558, + -1.3736284971237183 + ], + "max": [ + 0.6632757186889648, + 0.3814640939235687, + -0.6902344226837158 + ], + "type": "VEC3", + "name": "x0_COCKPIT_GLARESHIELD_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 3113436, + "componentType": 5120, + "count": 5242, + "type": "VEC4", + "name": "x0_COCKPIT_GLARESHIELD_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 3113440, + "componentType": 5120, + "count": 5242, + "type": "VEC4", + "name": "x0_COCKPIT_GLARESHIELD_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 3113444, + "componentType": 5122, + "count": 5242, + "type": "VEC2", + "name": "x0_COCKPIT_GLARESHIELD_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 3113448, + "componentType": 5122, + "count": 5242, + "type": "VEC2", + "name": "x0_COCKPIT_GLARESHIELD_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 3113452, + "componentType": 5123, + "count": 5242, + "type": "VEC4", + "name": "x0_COCKPIT_GLARESHIELD_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 614178, + "componentType": 5123, + "count": 21408, + "type": "SCALAR", + "name": "x0_COCKPIT_GLARESHIELD_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 3302136, + "componentType": 5126, + "count": 16763, + "min": [ + -1.2726854085922241, + 0.07592281699180603, + -1.8166934251785278 + ], + "max": [ + 0.9046947360038757, + 1.2208442687988281, + 1.5540235042572021 + ], + "type": "VEC3", + "name": "x0_COCKPIT_MAINPANEL_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 3302148, + "componentType": 5120, + "count": 16763, + "type": "VEC4", + "name": "x0_COCKPIT_MAINPANEL_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 3302152, + "componentType": 5120, + "count": 16763, + "type": "VEC4", + "name": "x0_COCKPIT_MAINPANEL_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 3302156, + "componentType": 5122, + "count": 16763, + "type": "VEC2", + "name": "x0_COCKPIT_MAINPANEL_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 3302160, + "componentType": 5122, + "count": 16763, + "type": "VEC2", + "name": "x0_COCKPIT_MAINPANEL_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 3302164, + "componentType": 5123, + "count": 16763, + "type": "VEC4", + "name": "x0_COCKPIT_MAINPANEL_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 656994, + "componentType": 5123, + "count": 62325, + "type": "SCALAR", + "name": "x0_COCKPIT_MAINPANEL_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 3905604, + "componentType": 5126, + "count": 27362, + "min": [ + -0.48907798528671265, + 0.7931820750236511, + -1.3459279537200928 + ], + "max": [ + 0.12108717858791351, + 1.5497113466262817, + -0.378397673368454 + ], + "type": "VEC3", + "name": "x0_COCKPIT_PEDESTAL_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 3905616, + "componentType": 5120, + "count": 27362, + "type": "VEC4", + "name": "x0_COCKPIT_PEDESTAL_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 3905620, + "componentType": 5120, + "count": 27362, + "type": "VEC4", + "name": "x0_COCKPIT_PEDESTAL_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 3905624, + "componentType": 5122, + "count": 27362, + "type": "VEC2", + "name": "x0_COCKPIT_PEDESTAL_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 3905628, + "componentType": 5122, + "count": 27362, + "type": "VEC2", + "name": "x0_COCKPIT_PEDESTAL_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 3905632, + "componentType": 5123, + "count": 27362, + "type": "VEC4", + "name": "x0_COCKPIT_PEDESTAL_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 781644, + "componentType": 5123, + "count": 97362, + "type": "SCALAR", + "name": "x0_COCKPIT_PEDESTAL_indices#0" + }, + { + "bufferView": 0, + "byteOffset": 4890636, + "componentType": 5126, + "count": 38850, + "min": [ + -1.3913885354995728, + -0.004859656095504761, + -0.9819670915603638 + ], + "max": [ + 0.9155333638191223, + 1.5530450344085693, + 1.0820602178573608 + ], + "type": "VEC3", + "name": "x0_COCKPIT_THROTTLE_vertices#0_POSITION" + }, + { + "bufferView": 0, + "byteOffset": 4890648, + "componentType": 5120, + "count": 38850, + "type": "VEC4", + "name": "x0_COCKPIT_THROTTLE_vertices#0_TANGENT" + }, + { + "bufferView": 0, + "byteOffset": 4890652, + "componentType": 5120, + "count": 38850, + "type": "VEC4", + "name": "x0_COCKPIT_THROTTLE_vertices#0_NORMAL" + }, + { + "bufferView": 0, + "byteOffset": 4890656, + "componentType": 5122, + "count": 38850, + "type": "VEC2", + "name": "x0_COCKPIT_THROTTLE_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 0, + "byteOffset": 4890660, + "componentType": 5122, + "count": 38850, + "type": "VEC2", + "name": "x0_COCKPIT_THROTTLE_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 0, + "byteOffset": 4890664, + "componentType": 5123, + "count": 38850, + "type": "VEC4", + "name": "x0_COCKPIT_THROTTLE_vertices#0_COLOR_0" + }, + { + "bufferView": 1, + "byteOffset": 976368, + "componentType": 5123, + "count": 208026, + "type": "SCALAR", + "name": "x0_COCKPIT_THROTTLE_indices#0" + } + ], + "asset": { + "generator": "Khronos glTF Blender I/O v3.2.40 and Asobo Studio MSFS Blender I/O v1.1.6", + "version": "2.0", + "extensions": { + "ASOBO_asset_optimized": { + "BoundingBoxMax": [ + 1.3417145013809204, + 2.0699973106384277, + 1.6307673454284668 + ], + "BoundingBoxMin": [ + -1.7529988288879395, + -0.09175551682710648, + -1.8179785013198853 + ], + "MajorVersion": 4, + "MinorVersion": 4, + "UseCheckerboardMaterialForMissingTextures": true, + "UseOnlyFilenameForImageURI": true + }, + "ASOBO_normal_map_convention": { + "tangent_space_convention": "DirectX" + } + } + }, + "bufferViews": [ + { + "buffer": 0, + "byteLength": 6289236, + "byteStride": 36, + "target": 34962, + "name": "BufferViewVertexND" + }, + { + "buffer": 0, + "byteLength": 1392420, + "byteOffset": 6289236, + "target": 34963, + "name": "BufferViewIndex" + } + ], + "materials": [], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "COLOR_0": 5, + "NORMAL": 2, + "POSITION": 0, + "TANGENT": 1, + "TEXCOORD_0": 3, + "TEXCOORD_1": 4 + }, + "indices": 6, + "material": "BACK01", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 3052, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_BACK01" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 12, + "NORMAL": 9, + "POSITION": 7, + "TANGENT": 8, + "TEXCOORD_0": 10, + "TEXCOORD_1": 11 + }, + "indices": 13, + "material": "BACK02", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 3687, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_BACK02" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 19, + "NORMAL": 16, + "POSITION": 14, + "TANGENT": 15, + "TEXCOORD_0": 17, + "TEXCOORD_1": 18 + }, + "indices": 20, + "material": "DECALSALPHA", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 1228, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_DECALSALPHA" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 26, + "NORMAL": 23, + "POSITION": 21, + "TANGENT": 22, + "TEXCOORD_0": 24, + "TEXCOORD_1": 25 + }, + "indices": 27, + "material": "FLOOR", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 4098, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_FLOOR" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 33, + "NORMAL": 30, + "POSITION": 28, + "TANGENT": 29, + "TEXCOORD_0": 31, + "TEXCOORD_1": 32 + }, + "indices": 34, + "material": "INPUTS01", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 88, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_INPUTS01" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 40, + "NORMAL": 37, + "POSITION": 35, + "TANGENT": 36, + "TEXCOORD_0": 38, + "TEXCOORD_1": 39 + }, + "indices": 41, + "material": "OVERHEAD", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 38340, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_OVERHEAD" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 47, + "NORMAL": 44, + "POSITION": 42, + "TANGENT": 43, + "TEXCOORD_0": 45, + "TEXCOORD_1": 46 + }, + "indices": 48, + "material": "PEDALS", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 5772, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_PEDALS" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 54, + "NORMAL": 51, + "POSITION": 49, + "TANGENT": 50, + "TEXCOORD_0": 52, + "TEXCOORD_1": 53 + }, + "indices": 55, + "material": "PLASTICGLASS", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 627, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_PLASTICGLASS" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 61, + "NORMAL": 58, + "POSITION": 56, + "TANGENT": 57, + "TEXCOORD_0": 59, + "TEXCOORD_1": 60 + }, + "indices": 62, + "material": "SEATS", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 9638, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_SEATS" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 68, + "NORMAL": 65, + "POSITION": 63, + "TANGENT": 64, + "TEXCOORD_0": 66, + "TEXCOORD_1": 67 + }, + "indices": 69, + "material": "SIDEPANELS", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 12982, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_SIDEPANELS" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 75, + "NORMAL": 72, + "POSITION": 70, + "TANGENT": 71, + "TEXCOORD_0": 73, + "TEXCOORD_1": 74 + }, + "indices": 76, + "material": "SIDETOP", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 12718, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_SIDETOP" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 82, + "NORMAL": 79, + "POSITION": 77, + "TANGENT": 78, + "TEXCOORD_0": 80, + "TEXCOORD_1": 81 + }, + "indices": 83, + "material": "TEXT", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 56, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_TEXT" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 89, + "NORMAL": 86, + "POSITION": 84, + "TANGENT": 85, + "TEXCOORD_0": 87, + "TEXCOORD_1": 88 + }, + "indices": 90, + "material": "TOPGLASS", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 168, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_TOPGLASS" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 96, + "NORMAL": 93, + "POSITION": 91, + "TANGENT": 92, + "TEXCOORD_0": 94, + "TEXCOORD_1": 95 + }, + "indices": 97, + "material": "TOPPANEL", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 9909, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_TOPPANEL" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 103, + "NORMAL": 100, + "POSITION": 98, + "TANGENT": 99, + "TEXCOORD_0": 101, + "TEXCOORD_1": 102 + }, + "indices": 104, + "material": "GLARESHIELD", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 7136, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_GLARESHIELD" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 110, + "NORMAL": 107, + "POSITION": 105, + "TANGENT": 106, + "TEXCOORD_0": 108, + "TEXCOORD_1": 109 + }, + "indices": 111, + "material": "MAINPANEL", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 20775, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_MAINPANEL" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 117, + "NORMAL": 114, + "POSITION": 112, + "TANGENT": 113, + "TEXCOORD_0": 115, + "TEXCOORD_1": 116 + }, + "indices": 118, + "material": "PEDESTAL", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 32454, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_PEDESTAL" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 124, + "NORMAL": 121, + "POSITION": 119, + "TANGENT": 120, + "TEXCOORD_0": 122, + "TEXCOORD_1": 123 + }, + "indices": 125, + "material": "THROTTLE", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 69342, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_COCKPIT_THROTTLE" + } + ], + "nodes": [ + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 0, + "name": "COCKPIT_BACK01" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 1, + "name": "COCKPIT_BACK02" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 2, + "name": "COCKPIT_DECALSALPHA" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 3, + "name": "COCKPIT_FLOOR" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 4, + "name": "COCKPIT_INPUTS01" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 5, + "name": "COCKPIT_OVERHEAD" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 6, + "name": "COCKPIT_PEDALS" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 7, + "name": "COCKPIT_PLASTICGLASS" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 8, + "name": "COCKPIT_SEATS" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 9, + "name": "COCKPIT_SIDEPANELS" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 10, + "name": "COCKPIT_SIDETOP" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 11, + "name": "COCKPIT_TEXT" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 12, + "name": "COCKPIT_TOPGLASS" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 13, + "name": "COCKPIT_TOPPANEL" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 14, + "name": "COCKPIT_GLARESHIELD" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 15, + "name": "COCKPIT_MAINPANEL" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 16, + "name": "COCKPIT_PEDESTAL" + }, + { + "translation": [ + 0.18399550020694733, + 2.5197179317474365, + 10.99502182006836 + ], + "rotation": [ + -0.9819666743278505, + 0, + 0, + 0.18905407190322876 + ], + "mesh": 17, + "name": "COCKPIT_THROTTLE" + } + ], + "scene": 0, + "scenes": [ + { + "nodes": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17 + ], + "name": "Scene" + } + ], + "extensionsUsed": [ + "ASOBO_normal_map_convention", + "ASOBO_asset_optimized" + ], + "buffers": [ + { + "byteLength": 7681656, + "uri": "cockpit.bin" + } + ] +} \ No newline at end of file diff --git a/src/model/cockpit/gearemer.bin b/src/model/cockpit/gearemer.bin new file mode 100644 index 000000000000..5398888915a5 Binary files /dev/null and b/src/model/cockpit/gearemer.bin differ diff --git a/src/model/cockpit/gearemer.gltf b/src/model/cockpit/gearemer.gltf new file mode 100644 index 000000000000..6e648b089173 --- /dev/null +++ b/src/model/cockpit/gearemer.gltf @@ -0,0 +1,1325 @@ +{ + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "count": 300, + "min": [ + 0 + ], + "max": [ + 12.458333333333334 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 300, + "type": "VEC3" + }, + { + "bufferView": 2, + "componentType": 5126, + "count": 300, + "type": "VEC4" + }, + { + "bufferView": 3, + "componentType": 5126, + "count": 300, + "type": "VEC3" + }, + { + "bufferView": 4, + "componentType": 5126, + "count": 300, + "type": "VEC3" + }, + { + "bufferView": 5, + "componentType": 5126, + "count": 300, + "type": "VEC4" + }, + { + "bufferView": 6, + "componentType": 5126, + "count": 300, + "type": "VEC3" + }, + { + "bufferView": 7, + "componentType": 5126, + "count": 101, + "min": [ + 4.166666666666667 + ], + "max": [ + 8.333333333333334 + ], + "type": "SCALAR" + }, + { + "bufferView": 8, + "componentType": 5126, + "count": 101, + "type": "VEC4" + }, + { + "bufferView": 9, + "componentType": 5126, + "count": 2, + "type": "MAT4" + }, + { + "bufferView": 10, + "componentType": 5126, + "count": 1508, + "min": [ + -0.008255073800683022, + -3.765705685054854e-7, + -0.02963937260210514 + ], + "max": [ + 0.008255073800683022, + 0.0029436075128614902, + 0.030382798984646797 + ], + "type": "VEC3", + "name": "Cylinder.010_vertices#0_POSITION" + }, + { + "bufferView": 10, + "byteOffset": 12, + "componentType": 5120, + "count": 1508, + "type": "VEC4", + "name": "Cylinder.010_vertices#0_TANGENT" + }, + { + "bufferView": 10, + "byteOffset": 16, + "componentType": 5120, + "count": 1508, + "type": "VEC4", + "name": "Cylinder.010_vertices#0_NORMAL" + }, + { + "bufferView": 10, + "byteOffset": 20, + "componentType": 5122, + "count": 1508, + "type": "VEC2", + "name": "Cylinder.010_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 10, + "byteOffset": 24, + "componentType": 5122, + "count": 1508, + "type": "VEC2", + "name": "Cylinder.010_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 10, + "byteOffset": 28, + "componentType": 5123, + "count": 1508, + "type": "VEC4", + "name": "Cylinder.010_vertices#0_COLOR_0" + }, + { + "bufferView": 11, + "componentType": 5123, + "count": 8436, + "type": "SCALAR", + "name": "Cylinder.010_indices#0" + }, + { + "bufferView": 10, + "byteOffset": 54288, + "componentType": 5126, + "count": 24, + "min": [ + -0.011657074093818665, + -0.034728121012449265, + -0.010439824312925339 + ], + "max": [ + 0.011656958609819412, + 0.03472788259387016, + 0.000955621711909771 + ], + "type": "VEC3", + "name": "x0_LEVER_GRAVITYGEAR_BUTTON_CLICKZONE_vertices#0_POSITION" + }, + { + "bufferView": 10, + "byteOffset": 54300, + "componentType": 5120, + "count": 24, + "type": "VEC4", + "name": "x0_LEVER_GRAVITYGEAR_BUTTON_CLICKZONE_vertices#0_TANGENT" + }, + { + "bufferView": 10, + "byteOffset": 54304, + "componentType": 5120, + "count": 24, + "type": "VEC4", + "name": "x0_LEVER_GRAVITYGEAR_BUTTON_CLICKZONE_vertices#0_NORMAL" + }, + { + "bufferView": 10, + "byteOffset": 54308, + "componentType": 5122, + "count": 24, + "type": "VEC2", + "name": "x0_LEVER_GRAVITYGEAR_BUTTON_CLICKZONE_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 10, + "byteOffset": 54312, + "componentType": 5122, + "count": 24, + "type": "VEC2", + "name": "x0_LEVER_GRAVITYGEAR_BUTTON_CLICKZONE_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 10, + "byteOffset": 54316, + "componentType": 5123, + "count": 24, + "type": "VEC4", + "name": "x0_LEVER_GRAVITYGEAR_BUTTON_CLICKZONE_vertices#0_COLOR_0" + }, + { + "bufferView": 11, + "byteOffset": 16872, + "componentType": 5123, + "count": 36, + "type": "SCALAR", + "name": "x0_LEVER_GRAVITYGEAR_BUTTON_CLICKZONE_indices#0" + }, + { + "bufferView": 10, + "byteOffset": 55152, + "componentType": 5126, + "count": 13806, + "min": [ + -0.023231318220496178, + -0.013297613710165024, + -0.03927605226635933 + ], + "max": [ + 0.023231318220496178, + 0.07573427259922028, + 0.11693088710308075 + ], + "type": "VEC3", + "name": "Cylinder.011_vertices#0_POSITION" + }, + { + "bufferView": 10, + "byteOffset": 55164, + "componentType": 5120, + "count": 13806, + "type": "VEC4", + "name": "Cylinder.011_vertices#0_TANGENT" + }, + { + "bufferView": 10, + "byteOffset": 55168, + "componentType": 5120, + "count": 13806, + "type": "VEC4", + "name": "Cylinder.011_vertices#0_NORMAL" + }, + { + "bufferView": 10, + "byteOffset": 55172, + "componentType": 5122, + "count": 13806, + "type": "VEC2", + "name": "Cylinder.011_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 10, + "byteOffset": 55176, + "componentType": 5122, + "count": 13806, + "type": "VEC2", + "name": "Cylinder.011_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 10, + "byteOffset": 55180, + "componentType": 5123, + "count": 13806, + "type": "VEC4", + "name": "Cylinder.011_vertices#0_COLOR_0" + }, + { + "bufferView": 11, + "byteOffset": 16944, + "componentType": 5123, + "count": 67656, + "type": "SCALAR", + "name": "Cylinder.011_indices#0" + }, + { + "bufferView": 10, + "byteOffset": 552168, + "componentType": 5126, + "count": 24, + "min": [ + -0.024191241711378098, + -0.02419125847518444, + -0.0241912379860878 + ], + "max": [ + 0.024191241711378098, + 0.02419126033782959, + 0.024191245436668396 + ], + "type": "VEC3", + "name": "Cube.002_vertices#0_POSITION" + }, + { + "bufferView": 10, + "byteOffset": 552180, + "componentType": 5120, + "count": 24, + "type": "VEC4", + "name": "Cube.002_vertices#0_TANGENT" + }, + { + "bufferView": 10, + "byteOffset": 552184, + "componentType": 5120, + "count": 24, + "type": "VEC4", + "name": "Cube.002_vertices#0_NORMAL" + }, + { + "bufferView": 10, + "byteOffset": 552188, + "componentType": 5122, + "count": 24, + "type": "VEC2", + "name": "Cube.002_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 10, + "byteOffset": 552192, + "componentType": 5122, + "count": 24, + "type": "VEC2", + "name": "Cube.002_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 10, + "byteOffset": 552196, + "componentType": 5123, + "count": 24, + "type": "VEC4", + "name": "Cube.002_vertices#0_COLOR_0" + }, + { + "bufferView": 11, + "byteOffset": 152256, + "componentType": 5123, + "count": 36, + "type": "SCALAR", + "name": "Cube.002_indices#0" + }, + { + "bufferView": 10, + "byteOffset": 553032, + "componentType": 5126, + "count": 44, + "min": [ + -0.01227574422955513, + -0.00024389397003687918, + -0.032475024461746216 + ], + "max": [ + 0.011588790453970432, + 0.00018276508490089327, + 0.01718912087380886 + ], + "type": "VEC3", + "name": "x0_GEAR_EXTN_Decal_vertices#0_POSITION" + }, + { + "bufferView": 10, + "byteOffset": 553044, + "componentType": 5120, + "count": 44, + "type": "VEC4", + "name": "x0_GEAR_EXTN_Decal_vertices#0_TANGENT" + }, + { + "bufferView": 10, + "byteOffset": 553048, + "componentType": 5120, + "count": 44, + "type": "VEC4", + "name": "x0_GEAR_EXTN_Decal_vertices#0_NORMAL" + }, + { + "bufferView": 10, + "byteOffset": 553052, + "componentType": 5122, + "count": 44, + "type": "VEC2", + "name": "x0_GEAR_EXTN_Decal_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 10, + "byteOffset": 553056, + "componentType": 5122, + "count": 44, + "type": "VEC2", + "name": "x0_GEAR_EXTN_Decal_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 10, + "byteOffset": 553060, + "componentType": 5123, + "count": 44, + "type": "VEC4", + "name": "x0_GEAR_EXTN_Decal_vertices#0_COLOR_0" + }, + { + "bufferView": 11, + "byteOffset": 152328, + "componentType": 5123, + "count": 66, + "type": "SCALAR", + "name": "x0_GEAR_EXTN_Decal_indices#0" + }, + { + "bufferView": 10, + "byteOffset": 554616, + "componentType": 5126, + "count": 50, + "min": [ + -0.02056143805384636, + -0.15580104291439056, + -0.08455947786569595 + ], + "max": [ + 0.020561382174491882, + 0.020561516284942627, + 0.030059248208999634 + ], + "type": "VEC3", + "name": "x0_LEVER_GRAVITYGEAR_ACTIVATE_CLICKZONE_vertices#0_POSITION" + }, + { + "bufferView": 10, + "byteOffset": 554628, + "componentType": 5120, + "count": 50, + "type": "VEC4", + "name": "x0_LEVER_GRAVITYGEAR_ACTIVATE_CLICKZONE_vertices#0_TANGENT" + }, + { + "bufferView": 10, + "byteOffset": 554632, + "componentType": 5120, + "count": 50, + "type": "VEC4", + "name": "x0_LEVER_GRAVITYGEAR_ACTIVATE_CLICKZONE_vertices#0_NORMAL" + }, + { + "bufferView": 10, + "byteOffset": 554636, + "componentType": 5122, + "count": 50, + "type": "VEC2", + "name": "x0_LEVER_GRAVITYGEAR_ACTIVATE_CLICKZONE_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 10, + "byteOffset": 554640, + "componentType": 5122, + "count": 50, + "type": "VEC2", + "name": "x0_LEVER_GRAVITYGEAR_ACTIVATE_CLICKZONE_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 10, + "byteOffset": 554644, + "componentType": 5123, + "count": 50, + "type": "VEC4", + "name": "x0_LEVER_GRAVITYGEAR_ACTIVATE_CLICKZONE_vertices#0_COLOR_0" + }, + { + "bufferView": 11, + "byteOffset": 152460, + "componentType": 5123, + "count": 84, + "type": "SCALAR", + "name": "x0_LEVER_GRAVITYGEAR_ACTIVATE_CLICKZONE_indices#0" + }, + { + "bufferView": 10, + "byteOffset": 556416, + "componentType": 5126, + "count": 8682, + "min": [ + -0.04486844316124916, + -0.005818461999297142, + -0.02952420897781849 + ], + "max": [ + 0.04486844316124916, + 0.0003153801371809095, + 0.029504334554076195 + ], + "type": "VEC3", + "name": "Cube.001_vertices#0_POSITION" + }, + { + "bufferView": 10, + "byteOffset": 556428, + "componentType": 5120, + "count": 8682, + "type": "VEC4", + "name": "Cube.001_vertices#0_TANGENT" + }, + { + "bufferView": 10, + "byteOffset": 556432, + "componentType": 5120, + "count": 8682, + "type": "VEC4", + "name": "Cube.001_vertices#0_NORMAL" + }, + { + "bufferView": 10, + "byteOffset": 556436, + "componentType": 5122, + "count": 8682, + "type": "VEC2", + "name": "Cube.001_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 10, + "byteOffset": 556440, + "componentType": 5122, + "count": 8682, + "type": "VEC2", + "name": "Cube.001_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 10, + "byteOffset": 556444, + "componentType": 5123, + "count": 8682, + "type": "VEC4", + "name": "Cube.001_vertices#0_COLOR_0" + }, + { + "bufferView": 11, + "byteOffset": 152628, + "componentType": 5123, + "count": 40962, + "type": "SCALAR", + "name": "Cube.001_indices#0" + }, + { + "bufferView": 10, + "byteOffset": 868968, + "componentType": 5126, + "count": 1873, + "min": [ + -0.024881934747099876, + -0.03018627129495144, + -0.02490352839231491 + ], + "max": [ + 0.024881934747099876, + -0.00007844436186132953, + 0.020607840269804 + ], + "type": "VEC3", + "name": "Cylinder.001_vertices#0_POSITION" + }, + { + "bufferView": 10, + "byteOffset": 868980, + "componentType": 5120, + "count": 1873, + "type": "VEC4", + "name": "Cylinder.001_vertices#0_TANGENT" + }, + { + "bufferView": 10, + "byteOffset": 868984, + "componentType": 5120, + "count": 1873, + "type": "VEC4", + "name": "Cylinder.001_vertices#0_NORMAL" + }, + { + "bufferView": 10, + "byteOffset": 868988, + "componentType": 5122, + "count": 1873, + "type": "VEC2", + "name": "Cylinder.001_vertices#0_TEXCOORD_0" + }, + { + "bufferView": 10, + "byteOffset": 868992, + "componentType": 5122, + "count": 1873, + "type": "VEC2", + "name": "Cylinder.001_vertices#0_TEXCOORD_1" + }, + { + "bufferView": 10, + "byteOffset": 868996, + "componentType": 5123, + "count": 1873, + "type": "VEC4", + "name": "Cylinder.001_vertices#0_COLOR_0" + }, + { + "bufferView": 11, + "byteOffset": 234552, + "componentType": 5123, + "count": 9312, + "type": "SCALAR", + "name": "Cylinder.001_indices#0" + } + ], + "animations": [ + { + "name": "LEVER_GRAVITYGEAR", + "channels": [ + { + "sampler": 0, + "target": { + "node": 6, + "path": "translation" + } + }, + { + "sampler": 1, + "target": { + "node": 6, + "path": "rotation" + } + }, + { + "sampler": 2, + "target": { + "node": 6, + "path": "scale" + } + }, + { + "sampler": 3, + "target": { + "node": 2, + "path": "translation" + } + }, + { + "sampler": 4, + "target": { + "node": 2, + "path": "rotation" + } + }, + { + "sampler": 5, + "target": { + "node": 2, + "path": "scale" + } + }, + { + "sampler": 6, + "target": { + "node": 10, + "path": "rotation" + } + } + ], + "samplers": [ + { + "input": 0, + "output": 1, + "interpolation": "LINEAR" + }, + { + "input": 0, + "output": 2, + "interpolation": "LINEAR" + }, + { + "input": 0, + "output": 3, + "interpolation": "LINEAR" + }, + { + "input": 0, + "output": 4, + "interpolation": "LINEAR" + }, + { + "input": 0, + "output": 5, + "interpolation": "LINEAR" + }, + { + "input": 0, + "output": 6, + "interpolation": "LINEAR" + }, + { + "input": 7, + "output": 8, + "interpolation": "LINEAR" + } + ] + } + ], + "asset": { + "generator": "Khronos glTF Blender I/O v3.2.40 and Asobo Studio MSFS Blender I/O v1.1.6", + "version": "2.0", + "extensions": { + "ASOBO_asset_optimized": { + "BoundingBoxMax": [ + 0.04486844316124916, + 0.07573427259922028, + 0.11693088710308075 + ], + "BoundingBoxMin": [ + -0.04486844316124916, + -0.15580104291439056, + -0.08455947786569595 + ], + "MajorVersion": 4, + "MinorVersion": 4, + "UseCheckerboardMaterialForMissingTextures": true, + "UseOnlyFilenameForImageURI": true + }, + "ASOBO_normal_map_convention": { + "tangent_space_convention": "DirectX" + } + } + }, + "bufferViews": [ + { + "buffer": 0, + "byteLength": 1200 + }, + { + "buffer": 0, + "byteLength": 3600, + "byteOffset": 1200 + }, + { + "buffer": 0, + "byteLength": 4800, + "byteOffset": 4800 + }, + { + "buffer": 0, + "byteLength": 3600, + "byteOffset": 9600 + }, + { + "buffer": 0, + "byteLength": 3600, + "byteOffset": 13200 + }, + { + "buffer": 0, + "byteLength": 4800, + "byteOffset": 16800 + }, + { + "buffer": 0, + "byteLength": 3600, + "byteOffset": 21600 + }, + { + "buffer": 0, + "byteLength": 404, + "byteOffset": 25200 + }, + { + "buffer": 0, + "byteLength": 1616, + "byteOffset": 25604 + }, + { + "buffer": 0, + "byteLength": 128, + "byteOffset": 27220 + }, + { + "buffer": 0, + "byteLength": 936396, + "byteStride": 36, + "byteOffset": 27348, + "target": 34962, + "name": "BufferViewVertexND" + }, + { + "buffer": 0, + "byteLength": 253176, + "byteOffset": 963744, + "target": 34963, + "name": "BufferViewIndex" + } + ], + "extensionsRequired": [ + "MSFT_texture_dds" + ], + "materials": [ + { + "name": "GravityGear_Invisible", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ] + }, + "extensions": { + "ASOBO_material_invisible": {} + } + }, + { + "name": "EMERG_GEAR_EXT", + "normalTexture": { + "index": 0 + }, + "occlusionTexture": { + "index": 1 + }, + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 2 + }, + "metallicRoughnessTexture": { + "index": 1 + } + } + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "COLOR_0": 22, + "NORMAL": 19, + "POSITION": 17, + "TANGENT": 18, + "TEXCOORD_0": 20, + "TEXCOORD_1": 21 + }, + "indices": 23, + "material": 0, + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 12, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_LEVER_GRAVITYGEAR_BUTTON_CLICKZONE" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 15, + "NORMAL": 12, + "POSITION": 10, + "TANGENT": 11, + "TEXCOORD_0": 13, + "TEXCOORD_1": 14 + }, + "indices": 16, + "material": 1, + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 2812, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "Cylinder.010" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 36, + "NORMAL": 33, + "POSITION": 31, + "TANGENT": 32, + "TEXCOORD_0": 34, + "TEXCOORD_1": 35 + }, + "indices": 37, + "material": 0, + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 12, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "Cube.002" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 43, + "NORMAL": 40, + "POSITION": 38, + "TANGENT": 39, + "TEXCOORD_0": 41, + "TEXCOORD_1": 42 + }, + "indices": 44, + "material": "TEXT_NoEmi", + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 22, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_GEAR_EXTN_Decal" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 29, + "NORMAL": 26, + "POSITION": 24, + "TANGENT": 25, + "TEXCOORD_0": 27, + "TEXCOORD_1": 28 + }, + "indices": 30, + "material": 1, + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 22552, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "Cylinder.011" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 50, + "NORMAL": 47, + "POSITION": 45, + "TANGENT": 46, + "TEXCOORD_0": 48, + "TEXCOORD_1": 49 + }, + "indices": 51, + "material": 0, + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 28, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "x0_LEVER_GRAVITYGEAR_ACTIVATE_CLICKZONE" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 57, + "NORMAL": 54, + "POSITION": 52, + "TANGENT": 53, + "TEXCOORD_0": 55, + "TEXCOORD_1": 56 + }, + "indices": 58, + "material": 1, + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 13654, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "Cube.001" + }, + { + "primitives": [ + { + "attributes": { + "COLOR_0": 64, + "NORMAL": 61, + "POSITION": 59, + "TANGENT": 60, + "TEXCOORD_0": 62, + "TEXCOORD_1": 63 + }, + "indices": 65, + "material": 1, + "mode": 4, + "extras": { + "ASOBO_primitive": { + "PrimitiveCount": 3104, + "VertexType": "VTX", + "VertexVersion": 2 + } + } + } + ], + "name": "Cylinder.001" + } + ], + "nodes": [ + { + "translation": [ + 0, + -0.000002063562760667992, + 0 + ], + "rotation": [ + 0, + -0.7048596739768982, + 0.7093467712402344, + 9.752679375196749e-8 + ], + "scale": [ + 0.8849125504493713, + 0.8849127292633057, + 0.8849127292633057 + ], + "mesh": 0, + "name": "LEVER_GRAVITYGEAR_BUTTON_CLICKZONE" + }, + { + "translation": [ + 0.0001805223000701517, + 0.0005181958549655974, + 0.0008159926510415971 + ], + "rotation": [ + -0.7048595547676086, + 4.449356652003189e-7, + 2.123630480355132e-7, + 0.7093468904495239 + ], + "mesh": 1, + "name": "LEVER_GRAVITYGEAR_BUTTON", + "children": [ + 0 + ] + }, + { + "translation": [ + -1.637992852465686e-8, + 0.05134981498122215, + -0.005761315114796162 + ], + "rotation": [ + 0.7071067094802856, + -8.432149201098582e-9, + -1.1520227616301781e-7, + 0.7071068286895752 + ], + "name": "button", + "children": [ + 1 + ] + }, + { + "translation": [ + -0.0004391938273329288, + 0.05593426525592804, + 0.10457681864500046 + ], + "rotation": [ + 0, + 1, + 0.00021082900639157745, + 7.557741099617489e-8 + ], + "scale": [ + 0.8849125504493713, + 0.8849125504493713, + 0.8849125504493713 + ], + "mesh": 2, + "name": "LEVER_GRAVITYGEAR_CRANE_CLICKZONE" + }, + { + "translation": [ + -0.00043922202894464135, + 0.010206574574112892, + -0.014734471216797829 + ], + "rotation": [ + 0, + -0.70710688829422, + 0.7071066498756409, + 1.067701376200603e-7 + ], + "scale": [ + 0.8849125504493713, + 0.8849125504493713, + 0.8849125504493713 + ], + "mesh": 3, + "name": "LEVER_GRAVITYGEAR_DECAL" + }, + { + "translation": [ + 0.0001805584179237485, + -0.0003965531650464982, + 0.0003793853975366801 + ], + "rotation": [ + -8.146032826061855e-8, + 7.549785863147919e-8, + 2.264936824758479e-7, + 1 + ], + "mesh": 4, + "name": "LEVER_GRAVITYGEAR_CRANE", + "children": [ + 3, + 4 + ] + }, + { + "translation": [ + 3.183231456205249e-12, + 0, + -9.536470315651968e-7 + ], + "rotation": [ + 0.7071067094802856, + -5.338507236274381e-8, + -5.338507591545749e-8, + 0.7071068286895752 + ], + "name": "crank", + "children": [ + 2, + 5 + ] + }, + { + "translation": [ + 0.00018055501277558503, + 0.048293232917785645, + 0.0004749298677779734 + ], + "name": "GearEmerExt", + "children": [ + 6 + ] + }, + { + "translation": [ + -0.00007811502291588113, + 0.06264817714691162, + 0.010286331176757812 + ], + "rotation": [ + 0, + 1, + 0, + 1.6292068494294656e-7 + ], + "scale": [ + 0.8849125504493713, + 0.8849125504493713, + 0.8849125504493713 + ], + "mesh": 5, + "name": "LEVER_GRAVITYGEAR_ACTIVATE_CLICKZONE" + }, + { + "translation": [ + 0.00018055483815260231, + 0.0624462366104126, + 0.001103401300497353 + ], + "mesh": 6, + "name": "LEVER_GRAVITYGEAR_BASE" + }, + { + "translation": [ + 0.00018055576947517693, + 0.0622633695602417, + 0.00012874609092250466 + ], + "mesh": 7, + "name": "LEVER_GRAVITYGEAR_ROTOR" + }, + { + "translation": [ + 0, + 1.1405932903289795, + 10.956473350524902 + ], + "rotation": [ + 0, + -1, + 0, + 1.6292068494294656e-7 + ], + "scale": [ + 1.1300551891326904, + 1.1300551891326904, + 1.1300551891326904 + ], + "name": "LEVER_GRAVITYGEAR", + "children": [ + 7, + 8, + 9, + 10 + ] + } + ], + "scene": 0, + "scenes": [ + { + "nodes": [ + 11 + ], + "name": "Scene" + } + ], + "skins": [ + { + "inverseBindMatrices": 9, + "joints": [ + 6, + 2 + ], + "skeleton": -1, + "name": "GearEmerExt" + } + ], + "textures": [ + { + "extensions": { + "MSFT_texture_dds": { + "source": 0 + } + } + }, + { + "extensions": { + "MSFT_texture_dds": { + "source": 1 + } + } + }, + { + "extensions": { + "MSFT_texture_dds": { + "source": 2 + } + } + }, + { + "extensions": { + "MSFT_texture_dds": { + "source": 3 + } + } + } + ], + "samplers": [ + { + "magFilter": 9729, + "minFilter": 9987, + "wrapS": 10497, + "wrapT": 10497 + } + ], + "extensionsUsed": [ + "ASOBO_normal_map_convention", + "ASOBO_material_invisible", + "ASOBO_material_shadow_options", + "MSFT_texture_dds", + "ASOBO_asset_optimized" + ], + "buffers": [ + { + "byteLength": 1216920, + "uri": "gearemer.bin" + } + ], + "images": [ + { + "uri": "EMERG_GEAR_EXT_NORMAL.PNG.DDS", + "name": "EMERG_GEAR_EXT_NORMAL", + "extras": "ASOBO_image_converted_meta" + }, + { + "uri": "EMERG_GEAR_EXT_COMP.PNG.DDS", + "name": "EMERG_GEAR_EXT_COMP", + "extras": "ASOBO_image_converted_meta" + }, + { + "uri": "EMERG_GEAR_EXT_ALBEDO.PNG.DDS", + "name": "EMERG_GEAR_EXT_ALBEDO", + "extras": "ASOBO_image_converted_meta" + }, + { + "uri": "A320NEO_COCKPIT_DECALSTEXT_ALBD_TIF.PNG.DDS", + "name": "A320NEO_COCKPIT_DECALSTEXT_ALBD.TIF", + "extras": "ASOBO_image_converted_meta" + } + ] +} \ No newline at end of file diff --git a/src/model/models.json b/src/model/models.json index a05be5f21602..4043f6e9c0fb 100644 --- a/src/model/models.json +++ b/src/model/models.json @@ -90,6 +90,16 @@ { "gltf": "./glass/glass.gltf", "bin": "./glass/glass.bin" + }, + { + "gltf": "./cockpit/cockpit.gltf", + "bin": "./cockpit/cockpit.bin", + "maxLod": 0 + }, + { + "gltf": "./cockpit/gearemer.gltf", + "bin": "./cockpit/gearemer.bin", + "maxLod": 0 } ], "modifications": [ @@ -957,6 +967,10 @@ "gltf": "./cone/cone.gltf", "bin": "./cone/cone.bin" }, + { + "gltf": "./satcom/HoneywellJetwave.gltf", + "bin": "./satcom/HoneywellJetwave.bin" + }, { "combineFiles": ["./a320-exterior/A320_NEO_LOD00.gltf"], "nodes": [ diff --git a/src/model/satcom/HoneywellJetwave.bin b/src/model/satcom/HoneywellJetwave.bin new file mode 100644 index 000000000000..070bfceb9622 Binary files /dev/null and b/src/model/satcom/HoneywellJetwave.bin differ diff --git a/src/model/satcom/HoneywellJetwave.gltf b/src/model/satcom/HoneywellJetwave.gltf new file mode 100644 index 000000000000..69c489c73ee9 --- /dev/null +++ b/src/model/satcom/HoneywellJetwave.gltf @@ -0,0 +1 @@ +{"accessors":[{"bufferView":0,"componentType":5126,"count":664,"min":[-0.5719958543777466,3.438021421432495,-13.610136032104493],"max":[0.577104926109314,3.8509674072265627,-11.07213020324707],"type":"VEC3","name":"node7.004_vertices#0_POSITION"},{"bufferView":0,"byteOffset":12,"componentType":5120,"count":664,"type":"VEC4","name":"node7.004_vertices#0_TANGENT"},{"bufferView":0,"byteOffset":16,"componentType":5120,"count":664,"type":"VEC4","name":"node7.004_vertices#0_NORMAL"},{"bufferView":0,"byteOffset":20,"componentType":5122,"count":664,"type":"VEC2","name":"node7.004_vertices#0_TEXCOORD_0"},{"bufferView":0,"byteOffset":24,"componentType":5122,"count":664,"type":"VEC2","name":"node7.004_vertices#0_TEXCOORD_1"},{"bufferView":0,"byteOffset":28,"componentType":5123,"count":664,"type":"VEC4","name":"node7.004_vertices#0_COLOR_0"},{"bufferView":1,"componentType":5123,"count":3786,"type":"SCALAR","name":"node7.004_indices#0"}],"asset":{"generator":"Khronos glTF Blender I/O v3.2.43 and Asobo Studio MSFS Blender I/O v1.1.6","version":"2.0","extensions":{"ASOBO_asset_optimized":{"BoundingBoxMax":[0.577104926109314,3.8509674072265627,-11.07213020324707],"BoundingBoxMin":[-0.5719958543777466,3.438021421432495,-13.610136032104493],"MajorVersion":4,"MinorVersion":4,"UseCheckerboardMaterialForMissingTextures":true,"UseOnlyFilenameForImageURI":true},"ASOBO_normal_map_convention":{"tangent_space_convention":"DirectX"}}},"bufferViews":[{"buffer":0,"byteLength":23904,"byteStride":36,"target":34962,"name":"BufferViewVertexND"},{"buffer":0,"byteLength":7572,"byteOffset":23904,"target":34963,"name":"BufferViewIndex"}],"extensionsRequired":["MSFT_texture_dds"],"materials":[{"name":"Jetwave","normalTexture":{"index":0},"occlusionTexture":{"index":1},"pbrMetallicRoughness":{"baseColorTexture":{"index":2},"metallicRoughnessTexture":{"index":1}}}],"meshes":[{"primitives":[{"attributes":{"COLOR_0":5,"NORMAL":2,"POSITION":0,"TANGENT":1,"TEXCOORD_0":3,"TEXCOORD_1":4},"indices":6,"material":0,"mode":4,"extras":{"ASOBO_primitive":{"PrimitiveCount":1262,"VertexType":"VTX","VertexVersion":2}}}],"name":"node7.004"}],"nodes":[{"mesh":0,"name":"HoneywellJetwave"}],"scene":0,"scenes":[{"nodes":[0],"name":"Scene"}],"textures":[{"extensions":{"MSFT_texture_dds":{"source":0}}},{"extensions":{"MSFT_texture_dds":{"source":1}}},{"extensions":{"MSFT_texture_dds":{"source":2}}}],"samplers":[{"magFilter":9729,"minFilter":9987,"wrapS":10497,"wrapT":10497}],"extensionsUsed":["ASOBO_normal_map_convention","MSFT_texture_dds","ASOBO_asset_optimized"],"buffers":[{"byteLength":31476,"uri":"HoneywellJetwave.bin"}],"images":[{"uri":"HONEYWELLJETWAVE_NORMAL.PNG.DDS","name":"HoneywellJetwave_normal","extras":"ASOBO_image_converted_meta"},{"uri":"HONEYWELLJETWAVE_METAL.PNG.DDS","name":"HoneywellJetwave_metal","extras":"ASOBO_image_converted_meta"},{"uri":"HONEYWELLJETWAVE_ALBEDO.PNG.DDS","name":"HoneywellJetwave_albedo","extras":"ASOBO_image_converted_meta"}]} diff --git a/src/shared/src/FmMessages.ts b/src/shared/src/FmMessages.ts index 273275bf073d..b8faf52595f6 100644 --- a/src/shared/src/FmMessages.ts +++ b/src/shared/src/FmMessages.ts @@ -180,4 +180,9 @@ export const FMMessageTypes: Readonly> = { color: 'Amber', ndPriority: 9, }, + TurnAreaExceedance: { + id: 16, + text: 'TURN AREA EXCEEDANCE', + color: 'Amber', + }, }; diff --git a/src/shared/src/ata.ts b/src/shared/src/ata.ts index 80f2aa265631..6416b1873f4b 100644 --- a/src/shared/src/ata.ts +++ b/src/shared/src/ata.ts @@ -93,7 +93,9 @@ export const AtaChaptersTitle = { }; export const AtaChaptersDescription = Object.freeze({ + 22: 'The Autoflight systems are responsible for a multitude of functions, including but not limited to: Flight Guidance (AP, FD, A/THR), Flight Management, Flight Augmentation (yaw damper, etc.), and Flight Envelope (Speed scale, Alpha floor, etc.).', 24: 'All things related to the electrical system. The electrical system supplies power from the engines, APU, batteries, or emergency generator to all cockpit instruments.', + 27: 'The flight controls contain the various systems used to control the aircraft in flight, such as control surfaces, but also flight control computers. Failure of these systems may lead to loss of control over the aircraft, and/or loss of information about the status of the flight controls.', 29: 'The hydraulic system connects to the flight controls, flaps and landing gear to provide pressure to these surfaces. Failing these can cause loss of control over some flight surfaces.', 31: 'The cockpit displays give critical flight information to the pilots. In a failure where displays are lost, the pilots must deal with a lack of flight data given to them.', 32: 'The landing gear components are responsible for supporting and steering the aircraft on the ground, and make it possible to retract and store the landing gear in flight. Includes the functioning and maintenance aspects of the landing gear doors.', diff --git a/src/shared/src/bitFlags.ts b/src/shared/src/bitFlags.ts new file mode 100644 index 000000000000..cbb4f5cc5317 --- /dev/null +++ b/src/shared/src/bitFlags.ts @@ -0,0 +1,40 @@ +export class BitFlags { + flags: number[]; + + static f64View = new Float64Array(1); + + static u32View = new Uint32Array(BitFlags.f64View.buffer); + + constructor(float: number) { + this.setFlags(float); + } + + setFlags(float: number): void { + BitFlags.f64View[0] = float; + this.flags = Array.from(BitFlags.u32View); + } + + getBitIndex(bit: number): boolean { + if (bit > 63) return false; + const f = Math.floor(bit / 31); + const b = bit % 31; + + return ((this.flags[f] >> b) & 1) !== 0; + } + + toggleBitIndex(bit: number): void { + if (bit > 63) return; + const f = Math.floor(bit / 31); + const b = bit % 31; + + this.flags[f] ^= (1 << b); + } + + toDouble(): number { + return (new Float64Array(Uint32Array.from(this.flags).buffer))[0]; + } + + toString(): string { + return (`[ ${this.flags[0].toString(2)} | ${this.flags[1].toString(2)} ]`); + } +} diff --git a/src/shared/src/units.ts b/src/shared/src/units.ts index bcf4fed04d4e..74472a376eca 100644 --- a/src/shared/src/units.ts +++ b/src/shared/src/units.ts @@ -43,6 +43,10 @@ export class Units { return Units.usingMetric ? value : Units.kilogramToPound(value); } + static poundToUser(value: KiloGram): Pound | KiloGram { + return Units.usingMetric ? Units.poundToKilogram(value) : value; + } + static get userWeightSuffixEis2(): 'kg' | 'lbs' { // EIS uses S suffix on LB return Units.usingMetric ? 'kg' : 'lbs'; diff --git a/src/simbridge-client/rollup.config.js b/src/simbridge-client/rollup.config.js new file mode 100644 index 000000000000..e4b7213052b7 --- /dev/null +++ b/src/simbridge-client/rollup.config.js @@ -0,0 +1,75 @@ +/* + * MIT License + * + * Copyright (c) 2022 FlyByWire Simulations + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +'use strict'; + +const { join } = require('path'); +const babel = require('@rollup/plugin-babel').default; +const { typescriptPaths } = require('rollup-plugin-typescript-paths'); +const dotenv = require('dotenv'); +const commonjs = require('@rollup/plugin-commonjs'); +const nodeResolve = require('@rollup/plugin-node-resolve').default; +const json = require('@rollup/plugin-json'); + +const replace = require('@rollup/plugin-replace'); + +const extensions = ['.js', '.ts']; + +const src = join(__dirname, '..'); +const root = join(__dirname, '..', '..'); + +dotenv.config(); + +process.chdir(src); + +module.exports = { + input: join(__dirname, 'src/index.ts'), + plugins: [ + nodeResolve({ extensions, browser: true }), + commonjs(), + json(), + babel({ + presets: ['@babel/preset-typescript', ['@babel/preset-env', { targets: { browsers: ['safari 11'] } }]], + plugins: [ + '@babel/plugin-proposal-class-properties', + ], + extensions, + }), + typescriptPaths({ + tsConfigPath: join(src, 'tsconfig.json'), + preserveExtensions: true, + }), + replace({ + 'DEBUG': 'false', + 'process.env.NODE_ENV': '"production"', + 'process.env.SENTRY_DSN': JSON.stringify(process.env.SENTRY_DSN), + 'preventAssignment': true, + }), + ], + output: { + file: join(root, 'flybywire-aircraft-a320-neo/html_ui/JS/simbridge-client/simbridge-client.js'), + format: 'umd', + name: 'SimbridgeClient', + }, +}; diff --git a/src/simbridge-client/src/Coroute/Airport.ts b/src/simbridge-client/src/Coroute/Airport.ts new file mode 100644 index 000000000000..f61e1da0dae7 --- /dev/null +++ b/src/simbridge-client/src/Coroute/Airport.ts @@ -0,0 +1,4 @@ +/* eslint-disable camelcase */ +export interface Airport { + icao_code: String +} diff --git a/src/simbridge-client/src/Coroute/Fix.ts b/src/simbridge-client/src/Coroute/Fix.ts new file mode 100644 index 000000000000..7fb4f938fc5f --- /dev/null +++ b/src/simbridge-client/src/Coroute/Fix.ts @@ -0,0 +1,16 @@ +/* eslint-disable camelcase */ +export interface Fix { + ident: String + + name: String + + type: String + + via_airway: String + + is_sid_star: String + + pos_lat: String + + pos_long: String +} diff --git a/src/simbridge-client/src/Coroute/General.ts b/src/simbridge-client/src/Coroute/General.ts new file mode 100644 index 000000000000..15c0a1a83685 --- /dev/null +++ b/src/simbridge-client/src/Coroute/General.ts @@ -0,0 +1,3 @@ +export interface General { + route: string +} diff --git a/src/simbridge-client/src/Coroute/Navlog.ts b/src/simbridge-client/src/Coroute/Navlog.ts new file mode 100644 index 000000000000..74eba2b356aa --- /dev/null +++ b/src/simbridge-client/src/Coroute/Navlog.ts @@ -0,0 +1,5 @@ +import { Fix } from './fix'; + +export class Navlog { + fix: Fix[] = [] +} diff --git a/src/simbridge-client/src/Coroute/coroute.ts b/src/simbridge-client/src/Coroute/coroute.ts new file mode 100644 index 000000000000..bb64d95e2254 --- /dev/null +++ b/src/simbridge-client/src/Coroute/coroute.ts @@ -0,0 +1,15 @@ +import { Airport } from './Airport'; +import { General } from './General'; +import { Navlog } from './Navlog'; + +export interface CoRouteDto { + name: String; + + origin: Airport; + + destination: Airport; + + general: General; + + navlog: Navlog +} diff --git a/src/simbridge-client/src/common.ts b/src/simbridge-client/src/common.ts new file mode 100644 index 000000000000..86bd18e02248 --- /dev/null +++ b/src/simbridge-client/src/common.ts @@ -0,0 +1,3 @@ +import { NXDataStore } from '@shared/persistence'; + +export const getSimBridgeUrl = (): string => `http://localhost:${NXDataStore.get('CONFIG_SIMBRIDGE_PORT', '8380')}`; diff --git a/src/simbridge-client/src/components/Coroute.ts b/src/simbridge-client/src/components/Coroute.ts new file mode 100644 index 000000000000..6b633ba676b0 --- /dev/null +++ b/src/simbridge-client/src/components/Coroute.ts @@ -0,0 +1,40 @@ +import { getSimBridgeUrl } from '../common'; +import { CoRouteDto } from '../Coroute/coroute'; + +/** + * Class responsible for retrieving data related to company routes from SimBridge + */ +export class CompanyRoute { + /** + * Used to retrieve a given company route + * @param route The routename in question + * @returns Returns the CoRoute DTO + */ + public static async getCoRoute(route: String): Promise { + if (route) { + const response = await fetch(`${getSimBridgeUrl()}/api/v1/coroute?rteNum=${route}`); + if (response.status === 200) { + response.json(); + } + throw new Error('Server Error'); + } + throw new Error('No Company route provided'); + } + + /** + * Used to retrieve a list of company routes for a given origin and dest + * @param origin the origin + * @param dest the destination + * @returns Returns a list of CoRoute DTOs + */ + public static async getRouteList(origin: String, dest: String): Promise { + if (origin || dest) { + const response = await fetch(`${getSimBridgeUrl()}/api/v1/coroute/list?origin=${origin}&destination=${dest}`); + if (response.ok) { + response.json(); + } + throw new Error('Server Error'); + } + throw new Error('Origin or Destination missing'); + } +} diff --git a/src/simbridge-client/src/components/Health.ts b/src/simbridge-client/src/components/Health.ts new file mode 100644 index 000000000000..ea4967021a6f --- /dev/null +++ b/src/simbridge-client/src/components/Health.ts @@ -0,0 +1,39 @@ +import { getSimBridgeUrl } from '../common'; + +/** + * Class responsible for retrieving data related to company routes from SimBridge + */ +export class Health { + /** + * Used to check the state of a given service. If none is given, then main status is returned. + * @param serviceName The name of the service or omit for the overall status + * @returns true if service is available, false otherwise + */ + public static async getHealth(serviceName?: 'api'|'mcdu'): Promise { + const response = await fetch(`${getSimBridgeUrl()}/health`); + if (!response.ok) { + throw new Error(`SimBridge Error: ${response.status}`); + } + const healthJson = await response.json(); + switch (serviceName) { + case undefined: + if (healthJson.status === 'ok') { + return true; + } + break; + case 'api': + if (healthJson.info.api.status === 'up') { + return true; + } + break; + case 'mcdu': + if (healthJson.info.mcdu.status === 'up') { + return true; + } + break; + default: + throw new Error(`Unknown service name: '${serviceName}'`); + } + return false; + } +} diff --git a/src/simbridge-client/src/components/Terrain.ts b/src/simbridge-client/src/components/Terrain.ts new file mode 100644 index 000000000000..919ca3d3a1a1 --- /dev/null +++ b/src/simbridge-client/src/components/Terrain.ts @@ -0,0 +1,103 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +import { EfisSide } from '@shared/NavigationDisplay'; +import { getSimBridgeUrl } from '../common'; + +export class Terrain { + private static endpointsAvailable: boolean = false; + + public static async mapdataAvailable(): Promise { + return fetch(`${getSimBridgeUrl()}/api/v1/terrain/available`).then((response) => { + Terrain.endpointsAvailable = response.ok; + return response.ok; + }).catch((_ex) => { + Terrain.endpointsAvailable = false; + return false; + }); + } + + public static async setCurrentPosition(latitude: number, longitude: number, heading: number, altitude: number, verticalSpeed: number): Promise { + if (Terrain.endpointsAvailable) { + fetch(`${getSimBridgeUrl()}/api/v1/terrain/position`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ latitude, longitude, heading, altitude, verticalSpeed }), + }); + } else { + throw new Error('Endpoints unavailable'); + } + } + + public static async setDisplaySettings(side: EfisSide, settings: { + active: boolean, + mapWidth: number, + mapHeight: number, + meterPerPixel: number, + mapTransitionTime: number, + mapTransitionFps: number, + arcMode: boolean, + gearDown: boolean + }): Promise { + if (Terrain.endpointsAvailable) { + fetch(`${getSimBridgeUrl()}/api/v1/terrain/displaysettings?display=${side}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(settings), + }); + } else { + throw new Error('Endpoints unavailable'); + } + } + + public static async ndMapAvailable(side: EfisSide, timestamp: number): Promise { + if (Terrain.endpointsAvailable) { + return fetch(`${getSimBridgeUrl()}/api/v1/terrain/ndMapAvailable?display=${side}×tamp=${timestamp}`).then((response) => { + if (response.ok) { + return response.text().then((text) => text === 'true'); + } + return false; + }); + } + + throw new Error('Endpoints unavailable'); + } + + public static async ndTransitionMaps(side: EfisSide, timestamp: number): Promise { + if (Terrain.endpointsAvailable) { + return fetch(`${getSimBridgeUrl()}/api/v1/terrain/ndmaps?display=${side}×tamp=${timestamp}`, { + method: 'GET', + headers: { Accept: 'application/json' }, + }).then((response) => response.json().then((imageBase64) => imageBase64)); + } + + throw new Error('Endpoints unavailable'); + } + + public static async ndTerrainRange(side: EfisSide, timestamp: number): + Promise<{ + minElevation: number, + minElevationIsWarning: boolean, + minElevationIsCaution: boolean, + maxElevation: number, + maxElevationIsWarning: boolean, + maxElevationIsCaution: boolean + }> { + if (Terrain.endpointsAvailable) { + return fetch(`${getSimBridgeUrl()}/api/v1/terrain/terrainRange?display=${side}×tamp=${timestamp}`, { + method: 'GET', + headers: { Accept: 'application/json' }, + }).then((response) => response.json().then((data) => data)); + } + + throw new Error('Endpoints unavailable'); + } + + public static async renderNdMap(side: EfisSide): Promise { + if (Terrain.endpointsAvailable) { + return fetch(`${getSimBridgeUrl()}/api/v1/terrain/renderMap?display=${side}`).then((response) => response.text().then((text) => parseInt(text))); + } + + throw new Error('Endpoints unavailable'); + } +} diff --git a/src/simbridge-client/src/components/Viewer.ts b/src/simbridge-client/src/components/Viewer.ts new file mode 100644 index 000000000000..6a305a4cc767 --- /dev/null +++ b/src/simbridge-client/src/components/Viewer.ts @@ -0,0 +1,79 @@ +import { getSimBridgeUrl } from '../common'; + +/** + * Class pertaining to retrieving static files for general viewing from SimBridge + */ +export class Viewer { + /** + * Used to retrieve a streamable image of specified page within a given PDF file + * @param filename required field, filename of the pdf + * @param pageNumber required field, The page of the PDF file + * @returns a Blob + */ + public static async getPDFPage(filename: string, pageNumber: number): Promise { + if (filename || pageNumber) { + const response = await fetch(`${getSimBridgeUrl()}/api/v1/utility/pdf?filename=${filename}&pagenumber=${pageNumber}`); + if (response.ok) { + return response.blob(); + } + throw new Error(`SimBridge Error: ${response.status}`); + } + throw new Error('File name or page number missing'); + } + + /** + * Retrieve the number of pages within a specified PDF file + * @param filename required field, filename of the pdf + * @returns A number + */ + public static async getPDFPageNum(filename: string): Promise { + if (filename) { + const response = await fetch(`${getSimBridgeUrl()}/api/v1/utility/pdf/numpages?filename=${filename}`); + if (response.ok) { + return response.json(); + } + throw new Error(`SimBridge Error: ${response.status}`); + } + throw new Error('File name or page number missing'); + } + + /** + * Used to retrieve a list of filenames within the PDF folder + * @returns an Array of strings + */ + public static async getPDFList(): Promise { + const response = await fetch(`${getSimBridgeUrl()}/api/v1/utility/pdf/list`); + if (response.ok) { + return response.json(); + } + throw new Error(`SimBridge Error: ${response.status}`); + } + + /** + * Used to retrieve a streamable image of a specified image in the images folder + * @param filename required field, filename of the image + * @returns A Blob + */ + public static async getImage(filename: string, pageNumber: number): Promise { + if (filename || pageNumber) { + const response = await fetch(`${getSimBridgeUrl()}/api/v1/utility/image?filename=${filename}`); + if (response.ok) { + return response.blob(); + } + throw new Error(`SimBridge Error: ${response.status}`); + } + throw new Error('File name or page number missing'); + } + + /** + * Used to retrieve a list of filenames within the PDF folder + * @returns an Array of strings + */ + public static async getImageList(): Promise { + const response = await fetch(`${getSimBridgeUrl()}/api/v1/utility/image/list`); + if (response.ok) { + return response.json(); + } + throw new Error(`SimBridge Error: ${response.status}`); + } +} diff --git a/src/simbridge-client/src/index.ts b/src/simbridge-client/src/index.ts new file mode 100644 index 000000000000..2ca7c74ebf40 --- /dev/null +++ b/src/simbridge-client/src/index.ts @@ -0,0 +1,6 @@ +import { Terrain } from './components/Terrain'; +import { CompanyRoute } from './components/Coroute'; +import { Viewer } from './components/Viewer'; +import { Health } from './components/Health'; + +export { Health, CompanyRoute, Viewer, Terrain }; diff --git a/src/simbridge-client/tsconfig.json b/src/simbridge-client/tsconfig.json new file mode 100644 index 000000000000..b7d74e181542 --- /dev/null +++ b/src/simbridge-client/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "moduleResolution": "node", + "target": "ESNext", + "noEmit": true, + "typeRoots": ["../../typings"] + }, + "include": [ + "src/**/*", + "../../typings/**/*.d.ts" + ] + } diff --git a/src/systems/a320_hydraulic_simulation_graphs/Cargo.toml b/src/systems/a320_hydraulic_simulation_graphs/Cargo.toml index 24bdcda96a7c..5db801daa127 100644 --- a/src/systems/a320_hydraulic_simulation_graphs/Cargo.toml +++ b/src/systems/a320_hydraulic_simulation_graphs/Cargo.toml @@ -11,7 +11,7 @@ doc = false [dependencies] systems = { path = "../systems" } a320_systems = { path = "../a320_systems" } -uom = "0.32.0" +uom = "0.33.0" rand = "0.8.0" ntest = "0.7.2" num-derive = "0.3.3" diff --git a/src/systems/a320_hydraulic_simulation_graphs/src/main.rs b/src/systems/a320_hydraulic_simulation_graphs/src/main.rs index 01879b885f42..214b1c9cffa4 100644 --- a/src/systems/a320_hydraulic_simulation_graphs/src/main.rs +++ b/src/systems/a320_hydraulic_simulation_graphs/src/main.rs @@ -3,6 +3,7 @@ use plotlib::repr::Plot; use plotlib::style::LineStyle; use plotlib::view::ContinuousView; use std::time::Duration; +use systems::hydraulic::pumps::PumpCharacteristics; use systems::hydraulic::*; @@ -49,6 +50,10 @@ impl HydraulicCircuitController for TestHydraulicCircuitController { fn should_open_leak_measurement_valve(&self) -> bool { true } + + fn should_route_pump_to_auxiliary(&self, _: usize) -> bool { + false + } } struct TestPumpController { @@ -356,11 +361,12 @@ fn electric_pump(context: &mut InitContext) -> ElectricPump { "DEFAULT", ElectricalBusType::AlternatingCurrentGndFltService, ElectricCurrent::new::(45.), + PumpCharacteristics::a320_electric_pump(), ) } fn _engine_driven_pump(context: &mut InitContext) -> EngineDrivenPump { - EngineDrivenPump::new(context, "DEFAULT") + EngineDrivenPump::new(context, "DEFAULT", PumpCharacteristics::a320_edp()) } struct A320TestPneumatics { @@ -495,6 +501,7 @@ impl Aircraft for A320SimpleMainElecHydraulicsTestAircraft { &context.with_delta(cur_time_step), &mut vec![&mut self.elec_pump], None::<&mut ElectricPump>, + None::<&mut ElectricPump>, None, &self.circuit_controller, self.pneumatics.pressure(), diff --git a/src/systems/a320_systems/Cargo.toml b/src/systems/a320_systems/Cargo.toml index 02a9a36aafa5..a832500fdca9 100644 --- a/src/systems/a320_systems/Cargo.toml +++ b/src/systems/a320_systems/Cargo.toml @@ -5,7 +5,7 @@ authors = ["FlyByWire Simulations"] edition = "2018" [dependencies] -uom = "0.32.0" +uom = "0.33.0" rand = "0.8.0" nalgebra = "0.25.0" ntest = "0.7.2" diff --git a/src/systems/a320_systems/src/air_conditioning.rs b/src/systems/a320_systems/src/air_conditioning.rs index 5fb62a153ad2..ec7dc08e4364 100644 --- a/src/systems/a320_systems/src/air_conditioning.rs +++ b/src/systems/a320_systems/src/air_conditioning.rs @@ -1,12 +1,14 @@ use systems::{ accept_iterable, air_conditioning::{ - cabin_air::CabinZone, AirConditioningSystem, DuctTemperature, PackFlow, ZoneType, + acs_controller::{Pack, PackFlowController}, + cabin_air::CabinZone, + AirConditioningSystem, DuctTemperature, PackFlow, PackFlowControllers, ZoneType, }, pressurization::PressurizationOverheadPanel, shared::{ - Cabin, EngineBleedPushbutton, EngineCorrectedN1, EngineFirePushButtons, EngineStartState, - GroundSpeed, LgciuWeightOnWheels, PneumaticBleed, + Cabin, ElectricalBusType, EngineBleedPushbutton, EngineCorrectedN1, EngineFirePushButtons, + EngineStartState, GroundSpeed, LgciuWeightOnWheels, PackFlowValveState, PneumaticBleed, }, simulation::{InitContext, SimulationElement, SimulationElementVisitor, UpdateContext}, }; @@ -24,7 +26,18 @@ impl A320AirConditioning { Self { a320_cabin: A320Cabin::new(context), - a320_air_conditioning_system: AirConditioningSystem::new(context, cabin_zones), + a320_air_conditioning_system: AirConditioningSystem::new( + context, + cabin_zones, + vec![ + ElectricalBusType::DirectCurrent(1), + ElectricalBusType::AlternatingCurrent(1), + ], + vec![ + ElectricalBusType::DirectCurrent(2), + ElectricalBusType::AlternatingCurrent(2), + ], + ), } } @@ -34,7 +47,7 @@ impl A320AirConditioning { adirs: &impl GroundSpeed, engines: [&impl EngineCorrectedN1; 2], engine_fire_push_buttons: &impl EngineFirePushButtons, - pneumatic: &(impl PneumaticBleed + EngineStartState), + pneumatic: &(impl EngineStartState + PackFlowValveState + PneumaticBleed), pneumatic_overhead: &impl EngineBleedPushbutton, pressurization: &impl Cabin, pressurization_overhead: &PressurizationOverheadPanel, @@ -60,6 +73,13 @@ impl A320AirConditioning { } } +impl PackFlowControllers<3> for A320AirConditioning { + fn pack_flow_controller(&self, pack_id: Pack) -> PackFlowController<3> { + self.a320_air_conditioning_system + .pack_flow_controller(pack_id) + } +} + impl SimulationElement for A320AirConditioning { fn accept(&mut self, visitor: &mut T) { self.a320_cabin.accept(visitor); diff --git a/src/systems/a320_systems/src/hydraulic/flaps_computer.rs b/src/systems/a320_systems/src/hydraulic/flaps_computer.rs index 2bf1700ffd01..42bb93a7197d 100644 --- a/src/systems/a320_systems/src/hydraulic/flaps_computer.rs +++ b/src/systems/a320_systems/src/hydraulic/flaps_computer.rs @@ -66,10 +66,6 @@ impl SimulationElement for FlapsHandle { } struct SlatFlapControlComputer { - left_flaps_target_angle_id: VariableIdentifier, - right_flaps_target_angle_id: VariableIdentifier, - left_slats_target_angle_id: VariableIdentifier, - right_slats_target_angle_id: VariableIdentifier, flaps_conf_index_id: VariableIdentifier, slats_fppu_angle_id: VariableIdentifier, flaps_fppu_angle_id: VariableIdentifier, @@ -86,20 +82,12 @@ struct SlatFlapControlComputer { } impl SlatFlapControlComputer { - const EQUAL_ANGLE_DELTA_DEGREE: f64 = 0.01; + const EQUAL_ANGLE_DELTA_DEGREE: f64 = 0.177; const HANDLE_ONE_CONF_AIRSPEED_THRESHOLD_KNOTS: f64 = 100.; const CONF1F_TO_CONF1_AIRSPEED_THRESHOLD_KNOTS: f64 = 210.; fn new(context: &mut InitContext) -> Self { Self { - left_flaps_target_angle_id: context - .get_identifier("LEFT_FLAPS_TARGET_ANGLE".to_owned()), - right_flaps_target_angle_id: context - .get_identifier("RIGHT_FLAPS_TARGET_ANGLE".to_owned()), - left_slats_target_angle_id: context - .get_identifier("LEFT_SLATS_TARGET_ANGLE".to_owned()), - right_slats_target_angle_id: context - .get_identifier("RIGHT_SLATS_TARGET_ANGLE".to_owned()), flaps_conf_index_id: context.get_identifier("FLAPS_CONF_INDEX".to_owned()), slats_fppu_angle_id: context.get_identifier("SLATS_FPPU_ANGLE".to_owned()), flaps_fppu_angle_id: context.get_identifier("FLAPS_FPPU_ANGLE".to_owned()), @@ -120,25 +108,27 @@ impl SlatFlapControlComputer { } } - fn demanded_flaps_angle_from_conf(flap_conf: FlapsConf) -> Angle { + // Returns a flap demanded angle in FPPU reference degree (feedback sensor) + fn demanded_flaps_fppu_angle_from_conf(flap_conf: FlapsConf) -> Angle { match flap_conf { FlapsConf::Conf0 => Angle::new::(0.), FlapsConf::Conf1 => Angle::new::(0.), - FlapsConf::Conf1F => Angle::new::(10.), - FlapsConf::Conf2 => Angle::new::(15.), - FlapsConf::Conf3 => Angle::new::(20.), - FlapsConf::ConfFull => Angle::new::(40.), + FlapsConf::Conf1F => Angle::new::(120.22), + FlapsConf::Conf2 => Angle::new::(145.51), + FlapsConf::Conf3 => Angle::new::(168.35), + FlapsConf::ConfFull => Angle::new::(251.97), } } - fn demanded_slats_angle_from_conf(flap_conf: FlapsConf) -> Angle { + // Returns a slat demanded angle in FPPU reference degree (feedback sensor) + fn demanded_slats_fppu_angle_from_conf(flap_conf: FlapsConf) -> Angle { match flap_conf { FlapsConf::Conf0 => Angle::new::(0.), - FlapsConf::Conf1 => Angle::new::(18.), - FlapsConf::Conf1F => Angle::new::(18.), - FlapsConf::Conf2 => Angle::new::(22.), - FlapsConf::Conf3 => Angle::new::(22.), - FlapsConf::ConfFull => Angle::new::(27.), + FlapsConf::Conf1 => Angle::new::(222.27), + FlapsConf::Conf1F => Angle::new::(222.27), + FlapsConf::Conf2 => Angle::new::(272.27), + FlapsConf::Conf3 => Angle::new::(272.27), + FlapsConf::ConfFull => Angle::new::(334.16), } } @@ -188,8 +178,8 @@ impl SlatFlapControlComputer { ) { self.flaps_conf = self.generate_configuration(flaps_handle, context); - self.flaps_demanded_angle = Self::demanded_flaps_angle_from_conf(self.flaps_conf); - self.slats_demanded_angle = Self::demanded_slats_angle_from_conf(self.flaps_conf); + self.flaps_demanded_angle = Self::demanded_flaps_fppu_angle_from_conf(self.flaps_conf); + self.slats_demanded_angle = Self::demanded_slats_fppu_angle_from_conf(self.flaps_conf); self.flaps_feedback_angle = flaps_feedback.angle(); self.slats_feedback_angle = slats_feedback.angle(); } @@ -331,12 +321,6 @@ impl SlatFlapLane for SlatFlapControlComputer { impl SimulationElement for SlatFlapControlComputer { fn write(&self, writer: &mut SimulatorWriter) { - writer.write(&self.left_flaps_target_angle_id, self.flaps_demanded_angle); - writer.write(&self.right_flaps_target_angle_id, self.flaps_demanded_angle); - - writer.write(&self.left_slats_target_angle_id, self.slats_demanded_angle); - writer.write(&self.right_slats_target_angle_id, self.slats_demanded_angle); - writer.write(&self.flaps_conf_index_id, self.flaps_conf as u8); writer.write(&self.slats_fppu_angle_id, self.slats_feedback_angle); @@ -402,12 +386,8 @@ impl SimulationElement for SlatFlapComplex { #[cfg(test)] mod tests { - use crate::hydraulic::A320Hydraulic; - use super::*; - use ntest::assert_about_eq; use std::time::Duration; - use systems::shared::interpolation; use systems::simulation::{ test::{ReadByName, SimulationTestBed, TestBed, WriteByName}, Aircraft, @@ -427,26 +407,12 @@ mod tests { } impl FeedbackPositionPickoffUnit for SlatFlapGear { fn angle(&self) -> Angle { - let synchro_gear_breakpoints = match self.surface_type.as_str() { - "FLAPS" => A320Hydraulic::FLAP_FPPU_TO_SURFACE_ANGLE_BREAKPTS, - "SLATS" => A320Hydraulic::SLAT_FPPU_TO_SURFACE_ANGLE_BREAKPTS, - _ => panic!(), - }; - let synchro_gear_degrees = match self.surface_type.as_str() { - "FLAPS" => A320Hydraulic::FLAP_FPPU_TO_SURFACE_ANGLE_DEGREES, - "SLATS" => A320Hydraulic::SLAT_FPPU_TO_SURFACE_ANGLE_DEGREES, - _ => panic!(), - }; - Angle::new::(interpolation( - &synchro_gear_degrees, - &synchro_gear_breakpoints, - self.current_angle.get::(), - )) + self.current_angle } } impl SlatFlapGear { - const ANGLE_DELTA_DEGREE: f64 = 0.1; + const ANGLE_DELTA_DEGREE: f64 = 0.01; fn new( context: &mut InitContext, @@ -484,16 +450,25 @@ mod tests { || hydraulic_pressure_right_side.get::() > 1500. { if let Some(demanded_angle) = sfcc.signal_demanded_angle(&self.surface_type) { - let actual_minus_target = demanded_angle - self.current_angle; - if actual_minus_target.get::().abs() > Self::ANGLE_DELTA_DEGREE { + let actual_minus_target_ffpu = demanded_angle - self.angle(); + + let fppu_angle = self.angle(); + + if actual_minus_target_ffpu.get::().abs() > Self::ANGLE_DELTA_DEGREE { self.current_angle += Angle::new::( - actual_minus_target.get::().signum() + actual_minus_target_ffpu.get::().signum() * self.speed.get::() * context.delta_as_secs_f64(), ); self.current_angle = self.current_angle.max(Angle::new::(0.)); - } else { - self.current_angle = demanded_angle; + + let new_ffpu_angle = self.angle(); + // If demand was crossed between two frames: fixing to demand + if new_ffpu_angle > demanded_angle && fppu_angle < demanded_angle + || new_ffpu_angle < demanded_angle && fppu_angle > demanded_angle + { + self.current_angle = demanded_angle; + } } } } @@ -539,14 +514,14 @@ mod tests { flap_gear: SlatFlapGear::new( context, - AngularVelocity::new::(4.), - Angle::new::(40.), + AngularVelocity::new::(7.5), + Angle::new::(251.97), "FLAPS", ), slat_gear: SlatFlapGear::new( context, - AngularVelocity::new::(3.), - Angle::new::(27.), + AngularVelocity::new::(7.5), + Angle::new::(334.16), "SLATS", ), @@ -676,12 +651,12 @@ mod tests { self.query(|a| a.slat_flap_complex.sfcc.flaps_conf) } - fn get_flaps_angle(&self) -> f64 { - self.query(|a| a.flap_gear.current_angle.get::()) + fn get_flaps_fppu_feedback(&self) -> f64 { + self.query(|a| a.flap_gear.angle().get::()) } - fn get_slats_angle(&self) -> f64 { - self.query(|a| a.slat_gear.current_angle.get::()) + fn get_slats_fppu_feedback(&self) -> f64 { + self.query(|a| a.slat_gear.angle().get::()) } fn test_flap_conf( @@ -726,15 +701,11 @@ mod tests { assert!(test_bed.contains_variable_with_name("RIGHT_FLAPS_ANGLE")); assert!(test_bed.contains_variable_with_name("LEFT_FLAPS_POSITION_PERCENT")); assert!(test_bed.contains_variable_with_name("RIGHT_FLAPS_POSITION_PERCENT")); - assert!(test_bed.contains_variable_with_name("LEFT_FLAPS_TARGET_ANGLE")); - assert!(test_bed.contains_variable_with_name("RIGHT_FLAPS_TARGET_ANGLE")); assert!(test_bed.contains_variable_with_name("LEFT_SLATS_ANGLE")); assert!(test_bed.contains_variable_with_name("RIGHT_SLATS_ANGLE")); assert!(test_bed.contains_variable_with_name("LEFT_SLATS_POSITION_PERCENT")); assert!(test_bed.contains_variable_with_name("RIGHT_SLATS_POSITION_PERCENT")); - assert!(test_bed.contains_variable_with_name("LEFT_SLATS_TARGET_ANGLE")); - assert!(test_bed.contains_variable_with_name("RIGHT_SLATS_TARGET_ANGLE")); assert!(test_bed.contains_variable_with_name("FLAPS_CONF_INDEX")); } @@ -786,7 +757,7 @@ mod tests { assert!(!test_bed.read_slat_flap_system_status_word().get_bit(21)); assert!(test_bed.read_slat_flap_system_status_word().get_bit(26)); - test_bed = test_bed.run_waiting_for(Duration::from_secs(30)); + test_bed = test_bed.run_waiting_for(Duration::from_secs(31)); assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(12)); assert!(test_bed.read_slat_flap_actual_position_word().get_bit(13)); @@ -816,7 +787,7 @@ mod tests { assert!(!test_bed.read_slat_flap_system_status_word().get_bit(21)); assert!(!test_bed.read_slat_flap_system_status_word().get_bit(26)); - test_bed = test_bed.run_waiting_for(Duration::from_secs(30)); + test_bed = test_bed.run_waiting_for(Duration::from_secs(31)); assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(12)); assert!(test_bed.read_slat_flap_actual_position_word().get_bit(13)); @@ -846,7 +817,7 @@ mod tests { assert!(!test_bed.read_slat_flap_system_status_word().get_bit(21)); assert!(!test_bed.read_slat_flap_system_status_word().get_bit(26)); - test_bed = test_bed.run_waiting_for(Duration::from_secs(30)); + test_bed = test_bed.run_waiting_for(Duration::from_secs(31)); assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(12)); assert!(test_bed.read_slat_flap_actual_position_word().get_bit(13)); @@ -876,7 +847,7 @@ mod tests { assert!(!test_bed.read_slat_flap_system_status_word().get_bit(21)); assert!(!test_bed.read_slat_flap_system_status_word().get_bit(26)); - test_bed = test_bed.run_waiting_for(Duration::from_secs(30)); + test_bed = test_bed.run_waiting_for(Duration::from_secs(31)); assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(12)); assert!(test_bed.read_slat_flap_actual_position_word().get_bit(13)); @@ -906,7 +877,7 @@ mod tests { assert!(test_bed.read_slat_flap_system_status_word().get_bit(21)); assert!(!test_bed.read_slat_flap_system_status_word().get_bit(26)); - test_bed = test_bed.run_waiting_for(Duration::from_secs(30)); + test_bed = test_bed.run_waiting_for(Duration::from_secs(45)); assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(12)); assert!(test_bed.read_slat_flap_actual_position_word().get_bit(13)); @@ -934,19 +905,19 @@ mod tests { test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); - test_bed.test_flap_conf(1, 10., 18., FlapsConf::Conf1F, angle_delta); + test_bed.test_flap_conf(1, 120.22, 222.27, FlapsConf::Conf1F, angle_delta); test_bed = test_bed.set_flaps_handle_position(2).run_one_tick(); - test_bed.test_flap_conf(2, 15., 22., FlapsConf::Conf2, angle_delta); + test_bed.test_flap_conf(2, 145.51, 272.27, FlapsConf::Conf2, angle_delta); test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); - test_bed.test_flap_conf(3, 20., 22., FlapsConf::Conf3, angle_delta); + test_bed.test_flap_conf(3, 168.35, 272.27, FlapsConf::Conf3, angle_delta); test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); - test_bed.test_flap_conf(4, 40., 27., FlapsConf::ConfFull, angle_delta); + test_bed.test_flap_conf(4, 251.97, 334.16, FlapsConf::ConfFull, angle_delta); } // Tests flaps configuration and angles for regular @@ -964,19 +935,19 @@ mod tests { test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); - test_bed.test_flap_conf(1, 0., 18., FlapsConf::Conf1, angle_delta); + test_bed.test_flap_conf(1, 0., 222.27, FlapsConf::Conf1, angle_delta); test_bed = test_bed.set_flaps_handle_position(2).run_one_tick(); - test_bed.test_flap_conf(2, 15., 22., FlapsConf::Conf2, angle_delta); + test_bed.test_flap_conf(2, 145.51, 272.27, FlapsConf::Conf2, angle_delta); test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); - test_bed.test_flap_conf(3, 20., 22., FlapsConf::Conf3, angle_delta); + test_bed.test_flap_conf(3, 168.35, 272.27, FlapsConf::Conf3, angle_delta); test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); - test_bed.test_flap_conf(4, 40., 27., FlapsConf::ConfFull, angle_delta); + test_bed.test_flap_conf(4, 251.97, 334.16, FlapsConf::ConfFull, angle_delta); } //Tests regular transition 2->1 below and above 210 knots @@ -1038,19 +1009,19 @@ mod tests { test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); - test_bed.test_flap_conf(4, 40., 27., FlapsConf::ConfFull, angle_delta); + test_bed.test_flap_conf(4, 251.97, 334.16, FlapsConf::ConfFull, angle_delta); test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); - test_bed.test_flap_conf(3, 20., 22., FlapsConf::Conf3, angle_delta); + test_bed.test_flap_conf(3, 168.35, 272.27, FlapsConf::Conf3, angle_delta); test_bed = test_bed.set_flaps_handle_position(2).run_one_tick(); - test_bed.test_flap_conf(2, 15., 22., FlapsConf::Conf2, angle_delta); + test_bed.test_flap_conf(2, 145.51, 272.27, FlapsConf::Conf2, angle_delta); test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); - test_bed.test_flap_conf(1, 10., 18., FlapsConf::Conf1F, angle_delta); + test_bed.test_flap_conf(1, 120.22, 222.27, FlapsConf::Conf1F, angle_delta); test_bed = test_bed.set_flaps_handle_position(0).run_one_tick(); @@ -1070,19 +1041,19 @@ mod tests { test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); - test_bed.test_flap_conf(4, 40., 27., FlapsConf::ConfFull, angle_delta); + test_bed.test_flap_conf(4, 251.97, 334.16, FlapsConf::ConfFull, angle_delta); test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); - test_bed.test_flap_conf(3, 20., 22., FlapsConf::Conf3, angle_delta); + test_bed.test_flap_conf(3, 168.35, 272.27, FlapsConf::Conf3, angle_delta); test_bed = test_bed.set_flaps_handle_position(2).run_one_tick(); - test_bed.test_flap_conf(2, 15., 22., FlapsConf::Conf2, angle_delta); + test_bed.test_flap_conf(2, 145.51, 272.27, FlapsConf::Conf2, angle_delta); test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); - test_bed.test_flap_conf(1, 0., 18., FlapsConf::Conf1, angle_delta); + test_bed.test_flap_conf(1, 0., 222.27, FlapsConf::Conf1, angle_delta); test_bed = test_bed.set_flaps_handle_position(0).run_one_tick(); @@ -1360,11 +1331,9 @@ mod tests { assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); } - // The tests below test the movement of the - // flaps/slats. #[test] fn flaps_test_movement_0_to_1f() { - let angle_delta = 0.01; + let angle_delta = 0.2; let mut test_bed = test_bed_with() .set_green_hyd_pressure() .set_indicated_airspeed(0.) @@ -1373,40 +1342,19 @@ mod tests { assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); - test_bed = test_bed.set_flaps_handle_position(1); + test_bed = test_bed + .set_flaps_handle_position(1) + .run_waiting_for(Duration::from_secs(20)); - let mut previous_angle: f64 = test_bed.get_flaps_angle(); - test_bed = test_bed.run_one_tick(); - for _ in 0..300 { - println!( - "{}, {}, {}", - test_bed.get_flaps_angle(), - test_bed.get_flaps_demanded_angle(), - previous_angle - ); - if (test_bed.get_flaps_angle() - test_bed.get_flaps_demanded_angle()).abs() - <= angle_delta - { - test_bed = test_bed.run_waiting_for(Duration::from_secs(5)); - assert!( - (test_bed.get_flaps_angle() - test_bed.get_flaps_demanded_angle()).abs() - <= angle_delta - ); - break; - } else { - assert!(previous_angle < test_bed.get_flaps_angle()); - } - previous_angle = test_bed.get_flaps_angle(); - test_bed = test_bed.run_one_tick(); - } assert!( - (test_bed.get_flaps_angle() - test_bed.get_flaps_demanded_angle()).abs() <= angle_delta + (test_bed.get_flaps_fppu_feedback() - test_bed.get_flaps_demanded_angle()).abs() + <= angle_delta ); } #[test] fn flaps_test_movement_1f_to_2() { - let angle_delta = 0.01; + let angle_delta = 0.2; let mut test_bed = test_bed_with() .set_green_hyd_pressure() .set_indicated_airspeed(0.) @@ -1415,34 +1363,19 @@ mod tests { assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1F); - test_bed = test_bed.set_flaps_handle_position(2); + test_bed = test_bed + .set_flaps_handle_position(2) + .run_waiting_for(Duration::from_secs(20)); - let mut previous_angle: f64 = test_bed.get_flaps_angle(); - test_bed = test_bed.run_one_tick(); - for _ in 0..300 { - if (test_bed.get_flaps_angle() - test_bed.get_flaps_demanded_angle()).abs() - <= angle_delta - { - test_bed = test_bed.run_waiting_for(Duration::from_secs(5)); - assert!( - (test_bed.get_flaps_angle() - test_bed.get_flaps_demanded_angle()).abs() - <= angle_delta - ); - break; - } else { - assert!(previous_angle < test_bed.get_flaps_angle()); - } - previous_angle = test_bed.get_flaps_angle(); - test_bed = test_bed.run_one_tick(); - } assert!( - (test_bed.get_flaps_angle() - test_bed.get_flaps_demanded_angle()).abs() <= angle_delta + (test_bed.get_flaps_fppu_feedback() - test_bed.get_flaps_demanded_angle()).abs() + <= angle_delta ); } #[test] fn flaps_test_movement_2_to_3() { - let angle_delta = 0.01; + let angle_delta = 0.2; let mut test_bed = test_bed_with() .set_green_hyd_pressure() .set_indicated_airspeed(0.) @@ -1451,34 +1384,19 @@ mod tests { assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf2); - test_bed = test_bed.set_flaps_handle_position(3); + test_bed = test_bed + .set_flaps_handle_position(3) + .run_waiting_for(Duration::from_secs(30)); - let mut previous_angle: f64 = test_bed.get_flaps_angle(); - test_bed = test_bed.run_one_tick(); - for _ in 0..300 { - if (test_bed.get_flaps_angle() - test_bed.get_flaps_demanded_angle()).abs() - <= angle_delta - { - test_bed = test_bed.run_waiting_for(Duration::from_secs(5)); - assert!( - (test_bed.get_flaps_angle() - test_bed.get_flaps_demanded_angle()).abs() - <= angle_delta - ); - break; - } else { - assert!(previous_angle < test_bed.get_flaps_angle()); - } - previous_angle = test_bed.get_flaps_angle(); - test_bed = test_bed.run_one_tick(); - } assert!( - (test_bed.get_flaps_angle() - test_bed.get_flaps_demanded_angle()).abs() <= angle_delta + (test_bed.get_flaps_fppu_feedback() - test_bed.get_flaps_demanded_angle()).abs() + <= angle_delta ); } #[test] fn flaps_test_movement_3_to_full() { - let angle_delta = 0.01; + let angle_delta = 0.2; let mut test_bed = test_bed_with() .set_green_hyd_pressure() .set_indicated_airspeed(0.) @@ -1487,34 +1405,19 @@ mod tests { assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); - test_bed = test_bed.set_flaps_handle_position(4); + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(20)); - let mut previous_angle: f64 = test_bed.get_flaps_angle(); - test_bed = test_bed.run_one_tick(); - for _ in 0..300 { - if (test_bed.get_flaps_angle() - test_bed.get_flaps_demanded_angle()).abs() - <= angle_delta - { - test_bed = test_bed.run_waiting_for(Duration::from_secs(5)); - assert!( - (test_bed.get_flaps_angle() - test_bed.get_flaps_demanded_angle()).abs() - <= angle_delta - ); - break; - } else { - assert!(previous_angle < test_bed.get_flaps_angle()); - } - previous_angle = test_bed.get_flaps_angle(); - test_bed = test_bed.run_one_tick(); - } assert!( - (test_bed.get_flaps_angle() - test_bed.get_flaps_demanded_angle()).abs() <= angle_delta + (test_bed.get_flaps_fppu_feedback() - test_bed.get_flaps_demanded_angle()).abs() + <= angle_delta ); } #[test] fn slats_test_movement_0_to_1f() { - let angle_delta = 0.01; + let angle_delta = 0.2; let mut test_bed = test_bed_with() .set_green_hyd_pressure() .set_indicated_airspeed(0.) @@ -1523,34 +1426,19 @@ mod tests { assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); - test_bed = test_bed.set_flaps_handle_position(1); + test_bed = test_bed + .set_flaps_handle_position(1) + .run_waiting_for(Duration::from_secs(30)); - let mut previous_angle: f64 = test_bed.get_slats_angle(); - test_bed = test_bed.run_one_tick(); - for _ in 0..300 { - if (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() - <= angle_delta - { - test_bed = test_bed.run_waiting_for(Duration::from_secs(5)); - assert!( - (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() - <= angle_delta - ); - break; - } else { - assert!(previous_angle < test_bed.get_slats_angle()); - } - previous_angle = test_bed.get_slats_angle(); - test_bed = test_bed.run_one_tick(); - } assert!( - (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() <= angle_delta + (test_bed.get_slats_fppu_feedback() - test_bed.get_slats_demanded_angle()).abs() + <= angle_delta ); } #[test] fn slats_and_flaps_test_movement_0_to_1() { - let angle_delta = 0.01; + let angle_delta = 0.2; let mut test_bed = test_bed_with() .set_green_hyd_pressure() .set_blue_hyd_pressure() @@ -1560,37 +1448,23 @@ mod tests { assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); - test_bed = test_bed.set_flaps_handle_position(1); - - let mut previous_angle: f64 = test_bed.get_slats_angle(); - test_bed = test_bed.run_one_tick(); + test_bed = test_bed + .set_flaps_handle_position(1) + .run_waiting_for(Duration::from_secs(30)); - for _ in 0..300 { - if (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() + assert!( + (test_bed.get_flaps_fppu_feedback() - test_bed.get_flaps_demanded_angle()).abs() <= angle_delta - { - test_bed = test_bed.run_waiting_for(Duration::from_secs(5)); - assert!( - (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() - <= angle_delta - ); - break; - } else { - assert!(previous_angle < test_bed.get_slats_angle()); - } - previous_angle = test_bed.get_slats_angle(); - test_bed = test_bed.run_one_tick(); - } - + ); assert!( - (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() <= angle_delta + (test_bed.get_slats_fppu_feedback() - test_bed.get_slats_demanded_angle()).abs() + <= angle_delta ); - assert!((test_bed.get_flaps_angle() - 0.).abs() < f64::EPSILON); } #[test] fn slats_test_movement_1f_to_2() { - let angle_delta = 0.01; + let angle_delta = 0.2; let mut test_bed = test_bed_with() .set_green_hyd_pressure() .set_indicated_airspeed(0.) @@ -1599,34 +1473,19 @@ mod tests { assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1F); - test_bed = test_bed.set_flaps_handle_position(2); + test_bed = test_bed + .set_flaps_handle_position(2) + .run_waiting_for(Duration::from_secs(40)); - let mut previous_angle: f64 = test_bed.get_slats_angle(); - test_bed = test_bed.run_one_tick(); - for _ in 0..300 { - if (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() - <= angle_delta - { - test_bed = test_bed.run_waiting_for(Duration::from_secs(5)); - assert!( - (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() - <= angle_delta - ); - break; - } else { - assert!(previous_angle < test_bed.get_slats_angle()); - } - previous_angle = test_bed.get_slats_angle(); - test_bed = test_bed.run_one_tick(); - } assert!( - (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() <= angle_delta + (test_bed.get_slats_fppu_feedback() - test_bed.get_slats_demanded_angle()).abs() + <= angle_delta ); } #[test] fn slats_test_movement_2_to_3() { - let angle_delta = 0.01; + let angle_delta = 0.2; let mut test_bed = test_bed_with() .set_green_hyd_pressure() .set_indicated_airspeed(0.) @@ -1635,34 +1494,19 @@ mod tests { assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf2); - test_bed = test_bed.set_flaps_handle_position(3); + test_bed = test_bed + .set_flaps_handle_position(3) + .run_waiting_for(Duration::from_secs(40)); - let mut previous_angle: f64 = test_bed.get_slats_angle(); - test_bed = test_bed.run_one_tick(); - for _ in 0..300 { - if (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() - <= angle_delta - { - test_bed = test_bed.run_waiting_for(Duration::from_secs(5)); - assert!( - (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() - <= angle_delta - ); - break; - } else { - assert!(previous_angle < test_bed.get_slats_angle()); - } - previous_angle = test_bed.get_slats_angle(); - test_bed = test_bed.run_one_tick(); - } assert!( - (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() <= angle_delta + (test_bed.get_slats_fppu_feedback() - test_bed.get_slats_demanded_angle()).abs() + <= angle_delta ); } #[test] fn slats_test_movement_3_to_full() { - let angle_delta = 0.01; + let angle_delta = 0.2; let mut test_bed = test_bed_with() .set_green_hyd_pressure() .set_indicated_airspeed(0.) @@ -1671,70 +1515,13 @@ mod tests { assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); - test_bed = test_bed.set_flaps_handle_position(4); + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(50)); - let mut previous_angle: f64 = test_bed.get_slats_angle(); - test_bed = test_bed.run_one_tick(); - for _ in 0..300 { - if (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() - <= angle_delta - { - test_bed = test_bed.run_waiting_for(Duration::from_secs(5)); - assert!( - (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() - <= angle_delta - ); - break; - } else { - assert!(previous_angle < test_bed.get_slats_angle()); - } - previous_angle = test_bed.get_slats_angle(); - test_bed = test_bed.run_one_tick(); - } assert!( - (test_bed.get_slats_angle() - test_bed.get_slats_demanded_angle()).abs() <= angle_delta + (test_bed.get_slats_fppu_feedback() - test_bed.get_slats_demanded_angle()).abs() + <= angle_delta ); } - - #[test] - fn only_flaps_move_when_yellow_only() { - let mut test_bed = test_bed_with() - .set_yellow_hyd_pressure() - .set_indicated_airspeed(0.) - .set_flaps_handle_position(0) - .run_one_tick(); - - test_bed = test_bed.set_flaps_handle_position(1); - - let starting_flap_angle: f64 = test_bed.get_flaps_angle(); - let starting_slat_angle: f64 = test_bed.get_slats_angle(); - - test_bed = test_bed.run_one_tick(); - for _ in 0..300 { - test_bed = test_bed.run_one_tick(); - } - assert!(test_bed.get_flaps_angle() > starting_flap_angle); - assert_about_eq!(test_bed.get_slats_angle(), starting_slat_angle); - } - - #[test] - fn only_slats_move_when_blue_only() { - let mut test_bed = test_bed_with() - .set_blue_hyd_pressure() - .set_indicated_airspeed(0.) - .set_flaps_handle_position(0) - .run_one_tick(); - - test_bed = test_bed.set_flaps_handle_position(1); - - let starting_flap_angle: f64 = test_bed.get_flaps_angle(); - let starting_slat_angle: f64 = test_bed.get_slats_angle(); - - test_bed = test_bed.run_one_tick(); - for _ in 0..300 { - test_bed = test_bed.run_one_tick(); - } - assert_about_eq!(test_bed.get_flaps_angle(), starting_flap_angle); - assert!(test_bed.get_slats_angle() > starting_slat_angle); - } } diff --git a/src/systems/a320_systems/src/hydraulic/mod.rs b/src/systems/a320_systems/src/hydraulic/mod.rs index 527509eeabe9..97eb21c3694b 100644 --- a/src/systems/a320_systems/src/hydraulic/mod.rs +++ b/src/systems/a320_systems/src/hydraulic/mod.rs @@ -29,13 +29,18 @@ use systems::{ landing_gear::{GearGravityExtension, GearSystemController, HydraulicGearSystem}, linear_actuator::{ Actuator, BoundedLinearLength, HydraulicAssemblyController, - HydraulicLinearActuatorAssembly, LinearActuatedRigidBodyOnHingeAxis, LinearActuator, - LinearActuatorMode, + HydraulicLinearActuatorAssembly, HydraulicLocking, LinearActuatedRigidBodyOnHingeAxis, + LinearActuator, LinearActuatorCharacteristics, LinearActuatorMode, }, nose_steering::{ Pushback, SteeringActuator, SteeringAngleLimiter, SteeringController, SteeringRatioToAngle, }, + pumps::PumpCharacteristics, + trimmable_horizontal_stabilizer::{ + ManualPitchTrimController, PitchTrimActuatorController, + TrimmableHorizontalStabilizerAssembly, + }, ElectricPump, EngineDrivenPump, HydraulicCircuit, HydraulicCircuitController, HydraulicPressureSensors, PowerTransferUnit, PowerTransferUnitCharacteristics, PowerTransferUnitController, PressureSwitch, PressureSwitchType, PumpController, @@ -129,11 +134,15 @@ pub struct A320HydraulicCircuitFactory {} impl A320HydraulicCircuitFactory { const MIN_PRESS_EDP_SECTION_LO_HYST: f64 = 1740.0; const MIN_PRESS_EDP_SECTION_HI_HYST: f64 = 2200.0; + const MIN_PRESS_BLUE_ELEC_PUMP_SECTION_LO_HYST: f64 = 1450.0; + const MIN_PRESS_BLUE_ELEC_PUMP_SECTION_HI_HYST: f64 = 1750.0; const MIN_PRESS_PRESSURISED_LO_HYST: f64 = 1450.0; const MIN_PRESS_PRESSURISED_HI_HYST: f64 = 1750.0; const YELLOW_GREEN_BLUE_PUMPS_INDEXES: usize = 0; + const HYDRAULIC_TARGET_PRESSURE_PSI: f64 = 3000.; + pub fn new_green_circuit(context: &mut InitContext) -> HydraulicCircuit { let reservoir = A320HydraulicReservoirFactory::new_green_reservoir(context); HydraulicCircuit::new( @@ -149,6 +158,8 @@ impl A320HydraulicCircuitFactory { Pressure::new::(Self::MIN_PRESS_EDP_SECTION_HI_HYST), true, false, + false, + Pressure::new::(Self::HYDRAULIC_TARGET_PRESSURE_PSI), ) } @@ -163,10 +174,12 @@ impl A320HydraulicCircuitFactory { reservoir, Pressure::new::(Self::MIN_PRESS_PRESSURISED_LO_HYST), Pressure::new::(Self::MIN_PRESS_PRESSURISED_HI_HYST), - Pressure::new::(Self::MIN_PRESS_EDP_SECTION_LO_HYST), - Pressure::new::(Self::MIN_PRESS_EDP_SECTION_HI_HYST), + Pressure::new::(Self::MIN_PRESS_BLUE_ELEC_PUMP_SECTION_LO_HYST), + Pressure::new::(Self::MIN_PRESS_BLUE_ELEC_PUMP_SECTION_HI_HYST), + false, false, false, + Pressure::new::(Self::HYDRAULIC_TARGET_PRESSURE_PSI), ) } @@ -185,6 +198,8 @@ impl A320HydraulicCircuitFactory { Pressure::new::(Self::MIN_PRESS_EDP_SECTION_HI_HYST), false, true, + false, + Pressure::new::(Self::HYDRAULIC_TARGET_PRESSURE_PSI), ) } } @@ -210,11 +225,14 @@ impl A320CargoDoorFactory { 1000000., Duration::from_millis(100), [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], [0., 0.2, 0.21, 0.79, 0.8, 1.], Self::FLOW_CONTROL_PROPORTIONAL_GAIN, Self::FLOW_CONTROL_INTEGRAL_GAIN, Self::FLOW_CONTROL_FORCE_GAIN, false, + false, + None, ) } @@ -226,10 +244,12 @@ impl A320CargoDoorFactory { let control_arm = Vector3::new(-0.1597, -0.1614, 0.); let anchor = Vector3::new(-0.7596, -0.086, 0.); let axis_direction = Vector3::new(0., 0., 1.); + LinearActuatedRigidBodyOnHingeAxis::new( Mass::new::(130.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(-23.), @@ -273,16 +293,19 @@ impl A320CargoDoorFactory { struct A320AileronFactory {} impl A320AileronFactory { - const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.35; - const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.; + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.25; + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 3.; const FLOW_CONTROL_FORCE_GAIN: f64 = 450000.; const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 3500000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 1.; fn a320_aileron_actuator(bounded_linear_length: &impl BoundedLinearLength) -> LinearActuator { - let randomized_damping = random_from_range( + let actuator_characteristics = LinearActuatorCharacteristics::new( Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING / 3., Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING, + VolumeRate::new::(0.055), + Ratio::new::(Self::MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), ); // Aileron actuator real data: @@ -295,25 +318,31 @@ impl A320AileronFactory { 1, Length::new::(0.0537878), Length::new::(0.), - VolumeRate::new::(0.055), + actuator_characteristics.max_flow(), 80000., 1500., 5000., - randomized_damping, + actuator_characteristics.slow_damping(), Duration::from_millis(300), [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], [0., 0.2, 0.21, 0.79, 0.8, 1.], Self::FLOW_CONTROL_PROPORTIONAL_GAIN, Self::FLOW_CONTROL_INTEGRAL_GAIN, Self::FLOW_CONTROL_FORCE_GAIN, false, + false, + None, ) } /// Builds an aileron control surface body for A320 Neo fn a320_aileron_body(init_drooped_down: bool) -> LinearActuatedRigidBodyOnHingeAxis { let size = Vector3::new(3.325, 0.16, 0.58); + + // CG at half the size let cg_offset = Vector3::new(0., 0., -0.5 * size[2]); + let aero_center = Vector3::new(0., 0., -0.4 * size[2]); let control_arm = Vector3::new(0., -0.0525, 0.); let anchor = Vector3::new(0., -0.0525, 0.33); @@ -328,6 +357,7 @@ impl A320AileronFactory { Mass::new::(24.65), size, cg_offset, + aero_center, control_arm, anchor, Angle::new::(-25.), @@ -361,11 +391,14 @@ impl A320AileronFactory { fn new_a320_aileron_aero_model() -> AerodynamicModel { let body = Self::a320_aileron_body(true); + + // Aerodynamic object has a little rotation from horizontal direction so that at X° + // of wing AOA the aileron gets some X°+Y° AOA as the overwing pressure sucks the aileron up AerodynamicModel::new( &body, Some(Vector3::new(0., 1., 0.)), - Some(Vector3::new(0., 0., 1.)), - Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., 0.208, 0.978)), + Some(Vector3::new(0., 0.978, -0.208)), Ratio::new::(1.), ) } @@ -379,20 +412,14 @@ impl A320SpoilerFactory { const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 400000.; - const MAX_FLOW_GAL_P_S: f64 = 0.03; const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 3.; fn a320_spoiler_actuator(bounded_linear_length: &impl BoundedLinearLength) -> LinearActuator { - let randomized_damping = random_from_range( + let actuator_characteristics = LinearActuatorCharacteristics::new( Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING / 5., Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING, - ); - - let random_max_flow_margin = - Self::MAX_FLOW_GAL_P_S * Self::MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT / 100.; - let random_max_flow_gal_per_s = random_from_range( - Self::MAX_FLOW_GAL_P_S - random_max_flow_margin, - Self::MAX_FLOW_GAL_P_S + random_max_flow_margin, + VolumeRate::new::(0.03), + Ratio::new::(Self::MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), ); LinearActuator::new( @@ -400,18 +427,24 @@ impl A320SpoilerFactory { 1, Length::new::(0.03), Length::new::(0.), - VolumeRate::new::(random_max_flow_gal_per_s), + actuator_characteristics.max_flow(), 80000., 1500., 5000., - randomized_damping, + actuator_characteristics.slow_damping(), Duration::from_millis(300), [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], [0., 0.2, 0.21, 0.79, 0.8, 1.], Self::FLOW_CONTROL_PROPORTIONAL_GAIN, Self::FLOW_CONTROL_INTEGRAL_GAIN, Self::FLOW_CONTROL_FORCE_GAIN, false, + true, + Some(( + AngularVelocity::new::(-10000.), + AngularVelocity::new::(0.), + )), ) } @@ -419,6 +452,7 @@ impl A320SpoilerFactory { fn a320_spoiler_body() -> LinearActuatedRigidBodyOnHingeAxis { let size = Vector3::new(1.785, 0.1, 0.685); let cg_offset = Vector3::new(0., 0., -0.5 * size[2]); + let aero_center = Vector3::new(0., 0., -0.4 * size[2]); let control_arm = Vector3::new(0., -0.067 * size[2], -0.26 * size[2]); let anchor = Vector3::new(0., -0.26 * size[2], 0.26 * size[2]); @@ -427,10 +461,11 @@ impl A320SpoilerFactory { Mass::new::(16.), size, cg_offset, + aero_center, control_arm, anchor, Angle::new::(-10.), - Angle::new::(40.), + Angle::new::(50.), Angle::new::(-10.), 50., false, @@ -454,7 +489,18 @@ impl A320SpoilerFactory { let spoiler_4 = Self::new_a320_spoiler_element(context, id, 4); let spoiler_5 = Self::new_a320_spoiler_element(context, id, 5); - SpoilerGroup::new([spoiler_1, spoiler_2, spoiler_3, spoiler_4, spoiler_5]) + match id { + ActuatorSide::Left => SpoilerGroup::new( + context, + "LEFT", + [spoiler_1, spoiler_2, spoiler_3, spoiler_4, spoiler_5], + ), + ActuatorSide::Right => SpoilerGroup::new( + context, + "RIGHT", + [spoiler_1, spoiler_2, spoiler_3, spoiler_4, spoiler_5], + ), + } } fn new_a320_spoiler_element( @@ -495,11 +541,14 @@ impl A320ElevatorFactory { const FLOW_CONTROL_FORCE_GAIN: f64 = 450000.; const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 15000000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 1.; fn a320_elevator_actuator(bounded_linear_length: &impl BoundedLinearLength) -> LinearActuator { - let randomized_damping = random_from_range( + let actuator_characteristics = LinearActuatorCharacteristics::new( Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING / 5., Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING, + VolumeRate::new::(0.029), + Ratio::new::(Self::MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), ); LinearActuator::new( @@ -507,18 +556,21 @@ impl A320ElevatorFactory { 1, Length::new::(0.0407), Length::new::(0.), - VolumeRate::new::(0.029), + actuator_characteristics.max_flow(), 80000., 1500., 20000., - randomized_damping, + actuator_characteristics.slow_damping(), Duration::from_millis(300), [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], [0., 0.2, 0.21, 0.79, 0.8, 1.], Self::FLOW_CONTROL_PROPORTIONAL_GAIN, Self::FLOW_CONTROL_INTEGRAL_GAIN, Self::FLOW_CONTROL_FORCE_GAIN, false, + false, + None, ) } @@ -526,12 +578,13 @@ impl A320ElevatorFactory { fn a320_elevator_body(init_drooped_down: bool) -> LinearActuatedRigidBodyOnHingeAxis { let size = Vector3::new(6., 0.405, 1.125); let cg_offset = Vector3::new(0., 0., -0.5 * size[2]); + let aero_center = Vector3::new(0., 0., -0.3 * size[2]); let control_arm = Vector3::new(0., -0.091, 0.); let anchor = Vector3::new(0., -0.091, 0.41); let init_position = if init_drooped_down { - Angle::new::(-17.) + Angle::new::(-11.5) } else { Angle::new::(0.) }; @@ -540,10 +593,11 @@ impl A320ElevatorFactory { Mass::new::(58.6), size, cg_offset, + aero_center, control_arm, anchor, - Angle::new::(-17.), - Angle::new::(47.), + Angle::new::(-11.5), + Angle::new::(27.5), init_position, 100., false, @@ -590,11 +644,14 @@ impl A320RudderFactory { const FLOW_CONTROL_FORCE_GAIN: f64 = 350000.; const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 1000000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 1.; fn a320_rudder_actuator(bounded_linear_length: &impl BoundedLinearLength) -> LinearActuator { - let randomized_damping = random_from_range( + let actuator_characteristics = LinearActuatorCharacteristics::new( Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING / 4., Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING, + VolumeRate::new::(0.0792), + Ratio::new::(Self::MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), ); LinearActuator::new( @@ -602,18 +659,21 @@ impl A320RudderFactory { 1, Length::new::(0.06), Length::new::(0.), - VolumeRate::new::(0.0792), + actuator_characteristics.max_flow(), 80000., 1500., 10000., - randomized_damping, + actuator_characteristics.slow_damping(), Duration::from_millis(300), [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], [0., 0.2, 0.21, 0.79, 0.8, 1.], Self::FLOW_CONTROL_PROPORTIONAL_GAIN, Self::FLOW_CONTROL_INTEGRAL_GAIN, Self::FLOW_CONTROL_FORCE_GAIN, false, + false, + None, ) } @@ -621,6 +681,7 @@ impl A320RudderFactory { fn a320_rudder_body(init_at_center: bool) -> LinearActuatedRigidBodyOnHingeAxis { let size = Vector3::new(0.42, 6.65, 1.8); let cg_offset = Vector3::new(0., 0.5 * size[1], -0.5 * size[2]); + let aero_center = Vector3::new(0., 0.5 * size[1], -0.3 * size[2]); let control_arm = Vector3::new(-0.144, 0., 0.); let anchor = Vector3::new(-0.144, 0., 0.50); @@ -635,6 +696,7 @@ impl A320RudderFactory { Mass::new::(95.), size, cg_offset, + aero_center, control_arm, anchor, Angle::new::(-25.), @@ -651,15 +713,15 @@ impl A320RudderFactory { fn a320_rudder_assembly(init_at_center: bool) -> HydraulicLinearActuatorAssembly<3> { let rudder_body = Self::a320_rudder_body(init_at_center); - let elevator_actuator_green = Self::a320_rudder_actuator(&rudder_body); - let elevator_actuator_blue = Self::a320_rudder_actuator(&rudder_body); - let elevator_actuator_yellow = Self::a320_rudder_actuator(&rudder_body); + let rudder_actuator_green = Self::a320_rudder_actuator(&rudder_body); + let rudder_actuator_blue = Self::a320_rudder_actuator(&rudder_body); + let rudder_actuator_yellow = Self::a320_rudder_actuator(&rudder_body); HydraulicLinearActuatorAssembly::new( [ - elevator_actuator_green, - elevator_actuator_blue, - elevator_actuator_yellow, + rudder_actuator_green, + rudder_actuator_blue, + rudder_actuator_yellow, ], rudder_body, ) @@ -688,6 +750,38 @@ impl A320RudderFactory { struct A320GearDoorFactory {} impl A320GearDoorFactory { + fn a320_nose_gear_door_aerodynamics() -> AerodynamicModel { + // Faking the single door by only considering right door aerodynamics. + // Will work with headwind, but will cause strange behaviour with massive crosswind. + AerodynamicModel::new( + &Self::a320_nose_gear_door_body(), + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., -0.2, 1.)), + Some(Vector3::new(0., -1., -0.2)), + Ratio::new::(0.7), + ) + } + + fn a320_left_gear_door_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a320_left_gear_door_body(), + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., -0.1, 1.)), + Some(Vector3::new(0., 1., 0.1)), + Ratio::new::(0.7), + ) + } + + fn a320_right_gear_door_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a320_right_gear_door_body(), + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., -0.1, 1.)), + Some(Vector3::new(0., 1., 0.1)), + Ratio::new::(0.7), + ) + } + fn a320_nose_gear_door_actuator( bounded_linear_length: &impl BoundedLinearLength, ) -> LinearActuator { @@ -695,23 +789,36 @@ impl A320GearDoorFactory { const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.15; const FLOW_CONTROL_FORCE_GAIN: f64 = 200000.; + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 28000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 3.; + + let actuator_characteristics = LinearActuatorCharacteristics::new( + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 0.98, + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 1.02, + VolumeRate::new::(0.027), + Ratio::new::(MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + LinearActuator::new( bounded_linear_length, 1, Length::new::(0.0378), Length::new::(0.023), - VolumeRate::new::(0.027), + actuator_characteristics.max_flow(), 20000., 5000., 2000., - 28000., + actuator_characteristics.slow_damping(), Duration::from_millis(100), - [0.5, 0.5, 1., 1., 0.5, 0.5], + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], [0., 0.15, 0.16, 0.84, 0.85, 1.], FLOW_CONTROL_PROPORTIONAL_GAIN, FLOW_CONTROL_INTEGRAL_GAIN, FLOW_CONTROL_FORCE_GAIN, true, + false, + None, ) } @@ -722,23 +829,36 @@ impl A320GearDoorFactory { const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.7; const FLOW_CONTROL_FORCE_GAIN: f64 = 200000.; + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 30000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 5.; + + let actuator_characteristics = LinearActuatorCharacteristics::new( + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 0.98, + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 1.02, + VolumeRate::new::(0.09), + Ratio::new::(MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + LinearActuator::new( bounded_linear_length, 1, Length::new::(0.055), Length::new::(0.03), - VolumeRate::new::(0.09), - 20000., - 5000., + actuator_characteristics.max_flow(), + 200000., + 2500., 2000., - 9000., + actuator_characteristics.slow_damping(), Duration::from_millis(100), - [0.5, 0.5, 1., 1., 0.5, 0.5], - [0., 0.15, 0.16, 0.84, 0.85, 1.], + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], + [0., 0.07, 0.08, 0.9, 0.91, 1.], FLOW_CONTROL_PROPORTIONAL_GAIN, FLOW_CONTROL_INTEGRAL_GAIN, FLOW_CONTROL_FORCE_GAIN, true, + false, + None, ) } @@ -753,6 +873,7 @@ impl A320GearDoorFactory { Mass::new::(50.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(0.), @@ -775,6 +896,7 @@ impl A320GearDoorFactory { Mass::new::(50.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(-85.), @@ -787,8 +909,8 @@ impl A320GearDoorFactory { } fn a320_nose_gear_door_body() -> LinearActuatedRigidBodyOnHingeAxis { - let size = Vector3::new(-0.4, 0.02, 1.5); - let cg_offset = Vector3::new(0.5 * size[0], 0., 0.); + let size = Vector3::new(0.4, 0.02, 1.5); + let cg_offset = Vector3::new(-0.5 * size[0], 0., 0.); let control_arm = Vector3::new(-0.1465, 0., 0.); let anchor = Vector3::new(-0.1465, 0.40, 0.); @@ -797,6 +919,7 @@ impl A320GearDoorFactory { Mass::new::(40.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(0.), @@ -827,28 +950,71 @@ impl A320GearDoorFactory { struct A320GearFactory {} impl A320GearFactory { + fn a320_nose_gear_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a320_nose_gear_body(true), + Some(Vector3::new(0., 0., 1.)), + None, + None, + Ratio::new::(0.25), + ) + } + + fn a320_right_gear_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a320_right_gear_body(true), + Some(Vector3::new(0., 0., 1.)), + Some(Vector3::new(0.3, 0., 1.)), + Some(Vector3::new(1., 0., -0.3)), + Ratio::new::(0.7), + ) + } + + fn a320_left_gear_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a320_left_gear_body(true), + Some(Vector3::new(0., 0., 1.)), + Some(Vector3::new(-0.3, 0., 1.)), + Some(Vector3::new(-1., 0., -0.3)), + Ratio::new::(0.7), + ) + } + fn a320_nose_gear_actuator(bounded_linear_length: &impl BoundedLinearLength) -> LinearActuator { const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.; const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.3; const FLOW_CONTROL_FORCE_GAIN: f64 = 250000.; + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 900000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 3.; + + let actuator_characteristics = LinearActuatorCharacteristics::new( + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 0.98, + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 1.02, + VolumeRate::new::(0.053), + Ratio::new::(MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + LinearActuator::new( bounded_linear_length, 1, Length::new::(0.0792), Length::new::(0.035), - VolumeRate::new::(0.053), + actuator_characteristics.max_flow(), 800000., 150000., 50000., - 1000000., + actuator_characteristics.slow_damping(), Duration::from_millis(100), - [0.5, 0.5, 1., 1., 0.5, 0.5], + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], [0., 0.1, 0.11, 0.89, 0.9, 1.], FLOW_CONTROL_PROPORTIONAL_GAIN, FLOW_CONTROL_INTEGRAL_GAIN, FLOW_CONTROL_FORCE_GAIN, true, + false, + None, ) } @@ -857,23 +1023,36 @@ impl A320GearFactory { const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.3; const FLOW_CONTROL_FORCE_GAIN: f64 = 250000.; + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 2500000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 5.; + + let actuator_characteristics = LinearActuatorCharacteristics::new( + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 0.98, + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 1.02, + VolumeRate::new::(0.17), + Ratio::new::(MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + LinearActuator::new( bounded_linear_length, 1, Length::new::(0.145), Length::new::(0.105), - VolumeRate::new::(0.17), + actuator_characteristics.max_flow(), 800000., 350000., 50000., - 2500000., + actuator_characteristics.slow_damping(), Duration::from_millis(100), - [0.5, 0.5, 1., 1., 0.5, 0.5], - [0., 0.1, 0.11, 0.89, 0.9, 1.], + [1., 1., 1., 1., 0.5, 0.5], + [0.2, 0.4, 1., 1., 1., 1.], + [0., 0.13, 0.17, 0.95, 0.96, 1.], FLOW_CONTROL_PROPORTIONAL_GAIN, FLOW_CONTROL_INTEGRAL_GAIN, FLOW_CONTROL_FORCE_GAIN, true, + false, + None, ) } @@ -888,6 +1067,7 @@ impl A320GearFactory { Mass::new::(700.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(0.), @@ -914,6 +1094,7 @@ impl A320GearFactory { Mass::new::(700.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(-80.), @@ -940,6 +1121,7 @@ impl A320GearFactory { Mass::new::(300.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(-101.), @@ -990,6 +1172,12 @@ impl A320GearSystemFactory { A320GearFactory::a320_gear_assembly(GearWheel::NOSE, init_downlocked), A320GearFactory::a320_gear_assembly(GearWheel::LEFT, init_downlocked), A320GearFactory::a320_gear_assembly(GearWheel::RIGHT, init_downlocked), + A320GearDoorFactory::a320_left_gear_door_aerodynamics(), + A320GearDoorFactory::a320_right_gear_door_aerodynamics(), + A320GearDoorFactory::a320_nose_gear_door_aerodynamics(), + A320GearFactory::a320_left_gear_aerodynamics(), + A320GearFactory::a320_right_gear_aerodynamics(), + A320GearFactory::a320_nose_gear_aerodynamics(), ) } } @@ -1194,16 +1382,17 @@ pub(super) struct A320Hydraulic { aft_cargo_door: CargoDoor, aft_cargo_door_controller: A320DoorController, - elac_computer: ElacComputer, + elevator_system_controller: ElevatorSystemHydraulicController, + aileron_system_controller: AileronSystemHydraulicController, + left_aileron: AileronAssembly, right_aileron: AileronAssembly, left_elevator: ElevatorAssembly, right_elevator: ElevatorAssembly, - fac_computer: FacComputer, + fac_computer: RudderSystemHydraulicController, rudder: RudderAssembly, - spoiler_computer: SpoilerComputer, left_spoilers: SpoilerGroup, right_spoilers: SpoilerGroup, @@ -1212,6 +1401,10 @@ pub(super) struct A320Hydraulic { gear_system: HydraulicGearSystem, ptu_high_pitch_sound_active: DelayedFalseLogicGate, + + trim_controller: A320TrimInputController, + + trim_assembly: TrimmableHorizontalStabilizerAssembly, } impl A320Hydraulic { const HIGH_PITCH_PTU_SOUND_DELTA_PRESS_THRESHOLD_PSI: f64 = 2400.; @@ -1305,14 +1498,22 @@ impl A320Hydraulic { HydraulicColor::Yellow, ), - engine_driven_pump_1: EngineDrivenPump::new(context, "GREEN"), + engine_driven_pump_1: EngineDrivenPump::new( + context, + "GREEN", + PumpCharacteristics::a320_edp(), + ), engine_driven_pump_1_controller: A320EngineDrivenPumpController::new( context, 1, vec![Self::GREEN_EDP_CONTROL_POWER_BUS1], ), - engine_driven_pump_2: EngineDrivenPump::new(context, "YELLOW"), + engine_driven_pump_2: EngineDrivenPump::new( + context, + "YELLOW", + PumpCharacteristics::a320_edp(), + ), engine_driven_pump_2_controller: A320EngineDrivenPumpController::new( context, 2, @@ -1327,6 +1528,7 @@ impl A320Hydraulic { "BLUE", Self::BLUE_ELEC_PUMP_SUPPLY_POWER_BUS, ElectricCurrent::new::(Self::ELECTRIC_PUMP_MAX_CURRENT_AMPERE), + PumpCharacteristics::a320_electric_pump(), ), blue_electric_pump_controller: A320BlueElectricPumpController::new( context, @@ -1338,6 +1540,7 @@ impl A320Hydraulic { "YELLOW", Self::YELLOW_ELEC_PUMP_SUPPLY_POWER_BUS, ElectricCurrent::new::(Self::ELECTRIC_PUMP_MAX_CURRENT_AMPERE), + PumpCharacteristics::a320_electric_pump(), ), yellow_electric_pump_controller: A320YellowElectricPumpController::new( context, @@ -1347,7 +1550,7 @@ impl A320Hydraulic { pushback_tug: PushbackTug::new(context), - ram_air_turbine: RamAirTurbine::new(context), + ram_air_turbine: RamAirTurbine::new(context, PumpCharacteristics::a320_rat()), ram_air_turbine_controller: A320RamAirTurbineController::new( Self::RAT_CONTROL_SOLENOID1_POWER_BUS, Self::RAT_CONTROL_SOLENOID2_POWER_BUS, @@ -1368,6 +1571,7 @@ impl A320Hydraulic { Volume::new::(0.), Volume::new::(0.), Volume::new::(0.13), + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), ), // Alternate brakes accumulator in real A320 is 1.5 gal capacity. @@ -1379,6 +1583,7 @@ impl A320Hydraulic { Volume::new::(1.0), Volume::new::(0.4), Volume::new::(0.13), + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), ), braking_force: A320BrakingForce::new(context), @@ -1394,6 +1599,7 @@ impl A320Hydraulic { Ratio::new::(314.98), Self::FLAP_FPPU_TO_SURFACE_ANGLE_BREAKPTS, Self::FLAP_FPPU_TO_SURFACE_ANGLE_DEGREES, + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), ), slat_system: FlapSlatAssembly::new( context, @@ -1406,6 +1612,7 @@ impl A320Hydraulic { Ratio::new::(314.98), Self::SLAT_FPPU_TO_SURFACE_ANGLE_BREAKPTS, Self::SLAT_FPPU_TO_SURFACE_ANGLE_DEGREES, + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), ), slats_flaps_complex: SlatFlapComplex::new(context), @@ -1433,16 +1640,17 @@ impl A320Hydraulic { ), aft_cargo_door_controller: A320DoorController::new(context, Self::AFT_CARGO_DOOR_ID), - elac_computer: ElacComputer::new(context), + elevator_system_controller: ElevatorSystemHydraulicController::new(context), + aileron_system_controller: AileronSystemHydraulicController::new(context), + left_aileron: A320AileronFactory::new_aileron(context, ActuatorSide::Left), right_aileron: A320AileronFactory::new_aileron(context, ActuatorSide::Right), left_elevator: A320ElevatorFactory::new_elevator(context, ActuatorSide::Left), right_elevator: A320ElevatorFactory::new_elevator(context, ActuatorSide::Right), - fac_computer: FacComputer::new(context), + fac_computer: RudderSystemHydraulicController::new(context), rudder: A320RudderFactory::new_rudder(context), - spoiler_computer: SpoilerComputer::new(context), left_spoilers: A320SpoilerFactory::new_a320_spoiler_group(context, ActuatorSide::Left), right_spoilers: A320SpoilerFactory::new_a320_spoiler_group( context, @@ -1456,6 +1664,19 @@ impl A320Hydraulic { ptu_high_pitch_sound_active: DelayedFalseLogicGate::new( Self::HIGH_PITCH_PTU_SOUND_DURATION, ), + + trim_controller: A320TrimInputController::new(context), + trim_assembly: TrimmableHorizontalStabilizerAssembly::new( + context, + Angle::new::(360. * -1.4), + Angle::new::(360. * 6.13), + Angle::new::(360. * -1.87), + Angle::new::(360. * 8.19), // 1.87 rotations down 6.32 up, + AngularVelocity::new::(5000.), + Ratio::new::(2035. / 6.13), + Angle::new::(-4.), + Angle::new::(17.5), + ), } } @@ -1607,28 +1828,28 @@ impl A320Hydraulic { ) { self.left_aileron.update( context, - self.elac_computer.left_controllers(), + self.aileron_system_controller.left_controllers(), self.blue_circuit.system_section(), self.green_circuit.system_section(), ); self.right_aileron.update( context, - self.elac_computer.right_controllers(), + self.aileron_system_controller.right_controllers(), self.blue_circuit.system_section(), self.green_circuit.system_section(), ); self.left_elevator.update( context, - self.elac_computer.left_elevator_controllers(), + self.elevator_system_controller.left_controllers(), self.blue_circuit.system_section(), self.green_circuit.system_section(), ); self.right_elevator.update( context, - self.elac_computer.right_elevator_controllers(), + self.elevator_system_controller.right_controllers(), self.blue_circuit.system_section(), self.yellow_circuit.system_section(), ); @@ -1643,7 +1864,6 @@ impl A320Hydraulic { self.left_spoilers.update( context, - self.spoiler_computer.left_controllers(), self.green_circuit.system_section(), self.blue_circuit.system_section(), self.yellow_circuit.system_section(), @@ -1651,7 +1871,6 @@ impl A320Hydraulic { self.right_spoilers.update( context, - self.spoiler_computer.right_controllers(), self.green_circuit.system_section(), self.blue_circuit.system_section(), self.yellow_circuit.system_section(), @@ -1709,15 +1928,26 @@ impl A320Hydraulic { emergency_elec, ); - self.gear_system_gravity_extension_controller - .update(context); - self.gear_system_hydraulic_controller.update( adirs, lgciu1, lgciu2, &self.gear_system_gravity_extension_controller, ); + + self.trim_assembly.update( + context, + &self.trim_controller, + &self.trim_controller, + [ + self.green_circuit + .system_section() + .pressure_downstream_leak_valve(), + self.yellow_circuit + .system_section() + .pressure_downstream_leak_valve(), + ], + ); } fn update_with_sim_rate( @@ -1804,12 +2034,6 @@ impl A320Hydraulic { self.yellow_circuit.system_section(), ); - self.elac_computer.update( - self.blue_circuit.system_section(), - self.green_circuit.system_section(), - self.yellow_circuit.system_section(), - ); - self.slats_flaps_complex .update(context, &self.flap_system, &self.slat_system); @@ -1829,110 +2053,114 @@ impl A320Hydraulic { fn update_green_actuators_volume(&mut self) { self.green_circuit - .update_actuator_volumes(&mut self.braking_circuit_norm); + .update_system_actuator_volumes(&mut self.braking_circuit_norm); - self.green_circuit - .update_actuator_volumes(self.left_aileron.actuator(AileronActuatorPosition::Inboard)); - self.green_circuit.update_actuator_volumes( - self.right_aileron - .actuator(AileronActuatorPosition::Inboard), + self.green_circuit.update_system_actuator_volumes( + self.left_aileron.actuator(AileronActuatorPosition::Green), + ); + self.green_circuit.update_system_actuator_volumes( + self.right_aileron.actuator(AileronActuatorPosition::Green), ); - self.green_circuit.update_actuator_volumes( + self.green_circuit.update_system_actuator_volumes( self.left_elevator - .actuator(ElevatorActuatorPosition::Inboard), + .actuator(LeftElevatorActuatorCircuit::Green as usize), ); self.green_circuit - .update_actuator_volumes(self.rudder.actuator(RudderActuatorPosition::Green)); + .update_system_actuator_volumes(self.rudder.actuator(RudderActuatorPosition::Green)); self.green_circuit - .update_actuator_volumes(self.flap_system.left_motor()); + .update_system_actuator_volumes(self.flap_system.left_motor()); self.green_circuit - .update_actuator_volumes(self.slat_system.right_motor()); + .update_system_actuator_volumes(self.slat_system.right_motor()); self.green_circuit - .update_actuator_volumes(self.left_spoilers.actuator(0)); + .update_system_actuator_volumes(self.left_spoilers.actuator(0)); self.green_circuit - .update_actuator_volumes(self.left_spoilers.actuator(4)); + .update_system_actuator_volumes(self.left_spoilers.actuator(4)); self.green_circuit - .update_actuator_volumes(self.right_spoilers.actuator(0)); + .update_system_actuator_volumes(self.right_spoilers.actuator(0)); self.green_circuit - .update_actuator_volumes(self.right_spoilers.actuator(4)); + .update_system_actuator_volumes(self.right_spoilers.actuator(4)); for actuator in self.gear_system.all_actuators() { - self.green_circuit.update_actuator_volumes(actuator); + self.green_circuit.update_system_actuator_volumes(actuator); } + + self.green_circuit + .update_system_actuator_volumes(self.trim_assembly.left_motor()); } fn update_yellow_actuators_volume(&mut self) { self.yellow_circuit - .update_actuator_volumes(&mut self.braking_circuit_altn); + .update_system_actuator_volumes(&mut self.braking_circuit_altn); self.yellow_circuit - .update_actuator_volumes(self.flap_system.right_motor()); + .update_system_actuator_volumes(self.flap_system.right_motor()); self.yellow_circuit - .update_actuator_volumes(self.forward_cargo_door.actuator()); + .update_system_actuator_volumes(self.forward_cargo_door.actuator()); self.yellow_circuit - .update_actuator_volumes(self.aft_cargo_door.actuator()); + .update_system_actuator_volumes(self.aft_cargo_door.actuator()); self.yellow_circuit - .update_actuator_volumes(&mut self.nose_steering); + .update_system_actuator_volumes(&mut self.nose_steering); - self.yellow_circuit.update_actuator_volumes( + self.yellow_circuit.update_system_actuator_volumes( self.right_elevator - .actuator(ElevatorActuatorPosition::Inboard), + .actuator(RightElevatorActuatorCircuit::Yellow as usize), ); self.yellow_circuit - .update_actuator_volumes(self.rudder.actuator(RudderActuatorPosition::Yellow)); + .update_system_actuator_volumes(self.rudder.actuator(RudderActuatorPosition::Yellow)); self.yellow_circuit - .update_actuator_volumes(self.left_spoilers.actuator(1)); + .update_system_actuator_volumes(self.left_spoilers.actuator(1)); self.yellow_circuit - .update_actuator_volumes(self.left_spoilers.actuator(3)); + .update_system_actuator_volumes(self.left_spoilers.actuator(3)); self.yellow_circuit - .update_actuator_volumes(self.right_spoilers.actuator(1)); + .update_system_actuator_volumes(self.right_spoilers.actuator(1)); + self.yellow_circuit + .update_system_actuator_volumes(self.right_spoilers.actuator(3)); + self.yellow_circuit - .update_actuator_volumes(self.right_spoilers.actuator(3)); + .update_system_actuator_volumes(self.trim_assembly.right_motor()); } fn update_blue_actuators_volume(&mut self) { self.blue_circuit - .update_actuator_volumes(self.slat_system.left_motor()); + .update_system_actuator_volumes(self.slat_system.left_motor()); self.blue_circuit - .update_actuator_volumes(&mut self.emergency_gen); + .update_system_actuator_volumes(&mut self.emergency_gen); - self.blue_circuit.update_actuator_volumes( - self.left_aileron - .actuator(AileronActuatorPosition::Outboard), + self.blue_circuit.update_system_actuator_volumes( + self.left_aileron.actuator(AileronActuatorPosition::Blue), ); - self.blue_circuit.update_actuator_volumes( - self.right_aileron - .actuator(AileronActuatorPosition::Outboard), + self.blue_circuit.update_system_actuator_volumes( + self.right_aileron.actuator(AileronActuatorPosition::Blue), ); - self.blue_circuit.update_actuator_volumes( + self.blue_circuit.update_system_actuator_volumes( self.left_elevator - .actuator(ElevatorActuatorPosition::Outboard), + .actuator(LeftElevatorActuatorCircuit::Blue as usize), ); - self.blue_circuit.update_actuator_volumes( + self.blue_circuit.update_system_actuator_volumes( self.right_elevator - .actuator(ElevatorActuatorPosition::Outboard), + .actuator(RightElevatorActuatorCircuit::Blue as usize), ); self.blue_circuit - .update_actuator_volumes(self.rudder.actuator(RudderActuatorPosition::Blue)); + .update_system_actuator_volumes(self.rudder.actuator(RudderActuatorPosition::Blue)); self.blue_circuit - .update_actuator_volumes(self.left_spoilers.actuator(2)); + .update_system_actuator_volumes(self.left_spoilers.actuator(2)); self.blue_circuit - .update_actuator_volumes(self.right_spoilers.actuator(2)); + .update_system_actuator_volumes(self.right_spoilers.actuator(2)); } // All the core hydraulics updates that needs to be done at the slowest fixed step rate @@ -2053,6 +2281,7 @@ impl A320Hydraulic { context, &mut vec![&mut self.engine_driven_pump_1], None::<&mut ElectricPump>, + None::<&mut ElectricPump>, Some(&self.power_transfer_unit), &self.green_circuit_controller, reservoir_pneumatics.green_reservoir_pressure(), @@ -2068,6 +2297,7 @@ impl A320Hydraulic { context, &mut vec![&mut self.engine_driven_pump_2], Some(&mut self.yellow_electric_pump), + None::<&mut ElectricPump>, Some(&self.power_transfer_unit), &self.yellow_circuit_controller, reservoir_pneumatics.yellow_reservoir_pressure(), @@ -2083,6 +2313,7 @@ impl A320Hydraulic { context, &mut vec![&mut self.blue_electric_pump], Some(&mut self.ram_air_turbine), + None::<&mut ElectricPump>, None, &self.blue_circuit_controller, reservoir_pneumatics.blue_reservoir_pressure(), @@ -2197,7 +2428,9 @@ impl SimulationElement for A320Hydraulic { self.flap_system.accept(visitor); self.slat_system.accept(visitor); - self.elac_computer.accept(visitor); + self.elevator_system_controller.accept(visitor); + self.aileron_system_controller.accept(visitor); + self.left_aileron.accept(visitor); self.right_aileron.accept(visitor); self.left_elevator.accept(visitor); @@ -2206,7 +2439,6 @@ impl SimulationElement for A320Hydraulic { self.fac_computer.accept(visitor); self.rudder.accept(visitor); - self.spoiler_computer.accept(visitor); self.left_spoilers.accept(visitor); self.right_spoilers.accept(visitor); @@ -2214,6 +2446,9 @@ impl SimulationElement for A320Hydraulic { .accept(visitor); self.gear_system.accept(visitor); + self.trim_controller.accept(visitor); + self.trim_assembly.accept(visitor); + visitor.visit(self); } @@ -3756,6 +3991,7 @@ impl SimulationElement for A320DoorController { self.position_requested = Ratio::new::(reader.read(&self.requested_position_id)); } } +impl HydraulicLocking for A320DoorController {} struct CargoDoor { hydraulic_assembly: HydraulicLinearActuatorAssembly<1>, @@ -3803,7 +4039,7 @@ impl CargoDoor { fn update( &mut self, context: &UpdateContext, - cargo_door_controller: &impl HydraulicAssemblyController, + cargo_door_controller: &(impl HydraulicAssemblyController + HydraulicLocking), current_pressure: &impl SectionPressure, ) { self.aerodynamic_model @@ -3913,7 +4149,9 @@ pub struct A320AutobrakeController { armed_mode_id_set: VariableIdentifier, decel_light_id: VariableIdentifier, active_id: VariableIdentifier, - spoilers_ground_spoilers_active_id: VariableIdentifier, + ground_spoilers_out_sec1_id: VariableIdentifier, + ground_spoilers_out_sec2_id: VariableIdentifier, + ground_spoilers_out_sec3_id: VariableIdentifier, external_disarm_event_id: VariableIdentifier, deceleration_governor: AutobrakeDecelerationGovernor, @@ -3955,8 +4193,12 @@ impl A320AutobrakeController { armed_mode_id_set: context.get_identifier("AUTOBRAKES_ARMED_MODE_SET".to_owned()), decel_light_id: context.get_identifier("AUTOBRAKES_DECEL_LIGHT".to_owned()), active_id: context.get_identifier("AUTOBRAKES_ACTIVE".to_owned()), - spoilers_ground_spoilers_active_id: context - .get_identifier("SPOILERS_GROUND_SPOILERS_ACTIVE".to_owned()), + ground_spoilers_out_sec1_id: context + .get_identifier("SEC_1_GROUND_SPOILER_OUT".to_owned()), + ground_spoilers_out_sec2_id: context + .get_identifier("SEC_2_GROUND_SPOILER_OUT".to_owned()), + ground_spoilers_out_sec3_id: context + .get_identifier("SEC_3_GROUND_SPOILER_OUT".to_owned()), external_disarm_event_id: context.get_identifier("AUTOBRAKE_DISARM".to_owned()), deceleration_governor: AutobrakeDecelerationGovernor::new(), @@ -4153,7 +4395,12 @@ impl SimulationElement for A320AutobrakeController { fn read(&mut self, reader: &mut SimulatorReader) { self.last_ground_spoilers_are_deployed = self.ground_spoilers_are_deployed; - self.ground_spoilers_are_deployed = reader.read(&self.spoilers_ground_spoilers_active_id); + let sec_1_gnd_splrs_out = reader.read(&self.ground_spoilers_out_sec1_id); + let sec_2_gnd_splrs_out = reader.read(&self.ground_spoilers_out_sec2_id); + let sec_3_gnd_splrs_out = reader.read(&self.ground_spoilers_out_sec3_id); + self.ground_spoilers_are_deployed = (sec_1_gnd_splrs_out && sec_2_gnd_splrs_out) + || (sec_1_gnd_splrs_out && sec_3_gnd_splrs_out) + || (sec_2_gnd_splrs_out && sec_3_gnd_splrs_out); self.external_disarm_event = reader.read(&self.external_disarm_event_id); // Reading current mode in sim to initialize correct mode if sim changes it (from .FLT files for example) @@ -4336,414 +4583,333 @@ impl HydraulicAssemblyController for AileronController { Ratio::default() } } +impl HydraulicLocking for AileronController {} -enum AileronHydConfiguration { - GB, - G, - B, - NoHyd, -} -impl AileronHydConfiguration { - fn from_hyd_state( - green_circuit_available: bool, - blue_circuit_available: bool, - ) -> AileronHydConfiguration { - if green_circuit_available && blue_circuit_available { - AileronHydConfiguration::GB - } else if green_circuit_available { - AileronHydConfiguration::G - } else if blue_circuit_available { - AileronHydConfiguration::B - } else { - AileronHydConfiguration::NoHyd - } - } -} +struct AileronSystemHydraulicController { + left_aileron_blue_actuator_solenoid_id: VariableIdentifier, + right_aileron_blue_actuator_solenoid_id: VariableIdentifier, + left_aileron_green_actuator_solenoid_id: VariableIdentifier, + right_aileron_green_actuator_solenoid_id: VariableIdentifier, -enum RightElevatorHydConfiguration { - YB, - Y, - B, - NoHyd, -} -impl RightElevatorHydConfiguration { - fn from_hyd_state( - blue_circuit_available: bool, - yellow_circuit_available: bool, - ) -> RightElevatorHydConfiguration { - if yellow_circuit_available && blue_circuit_available { - RightElevatorHydConfiguration::YB - } else if yellow_circuit_available { - RightElevatorHydConfiguration::Y - } else if blue_circuit_available { - RightElevatorHydConfiguration::B - } else { - RightElevatorHydConfiguration::NoHyd - } - } -} -enum LeftElevatorHydConfiguration { - GB, - G, - B, - NoHyd, -} -impl LeftElevatorHydConfiguration { - fn from_hyd_state( - green_circuit_available: bool, - blue_circuit_available: bool, - ) -> LeftElevatorHydConfiguration { - if green_circuit_available && blue_circuit_available { - LeftElevatorHydConfiguration::GB - } else if green_circuit_available { - LeftElevatorHydConfiguration::G - } else if blue_circuit_available { - LeftElevatorHydConfiguration::B - } else { - LeftElevatorHydConfiguration::NoHyd - } - } -} - -/// Implements a placeholder elac computer logic commanding correct hydraulic modes depending -/// on pressure state. -/// TODO: Receive each actuator mode and commands directly from a FBW Elac implementation -struct ElacComputer { - left_aileron_requested_position_id: VariableIdentifier, - right_aileron_requested_position_id: VariableIdentifier, - elevator_requested_position_id: VariableIdentifier, - - left_aileron_requested_position: Ratio, - right_aileron_requested_position: Ratio, - elevator_requested_position: Ratio, + left_aileron_blue_actuator_position_demand_id: VariableIdentifier, + right_aileron_blue_actuator_position_demand_id: VariableIdentifier, + left_aileron_green_actuator_position_demand_id: VariableIdentifier, + right_aileron_green_actuator_position_demand_id: VariableIdentifier, left_aileron_controllers: [AileronController; 2], right_aileron_controllers: [AileronController; 2], - - left_elevator_controllers: [AileronController; 2], - right_elevator_controllers: [AileronController; 2], - - green_circuit_available: bool, - blue_circuit_available: bool, - yellow_circuit_available: bool, - - is_powered: bool, } -impl ElacComputer { - const PRESSURE_AVAILABLE_HIGH_HYSTERESIS_PSI: f64 = 1450.; - const PRESSURE_AVAILABLE_LOW_HYSTERESIS_PSI: f64 = 800.; - - //TODO hot busses are in reality sub busses 703pp and 704pp - const ALL_POWER_BUSES: [ElectricalBusType; 4] = [ - ElectricalBusType::DirectCurrentEssential, - ElectricalBusType::DirectCurrent(2), - ElectricalBusType::DirectCurrentHot(1), - ElectricalBusType::DirectCurrentHot(2), - ]; - +impl AileronSystemHydraulicController { fn new(context: &mut InitContext) -> Self { Self { - left_aileron_requested_position_id: context - .get_identifier("HYD_AILERON_LEFT_DEMAND".to_owned()), - right_aileron_requested_position_id: context - .get_identifier("HYD_AILERON_RIGHT_DEMAND".to_owned()), - elevator_requested_position_id: context - .get_identifier("HYD_ELEVATOR_DEMAND".to_owned()), - - left_aileron_requested_position: Ratio::default(), - right_aileron_requested_position: Ratio::default(), - elevator_requested_position: Ratio::default(), + left_aileron_blue_actuator_solenoid_id: context + .get_identifier("LEFT_AIL_BLUE_SERVO_SOLENOID_ENERGIZED".to_owned()), + right_aileron_blue_actuator_solenoid_id: context + .get_identifier("RIGHT_AIL_BLUE_SERVO_SOLENOID_ENERGIZED".to_owned()), + left_aileron_green_actuator_solenoid_id: context + .get_identifier("LEFT_AIL_GREEN_SERVO_SOLENOID_ENERGIZED".to_owned()), + right_aileron_green_actuator_solenoid_id: context + .get_identifier("RIGHT_AIL_GREEN_SERVO_SOLENOID_ENERGIZED".to_owned()), + + left_aileron_blue_actuator_position_demand_id: context + .get_identifier("LEFT_AIL_BLUE_COMMANDED_POSITION".to_owned()), + right_aileron_blue_actuator_position_demand_id: context + .get_identifier("RIGHT_AIL_BLUE_COMMANDED_POSITION".to_owned()), + left_aileron_green_actuator_position_demand_id: context + .get_identifier("LEFT_AIL_GREEN_COMMANDED_POSITION".to_owned()), + right_aileron_green_actuator_position_demand_id: context + .get_identifier("RIGHT_AIL_GREEN_COMMANDED_POSITION".to_owned()), // Controllers are in outward->inward order, so for aileron [Blue circuit, Green circuit] left_aileron_controllers: [AileronController::new(), AileronController::new()], right_aileron_controllers: [AileronController::new(), AileronController::new()], - - // Controllers are in outboard->inboard order - left_elevator_controllers: [AileronController::new(), AileronController::new()], - right_elevator_controllers: [AileronController::new(), AileronController::new()], - - green_circuit_available: false, - blue_circuit_available: false, - yellow_circuit_available: false, - - is_powered: false, } } - fn update_aileron_requested_position(&mut self) { - for controller in &mut self.left_aileron_controllers { - controller.set_requested_position(self.left_aileron_requested_position); - } - - for controller in &mut self.right_aileron_controllers { - controller.set_requested_position(self.right_aileron_requested_position); - } - } - - fn update_elevator_requested_position(&mut self) { - for controller in &mut self.left_elevator_controllers { - controller.set_requested_position(self.elevator_requested_position); - } - - for controller in &mut self.right_elevator_controllers { - controller.set_requested_position(self.elevator_requested_position); - } - } - - fn set_aileron_no_position_control(&mut self) { - for controller in &mut self.left_aileron_controllers { - controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); - } - - for controller in &mut self.right_aileron_controllers { - controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); - } + fn left_controllers(&self) -> &[impl HydraulicAssemblyController + HydraulicLocking] { + &self.left_aileron_controllers[..] } - fn set_elevator_no_position_control(&mut self) { - for controller in &mut self.left_elevator_controllers { - controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); - } - - for controller in &mut self.right_elevator_controllers { - controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); - } + fn right_controllers(&self) -> &[impl HydraulicAssemblyController + HydraulicLocking] { + &self.right_aileron_controllers[..] } - fn set_left_aileron_position_control( + fn update_aileron_controllers_positions( &mut self, - hydraulic_configuration: AileronHydConfiguration, + left_position_requests: [Ratio; 2], + right_position_requests: [Ratio; 2], ) { - match hydraulic_configuration { - AileronHydConfiguration::GB | AileronHydConfiguration::B => { - self.left_aileron_controllers[AileronActuatorPosition::Outboard as usize] - .set_mode(LinearActuatorMode::PositionControl); - self.left_aileron_controllers[AileronActuatorPosition::Inboard as usize] - .set_mode(LinearActuatorMode::ActiveDamping); - } + self.left_aileron_controllers[AileronActuatorPosition::Blue as usize] + .set_requested_position(left_position_requests[AileronActuatorPosition::Blue as usize]); + self.left_aileron_controllers[AileronActuatorPosition::Green as usize] + .set_requested_position( + left_position_requests[AileronActuatorPosition::Green as usize], + ); - AileronHydConfiguration::G => { - self.left_aileron_controllers[AileronActuatorPosition::Outboard as usize] - .set_mode(LinearActuatorMode::ActiveDamping); - self.left_aileron_controllers[AileronActuatorPosition::Inboard as usize] - .set_mode(LinearActuatorMode::PositionControl); - } - AileronHydConfiguration::NoHyd => { - self.left_aileron_controllers[AileronActuatorPosition::Outboard as usize] - .set_mode(LinearActuatorMode::ClosedCircuitDamping); - self.left_aileron_controllers[AileronActuatorPosition::Inboard as usize] - .set_mode(LinearActuatorMode::ClosedCircuitDamping); - } - } + self.right_aileron_controllers[AileronActuatorPosition::Blue as usize] + .set_requested_position( + right_position_requests[AileronActuatorPosition::Blue as usize], + ); + self.right_aileron_controllers[AileronActuatorPosition::Green as usize] + .set_requested_position( + right_position_requests[AileronActuatorPosition::Green as usize], + ); } - fn set_right_aileron_position_control( + /// Will drive mode from solenoid state + /// -If energized actuator controls position + /// -If not energized actuator is slaved in damping + /// -We differentiate case of all actuators in damping mode where we set a more dampened + /// mode to reach realistic slow droop speed. + fn update_aileron_controllers_solenoids( &mut self, - hydraulic_configuration: AileronHydConfiguration, + left_solenoids_energized: [bool; 2], + right_solenoids_energized: [bool; 2], ) { - match hydraulic_configuration { - AileronHydConfiguration::GB | AileronHydConfiguration::G => { - self.right_aileron_controllers[AileronActuatorPosition::Outboard as usize] - .set_mode(LinearActuatorMode::ActiveDamping); - self.right_aileron_controllers[AileronActuatorPosition::Inboard as usize] - .set_mode(LinearActuatorMode::PositionControl); + if left_solenoids_energized.iter().any(|x| *x) { + self.left_aileron_controllers[AileronActuatorPosition::Blue as usize].set_mode( + Self::aileron_actuator_mode_from_solenoid( + left_solenoids_energized[AileronActuatorPosition::Blue as usize], + ), + ); + self.left_aileron_controllers[AileronActuatorPosition::Green as usize].set_mode( + Self::aileron_actuator_mode_from_solenoid( + left_solenoids_energized[AileronActuatorPosition::Green as usize], + ), + ); + } else { + for controller in &mut self.left_aileron_controllers { + controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); } + } - AileronHydConfiguration::B => { - self.right_aileron_controllers[AileronActuatorPosition::Outboard as usize] - .set_mode(LinearActuatorMode::PositionControl); - self.right_aileron_controllers[AileronActuatorPosition::Inboard as usize] - .set_mode(LinearActuatorMode::ActiveDamping); - } - _ => { - self.right_aileron_controllers[AileronActuatorPosition::Outboard as usize] - .set_mode(LinearActuatorMode::ClosedCircuitDamping); - self.right_aileron_controllers[AileronActuatorPosition::Inboard as usize] - .set_mode(LinearActuatorMode::ClosedCircuitDamping); + if right_solenoids_energized.iter().any(|x| *x) { + self.right_aileron_controllers[AileronActuatorPosition::Blue as usize].set_mode( + Self::aileron_actuator_mode_from_solenoid( + right_solenoids_energized[AileronActuatorPosition::Blue as usize], + ), + ); + self.right_aileron_controllers[AileronActuatorPosition::Green as usize].set_mode( + Self::aileron_actuator_mode_from_solenoid( + right_solenoids_energized[AileronActuatorPosition::Green as usize], + ), + ); + } else { + for controller in &mut self.right_aileron_controllers { + controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); } } } - fn set_left_elevator_position_control( - &mut self, - hydraulic_configuration: LeftElevatorHydConfiguration, - ) { - match hydraulic_configuration { - LeftElevatorHydConfiguration::GB => { - if self.elevator_requested_position > Ratio::new::(0.8) { - self.left_elevator_controllers[LeftElevatorActuatorCircuit::Blue as usize] - .set_mode(LinearActuatorMode::PositionControl); - } else { - self.left_elevator_controllers[LeftElevatorActuatorCircuit::Blue as usize] - .set_mode(LinearActuatorMode::ActiveDamping); - } - self.left_elevator_controllers[LeftElevatorActuatorCircuit::Green as usize] - .set_mode(LinearActuatorMode::PositionControl); - } - LeftElevatorHydConfiguration::G => { - self.left_elevator_controllers[LeftElevatorActuatorCircuit::Blue as usize] - .set_mode(LinearActuatorMode::ActiveDamping); - self.left_elevator_controllers[LeftElevatorActuatorCircuit::Green as usize] - .set_mode(LinearActuatorMode::PositionControl); - } - LeftElevatorHydConfiguration::B => { - self.left_elevator_controllers[LeftElevatorActuatorCircuit::Blue as usize] - .set_mode(LinearActuatorMode::PositionControl); - self.left_elevator_controllers[LeftElevatorActuatorCircuit::Green as usize] - .set_mode(LinearActuatorMode::ActiveDamping); - } - LeftElevatorHydConfiguration::NoHyd => { - self.left_elevator_controllers[LeftElevatorActuatorCircuit::Blue as usize] - .set_mode(LinearActuatorMode::ClosedCircuitDamping); - self.left_elevator_controllers[LeftElevatorActuatorCircuit::Green as usize] - .set_mode(LinearActuatorMode::ClosedCircuitDamping); - } + fn aileron_actuator_mode_from_solenoid(solenoid_energized: bool) -> LinearActuatorMode { + if solenoid_energized { + LinearActuatorMode::PositionControl + } else { + LinearActuatorMode::ActiveDamping } } - fn set_right_elevator_position_control( - &mut self, - hydraulic_configuration: RightElevatorHydConfiguration, - ) { - match hydraulic_configuration { - RightElevatorHydConfiguration::YB => { - if self.elevator_requested_position > Ratio::new::(0.8) { - self.right_elevator_controllers[RightElevatorActuatorCircuit::Blue as usize] - .set_mode(LinearActuatorMode::PositionControl); - } else { - self.right_elevator_controllers[RightElevatorActuatorCircuit::Blue as usize] - .set_mode(LinearActuatorMode::ActiveDamping); - } - self.right_elevator_controllers[RightElevatorActuatorCircuit::Yellow as usize] - .set_mode(LinearActuatorMode::PositionControl); - } - RightElevatorHydConfiguration::Y => { - self.right_elevator_controllers[RightElevatorActuatorCircuit::Blue as usize] - .set_mode(LinearActuatorMode::ActiveDamping); - self.right_elevator_controllers[RightElevatorActuatorCircuit::Yellow as usize] - .set_mode(LinearActuatorMode::PositionControl); - } - RightElevatorHydConfiguration::B => { - self.right_elevator_controllers[RightElevatorActuatorCircuit::Blue as usize] - .set_mode(LinearActuatorMode::PositionControl); - self.right_elevator_controllers[RightElevatorActuatorCircuit::Yellow as usize] - .set_mode(LinearActuatorMode::ActiveDamping); - } - RightElevatorHydConfiguration::NoHyd => { - self.right_elevator_controllers[RightElevatorActuatorCircuit::Blue as usize] - .set_mode(LinearActuatorMode::ClosedCircuitDamping); - self.right_elevator_controllers[RightElevatorActuatorCircuit::Yellow as usize] - .set_mode(LinearActuatorMode::ClosedCircuitDamping); - } - } + fn aileron_actuator_position_from_surface_angle(surface_angle: Angle) -> Ratio { + Ratio::new::(surface_angle.get::() / 50. + 0.5) + } +} +impl SimulationElement for AileronSystemHydraulicController { + fn read(&mut self, reader: &mut SimulatorReader) { + // Note that we reverse left, as positions are just passed through msfs for now + self.update_aileron_controllers_positions( + [ + Self::aileron_actuator_position_from_surface_angle(-Angle::new::( + reader.read(&self.left_aileron_blue_actuator_position_demand_id), + )), + Self::aileron_actuator_position_from_surface_angle(-Angle::new::( + reader.read(&self.left_aileron_green_actuator_position_demand_id), + )), + ], + [ + Self::aileron_actuator_position_from_surface_angle(Angle::new::( + reader.read(&self.right_aileron_blue_actuator_position_demand_id), + )), + Self::aileron_actuator_position_from_surface_angle(Angle::new::( + reader.read(&self.right_aileron_green_actuator_position_demand_id), + )), + ], + ); + + self.update_aileron_controllers_solenoids( + [ + reader.read(&self.left_aileron_blue_actuator_solenoid_id), + reader.read(&self.left_aileron_green_actuator_solenoid_id), + ], + [ + reader.read(&self.right_aileron_blue_actuator_solenoid_id), + reader.read(&self.right_aileron_green_actuator_solenoid_id), + ], + ); } +} - fn update_elevator(&mut self) { - if self.is_powered { - self.set_left_elevator_position_control(LeftElevatorHydConfiguration::from_hyd_state( - self.green_circuit_available, - self.blue_circuit_available, - )); +struct ElevatorSystemHydraulicController { + left_elevator_blue_actuator_solenoid_id: VariableIdentifier, + right_elevator_blue_actuator_solenoid_id: VariableIdentifier, + left_elevator_green_actuator_solenoid_id: VariableIdentifier, + right_elevator_yellow_actuator_solenoid_id: VariableIdentifier, - self.set_right_elevator_position_control( - RightElevatorHydConfiguration::from_hyd_state( - self.blue_circuit_available, - self.yellow_circuit_available, - ), - ); - } else { - self.set_elevator_no_position_control(); + left_elevator_blue_actuator_position_demand_id: VariableIdentifier, + right_elevator_blue_actuator_position_demand_id: VariableIdentifier, + left_elevator_green_actuator_position_demand_id: VariableIdentifier, + right_elevator_yellow_actuator_position_demand_id: VariableIdentifier, + + left_controllers: [AileronController; 2], + right_controllers: [AileronController; 2], +} +impl ElevatorSystemHydraulicController { + fn new(context: &mut InitContext) -> Self { + Self { + left_elevator_blue_actuator_solenoid_id: context + .get_identifier("LEFT_ELEV_BLUE_SERVO_SOLENOID_ENERGIZED".to_owned()), + right_elevator_blue_actuator_solenoid_id: context + .get_identifier("RIGHT_ELEV_BLUE_SERVO_SOLENOID_ENERGIZED".to_owned()), + left_elevator_green_actuator_solenoid_id: context + .get_identifier("LEFT_ELEV_GREEN_SERVO_SOLENOID_ENERGIZED".to_owned()), + right_elevator_yellow_actuator_solenoid_id: context + .get_identifier("RIGHT_ELEV_YELLOW_SERVO_SOLENOID_ENERGIZED".to_owned()), + + left_elevator_blue_actuator_position_demand_id: context + .get_identifier("LEFT_ELEV_BLUE_COMMANDED_POSITION".to_owned()), + right_elevator_blue_actuator_position_demand_id: context + .get_identifier("RIGHT_ELEV_BLUE_COMMANDED_POSITION".to_owned()), + left_elevator_green_actuator_position_demand_id: context + .get_identifier("LEFT_ELEV_GREEN_COMMANDED_POSITION".to_owned()), + right_elevator_yellow_actuator_position_demand_id: context + .get_identifier("RIGHT_ELEV_YELLOW_COMMANDED_POSITION".to_owned()), + + // Controllers are in outboard->inboard order + left_controllers: [AileronController::new(), AileronController::new()], + right_controllers: [AileronController::new(), AileronController::new()], } } - fn update_aileron(&mut self) { - if self.is_powered { - self.set_right_aileron_position_control(AileronHydConfiguration::from_hyd_state( - self.green_circuit_available, - self.blue_circuit_available, - )); - self.set_left_aileron_position_control(AileronHydConfiguration::from_hyd_state( - self.green_circuit_available, - self.blue_circuit_available, - )); - } else { - self.set_aileron_no_position_control(); - } + fn left_controllers(&self) -> &[impl HydraulicAssemblyController + HydraulicLocking] { + &self.left_controllers[..] } - fn circuit_is_available(pressure: Pressure, current_availability: bool) -> bool { - if pressure.get::() > Self::PRESSURE_AVAILABLE_HIGH_HYSTERESIS_PSI { - true - } else if pressure.get::() < Self::PRESSURE_AVAILABLE_LOW_HYSTERESIS_PSI { - false - } else { - current_availability - } + fn right_controllers(&self) -> &[impl HydraulicAssemblyController + HydraulicLocking] { + &self.right_controllers[..] } - fn update( + fn update_elevator_controllers_positions( &mut self, - blue_pressure: &impl SectionPressure, - green_pressure: &impl SectionPressure, - yellow_pressure: &impl SectionPressure, + left_position_requests: [Ratio; 2], + right_position_requests: [Ratio; 2], ) { - self.update_aileron_requested_position(); - self.update_elevator_requested_position(); - - self.blue_circuit_available = Self::circuit_is_available( - blue_pressure.pressure_downstream_leak_valve(), - self.blue_circuit_available, + self.left_controllers[LeftElevatorActuatorCircuit::Blue as usize].set_requested_position( + left_position_requests[LeftElevatorActuatorCircuit::Blue as usize], ); - self.green_circuit_available = Self::circuit_is_available( - green_pressure.pressure_downstream_leak_valve(), - self.green_circuit_available, + self.left_controllers[LeftElevatorActuatorCircuit::Green as usize].set_requested_position( + left_position_requests[LeftElevatorActuatorCircuit::Green as usize], ); - self.yellow_circuit_available = Self::circuit_is_available( - yellow_pressure.pressure_downstream_leak_valve(), - self.yellow_circuit_available, - ); - - self.update_aileron(); - self.update_elevator(); + self.right_controllers[RightElevatorActuatorCircuit::Blue as usize].set_requested_position( + right_position_requests[RightElevatorActuatorCircuit::Blue as usize], + ); + self.right_controllers[RightElevatorActuatorCircuit::Yellow as usize] + .set_requested_position( + right_position_requests[RightElevatorActuatorCircuit::Yellow as usize], + ); } - fn left_elevator_controllers(&self) -> &[impl HydraulicAssemblyController] { - &self.left_elevator_controllers[..] - } + fn update_elevator_controllers_solenoids( + &mut self, + left_solenoids_energized: [bool; 2], + right_solenoids_energized: [bool; 2], + ) { + if left_solenoids_energized.iter().all(|x| *x) { + for controller in &mut self.left_controllers { + controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + } else { + self.left_controllers[LeftElevatorActuatorCircuit::Blue as usize].set_mode( + Self::elevator_actuator_mode_from_solenoid( + left_solenoids_energized[LeftElevatorActuatorCircuit::Blue as usize], + ), + ); + self.left_controllers[LeftElevatorActuatorCircuit::Green as usize].set_mode( + Self::elevator_actuator_mode_from_solenoid( + left_solenoids_energized[LeftElevatorActuatorCircuit::Green as usize], + ), + ); + } - fn right_elevator_controllers(&self) -> &[impl HydraulicAssemblyController] { - &self.right_elevator_controllers[..] + if right_solenoids_energized.iter().all(|x| *x) { + for controller in &mut self.right_controllers { + controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + } else { + self.right_controllers[RightElevatorActuatorCircuit::Blue as usize].set_mode( + Self::elevator_actuator_mode_from_solenoid( + right_solenoids_energized[RightElevatorActuatorCircuit::Blue as usize], + ), + ); + self.right_controllers[RightElevatorActuatorCircuit::Yellow as usize].set_mode( + Self::elevator_actuator_mode_from_solenoid( + right_solenoids_energized[RightElevatorActuatorCircuit::Yellow as usize], + ), + ); + } } - fn left_controllers(&self) -> &[impl HydraulicAssemblyController] { - &self.left_aileron_controllers[..] + fn elevator_actuator_mode_from_solenoid(solenoid_energized: bool) -> LinearActuatorMode { + // Elevator has reverted logic + if !solenoid_energized { + LinearActuatorMode::PositionControl + } else { + LinearActuatorMode::ActiveDamping + } } - fn right_controllers(&self) -> &[impl HydraulicAssemblyController] { - &self.right_aileron_controllers[..] + fn elevator_actuator_position_from_surface_angle(surface_angle: Angle) -> Ratio { + Ratio::new::( + (-surface_angle.get::() / 47. + 17. / 47.) + .min(1.) + .max(0.), + ) } } -impl SimulationElement for ElacComputer { +impl SimulationElement for ElevatorSystemHydraulicController { fn read(&mut self, reader: &mut SimulatorReader) { - self.left_aileron_requested_position = - Ratio::new::(reader.read(&self.left_aileron_requested_position_id)); - self.right_aileron_requested_position = - Ratio::new::(reader.read(&self.right_aileron_requested_position_id)); - - self.elevator_requested_position = - Ratio::new::(reader.read(&self.elevator_requested_position_id)); - } + self.update_elevator_controllers_positions( + [ + Self::elevator_actuator_position_from_surface_angle(Angle::new::( + reader.read(&self.left_elevator_blue_actuator_position_demand_id), + )), + Self::elevator_actuator_position_from_surface_angle(Angle::new::( + reader.read(&self.left_elevator_green_actuator_position_demand_id), + )), + ], + [ + Self::elevator_actuator_position_from_surface_angle(Angle::new::( + reader.read(&self.right_elevator_blue_actuator_position_demand_id), + )), + Self::elevator_actuator_position_from_surface_angle(Angle::new::( + reader.read(&self.right_elevator_yellow_actuator_position_demand_id), + )), + ], + ); - fn receive_power(&mut self, buses: &impl ElectricalBuses) { - self.is_powered = buses.any_is_powered(&Self::ALL_POWER_BUSES); + self.update_elevator_controllers_solenoids( + [ + reader.read(&self.left_elevator_blue_actuator_solenoid_id), + reader.read(&self.left_elevator_green_actuator_solenoid_id), + ], + [ + reader.read(&self.right_elevator_blue_actuator_solenoid_id), + reader.read(&self.right_elevator_yellow_actuator_solenoid_id), + ], + ); } } -/// Implements a placeholder fac computer logic commanding correct hydraulic modes depending -/// on pressure state. -/// TODO: Receive each actuator mode and commands directly from a FBW fac implementation -struct FacComputer { +struct RudderSystemHydraulicController { requested_rudder_position_id: VariableIdentifier, rudder_position_requested: Ratio, @@ -4752,7 +4918,7 @@ struct FacComputer { is_powered: bool, } -impl FacComputer { +impl RudderSystemHydraulicController { //TODO hot busses of FAC to check const ALL_POWER_BUSES: [ElectricalBusType; 4] = [ ElectricalBusType::DirectCurrentEssential, @@ -4865,11 +5031,11 @@ impl FacComputer { ); } - fn rudder_controllers(&self) -> &[impl HydraulicAssemblyController] { + fn rudder_controllers(&self) -> &[impl HydraulicAssemblyController + HydraulicLocking] { &self.rudder_controllers[..] } } -impl SimulationElement for FacComputer { +impl SimulationElement for RudderSystemHydraulicController { fn read(&mut self, reader: &mut SimulatorReader) { self.rudder_position_requested = Ratio::new::(reader.read(&self.requested_rudder_position_id)); @@ -4888,14 +5054,8 @@ enum ActuatorSide { #[derive(PartialEq, Clone, Copy)] enum AileronActuatorPosition { - Outboard = 0, - Inboard = 1, -} - -#[derive(PartialEq, Clone, Copy)] -enum ElevatorActuatorPosition { - Outboard = 0, - Inboard = 1, + Blue = 0, + Green = 1, } enum RudderActuatorPosition { @@ -4950,7 +5110,7 @@ impl AileronAssembly { fn update( &mut self, context: &UpdateContext, - aileron_controllers: &[impl HydraulicAssemblyController], + aileron_controllers: &[impl HydraulicAssemblyController + HydraulicLocking], current_pressure_outward: &impl SectionPressure, current_pressure_inward: &impl SectionPressure, ) { @@ -5003,14 +5163,14 @@ impl ElevatorAssembly { } } - fn actuator(&mut self, circuit_position: ElevatorActuatorPosition) -> &mut impl Actuator { - self.hydraulic_assembly.actuator(circuit_position as usize) + fn actuator(&mut self, circuit_position: usize) -> &mut impl Actuator { + self.hydraulic_assembly.actuator(circuit_position) } fn update( &mut self, context: &UpdateContext, - aileron_controllers: &[impl HydraulicAssemblyController], + elevator_controllers: &[impl HydraulicAssemblyController + HydraulicLocking], current_pressure_outward: &impl SectionPressure, current_pressure_inward: &impl SectionPressure, ) { @@ -5018,7 +5178,7 @@ impl ElevatorAssembly { .update_body(context, self.hydraulic_assembly.body()); self.hydraulic_assembly.update( context, - aileron_controllers, + elevator_controllers, [ current_pressure_outward.pressure_downstream_leak_valve(), current_pressure_inward.pressure_downstream_leak_valve(), @@ -5066,7 +5226,7 @@ impl RudderAssembly { fn update( &mut self, context: &UpdateContext, - rudder_controllers: &[impl HydraulicAssemblyController], + rudder_controllers: &[impl HydraulicAssemblyController + HydraulicLocking], current_pressure_green: &impl SectionPressure, current_pressure_blue: &impl SectionPressure, current_pressure_yellow: &impl SectionPressure, @@ -5114,10 +5274,10 @@ impl SpoilerElement { hydraulic_assembly, position_id: match id { ActuatorSide::Left => { - context.get_identifier(format!("HYD_SPOIL_{}_LEFT_DEFLECTION", id_num)) + context.get_identifier(format!("HYD_SPOILER_{}_LEFT_DEFLECTION", id_num)) } ActuatorSide::Right => { - context.get_identifier(format!("HYD_SPOIL_{}_RIGHT_DEFLECTION", id_num)) + context.get_identifier(format!("HYD_SPOILER_{}_RIGHT_DEFLECTION", id_num)) } }, position: Ratio::new::(0.), @@ -5132,7 +5292,7 @@ impl SpoilerElement { fn update( &mut self, context: &UpdateContext, - spoiler_controller: &impl HydraulicAssemblyController, + spoiler_controller: &(impl HydraulicAssemblyController + HydraulicLocking), current_pressure: Pressure, ) { self.aerodynamic_model @@ -5143,9 +5303,7 @@ impl SpoilerElement { [current_pressure], ); - // We return actuator position so it's consistent with demand - // Later we must decide who works in actuator position and surface position between demand and control - self.position = self.hydraulic_assembly.actuator_position_normalized(0); + self.position = self.hydraulic_assembly.position_normalized(); } } impl SimulationElement for SpoilerElement { @@ -5156,44 +5314,53 @@ impl SimulationElement for SpoilerElement { struct SpoilerGroup { spoilers: [SpoilerElement; 5], + hydraulic_controllers: [SpoilerController; 5], } impl SpoilerGroup { - fn new(spoilers: [SpoilerElement; 5]) -> Self { - Self { spoilers } + fn new(context: &mut InitContext, spoiler_side: &str, spoilers: [SpoilerElement; 5]) -> Self { + Self { + spoilers, + hydraulic_controllers: [ + SpoilerController::new(context, spoiler_side, 1), + SpoilerController::new(context, spoiler_side, 2), + SpoilerController::new(context, spoiler_side, 3), + SpoilerController::new(context, spoiler_side, 4), + SpoilerController::new(context, spoiler_side, 5), + ], + } } fn update( &mut self, context: &UpdateContext, - spoiler_controllers: &[impl HydraulicAssemblyController], - green_pressure: &impl SectionPressure, - blue_pressure: &impl SectionPressure, - yellow_pressure: &impl SectionPressure, + green_section: &impl SectionPressure, + blue_section: &impl SectionPressure, + yellow_section: &impl SectionPressure, ) { self.spoilers[0].update( context, - &spoiler_controllers[0], - green_pressure.pressure_downstream_leak_valve(), + &self.hydraulic_controllers[0], + green_section.pressure_downstream_leak_valve(), ); self.spoilers[1].update( context, - &spoiler_controllers[1], - yellow_pressure.pressure_downstream_leak_valve(), + &self.hydraulic_controllers[1], + yellow_section.pressure_downstream_leak_valve(), ); self.spoilers[2].update( context, - &spoiler_controllers[2], - blue_pressure.pressure_downstream_leak_valve(), + &self.hydraulic_controllers[2], + blue_section.pressure_downstream_leak_valve(), ); self.spoilers[3].update( context, - &spoiler_controllers[3], - yellow_pressure.pressure_downstream_leak_valve(), + &self.hydraulic_controllers[3], + yellow_section.pressure_downstream_leak_valve(), ); self.spoilers[4].update( context, - &spoiler_controllers[4], - green_pressure.pressure_downstream_leak_valve(), + &self.hydraulic_controllers[4], + green_section.pressure_downstream_leak_valve(), ); } @@ -5203,6 +5370,10 @@ impl SpoilerGroup { } impl SimulationElement for SpoilerGroup { fn accept(&mut self, visitor: &mut T) { + for controller in &mut self.hydraulic_controllers { + controller.accept(visitor); + } + for spoiler in &mut self.spoilers { spoiler.accept(visitor); } @@ -5211,25 +5382,24 @@ impl SimulationElement for SpoilerGroup { } } -#[derive(PartialEq, Clone, Copy)] struct SpoilerController { - mode: LinearActuatorMode, + position_demand_id: VariableIdentifier, requested_position: Ratio, } impl SpoilerController { - fn new() -> Self { + fn new(context: &mut InitContext, spoiler_side: &str, spoiler_id_number: usize) -> Self { Self { - mode: LinearActuatorMode::PositionControl, + position_demand_id: context.get_identifier(format!( + "{}_SPOILER_{}_COMMANDED_POSITION", + spoiler_side, spoiler_id_number + )), requested_position: Ratio::new::(0.), } } - /// Receives a [0;1] position request, 0 is down 1 is up - fn set_requested_position(&mut self, requested_position: Ratio) { - self.requested_position = requested_position - .min(Ratio::new::(1.)) - .max(Ratio::new::(0.)); + fn spoiler_actuator_position_from_surface_angle(surface_angle: Angle) -> Ratio { + Ratio::new::((surface_angle.get::() / 50.).min(1.).max(0.)) } } impl HydraulicAssemblyController for SpoilerController { @@ -5249,180 +5419,123 @@ impl HydraulicAssemblyController for SpoilerController { Ratio::default() } } +impl SimulationElement for SpoilerController { + fn read(&mut self, reader: &mut SimulatorReader) { + self.requested_position = + Self::spoiler_actuator_position_from_surface_angle(Angle::new::( + reader.read(&self.position_demand_id), + )); + } +} +impl HydraulicLocking for SpoilerController {} -struct SpoilerComputer { - requested_position_left_1_id: VariableIdentifier, - requested_position_left_2_id: VariableIdentifier, - requested_position_left_3_id: VariableIdentifier, - requested_position_left_4_id: VariableIdentifier, - requested_position_left_5_id: VariableIdentifier, - - requested_position_right_1_id: VariableIdentifier, - requested_position_right_2_id: VariableIdentifier, - requested_position_right_3_id: VariableIdentifier, - requested_position_right_4_id: VariableIdentifier, - requested_position_right_5_id: VariableIdentifier, - - left_positions_requested: [Ratio; 5], - right_positions_requested: [Ratio; 5], +struct A320GravityExtension { + gear_gravity_extension_handle_position_id: VariableIdentifier, - left_controllers: [SpoilerController; 5], - right_controllers: [SpoilerController; 5], + handle_angle: Angle, } -impl SpoilerComputer { +impl A320GravityExtension { fn new(context: &mut InitContext) -> Self { Self { - requested_position_left_1_id: context - .get_identifier("HYD_SPOILER_1_LEFT_DEMAND".to_owned()), - requested_position_left_2_id: context - .get_identifier("HYD_SPOILER_2_LEFT_DEMAND".to_owned()), - requested_position_left_3_id: context - .get_identifier("HYD_SPOILER_3_LEFT_DEMAND".to_owned()), - requested_position_left_4_id: context - .get_identifier("HYD_SPOILER_4_LEFT_DEMAND".to_owned()), - requested_position_left_5_id: context - .get_identifier("HYD_SPOILER_5_LEFT_DEMAND".to_owned()), - - requested_position_right_1_id: context - .get_identifier("HYD_SPOILER_1_RIGHT_DEMAND".to_owned()), - requested_position_right_2_id: context - .get_identifier("HYD_SPOILER_2_RIGHT_DEMAND".to_owned()), - requested_position_right_3_id: context - .get_identifier("HYD_SPOILER_3_RIGHT_DEMAND".to_owned()), - requested_position_right_4_id: context - .get_identifier("HYD_SPOILER_4_RIGHT_DEMAND".to_owned()), - requested_position_right_5_id: context - .get_identifier("HYD_SPOILER_5_RIGHT_DEMAND".to_owned()), - - left_positions_requested: [Ratio::default(); 5], - right_positions_requested: [Ratio::default(); 5], - - // Controllers are in inward->outward order - left_controllers: [SpoilerController::new(); 5], - right_controllers: [SpoilerController::new(); 5], - } - } - - fn update_spoilers_requested_position(&mut self) { - for (idx, controller) in &mut self.left_controllers.iter_mut().enumerate() { - controller.set_requested_position(self.left_positions_requested[idx]); - } + gear_gravity_extension_handle_position_id: context + .get_identifier("GRAVITYGEAR_ROTATE_PCT".to_owned()), - for (idx, controller) in &mut self.right_controllers.iter_mut().enumerate() { - controller.set_requested_position(self.right_positions_requested[idx]); + handle_angle: Angle::default(), } } - - fn left_controllers(&self) -> &[impl HydraulicAssemblyController] { - &self.left_controllers[..] - } - - fn right_controllers(&self) -> &[impl HydraulicAssemblyController] { - &self.right_controllers[..] +} +impl GearGravityExtension for A320GravityExtension { + fn extension_handle_number_of_turns(&self) -> u8 { + (self.handle_angle.get::() / 360.).floor() as u8 } } -impl SimulationElement for SpoilerComputer { +impl SimulationElement for A320GravityExtension { fn read(&mut self, reader: &mut SimulatorReader) { - self.left_positions_requested = [ - Ratio::new::(reader.read(&self.requested_position_left_1_id)), - Ratio::new::(reader.read(&self.requested_position_left_2_id)), - Ratio::new::(reader.read(&self.requested_position_left_3_id)), - Ratio::new::(reader.read(&self.requested_position_left_4_id)), - Ratio::new::(reader.read(&self.requested_position_left_5_id)), - ]; - self.right_positions_requested = [ - Ratio::new::(reader.read(&self.requested_position_right_1_id)), - Ratio::new::(reader.read(&self.requested_position_right_2_id)), - Ratio::new::(reader.read(&self.requested_position_right_3_id)), - Ratio::new::(reader.read(&self.requested_position_right_4_id)), - Ratio::new::(reader.read(&self.requested_position_right_5_id)), - ]; - - self.update_spoilers_requested_position(); + let handle_percent: f64 = reader.read(&self.gear_gravity_extension_handle_position_id); + + self.handle_angle = Angle::new::(handle_percent * 3.6) + .max(Angle::new::(0.)) + .min(Angle::new::(360. * 3.)); } } -struct A320GravityExtension { - gear_gravity_extension_active_id: VariableIdentifier, - gear_gravity_extension_handle_is_turned_id: VariableIdentifier, +struct A320TrimInputController { + motor1_active_id: VariableIdentifier, + motor2_active_id: VariableIdentifier, + motor3_active_id: VariableIdentifier, - handle_angle: Angle, + motor1_position_id: VariableIdentifier, + motor2_position_id: VariableIdentifier, + motor3_position_id: VariableIdentifier, - is_extending_gear: bool, + manual_control_active_id: VariableIdentifier, + manual_control_speed_id: VariableIdentifier, - is_turned: bool, -} -impl A320GravityExtension { - const INCREMENT_ANGLE_DEGREE_PER_SECOND: f64 = 220.; - const MAX_CRANK_HANDLE_ANGLE_DEGREE: f64 = 360. * 3.; - const MIN_CRANK_HANDLE_ANGLE_DEGREE: f64 = 0.; - const CRANK_HANDLE_ANGLE_MARGIN_AT_MAX_ROTATION_DEGREE: f64 = 0.1; + motor_active: [bool; 3], + motor_position: [Angle; 3], + manual_control: bool, + manual_control_speed: AngularVelocity, +} +impl A320TrimInputController { fn new(context: &mut InitContext) -> Self { Self { - gear_gravity_extension_active_id: context - .get_identifier("GEAR_EMERGENCY_EXTENSION_ACTIVE".to_owned()), - gear_gravity_extension_handle_is_turned_id: context - .get_identifier("GEAR_EMERGENCY_EXTENSION_IS_TURNED".to_owned()), + motor1_active_id: context.get_identifier("THS_1_ACTIVE_MODE_COMMANDED".to_owned()), + motor2_active_id: context.get_identifier("THS_2_ACTIVE_MODE_COMMANDED".to_owned()), + motor3_active_id: context.get_identifier("THS_3_ACTIVE_MODE_COMMANDED".to_owned()), - handle_angle: Angle::default(), - is_extending_gear: true, - is_turned: false, - } - } + motor1_position_id: context.get_identifier("THS_1_COMMANDED_POSITION".to_owned()), + motor2_position_id: context.get_identifier("THS_2_COMMANDED_POSITION".to_owned()), + motor3_position_id: context.get_identifier("THS_3_COMMANDED_POSITION".to_owned()), - fn update(&mut self, context: &UpdateContext) { - if self.is_turned { - if self.is_extending_gear { - self.handle_angle += Angle::new::( - Self::INCREMENT_ANGLE_DEGREE_PER_SECOND * context.delta_as_secs_f64(), - ); - } else { - self.handle_angle -= Angle::new::( - Self::INCREMENT_ANGLE_DEGREE_PER_SECOND * context.delta_as_secs_f64(), - ); + manual_control_active_id: context + .get_identifier("THS_MANUAL_CONTROL_ACTIVE".to_owned()), + manual_control_speed_id: context.get_identifier("THS_MANUAL_CONTROL_SPEED".to_owned()), + + motor_active: [false; 3], + motor_position: [Angle::default(); 3], + + manual_control: false, + manual_control_speed: AngularVelocity::default(), + } + } +} +impl PitchTrimActuatorController for A320TrimInputController { + fn commanded_position(&self) -> Angle { + for (idx, motor_active) in self.motor_active.iter().enumerate() { + if *motor_active { + return self.motor_position[idx]; } } - self.handle_angle = self - .handle_angle - .min(Angle::new::( - Self::MAX_CRANK_HANDLE_ANGLE_DEGREE - + Self::CRANK_HANDLE_ANGLE_MARGIN_AT_MAX_ROTATION_DEGREE, - )) - .max(Angle::new::( - Self::MIN_CRANK_HANDLE_ANGLE_DEGREE - - Self::CRANK_HANDLE_ANGLE_MARGIN_AT_MAX_ROTATION_DEGREE, - )); + Angle::default() + } - if self.handle_angle.get::() > Self::MAX_CRANK_HANDLE_ANGLE_DEGREE - && !self.is_turned - && self.is_extending_gear - { - self.is_extending_gear = false; - } else if self.handle_angle.get::() < Self::MIN_CRANK_HANDLE_ANGLE_DEGREE - && !self.is_turned - && !self.is_extending_gear - { - self.is_extending_gear = true; - } + fn energised_motor(&self) -> [bool; 3] { + self.motor_active } } -impl GearGravityExtension for A320GravityExtension { - fn extension_handle_number_of_turns(&self) -> u8 { - (self.handle_angle.get::() / 360.).floor() as u8 +impl ManualPitchTrimController for A320TrimInputController { + fn is_manually_moved(&self) -> bool { + self.manual_control || self.manual_control_speed.get::() != 0. + } + + fn moving_speed(&self) -> AngularVelocity { + self.manual_control_speed } } -impl SimulationElement for A320GravityExtension { +impl SimulationElement for A320TrimInputController { fn read(&mut self, reader: &mut SimulatorReader) { - self.is_turned = reader.read(&self.gear_gravity_extension_active_id); - } + self.motor_active[0] = reader.read(&self.motor1_active_id); + self.motor_active[1] = reader.read(&self.motor2_active_id); + self.motor_active[2] = reader.read(&self.motor3_active_id); - fn write(&self, writer: &mut SimulatorWriter) { - writer.write( - &self.gear_gravity_extension_handle_is_turned_id, - self.handle_angle.get::() < 360. && self.is_turned, - ); + self.motor_position[0] = reader.read(&self.motor1_position_id); + self.motor_position[1] = reader.read(&self.motor2_position_id); + self.motor_position[2] = reader.read(&self.motor3_position_id); + + self.manual_control = reader.read(&self.manual_control_active_id); + self.manual_control_speed = reader.read(&self.manual_control_speed_id); } } @@ -5438,9 +5551,12 @@ mod tests { ExternalPowerSource, }, engine::{leap_engine::LeapEngine, EngineFireOverheadPanel}, + failures::FailureType, hydraulic::electrical_generator::TestGenerator, landing_gear::{GearSystemState, LandingGear, LandingGearControlInterfaceUnitSet}, - shared::{EmergencyElectricalState, HydraulicGeneratorControlUnit, PotentialOrigin}, + shared::{ + EmergencyElectricalState, HydraulicGeneratorControlUnit, LgciuId, PotentialOrigin, + }, simulation::{ test::{ReadByName, SimulationTestBed, TestBed, WriteByName}, Aircraft, InitContext, @@ -6091,14 +6207,6 @@ mod tests { self.read_by_name("A32NX_HYD_RAT_RPM") } - fn get_emergency_handle_number_of_turns(&self) -> u8 { - self.query(|a| { - a.hydraulics - .gear_system_gravity_extension_controller - .extension_handle_number_of_turns() - }) - } - fn get_left_aileron_position(&mut self) -> Ratio { Ratio::new::(self.read_by_name("HYD_AIL_LEFT_DEFLECTION")) } @@ -6111,6 +6219,24 @@ mod tests { Ratio::new::(self.read_by_name("HYD_ELEV_LEFT_DEFLECTION")) } + fn get_mean_right_spoilers_position(&mut self) -> Ratio { + (Ratio::new::(self.read_by_name("HYD_SPOILER_1_RIGHT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_2_RIGHT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_3_RIGHT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_4_RIGHT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_5_RIGHT_DEFLECTION"))) + / 5. + } + + fn get_mean_left_spoilers_position(&mut self) -> Ratio { + (Ratio::new::(self.read_by_name("HYD_SPOILER_1_LEFT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_2_LEFT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_3_LEFT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_4_LEFT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_5_LEFT_DEFLECTION"))) + / 5. + } + fn get_right_elevator_position(&mut self) -> Ratio { Ratio::new::(self.read_by_name("HYD_ELEV_RIGHT_DEFLECTION")) } @@ -6205,6 +6331,13 @@ mod tests { self } + fn on_the_ground_after_touchdown(mut self) -> Self { + self.set_indicated_altitude(Length::new::(0.)); + self.set_on_ground(true); + self.set_indicated_airspeed(Velocity::new::(100.)); + self + } + fn air_press_low(mut self) -> Self { self.command(|a| a.pneumatics.set_low_air_pressure()); self @@ -6400,44 +6533,6 @@ mod tests { self } - fn set_spoiler_position_demand( - mut self, - left_demand: Ratio, - right_demand: Ratio, - ) -> Self { - self.write_by_name("HYD_SPOILER_1_RIGHT_DEMAND", right_demand.get::()); - self.write_by_name("HYD_SPOILER_2_RIGHT_DEMAND", right_demand.get::()); - self.write_by_name("HYD_SPOILER_3_RIGHT_DEMAND", right_demand.get::()); - self.write_by_name("HYD_SPOILER_4_RIGHT_DEMAND", right_demand.get::()); - self.write_by_name("HYD_SPOILER_5_RIGHT_DEMAND", right_demand.get::()); - - self.write_by_name("HYD_SPOILER_1_LEFT_DEMAND", left_demand.get::()); - self.write_by_name("HYD_SPOILER_2_LEFT_DEMAND", left_demand.get::()); - self.write_by_name("HYD_SPOILER_3_LEFT_DEMAND", left_demand.get::()); - self.write_by_name("HYD_SPOILER_4_LEFT_DEMAND", left_demand.get::()); - self.write_by_name("HYD_SPOILER_5_LEFT_DEMAND", left_demand.get::()); - - self - } - - fn get_spoiler_left_mean_position(&mut self) -> Ratio { - (Ratio::new::(self.read_by_name("HYD_SPOIL_1_LEFT_DEFLECTION")) - + Ratio::new::(self.read_by_name("HYD_SPOIL_2_LEFT_DEFLECTION")) - + Ratio::new::(self.read_by_name("HYD_SPOIL_3_LEFT_DEFLECTION")) - + Ratio::new::(self.read_by_name("HYD_SPOIL_4_LEFT_DEFLECTION")) - + Ratio::new::(self.read_by_name("HYD_SPOIL_5_LEFT_DEFLECTION"))) - / 5. - } - - fn get_spoiler_right_mean_position(&mut self) -> Ratio { - (Ratio::new::(self.read_by_name("HYD_SPOIL_1_RIGHT_DEFLECTION")) - + Ratio::new::(self.read_by_name("HYD_SPOIL_2_RIGHT_DEFLECTION")) - + Ratio::new::(self.read_by_name("HYD_SPOIL_3_RIGHT_DEFLECTION")) - + Ratio::new::(self.read_by_name("HYD_SPOIL_4_RIGHT_DEFLECTION")) - + Ratio::new::(self.read_by_name("HYD_SPOIL_5_RIGHT_DEFLECTION"))) - / 5. - } - fn set_flaps_handle_position(mut self, pos: u8) -> Self { self.write_by_name("FLAPS_HANDLE_INDEX", pos as f64); self @@ -6459,7 +6554,7 @@ mod tests { self.read_by_name("RIGHT_SLATS_POSITION_PERCENT") } - fn get_real_gear_position(&mut self, wheel_id: GearWheel) -> f64 { + fn get_real_gear_position(&mut self, wheel_id: GearWheel) -> Ratio { match wheel_id { GearWheel::NOSE => self.read_by_name("GEAR_CENTER_POSITION"), GearWheel::LEFT => self.read_by_name("GEAR_LEFT_POSITION"), @@ -6467,7 +6562,7 @@ mod tests { } } - fn get_real_gear_door_position(&mut self, wheel_id: GearWheel) -> f64 { + fn get_real_gear_door_position(&mut self, wheel_id: GearWheel) -> Ratio { match wheel_id { GearWheel::NOSE => self.read_by_name("GEAR_DOOR_CENTER_POSITION"), GearWheel::LEFT => self.read_by_name("GEAR_DOOR_LEFT_POSITION"), @@ -6476,27 +6571,30 @@ mod tests { } fn is_all_gears_really_up(&mut self) -> bool { - self.get_real_gear_position(GearWheel::NOSE) <= 0. - && self.get_real_gear_position(GearWheel::LEFT) <= 0. - && self.get_real_gear_position(GearWheel::RIGHT) <= 0. + self.get_real_gear_position(GearWheel::NOSE) <= Ratio::new::(0.01) + && self.get_real_gear_position(GearWheel::LEFT) <= Ratio::new::(0.01) + && self.get_real_gear_position(GearWheel::RIGHT) <= Ratio::new::(0.01) } fn is_all_gears_really_down(&mut self) -> bool { - self.get_real_gear_position(GearWheel::NOSE) >= 1. - && self.get_real_gear_position(GearWheel::LEFT) >= 1. - && self.get_real_gear_position(GearWheel::RIGHT) >= 1. + self.get_real_gear_position(GearWheel::NOSE) >= Ratio::new::(0.99) + && self.get_real_gear_position(GearWheel::LEFT) >= Ratio::new::(0.99) + && self.get_real_gear_position(GearWheel::RIGHT) >= Ratio::new::(0.99) } fn is_all_doors_really_up(&mut self) -> bool { - self.get_real_gear_door_position(GearWheel::NOSE) <= 0. - && self.get_real_gear_door_position(GearWheel::LEFT) <= 0. - && self.get_real_gear_door_position(GearWheel::RIGHT) <= 0. + self.get_real_gear_door_position(GearWheel::NOSE) <= Ratio::new::(0.01) + && self.get_real_gear_door_position(GearWheel::LEFT) + <= Ratio::new::(0.01) + && self.get_real_gear_door_position(GearWheel::RIGHT) + <= Ratio::new::(0.01) } fn is_all_doors_really_down(&mut self) -> bool { - self.get_real_gear_door_position(GearWheel::NOSE) >= 0.9 - && self.get_real_gear_door_position(GearWheel::LEFT) >= 0.9 - && self.get_real_gear_door_position(GearWheel::RIGHT) >= 0.9 + self.get_real_gear_door_position(GearWheel::NOSE) >= Ratio::new::(0.9) + && self.get_real_gear_door_position(GearWheel::LEFT) >= Ratio::new::(0.9) + && self.get_real_gear_door_position(GearWheel::RIGHT) + >= Ratio::new::(0.9) } fn ac_bus_1_lost(mut self) -> Self { @@ -6553,6 +6651,7 @@ mod tests { .set_gear_lever_down() .set_pushback_state(false) .air_press_nominal() + .set_elac1_actuators_energized() .set_ailerons_neutral() .set_elevator_neutral() } @@ -6608,36 +6707,116 @@ mod tests { self } - fn set_deploy_spoilers(mut self) -> Self { - self.write_by_name("SPOILERS_GROUND_SPOILERS_ACTIVE", true); + fn set_deploy_ground_spoilers(mut self) -> Self { + self.write_by_name("SEC_1_GROUND_SPOILER_OUT", true); + self.write_by_name("SEC_2_GROUND_SPOILER_OUT", true); + self.write_by_name("SEC_3_GROUND_SPOILER_OUT", true); self } - fn set_retract_spoilers(mut self) -> Self { - self.write_by_name("SPOILERS_GROUND_SPOILERS_ACTIVE", false); + fn set_retract_ground_spoilers(mut self) -> Self { + self.write_by_name("SEC_1_GROUND_SPOILER_OUT", false); + self.write_by_name("SEC_2_GROUND_SPOILER_OUT", false); + self.write_by_name("SEC_3_GROUND_SPOILER_OUT", false); self } fn set_ailerons_neutral(mut self) -> Self { - self.write_by_name("HYD_AILERON_LEFT_DEMAND", 0.5); - self.write_by_name("HYD_AILERON_RIGHT_DEMAND", 0.5); + self.write_by_name("LEFT_AIL_BLUE_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_AIL_BLUE_COMMANDED_POSITION", 0.); + self.write_by_name("LEFT_AIL_GREEN_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_AIL_GREEN_COMMANDED_POSITION", 0.); self } fn set_elevator_neutral(mut self) -> Self { - self.write_by_name("HYD_ELEVATOR_DEMAND", 0.5); + self.write_by_name("LEFT_ELEV_BLUE_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_ELEV_BLUE_COMMANDED_POSITION", 0.); + self.write_by_name("LEFT_ELEV_GREEN_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_ELEV_YELLOW_COMMANDED_POSITION", 0.); self } fn set_ailerons_left_turn(mut self) -> Self { - self.write_by_name("HYD_AILERON_LEFT_DEMAND", 1.); - self.write_by_name("HYD_AILERON_RIGHT_DEMAND", 0.); + self.write_by_name("LEFT_AIL_BLUE_COMMANDED_POSITION", -25.); + self.write_by_name("RIGHT_AIL_BLUE_COMMANDED_POSITION", -25.); + self.write_by_name("LEFT_AIL_GREEN_COMMANDED_POSITION", -25.); + self.write_by_name("RIGHT_AIL_GREEN_COMMANDED_POSITION", -25.); + self + } + + fn set_elac1_actuators_energized(mut self) -> Self { + self.write_by_name("LEFT_AIL_BLUE_SERVO_SOLENOID_ENERGIZED", 1.); + self.write_by_name("RIGHT_AIL_BLUE_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("LEFT_AIL_GREEN_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("RIGHT_AIL_GREEN_SERVO_SOLENOID_ENERGIZED", 1.); + + self.write_by_name("LEFT_ELEV_BLUE_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("RIGHT_ELEV_BLUE_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("LEFT_ELEV_GREEN_SERVO_SOLENOID_ENERGIZED", 1.); + self.write_by_name("RIGHT_ELEV_YELLOW_SERVO_SOLENOID_ENERGIZED", 1.); + self + } + + fn set_elac_actuators_de_energized(mut self) -> Self { + self.write_by_name("LEFT_AIL_BLUE_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("RIGHT_AIL_BLUE_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("LEFT_AIL_GREEN_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("RIGHT_AIL_GREEN_SERVO_SOLENOID_ENERGIZED", 0.); + + self.write_by_name("LEFT_ELEV_BLUE_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("RIGHT_ELEV_BLUE_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("LEFT_ELEV_GREEN_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("RIGHT_ELEV_YELLOW_SERVO_SOLENOID_ENERGIZED", 0.); + + self.write_by_name("LEFT_ELEV_BLUE_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_ELEV_BLUE_COMMANDED_POSITION", 0.); + self.write_by_name("LEFT_ELEV_GREEN_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_ELEV_YELLOW_COMMANDED_POSITION", 0.); + self + } + + fn set_left_spoilers_out(mut self) -> Self { + self.write_by_name("LEFT_SPOILER_1_COMMANDED_POSITION", 50.); + self.write_by_name("LEFT_SPOILER_2_COMMANDED_POSITION", 50.); + self.write_by_name("LEFT_SPOILER_3_COMMANDED_POSITION", 50.); + self.write_by_name("LEFT_SPOILER_4_COMMANDED_POSITION", 50.); + self.write_by_name("LEFT_SPOILER_5_COMMANDED_POSITION", 50.); + self + } + + fn set_left_spoilers_in(mut self) -> Self { + self.write_by_name("LEFT_SPOILER_1_COMMANDED_POSITION", 0.); + self.write_by_name("LEFT_SPOILER_2_COMMANDED_POSITION", 0.); + self.write_by_name("LEFT_SPOILER_3_COMMANDED_POSITION", 0.); + self.write_by_name("LEFT_SPOILER_4_COMMANDED_POSITION", 0.); + self.write_by_name("LEFT_SPOILER_5_COMMANDED_POSITION", 0.); + self + } + + fn set_right_spoilers_out(mut self) -> Self { + self.write_by_name("RIGHT_SPOILER_1_COMMANDED_POSITION", 50.); + self.write_by_name("RIGHT_SPOILER_2_COMMANDED_POSITION", 50.); + self.write_by_name("RIGHT_SPOILER_3_COMMANDED_POSITION", 50.); + self.write_by_name("RIGHT_SPOILER_4_COMMANDED_POSITION", 50.); + self.write_by_name("RIGHT_SPOILER_5_COMMANDED_POSITION", 50.); + self + } + + fn set_right_spoilers_in(mut self) -> Self { + self.write_by_name("RIGHT_SPOILER_1_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_SPOILER_2_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_SPOILER_3_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_SPOILER_4_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_SPOILER_5_COMMANDED_POSITION", 0.); self } fn set_ailerons_right_turn(mut self) -> Self { - self.write_by_name("HYD_AILERON_LEFT_DEMAND", 0.); - self.write_by_name("HYD_AILERON_RIGHT_DEMAND", 1.); + self.write_by_name("LEFT_AIL_BLUE_COMMANDED_POSITION", 25.); + self.write_by_name("RIGHT_AIL_BLUE_COMMANDED_POSITION", 25.); + self.write_by_name("LEFT_AIL_GREEN_COMMANDED_POSITION", 25.); + self.write_by_name("RIGHT_AIL_GREEN_COMMANDED_POSITION", 25.); self } @@ -6645,6 +6824,22 @@ mod tests { self.query(|a| a.lgcius.active_lgciu().gear_system_state()) } + fn set_elevator_full_up(mut self) -> Self { + self.write_by_name("LEFT_ELEV_BLUE_COMMANDED_POSITION", -30.); + self.write_by_name("RIGHT_ELEV_BLUE_COMMANDED_POSITION", -30.); + self.write_by_name("LEFT_ELEV_GREEN_COMMANDED_POSITION", -30.); + self.write_by_name("RIGHT_ELEV_YELLOW_COMMANDED_POSITION", -30.); + self + } + + fn set_elevator_full_down(mut self) -> Self { + self.write_by_name("LEFT_ELEV_BLUE_COMMANDED_POSITION", 17.); + self.write_by_name("RIGHT_ELEV_BLUE_COMMANDED_POSITION", 17.); + self.write_by_name("LEFT_ELEV_GREEN_COMMANDED_POSITION", 17.); + self.write_by_name("RIGHT_ELEV_YELLOW_COMMANDED_POSITION", 17.); + self + } + fn empty_brake_accumulator_using_park_brake(mut self) -> Self { self = self .set_park_brake(true) @@ -6703,38 +6898,13 @@ mod tests { self } - fn set_gear_emergency_extension_active(mut self, is_active: bool) -> Self { - self.write_by_name("GEAR_EMERGENCY_EXTENSION_ACTIVE", is_active); - self - } - fn turn_emergency_gear_extension_n_turns(mut self, number_of_turns: u8) -> Self { - let mut number_of_loops = 0; - while self.get_emergency_handle_number_of_turns() < number_of_turns { - self = self - .set_gear_emergency_extension_active(true) - .run_waiting_for(Duration::from_secs_f64(0.5)); - number_of_loops += 1; - assert!(number_of_loops < 50); - } - - self = self.set_gear_emergency_extension_active(false); - + self.write_by_name("GRAVITYGEAR_ROTATE_PCT", number_of_turns as f64 * 100.); self } fn stow_emergency_gear_extension(mut self) -> Self { - let mut number_of_loops = 0; - while self.get_emergency_handle_number_of_turns() != 0 { - self = self - .set_gear_emergency_extension_active(true) - .run_waiting_for(Duration::from_secs_f64(0.5)); - number_of_loops += 1; - assert!(number_of_loops < 50); - } - - self = self.set_gear_emergency_extension_active(false); - + self.write_by_name("GRAVITYGEAR_ROTATE_PCT", 0.); self } @@ -8195,7 +8365,7 @@ mod tests { } #[test] - fn no_brake_inversion() { + fn no_norm_brake_inversion() { let mut test_bed = test_bed_on_ground_with() .engines_off() .on_the_ground() @@ -8210,6 +8380,7 @@ mod tests { assert!(test_bed.is_green_pressure_switch_pressurised()); assert!(test_bed.is_yellow_pressure_switch_pressurised()); + // Braking left test_bed = test_bed .set_left_brake(Ratio::new::(100.)) @@ -8231,6 +8402,24 @@ mod tests { assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(2000.)); assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn no_alternate_brake_inversion() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .set_park_brake(false) + .run_waiting_for(Duration::from_secs(5)); + + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); // Disabling Askid causes alternate braking to work and release green brakes test_bed = test_bed @@ -8682,7 +8871,7 @@ mod tests { assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); test_bed = test_bed - .set_deploy_spoilers() + .set_deploy_ground_spoilers() .run_waiting_for(Duration::from_secs(6)); assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); @@ -8710,13 +8899,13 @@ mod tests { assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); test_bed = test_bed - .set_deploy_spoilers() + .set_deploy_ground_spoilers() .run_waiting_for(Duration::from_secs(6)); assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); test_bed = test_bed - .set_retract_spoilers() + .set_retract_ground_spoilers() .run_waiting_for(Duration::from_secs(1)); assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); @@ -8742,7 +8931,7 @@ mod tests { assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); test_bed = test_bed - .set_deploy_spoilers() + .set_deploy_ground_spoilers() .run_waiting_for(Duration::from_secs(6)); assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); @@ -8787,7 +8976,7 @@ mod tests { assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); test_bed = test_bed - .set_deploy_spoilers() + .set_deploy_ground_spoilers() .run_waiting_for(Duration::from_secs(6)); assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); @@ -8835,7 +9024,7 @@ mod tests { assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); test_bed = test_bed - .set_deploy_spoilers() + .set_deploy_ground_spoilers() .run_waiting_for(Duration::from_secs(6)); assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); @@ -8880,7 +9069,7 @@ mod tests { assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); test_bed = test_bed - .set_deploy_spoilers() + .set_deploy_ground_spoilers() .run_waiting_for(Duration::from_secs(6)); assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); @@ -10098,13 +10287,14 @@ mod tests { .set_cold_dark_inputs() .set_ptu_state(true) .set_yellow_e_pump(false) + .set_blue_e_pump_ovrd_pressed(true) .run_one_tick(); test_bed = test_bed .set_ailerons_left_turn() .run_waiting_for(Duration::from_secs_f64(6.)); - assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.is_blue_pressure_switch_pressurised()); assert!(test_bed.is_green_pressure_switch_pressurised()); assert!(test_bed.get_left_aileron_position().get::() > 0.9); assert!(test_bed.get_right_aileron_position().get::() < 0.1); @@ -10113,7 +10303,7 @@ mod tests { .set_ailerons_right_turn() .run_waiting_for(Duration::from_secs_f64(5.)); - assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.is_blue_pressure_switch_pressurised()); assert!(test_bed.is_green_pressure_switch_pressurised()); assert!(test_bed.get_left_aileron_position().get::() < 0.1); assert!(test_bed.get_right_aileron_position().get::() > 0.9); @@ -10127,11 +10317,12 @@ mod tests { .set_cold_dark_inputs() .set_ptu_state(true) .set_yellow_e_pump(false) + .set_blue_e_pump_ovrd_pressed(true) .run_one_tick(); test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(8.)); - assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.is_blue_pressure_switch_pressurised()); assert!(test_bed.is_green_pressure_switch_pressurised()); assert!(test_bed.get_left_aileron_position().get::() > 0.45); assert!(test_bed.get_right_aileron_position().get::() > 0.45); @@ -10139,9 +10330,10 @@ mod tests { test_bed = test_bed .set_ptu_state(false) .set_yellow_e_pump(true) + .set_blue_e_pump(false) .run_waiting_for(Duration::from_secs_f64(50.)); - assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); assert!(!test_bed.is_green_pressure_switch_pressurised()); assert!(test_bed.get_left_aileron_position().get::() < 0.42); assert!(test_bed.get_right_aileron_position().get::() < 0.42); @@ -10155,24 +10347,83 @@ mod tests { .set_cold_dark_inputs() .set_ptu_state(true) .set_yellow_e_pump(false) + .set_blue_e_pump_ovrd_pressed(true) .run_one_tick(); test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(8.)); + assert!(test_bed.is_blue_pressure_switch_pressurised()); assert!(test_bed.is_yellow_pressure_switch_pressurised()); assert!(test_bed.is_green_pressure_switch_pressurised()); - assert!(test_bed.get_left_elevator_position().get::() > 0.45); - assert!(test_bed.get_right_elevator_position().get::() > 0.45); + assert!(test_bed.get_left_elevator_position().get::() > 0.35); + assert!(test_bed.get_right_elevator_position().get::() > 0.35); test_bed = test_bed .set_ptu_state(false) .set_yellow_e_pump(true) + .set_blue_e_pump(false) .run_waiting_for(Duration::from_secs_f64(75.)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); assert!(!test_bed.is_yellow_pressure_switch_pressurised()); assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_elevator_position().get::() < 0.3); + assert!(test_bed.get_right_elevator_position().get::() < 0.3); + } + + #[test] + fn elevators_can_go_up_and_down_with_pressure() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_blue_e_pump_ovrd_pressed(true) + .run_one_tick(); + + test_bed = test_bed + .set_elevator_full_up() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + assert!(test_bed.get_left_elevator_position().get::() > 0.9); + assert!(test_bed.get_right_elevator_position().get::() > 0.9); + + test_bed = test_bed + .set_elevator_full_down() + .run_waiting_for(Duration::from_secs_f64(1.5)); + + assert!(test_bed.get_left_elevator_position().get::() < 0.1); + assert!(test_bed.get_right_elevator_position().get::() < 0.1); + } + + #[test] + fn elevators_centers_with_pressure_but_no_computer_command() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_blue_e_pump_ovrd_pressed(true) + .run_one_tick(); + + test_bed = test_bed + .set_elevator_full_up() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + assert!(test_bed.get_left_elevator_position().get::() > 0.9); + assert!(test_bed.get_right_elevator_position().get::() > 0.9); + + test_bed = test_bed + .set_elac_actuators_de_energized() + .run_waiting_for(Duration::from_secs_f64(2.)); + assert!(test_bed.get_left_elevator_position().get::() < 0.4); assert!(test_bed.get_right_elevator_position().get::() < 0.4); + + assert!(test_bed.get_left_elevator_position().get::() > 0.3); + assert!(test_bed.get_right_elevator_position().get::() > 0.3); } #[test] @@ -10360,7 +10611,7 @@ mod tests { test_bed = test_bed .turn_emergency_gear_extension_n_turns(2) - .run_waiting_for(Duration::from_secs_f64(10.)); + .run_waiting_for(Duration::from_secs_f64(25.)); assert!(test_bed.is_all_doors_really_down()); } @@ -10443,7 +10694,7 @@ mod tests { test_bed = test_bed .set_gear_lever_down() .turn_emergency_gear_extension_n_turns(3) - .run_waiting_for(Duration::from_secs_f64(20.)); + .run_waiting_for(Duration::from_secs_f64(30.)); assert!(test_bed.is_all_gears_really_down()); assert!(test_bed.is_all_doors_really_down()); @@ -10466,22 +10717,36 @@ mod tests { fn spoilers_move_to_requested_position() { let mut test_bed = test_bed_on_ground_with() .set_cold_dark_inputs() - .in_flight() - .run_waiting_for(Duration::from_secs(10)); + .set_blue_e_pump_ovrd_pressed(true) + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs_f64(5.)); - assert!(test_bed.green_pressure() > Pressure::new::(2900.)); - assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); - assert!(test_bed.blue_pressure() > Pressure::new::(2900.)); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.is_green_pressure_switch_pressurised()); test_bed = test_bed - .set_spoiler_position_demand(Ratio::new::(0.7), Ratio::new::(0.5)) - .run_waiting_for(Duration::from_secs(2)); + .set_left_spoilers_out() + .run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.get_mean_left_spoilers_position().get::() > 0.9); + assert!(test_bed.get_mean_right_spoilers_position().get::() < 0.01); + + test_bed = test_bed + .set_left_spoilers_in() + .set_right_spoilers_out() + .run_waiting_for(Duration::from_secs_f64(2.)); - assert!(test_bed.get_spoiler_left_mean_position().get::() > 0.68); - assert!(test_bed.get_spoiler_left_mean_position().get::() < 0.72); + assert!(test_bed.get_mean_right_spoilers_position().get::() > 0.9); + assert!(test_bed.get_mean_left_spoilers_position().get::() < 0.01); + + test_bed = test_bed + .set_left_spoilers_in() + .set_right_spoilers_in() + .run_waiting_for(Duration::from_secs_f64(2.)); - assert!(test_bed.get_spoiler_right_mean_position().get::() > 0.48); - assert!(test_bed.get_spoiler_right_mean_position().get::() < 0.52); + assert!(test_bed.get_mean_left_spoilers_position().get::() < 0.01); + assert!(test_bed.get_mean_right_spoilers_position().get::() < 0.01); } #[test] @@ -10494,6 +10759,39 @@ mod tests { assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); } + #[test] + fn gear_gravity_extension_reverted_has_correct_sequence() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .with_worst_case_ptu() + .in_flight() + .run_one_tick(); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + + test_bed = test_bed + .turn_emergency_gear_extension_n_turns(3) + .run_waiting_for(Duration::from_secs_f64(35.)); + + assert!(test_bed.is_all_doors_really_down()); + assert!(test_bed.is_all_gears_really_down()); + + test_bed = test_bed + .stow_emergency_gear_extension() + .run_waiting_for(Duration::from_secs_f64(5.)); + + // After 5 seconds we expect gear being retracted and doors still down + assert!(test_bed.gear_system_state() == GearSystemState::Retracting); + assert!(test_bed.is_all_doors_really_down()); + assert!(!test_bed.is_all_gears_really_down()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(15.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + assert!(test_bed.is_all_doors_really_up()); + assert!(test_bed.is_all_gears_really_up()); + } + #[test] fn aileron_init_centered_if_spawning_in_air() { let mut test_bed = test_bed_in_flight_with() @@ -10559,5 +10857,56 @@ mod tests { assert!(test_bed.get_left_elevator_position().get::() < 0.1); assert!(test_bed.get_right_elevator_position().get::() < 0.1); } + + #[test] + fn brakes_on_ground_work_after_emergency_extension() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + + test_bed = test_bed + .turn_emergency_gear_extension_n_turns(3) + .run_waiting_for(Duration::from_secs_f64(30.)); + assert!(test_bed.is_all_gears_really_down()); + assert!(test_bed.is_all_doors_really_down()); + + test_bed = test_bed + .on_the_ground_after_touchdown() + .set_left_brake(Ratio::new::(1.)) + .set_right_brake(Ratio::new::(1.)) + .run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(500.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(500.)); + } + + #[test] + fn gears_do_not_deploy_with_all_lgciu_failed() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + + test_bed.fail(FailureType::LgciuPowerSupply(LgciuId::Lgciu1)); + test_bed.fail(FailureType::LgciuPowerSupply(LgciuId::Lgciu2)); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(5.)); + assert!(test_bed.is_all_gears_really_up()); + assert!(test_bed.is_all_doors_really_up()); + + test_bed = test_bed + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.is_all_gears_really_up()); + assert!(test_bed.is_all_doors_really_up()); + } } } diff --git a/src/systems/a320_systems/src/lib.rs b/src/systems/a320_systems/src/lib.rs index f9e1e59b238e..3c76d886cddf 100644 --- a/src/systems/a320_systems/src/lib.rs +++ b/src/systems/a320_systems/src/lib.rs @@ -201,6 +201,7 @@ impl Aircraft for A320 { &self.pneumatic_overhead, &self.engine_fire_overhead, &self.apu, + &self.air_conditioning, ); self.air_conditioning.update( context, diff --git a/src/systems/a320_systems/src/pneumatic.rs b/src/systems/a320_systems/src/pneumatic.rs index 28ba2448cd0b..37b253e76fe8 100644 --- a/src/systems/a320_systems/src/pneumatic.rs +++ b/src/systems/a320_systems/src/pneumatic.rs @@ -3,7 +3,6 @@ use std::{f64::consts::PI, time::Duration}; use uom::si::{ f64::*, - mass_rate::kilogram_per_second, pressure::psi, ratio::ratio, thermodynamic_temperature::degree_celsius, @@ -12,6 +11,7 @@ use uom::si::{ use systems::{ accept_iterable, + air_conditioning::PackFlowControllers, overhead::{AutoOffFaultPushButton, OnOffFaultPushButton}, pneumatic::{ valve::*, BleedMonitoringComputerChannelOperationMode, @@ -24,8 +24,8 @@ use systems::{ shared::{ pid::PidController, update_iterator::MaxStepLoop, ControllerSignal, ElectricalBusType, ElectricalBuses, EngineBleedPushbutton, EngineCorrectedN1, EngineCorrectedN2, - EngineFirePushButtons, EngineStartState, HydraulicColor, PneumaticBleed, PneumaticValve, - ReservoirAirPressure, + EngineFirePushButtons, EngineStartState, HydraulicColor, PackFlowValveState, + PneumaticBleed, PneumaticValve, ReservoirAirPressure, }, simulation::{ InitContext, Read, SimulationElement, SimulationElementVisitor, SimulatorReader, @@ -215,6 +215,7 @@ impl A320Pneumatic { overhead_panel: &A320PneumaticOverheadPanel, engine_fire_push_buttons: &impl EngineFirePushButtons, apu: &impl ControllerSignal, + pack_flow_valve_signals: &impl PackFlowControllers<3>, ) { self.physics_updater.update(context); @@ -225,6 +226,7 @@ impl A320Pneumatic { overhead_panel, engine_fire_push_buttons, apu, + pack_flow_valve_signals, ); } } @@ -236,6 +238,7 @@ impl A320Pneumatic { overhead_panel: &A320PneumaticOverheadPanel, engine_fire_push_buttons: &impl EngineFirePushButtons, apu: &impl ControllerSignal, + pack_flow_valve_signals: &impl PackFlowControllers<3>, ) { self.apu_compression_chamber.update(apu); @@ -314,7 +317,9 @@ impl A320Pneumatic { self.packs .iter_mut() .zip(self.engine_systems.iter_mut()) - .for_each(|(pack, engine_system)| pack.update(context, engine_system)); + .for_each(|(pack, engine_system)| { + pack.update(context, engine_system, pack_flow_valve_signals) + }); } // TODO: Returning a mutable reference here is not great. I was running into an issue with the update order: @@ -359,6 +364,14 @@ impl EngineStartState for A320Pneumatic { self.fadec.engine_mode_selector() } } +impl PackFlowValveState for A320Pneumatic { + fn pack_flow_valve_open_amount(&self, pack_id: usize) -> Ratio { + self.packs[pack_id].pack_flow_valve_open_amount() + } + fn pack_flow_valve_air_flow(&self, pack_id: usize) -> MassRate { + self.packs[pack_id].pack_flow_valve_air_flow() + } +} impl SimulationElement for A320Pneumatic { fn accept(&mut self, visitor: &mut T) { self.cross_bleed_valve.accept(visitor); @@ -820,7 +833,7 @@ impl EngineBleedAirSystem { ThermodynamicTemperature::new::(15.), ), precooler_outlet_pipe: PneumaticPipe::new( - Volume::new::(0.5), + Volume::new::(2.5), Pressure::new::(14.7), ThermodynamicTemperature::new::(15.), ), @@ -1150,34 +1163,43 @@ impl SimulationElement for FullAuthorityDigitalEngineControl { /// A struct to hold all the pack related components struct PackComplex { + engine_number: usize, + pack_flow_valve_id: VariableIdentifier, pack_flow_valve_flow_rate_id: VariableIdentifier, pack_container: PneumaticPipe, exhaust: PneumaticExhaust, pack_flow_valve: DefaultValve, - pack_flow_valve_controller: PackFlowValveController, } impl PackComplex { fn new(context: &mut InitContext, engine_number: usize) -> Self { Self { + engine_number, + pack_flow_valve_id: context.get_identifier(Self::pack_flow_valve_id(engine_number)), pack_flow_valve_flow_rate_id: context .get_identifier(format!("PNEU_PACK_{}_FLOW_VALVE_FLOW_RATE", engine_number)), pack_container: PneumaticPipe::new( - Volume::new::(1.), + Volume::new::(2.), Pressure::new::(14.7), ThermodynamicTemperature::new::(15.), ), exhaust: PneumaticExhaust::new(0.3, 0.3, Pressure::new::(0.)), pack_flow_valve: DefaultValve::new_closed(), - pack_flow_valve_controller: PackFlowValveController::new(context, engine_number), } } - fn update(&mut self, context: &UpdateContext, from: &mut impl PneumaticContainer) { - self.pack_flow_valve_controller - .update(context, self.pack_flow_valve.fluid_flow()); + fn pack_flow_valve_id(number: usize) -> String { + format!("COND_PACK_FLOW_VALVE_{}_IS_OPEN", number) + } - self.pack_flow_valve - .update_open_amount(&self.pack_flow_valve_controller); + fn update( + &mut self, + context: &UpdateContext, + from: &mut impl PneumaticContainer, + pack_flow_valve_signals: &impl PackFlowControllers<3>, + ) { + self.pack_flow_valve.update_open_amount( + &pack_flow_valve_signals.pack_flow_controller(self.engine_number.into()), + ); self.pack_flow_valve .update_move_fluid(context, from, &mut self.pack_container); @@ -1185,6 +1207,14 @@ impl PackComplex { self.exhaust .update_move_fluid(context, &mut self.pack_container); } + + fn pack_flow_valve_open_amount(&self) -> Ratio { + self.pack_flow_valve.open_amount() + } + + fn pack_flow_valve_air_flow(&self) -> MassRate { + self.pack_flow_valve.fluid_flow() + } } impl PneumaticContainer for PackComplex { fn pressure(&self) -> Pressure { @@ -1218,13 +1248,11 @@ impl PneumaticContainer for PackComplex { } } impl SimulationElement for PackComplex { - fn accept(&mut self, visitor: &mut T) { - self.pack_flow_valve_controller.accept(visitor); - - visitor.visit(self); - } - fn write(&self, writer: &mut SimulatorWriter) { + writer.write( + &self.pack_flow_valve_id, + self.pack_flow_valve_open_amount() > Ratio::new::(0.), + ); writer.write( &self.pack_flow_valve_flow_rate_id, self.pack_flow_valve.fluid_flow(), @@ -1232,44 +1260,6 @@ impl SimulationElement for PackComplex { } } -// In the future, this will be done by the ACSC, hence why I have used an external controller and not the BMC -struct PackFlowValveController { - pack_toggle_pb_id: VariableIdentifier, - pack_pb_is_auto: bool, - pid: PidController, -} -impl PackFlowValveController { - fn new(context: &mut InitContext, engine_number: usize) -> Self { - Self { - pack_toggle_pb_id: context - .get_identifier(format!("OVHD_COND_PACK_{}_PB_IS_ON", engine_number)), - pack_pb_is_auto: true, - pid: PidController::new(0., 0.05, 0., 0., 1., 0.75, 1.), - } - } - - fn update(&mut self, context: &UpdateContext, pack_flow_valve_flow_rate: MassRate) { - self.pid.next_control_output( - pack_flow_valve_flow_rate.get::(), - Some(context.delta()), - ); - } -} -impl ControllerSignal for PackFlowValveController { - fn signal(&self) -> Option { - Some(if self.pack_pb_is_auto { - PackFlowValveSignal::new(Ratio::new::(self.pid.output())) - } else { - PackFlowValveSignal::new_closed() - }) - } -} -impl SimulationElement for PackFlowValveController { - fn read(&mut self, reader: &mut SimulatorReader) { - self.pack_pb_is_auto = reader.read(&self.pack_toggle_pb_id); - } -} - /// This is a unique valve (and specific to the A320 probably) because it is controlled by two motors. One for manual control and one for automatic control pub struct CrossBleedValve { open_amount: Ratio, @@ -1344,6 +1334,10 @@ impl SimulationElement for CrossBleedValve { #[cfg(test)] mod tests { use systems::{ + air_conditioning::{ + acs_controller::{Pack, PackFlowController}, + AirConditioningSystem, PackFlowControllers, ZoneType, + }, electrical::{test::TestElectricitySource, ElectricalBus, Electricity}, engine::leap_engine::LeapEngine, failures::FailureType, @@ -1352,10 +1346,13 @@ mod tests { CrossBleedValveSelectorMode, EngineState, PneumaticContainer, PneumaticValveSignal, TargetPressureTemperatureSignal, }, + pressurization::PressurizationOverheadPanel, shared::{ - ApuBleedAirValveSignal, ControllerSignal, ElectricalBusType, ElectricalBuses, - EmergencyElectricalState, EngineFirePushButtons, HydraulicColor, - InternationalStandardAtmosphere, MachNumber, PneumaticValve, PotentialOrigin, + ApuBleedAirValveSignal, Cabin, ControllerSignal, ElectricalBusType, ElectricalBuses, + EmergencyElectricalState, EngineBleedPushbutton, EngineCorrectedN1, + EngineFirePushButtons, EngineStartState, GroundSpeed, HydraulicColor, + InternationalStandardAtmosphere, LgciuWeightOnWheels, MachNumber, PackFlowValveState, + PneumaticBleed, PneumaticValve, PotentialOrigin, }, simulation::{ test::{SimulationTestBed, TestBed, WriteByName}, @@ -1366,12 +1363,150 @@ mod tests { use std::{fs, fs::File, time::Duration}; use uom::si::{ - f64::*, length::foot, mass_rate::kilogram_per_second, pressure::psi, ratio::ratio, - thermodynamic_temperature::degree_celsius, velocity::knot, + f64::*, + length::foot, + mass_rate::kilogram_per_second, + pressure::{pascal, psi}, + ratio::ratio, + thermodynamic_temperature::degree_celsius, + velocity::knot, }; use super::{A320Pneumatic, A320PneumaticOverheadPanel}; + struct TestAirConditioning { + a320_air_conditioning_system: AirConditioningSystem<3>, + + adirs: TestAdirs, + lgciu: TestLgciu, + pressurization: TestPressurization, + pressurization_overhead: PressurizationOverheadPanel, + } + impl TestAirConditioning { + fn new(context: &mut InitContext) -> Self { + let cabin_zones: [ZoneType; 3] = + [ZoneType::Cockpit, ZoneType::Cabin(1), ZoneType::Cabin(2)]; + + Self { + a320_air_conditioning_system: AirConditioningSystem::new( + context, + cabin_zones, + vec![ElectricalBusType::DirectCurrent(1)], + vec![ElectricalBusType::DirectCurrent(2)], + ), + + adirs: TestAdirs::new(), + lgciu: TestLgciu::new(true), + pressurization: TestPressurization::new(), + pressurization_overhead: PressurizationOverheadPanel::new(context), + } + } + fn update( + &mut self, + context: &UpdateContext, + engines: [&impl EngineCorrectedN1; 2], + engine_fire_push_buttons: &impl EngineFirePushButtons, + pneumatic: &(impl EngineStartState + PackFlowValveState + PneumaticBleed), + pneumatic_overhead: &impl EngineBleedPushbutton, + ) { + self.a320_air_conditioning_system.update( + context, + &self.adirs, + engines, + engine_fire_push_buttons, + pneumatic, + pneumatic_overhead, + &self.pressurization, + &self.pressurization_overhead, + [&self.lgciu; 2], + ); + } + } + impl PackFlowControllers<3> for TestAirConditioning { + fn pack_flow_controller(&self, pack_id: Pack) -> PackFlowController<3> { + self.a320_air_conditioning_system + .pack_flow_controller(pack_id) + } + } + impl SimulationElement for TestAirConditioning { + fn accept(&mut self, visitor: &mut V) { + self.a320_air_conditioning_system.accept(visitor); + + visitor.visit(self); + } + } + + struct TestAdirs { + ground_speed: Velocity, + } + impl TestAdirs { + fn new() -> Self { + Self { + ground_speed: Velocity::new::(0.), + } + } + } + impl GroundSpeed for TestAdirs { + fn ground_speed(&self) -> Velocity { + self.ground_speed + } + } + + struct TestPressurization { + cabin_pressure: Pressure, + } + impl TestPressurization { + fn new() -> Self { + Self { + cabin_pressure: Pressure::new::(101315.), + } + } + } + impl Cabin for TestPressurization { + fn altitude(&self) -> Length { + Length::new::(0.) + } + + fn pressure(&self) -> Pressure { + self.cabin_pressure + } + } + + struct TestLgciu { + compressed: bool, + } + impl TestLgciu { + fn new(compressed: bool) -> Self { + Self { compressed } + } + } + impl LgciuWeightOnWheels for TestLgciu { + fn left_and_right_gear_compressed(&self, _treat_ext_pwr_as_ground: bool) -> bool { + self.compressed + } + fn right_gear_compressed(&self, _treat_ext_pwr_as_ground: bool) -> bool { + true + } + fn right_gear_extended(&self, _treat_ext_pwr_as_ground: bool) -> bool { + false + } + fn left_gear_compressed(&self, _treat_ext_pwr_as_ground: bool) -> bool { + true + } + fn left_gear_extended(&self, _treat_ext_pwr_as_ground: bool) -> bool { + false + } + fn left_and_right_gear_extended(&self, _treat_ext_pwr_as_ground: bool) -> bool { + false + } + fn nose_gear_compressed(&self, _treat_ext_pwr_as_ground: bool) -> bool { + true + } + fn nose_gear_extended(&self, _treat_ext_pwr_as_ground: bool) -> bool { + false + } + } + struct TestApu { bleed_air_valve_signal: ApuBleedAirValveSignal, bleed_air_pressure: Pressure, @@ -1466,6 +1601,7 @@ mod tests { struct PneumaticTestAircraft { pneumatic: A320Pneumatic, + air_conditioning: TestAirConditioning, apu: TestApu, engine_1: LeapEngine, engine_2: LeapEngine, @@ -1487,6 +1623,7 @@ mod tests { fn new(context: &mut InitContext) -> Self { Self { pneumatic: A320Pneumatic::new(context), + air_conditioning: TestAirConditioning::new(context), apu: TestApu::new(), engine_1: LeapEngine::new(context, 1), engine_2: LeapEngine::new(context, 2), @@ -1554,7 +1691,15 @@ mod tests { &self.pneumatic_overhead_panel, &self.fire_pushbuttons, &self.apu, + &self.air_conditioning, ); + self.air_conditioning.update( + context, + [&self.engine_1, &self.engine_2], + &self.fire_pushbuttons, + &self.pneumatic, + &self.pneumatic_overhead_panel, + ) } } impl SimulationElement for PneumaticTestAircraft { @@ -1564,6 +1709,7 @@ mod tests { self.engine_1.accept(visitor); self.engine_2.accept(visitor); self.pneumatic_overhead_panel.accept(visitor); + self.air_conditioning.accept(visitor); visitor.visit(self); } @@ -1584,11 +1730,14 @@ mod tests { } impl PneumaticTestBed { fn new() -> Self { - Self { + let mut test_bed = Self { test_bed: SimulationTestBed::::new(|context| { PneumaticTestAircraft::new(context) }), - } + }; + test_bed.command_pack_flow_selector_position(1); + + test_bed } fn and_run(mut self) -> Self { @@ -1965,6 +2114,10 @@ mod tests { fn cross_bleed_valve_is_powered_for_manual_control(&self) -> bool { self.query(|a| a.pneumatic.cross_bleed_valve.is_powered_for_manual_control) } + + fn command_pack_flow_selector_position(&mut self, value: u8) { + self.write_by_name("KNOB_OVHD_AIRCOND_PACKFLOW_Position", value); + } } fn test_bed() -> PneumaticTestBed { @@ -2630,6 +2783,8 @@ mod tests { .stop_eng2() .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Auto) .set_bleed_air_running() + .set_pack_flow_pb_is_auto(1, false) + .set_pack_flow_pb_is_auto(2, false) .and_stabilize(); assert!(test_bed.cross_bleed_valve_is_open()); @@ -2712,7 +2867,8 @@ mod tests { .stop_eng2() .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Shut) .set_bleed_air_running() - // .both_packs_auto() + .set_pack_flow_pb_is_auto(1, false) + .set_pack_flow_pb_is_auto(2, false) .and_stabilize(); assert!(!test_bed.pr_valve_is_open(1)); @@ -2722,22 +2878,6 @@ mod tests { assert!(!test_bed.precooler_outlet_pressure(2).is_nan()); } - #[test] - fn pack_flow_valve_closes_with_pack_pb_off() { - let mut test_bed = test_bed_with() - .set_pack_flow_pb_is_auto(1, true) - .set_pack_flow_pb_is_auto(2, false) - .and_run(); - - assert!(test_bed.pack_flow_valve_is_open(1)); - assert!(!test_bed.pack_flow_valve_is_open(2)); - - test_bed = test_bed.set_pack_flow_pb_is_auto(1, false).and_run(); - - assert!(!test_bed.pack_flow_valve_is_open(1)); - assert!(!test_bed.pack_flow_valve_is_open(2)); - } - #[test] fn bleed_monitoring_computers_powered_by_correct_buses() { let mut test_bed = test_bed() @@ -2961,28 +3101,6 @@ mod tests { assert!(!test_bed.cross_bleed_valve_is_open()); } - #[test] - fn pack_flow_drops_when_valve_is_closed() { - let mut test_bed = test_bed_with() - .idle_eng1() - .idle_eng2() - .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Shut) - .mach_number(MachNumber(0.)) - .both_packs_auto() - .and_stabilize(); - - assert!(test_bed.pack_flow_valve_flow(1) > flow_rate_tolerance()); - assert!(test_bed.pack_flow_valve_flow(2) > flow_rate_tolerance()); - - test_bed = test_bed - .set_pack_flow_pb_is_auto(1, false) - .set_pack_flow_pb_is_auto(2, false) - .and_run(); - - assert!(test_bed.pack_flow_valve_flow(1) < flow_rate_tolerance()); - assert!(test_bed.pack_flow_valve_flow(2) < flow_rate_tolerance()); - } - #[test] fn large_time_step_stability() { let mut test_bed = test_bed_with() @@ -3048,4 +3166,73 @@ mod tests { ); } } + + mod pack_flow_valve_tests { + use super::*; + + #[test] + fn pack_flow_valve_starts_closed() { + let test_bed = test_bed(); + + assert!(!test_bed.pack_flow_valve_is_open(1)); + assert!(!test_bed.pack_flow_valve_is_open(2)); + } + + #[test] + fn pack_flow_valve_opens_when_conditions_met() { + let test_bed = test_bed_with() + .idle_eng1() + .idle_eng2() + .set_pack_flow_pb_is_auto(1, true) + .set_pack_flow_pb_is_auto(2, true) + .and_stabilize(); + + assert!(test_bed.pack_flow_valve_is_open(1)); + assert!(test_bed.pack_flow_valve_is_open(2)); + } + + #[test] + fn pack_flow_valve_closes_with_pack_pb_off() { + let mut test_bed = test_bed_with() + .idle_eng1() + .idle_eng2() + .set_pack_flow_pb_is_auto(1, true) + .set_pack_flow_pb_is_auto(2, false) + .and_stabilize(); + + assert!(test_bed.pack_flow_valve_is_open(1)); + assert!(!test_bed.pack_flow_valve_is_open(2)); + + test_bed = test_bed + .set_pack_flow_pb_is_auto(1, false) + .and_run() + .and_run(); + + assert!(!test_bed.pack_flow_valve_is_open(1)); + assert!(!test_bed.pack_flow_valve_is_open(2)); + } + + #[test] + fn pack_flow_drops_when_valve_is_closed() { + let mut test_bed = test_bed_with() + .idle_eng1() + .idle_eng2() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Shut) + .mach_number(MachNumber(0.)) + .both_packs_auto() + .and_stabilize(); + + assert!(test_bed.pack_flow_valve_flow(1) > flow_rate_tolerance()); + assert!(test_bed.pack_flow_valve_flow(2) > flow_rate_tolerance()); + + test_bed = test_bed + .set_pack_flow_pb_is_auto(1, false) + .set_pack_flow_pb_is_auto(2, false) + .and_run() + .and_run(); + + assert!(test_bed.pack_flow_valve_flow(1) < flow_rate_tolerance()); + assert!(test_bed.pack_flow_valve_flow(2) < flow_rate_tolerance()); + } + } } diff --git a/src/systems/a320_systems_wasm/Cargo.toml b/src/systems/a320_systems_wasm/Cargo.toml index d0ebab12deba..eee2513a186a 100644 --- a/src/systems/a320_systems_wasm/Cargo.toml +++ b/src/systems/a320_systems_wasm/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["cdylib"] test = false [dependencies] -uom = "0.32.0" +uom = "0.33.0" a320_systems = { path = "../a320_systems" } systems = { path = "../systems" } systems_wasm = { path = "../systems_wasm" } diff --git a/src/systems/a320_systems_wasm/src/ailerons.rs b/src/systems/a320_systems_wasm/src/ailerons.rs index 6342e6cec3ce..0e3089dfe605 100644 --- a/src/systems/a320_systems_wasm/src/ailerons.rs +++ b/src/systems/a320_systems_wasm/src/ailerons.rs @@ -1,24 +1,13 @@ use std::error::Error; -use systems_wasm::aspects::{ExecuteOn, MsfsAspectBuilder}; -use systems_wasm::Variable; -pub(super) fn ailerons(builder: &mut MsfsAspectBuilder) -> Result<(), Box> { - // Inputs from FBW becomes the aileron position demand for hydraulic system - // MSFS uses [-1;1] ranges, and Left aileron UP is -1 while right aileron UP is 1 - // Systems use [0;1], 1 is UP - builder.map( - ExecuteOn::PreTick, - Variable::named("AILERON_LEFT_DEFLECTION_DEMAND"), - |value| (-value + 1.) / 2., - Variable::aspect("HYD_AILERON_LEFT_DEMAND"), - ); - builder.map( - ExecuteOn::PreTick, - Variable::named("AILERON_RIGHT_DEFLECTION_DEMAND"), - |value| (value + 1.) / 2., - Variable::aspect("HYD_AILERON_RIGHT_DEMAND"), - ); +use systems::shared::to_bool; +use systems_wasm::aspects::{ExecuteOn, MsfsAspectBuilder, ObjectWrite, VariablesToObject}; +use systems_wasm::{set_data_on_sim_object, Variable}; +use msfs::sim_connect; +use msfs::{sim_connect::SimConnect, sim_connect::SIMCONNECT_OBJECT_ID_USER}; + +pub(super) fn ailerons(builder: &mut MsfsAspectBuilder) -> Result<(), Box> { // Aileron positions returned by hydraulic system are converted to MSFS format // It means we just invert left side direction and do [0;1] -> [-1;1] builder.map( @@ -34,5 +23,58 @@ pub(super) fn ailerons(builder: &mut MsfsAspectBuilder) -> Result<(), Box Vec { + vec![ + Variable::aspect("HYD_FINAL_AILERON_FEEDBACK"), + Variable::named("FLIGHT_CONTROLS_TRACKING_MODE"), + ] + } + + fn write(&mut self, values: Vec) -> ObjectWrite { + self.ailerons = values[0]; + + // Not writing control feedback when in tracking mode + ObjectWrite::on(!to_bool(values[1])) + } + + set_data_on_sim_object!(); +} diff --git a/src/systems/a320_systems_wasm/src/elevators.rs b/src/systems/a320_systems_wasm/src/elevators.rs index 3a42c63e66b4..79560e485364 100644 --- a/src/systems/a320_systems_wasm/src/elevators.rs +++ b/src/systems/a320_systems_wasm/src/elevators.rs @@ -1,27 +1,96 @@ use std::error::Error; -use systems_wasm::aspects::{ExecuteOn, MsfsAspectBuilder}; -use systems_wasm::Variable; + +use systems::shared::to_bool; +use systems_wasm::aspects::{ExecuteOn, MsfsAspectBuilder, ObjectWrite, VariablesToObject}; +use systems_wasm::{set_data_on_sim_object, Variable}; + +use msfs::sim_connect; +use msfs::{sim_connect::SimConnect, sim_connect::SIMCONNECT_OBJECT_ID_USER}; pub(super) fn elevators(builder: &mut MsfsAspectBuilder) -> Result<(), Box> { - builder.map( - ExecuteOn::PreTick, - Variable::aircraft("ELEVATOR DEFLECTION PCT", "Percent", 0), - |value| (value / 100. + 0.72) / 1.72, - Variable::aspect("HYD_ELEVATOR_DEMAND"), - ); + const MIN_ACTUAL_DEFLECTION_ANGLE: f64 = 17.; + const MAX_ACTUAL_DEFLECTION_ANGLE: f64 = 30.; builder.map( ExecuteOn::PostTick, Variable::aspect("HYD_ELEV_LEFT_DEFLECTION"), - |value| value * 1.72 - 0.72, + |value| { + hyd_deflection_to_msfs_deflection( + value, + MIN_ACTUAL_DEFLECTION_ANGLE, + MAX_ACTUAL_DEFLECTION_ANGLE, + ) + }, Variable::named("HYD_ELEVATOR_LEFT_DEFLECTION"), ); builder.map( ExecuteOn::PostTick, Variable::aspect("HYD_ELEV_RIGHT_DEFLECTION"), - |value| value * 1.72 - 0.72, + |value| { + hyd_deflection_to_msfs_deflection( + value, + MIN_ACTUAL_DEFLECTION_ANGLE, + MAX_ACTUAL_DEFLECTION_ANGLE, + ) + }, Variable::named("HYD_ELEVATOR_RIGHT_DEFLECTION"), ); + // ELEVATOR POSITION FEEDBACK TO SIM + builder.map_many( + ExecuteOn::PostTick, + vec![ + Variable::named("HYD_ELEVATOR_LEFT_DEFLECTION"), + Variable::named("HYD_ELEVATOR_RIGHT_DEFLECTION"), + ], + |values| (values[1] + values[0]) / 2., + Variable::aspect("HYD_FINAL_ELEVATOR_FEEDBACK"), + ); + + builder.variables_to_object(Box::new(PitchSimOutput { elevator: 0. })); + Ok(()) } + +#[sim_connect::data_definition] +struct PitchSimOutput { + #[name = "ELEVATOR POSITION"] + #[unit = "Position"] + elevator: f64, +} +impl VariablesToObject for PitchSimOutput { + fn variables(&self) -> Vec { + vec![ + Variable::aspect("HYD_FINAL_ELEVATOR_FEEDBACK"), + Variable::named("FLIGHT_CONTROLS_TRACKING_MODE"), + ] + } + + fn write(&mut self, values: Vec) -> ObjectWrite { + self.elevator = values[0]; + + // Not writing control feedback when in tracking mode + ObjectWrite::on(!to_bool(values[1])) + } + + set_data_on_sim_object!(); +} + +fn hyd_deflection_to_msfs_deflection( + hyd_deflection: f64, + min_actual_angle: f64, + max_actual_angle: f64, +) -> f64 { + let elevator_range = max_actual_angle + min_actual_angle; + + let angle_zero_offset = hyd_deflection * elevator_range; + let surface_angle = angle_zero_offset - min_actual_angle; + + let normalized_angle = surface_angle / max_actual_angle; + + if normalized_angle <= 0. { + normalized_angle * max_actual_angle / min_actual_angle + } else { + normalized_angle + } +} diff --git a/src/systems/a320_systems_wasm/src/flaps.rs b/src/systems/a320_systems_wasm/src/flaps.rs index a037fa24f947..d1f6ae2ebcaa 100644 --- a/src/systems/a320_systems_wasm/src/flaps.rs +++ b/src/systems/a320_systems_wasm/src/flaps.rs @@ -2,7 +2,7 @@ use msfs::sim_connect; use msfs::{sim_connect::SimConnect, sim_connect::SIMCONNECT_OBJECT_ID_USER}; use std::error::Error; use systems_wasm::aspects::{ - EventToVariableMapping, ExecuteOn, MsfsAspectBuilder, VariablesToObject, + EventToVariableMapping, ExecuteOn, MsfsAspectBuilder, ObjectWrite, VariablesToObject, }; use systems_wasm::{set_data_on_sim_object, Variable}; @@ -113,9 +113,11 @@ impl VariablesToObject for FlapsSurface { ] } - fn write(&mut self, values: Vec) { + fn write(&mut self, values: Vec) -> ObjectWrite { self.left_flap = values[0]; self.right_flap = values[1]; + + ObjectWrite::default() } set_data_on_sim_object!(); @@ -140,9 +142,11 @@ impl VariablesToObject for SlatsSurface { ] } - fn write(&mut self, values: Vec) { + fn write(&mut self, values: Vec) -> ObjectWrite { self.left_slat = values[0]; self.right_slat = values[1]; + + ObjectWrite::default() } set_data_on_sim_object!(); @@ -165,8 +169,9 @@ impl VariablesToObject for FlapsHandleIndex { ] } - fn write(&mut self, values: Vec) { + fn write(&mut self, values: Vec) -> ObjectWrite { self.index = Self::msfs_flap_index_from_surfaces_positions_percent(values); + ObjectWrite::default() } set_data_on_sim_object!(); diff --git a/src/systems/a320_systems_wasm/src/gear.rs b/src/systems/a320_systems_wasm/src/gear.rs index 8cc6e1fb5824..cebf2865ccc2 100644 --- a/src/systems/a320_systems_wasm/src/gear.rs +++ b/src/systems/a320_systems_wasm/src/gear.rs @@ -3,9 +3,8 @@ use std::error::Error; use msfs::sim_connect; use msfs::{sim_connect::SimConnect, sim_connect::SIMCONNECT_OBJECT_ID_USER}; -use systems::shared::to_bool; use systems_wasm::aspects::{ - EventToVariableMapping, ExecuteOn, MsfsAspectBuilder, VariableToEventMapping, + EventToVariableMapping, MsfsAspectBuilder, ObjectWrite, VariableToEventMapping, VariableToEventWriteOn, VariablesToObject, }; use systems_wasm::{set_data_on_sim_object, Variable}; @@ -56,30 +55,6 @@ pub(super) fn gear(builder: &mut MsfsAspectBuilder) -> Result<(), Box gear_set_set_event_id, ); - // MANUAL GRAVITY GEAR EXTENSION - builder.event_to_variable( - "GEAR_EMERGENCY_HANDLE_TOGGLE", - EventToVariableMapping::Value(1.), - Variable::aspect("GEAR_EMERGENCY_EXTENSION_EVENT"), - |options| options.mask().afterwards_reset_to(0.), - )?; - - builder.map_many( - ExecuteOn::PreTick, - vec![ - Variable::named("GEAR_EMERGENCY_EXTENSION_CLICKED"), - Variable::aspect("GEAR_EMERGENCY_EXTENSION_EVENT"), - ], - |values| { - if to_bool(values[0]) || to_bool(values[1]) { - 1. - } else { - 0. - } - }, - Variable::aspect("GEAR_EMERGENCY_EXTENSION_ACTIVE"), - ); - // GEAR POSITION FEEDBACK TO SIM builder.variables_to_object(Box::new(GearPosition { nose_position: 1., @@ -114,10 +89,12 @@ impl VariablesToObject for GearPosition { ] } - fn write(&mut self, values: Vec) { + fn write(&mut self, values: Vec) -> ObjectWrite { self.nose_position = values[0] / 100.; self.left_position = values[1] / 100.; self.right_position = values[2] / 100.; + + ObjectWrite::default() } set_data_on_sim_object!(); diff --git a/src/systems/a320_systems_wasm/src/lib.rs b/src/systems/a320_systems_wasm/src/lib.rs index 94f6006731fa..0cede93734cf 100644 --- a/src/systems/a320_systems_wasm/src/lib.rs +++ b/src/systems/a320_systems_wasm/src/lib.rs @@ -7,6 +7,7 @@ mod gear; mod nose_wheel_steering; mod rudder; mod spoilers; +mod trimmable_horizontal_stabilizer; use a320_systems::A320; use ailerons::ailerons; @@ -25,6 +26,7 @@ use systems::shared::{ }; use systems_wasm::aspects::ExecuteOn; use systems_wasm::{MsfsSimulationBuilder, Variable}; +use trimmable_horizontal_stabilizer::trimmable_horizontal_stabilizer; #[msfs::gauge(name=systems)] async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { @@ -263,11 +265,12 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { .with_aspect(autobrakes)? .with_aspect(nose_wheel_steering)? .with_aspect(flaps)? + .with_aspect(spoilers)? .with_aspect(ailerons)? .with_aspect(elevators)? .with_aspect(rudder)? - .with_aspect(spoilers)? .with_aspect(gear)? + .with_aspect(trimmable_horizontal_stabilizer)? .build(A320::new)?; while let Some(event) = gauge.next_event().await { diff --git a/src/systems/a320_systems_wasm/src/rudder.rs b/src/systems/a320_systems_wasm/src/rudder.rs index 2d5b6ff84b27..60e1fc26c227 100644 --- a/src/systems/a320_systems_wasm/src/rudder.rs +++ b/src/systems/a320_systems_wasm/src/rudder.rs @@ -1,12 +1,17 @@ use std::error::Error; -use systems_wasm::aspects::{ExecuteOn, MsfsAspectBuilder}; -use systems_wasm::Variable; + +use systems::shared::to_bool; +use systems_wasm::aspects::{ExecuteOn, MsfsAspectBuilder, ObjectWrite, VariablesToObject}; + +use msfs::sim_connect; +use msfs::{sim_connect::SimConnect, sim_connect::SIMCONNECT_OBJECT_ID_USER}; +use systems_wasm::{set_data_on_sim_object, Variable}; pub(super) fn rudder(builder: &mut MsfsAspectBuilder) -> Result<(), Box> { builder.map( ExecuteOn::PreTick, - Variable::aircraft("RUDDER DEFLECTION PCT", "Percent", 0), - |value| (value / 100. + 1.) / 2., + Variable::named("RUDDER_DEFLECTION_DEMAND"), + |value| (value + 1.) / 2., Variable::aspect("HYD_RUDDER_DEMAND"), ); @@ -17,5 +22,39 @@ pub(super) fn rudder(builder: &mut MsfsAspectBuilder) -> Result<(), Box Vec { + vec![ + Variable::aspect("HYD_FINAL_RUDDER_FEEDBACK"), + Variable::named("FLIGHT_CONTROLS_TRACKING_MODE"), + ] + } + + fn write(&mut self, values: Vec) -> ObjectWrite { + self.rudder = values[0]; + + // Not writing control feedback when in tracking mode + ObjectWrite::on(!to_bool(values[1])) + } + + set_data_on_sim_object!(); +} diff --git a/src/systems/a320_systems_wasm/src/spoilers.rs b/src/systems/a320_systems_wasm/src/spoilers.rs index f51998907154..224b3b373b12 100644 --- a/src/systems/a320_systems_wasm/src/spoilers.rs +++ b/src/systems/a320_systems_wasm/src/spoilers.rs @@ -3,76 +3,14 @@ use systems_wasm::aspects::{ExecuteOn, MsfsAspectBuilder}; use systems_wasm::Variable; pub(super) fn spoilers(builder: &mut MsfsAspectBuilder) -> Result<(), Box> { - builder.map( - ExecuteOn::PreTick, - Variable::named("SPOILERS_LEFT_DEFLECTION_DEMAND"), - |value| value, - Variable::aspect("HYD_SPOILER_1_LEFT_DEMAND"), - ); - builder.map( - ExecuteOn::PreTick, - Variable::named("SPOILERS_LEFT_DEFLECTION_DEMAND"), - |value| value, - Variable::aspect("HYD_SPOILER_2_LEFT_DEMAND"), - ); - builder.map( - ExecuteOn::PreTick, - Variable::named("SPOILERS_LEFT_DEFLECTION_DEMAND"), - |value| value, - Variable::aspect("HYD_SPOILER_3_LEFT_DEMAND"), - ); - builder.map( - ExecuteOn::PreTick, - Variable::named("SPOILERS_LEFT_DEFLECTION_DEMAND"), - |value| value, - Variable::aspect("HYD_SPOILER_4_LEFT_DEMAND"), - ); - builder.map( - ExecuteOn::PreTick, - Variable::named("SPOILERS_LEFT_DEFLECTION_DEMAND"), - |value| value, - Variable::aspect("HYD_SPOILER_5_LEFT_DEMAND"), - ); - - builder.map( - ExecuteOn::PreTick, - Variable::named("SPOILERS_RIGHT_DEFLECTION_DEMAND"), - |value| value, - Variable::aspect("HYD_SPOILER_1_RIGHT_DEMAND"), - ); - builder.map( - ExecuteOn::PreTick, - Variable::named("SPOILERS_RIGHT_DEFLECTION_DEMAND"), - |value| value, - Variable::aspect("HYD_SPOILER_2_RIGHT_DEMAND"), - ); - builder.map( - ExecuteOn::PreTick, - Variable::named("SPOILERS_RIGHT_DEFLECTION_DEMAND"), - |value| value, - Variable::aspect("HYD_SPOILER_3_RIGHT_DEMAND"), - ); - builder.map( - ExecuteOn::PreTick, - Variable::named("SPOILERS_RIGHT_DEFLECTION_DEMAND"), - |value| value, - Variable::aspect("HYD_SPOILER_4_RIGHT_DEMAND"), - ); - builder.map( - ExecuteOn::PreTick, - Variable::named("SPOILERS_RIGHT_DEFLECTION_DEMAND"), - |value| value, - Variable::aspect("HYD_SPOILER_5_RIGHT_DEMAND"), - ); - builder.map_many( ExecuteOn::PostTick, vec![ - Variable::aspect("HYD_SPOIL_1_LEFT_DEFLECTION"), - Variable::aspect("HYD_SPOIL_2_LEFT_DEFLECTION"), - Variable::aspect("HYD_SPOIL_3_LEFT_DEFLECTION"), - Variable::aspect("HYD_SPOIL_4_LEFT_DEFLECTION"), - Variable::aspect("HYD_SPOIL_5_LEFT_DEFLECTION"), + Variable::named("HYD_SPOILER_1_LEFT_DEFLECTION"), + Variable::named("HYD_SPOILER_2_LEFT_DEFLECTION"), + Variable::named("HYD_SPOILER_3_LEFT_DEFLECTION"), + Variable::named("HYD_SPOILER_4_LEFT_DEFLECTION"), + Variable::named("HYD_SPOILER_5_LEFT_DEFLECTION"), ], |values| values.iter().sum::() / (values.len() as f64), Variable::named("HYD_SPOILERS_LEFT_DEFLECTION"), @@ -81,11 +19,11 @@ pub(super) fn spoilers(builder: &mut MsfsAspectBuilder) -> Result<(), Box() / (values.len() as f64), Variable::named("HYD_SPOILERS_RIGHT_DEFLECTION"), diff --git a/src/systems/a320_systems_wasm/src/trimmable_horizontal_stabilizer.rs b/src/systems/a320_systems_wasm/src/trimmable_horizontal_stabilizer.rs new file mode 100644 index 000000000000..1090ae1ec0b6 --- /dev/null +++ b/src/systems/a320_systems_wasm/src/trimmable_horizontal_stabilizer.rs @@ -0,0 +1,130 @@ +use std::error::Error; + +use systems_wasm::aspects::{ + EventToVariableMapping, ExecuteOn, MsfsAspectBuilder, ObjectWrite, VariablesToObject, +}; +use systems_wasm::{set_data_on_sim_object, Variable}; + +use systems::shared::to_bool; + +use msfs::sim_connect; +use msfs::{sim_connect::SimConnect, sim_connect::SIMCONNECT_OBJECT_ID_USER}; + +pub(super) fn trimmable_horizontal_stabilizer( + builder: &mut MsfsAspectBuilder, +) -> Result<(), Box> { + builder.event_to_variable( + "ELEV_TRIM_UP", + EventToVariableMapping::Value(35.), + Variable::aspect("THS_MANUAL_CONTROL_SPEED_KEY"), + |options| options.mask().afterwards_reset_to(0.), + )?; + + builder.event_to_variable( + "ELEV_TRIM_DN", + EventToVariableMapping::Value(-35.), + Variable::aspect("THS_MANUAL_CONTROL_SPEED_KEY"), + |options| options.mask().afterwards_reset_to(0.), + )?; + + builder.event_to_variable( + "ELEVATOR_TRIM_SET", + EventToVariableMapping::EventDataToValue(|event_data| (event_data as f64) / 16383.), + Variable::aspect("THS_MAN_POS_SET_16K"), + |options| options.mask().afterwards_reset_to(-1.), + )?; + + builder.event_to_variable( + "AXIS_ELEV_TRIM_SET", + EventToVariableMapping::EventData32kPosition, + Variable::aspect("THS_MAN_POS_SET_32K"), + |options| options.mask().afterwards_reset_to(-1.), + )?; + + // Sends a trim speed from position error + builder.map_many( + ExecuteOn::PreTick, + vec![ + Variable::aspect("THS_MAN_POS_SET_16K"), + Variable::aspect("THS_MAN_POS_SET_32K"), + Variable::named("HYD_TRIM_WHEEL_PERCENT"), + ], + |values| { + if values[0] >= 0. { + pos_error_to_speed(values[0] - values[2] / 100.) + } else if values[1] >= 0. { + pos_error_to_speed(values[1] - values[2] / 100.) + } else { + 0. + } + }, + Variable::aspect("THS_MANUAL_CONTROL_SPEED_AXIS"), + ); + + // Selects final speed to use from keys or axis events + builder.map_many( + ExecuteOn::PreTick, + vec![ + Variable::aspect("THS_MANUAL_CONTROL_SPEED_KEY"), + Variable::aspect("THS_MANUAL_CONTROL_SPEED_AXIS"), + ], + |values| { + if values[0].abs() > 0. { + values[0] + } else { + values[1] + } + }, + Variable::aspect("THS_MANUAL_CONTROL_SPEED"), + ); + + // Sends manual control state when receiveing an event even if position is not moving or when keys event are used + builder.map_many( + ExecuteOn::PreTick, + vec![ + Variable::aspect("THS_MAN_POS_SET_16K"), + Variable::aspect("THS_MAN_POS_SET_32K"), + Variable::aspect("THS_MANUAL_CONTROL_SPEED_KEY"), + ], + |values| { + if values[0] >= 0. || values[1] >= 0. || values[2].abs() > 0. { + 1. + } else { + 0. + } + }, + Variable::aspect("THS_MANUAL_CONTROL_ACTIVE"), + ); + + builder.variables_to_object(Box::new(PitchTrimSimOutput { elevator_trim: 0. })); + + Ok(()) +} + +#[sim_connect::data_definition] +struct PitchTrimSimOutput { + #[name = "ELEVATOR TRIM POSITION"] + #[unit = "DEGREE"] + elevator_trim: f64, +} +impl VariablesToObject for PitchTrimSimOutput { + fn variables(&self) -> Vec { + vec![ + Variable::aspect("HYD_FINAL_THS_DEFLECTION"), + Variable::named("FLIGHT_CONTROLS_TRACKING_MODE"), + ] + } + + fn write(&mut self, values: Vec) -> ObjectWrite { + self.elevator_trim = values[0]; + + //Not writing control feedback when in tracking mode + ObjectWrite::on(!to_bool(values[1])) + } + + set_data_on_sim_object!(); +} + +fn pos_error_to_speed(error: f64) -> f64 { + (1000. * error).min(45.).max(-45.) +} diff --git a/src/systems/a380_systems/Cargo.toml b/src/systems/a380_systems/Cargo.toml new file mode 100644 index 000000000000..5bd52f2cb762 --- /dev/null +++ b/src/systems/a380_systems/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "a380_systems" +version = "0.1.0" +authors = ["FlyByWire Simulations"] +edition = "2018" + +[dependencies] +uom = "0.33.0" +rand = "0.8.0" +nalgebra = "0.25.0" +ntest = "0.7.2" +systems = { path = "../systems" } + +[dev-dependencies] +rstest = "0.10.0" diff --git a/src/systems/a380_systems/src/air_conditioning.rs b/src/systems/a380_systems/src/air_conditioning.rs new file mode 100644 index 000000000000..ed68605349b5 --- /dev/null +++ b/src/systems/a380_systems/src/air_conditioning.rs @@ -0,0 +1,157 @@ +use systems::{ + accept_iterable, + air_conditioning::{ + acs_controller::{Pack, PackFlowController}, + cabin_air::CabinZone, + AirConditioningSystem, DuctTemperature, PackFlow, PackFlowControllers, ZoneType, + }, + pressurization::PressurizationOverheadPanel, + shared::{ + Cabin, ElectricalBusType, EngineBleedPushbutton, EngineCorrectedN1, EngineFirePushButtons, + EngineStartState, GroundSpeed, LgciuWeightOnWheels, PackFlowValveState, PneumaticBleed, + }, + simulation::{InitContext, SimulationElement, SimulationElementVisitor, UpdateContext}, +}; +use uom::si::{f64::*, mass_rate::kilogram_per_second, volume::cubic_meter}; + +pub(super) struct A380AirConditioning { + a380_cabin: A380Cabin, + a380_air_conditioning_system: AirConditioningSystem<3>, +} + +impl A380AirConditioning { + pub fn new(context: &mut InitContext) -> Self { + let cabin_zones: [ZoneType; 3] = + [ZoneType::Cockpit, ZoneType::Cabin(1), ZoneType::Cabin(2)]; + + Self { + a380_cabin: A380Cabin::new(context), + a380_air_conditioning_system: AirConditioningSystem::new( + context, + cabin_zones, + vec![ + ElectricalBusType::DirectCurrent(1), + ElectricalBusType::AlternatingCurrent(1), + ], + vec![ + ElectricalBusType::DirectCurrent(2), + ElectricalBusType::AlternatingCurrent(2), + ], + ), + } + } + + pub fn update( + &mut self, + context: &UpdateContext, + adirs: &impl GroundSpeed, + engines: [&impl EngineCorrectedN1; 2], + engine_fire_push_buttons: &impl EngineFirePushButtons, + pneumatic: &(impl EngineStartState + PackFlowValveState + PneumaticBleed), + pneumatic_overhead: &impl EngineBleedPushbutton, + pressurization: &impl Cabin, + pressurization_overhead: &PressurizationOverheadPanel, + lgciu: [&impl LgciuWeightOnWheels; 2], + ) { + self.a380_air_conditioning_system.update( + context, + adirs, + engines, + engine_fire_push_buttons, + pneumatic, + pneumatic_overhead, + pressurization, + pressurization_overhead, + lgciu, + ); + self.a380_cabin.update( + context, + &self.a380_air_conditioning_system, + &self.a380_air_conditioning_system, + pressurization, + ); + } +} + +impl PackFlowControllers<3> for A380AirConditioning { + fn pack_flow_controller(&self, pack_id: Pack) -> PackFlowController<3> { + self.a380_air_conditioning_system + .pack_flow_controller(pack_id) + } +} + +impl SimulationElement for A380AirConditioning { + fn accept(&mut self, visitor: &mut T) { + self.a380_cabin.accept(visitor); + self.a380_air_conditioning_system.accept(visitor); + + visitor.visit(self); + } +} + +struct A380Cabin { + cabin_zone: [CabinZone<2>; 3], +} + +impl A380Cabin { + // TODO: Improve volume according to specs + const A380_CABIN_VOLUME_CUBIC_METER: f64 = 200.; // m3 + const A380_COCKPIT_VOLUME_CUBIC_METER: f64 = 10.; // m3 + + fn new(context: &mut InitContext) -> Self { + Self { + cabin_zone: [ + CabinZone::new( + context, + ZoneType::Cockpit, + Volume::new::(Self::A380_COCKPIT_VOLUME_CUBIC_METER), + 2, + None, + ), + CabinZone::new( + context, + ZoneType::Cabin(1), + Volume::new::(Self::A380_CABIN_VOLUME_CUBIC_METER / 2.), + 0, + Some([(1, 6), (7, 13)]), + ), + CabinZone::new( + context, + ZoneType::Cabin(2), + Volume::new::(Self::A380_CABIN_VOLUME_CUBIC_METER / 2.), + 0, + Some([(14, 21), (22, 29)]), + ), + ], + } + } + + fn update( + &mut self, + context: &UpdateContext, + duct_temperature: &impl DuctTemperature, + pack_flow: &impl PackFlow, + pressurization: &impl Cabin, + ) { + let flow_rate_per_cubic_meter: MassRate = MassRate::new::( + pack_flow.pack_flow().get::() + / (Self::A380_CABIN_VOLUME_CUBIC_METER + Self::A380_COCKPIT_VOLUME_CUBIC_METER), + ); + for zone in self.cabin_zone.iter_mut() { + zone.update( + context, + duct_temperature, + flow_rate_per_cubic_meter, + pressurization, + ); + } + } +} + +impl SimulationElement for A380Cabin { + fn accept(&mut self, visitor: &mut T) { + accept_iterable!(self.cabin_zone, visitor); + + visitor.visit(self); + } +} diff --git a/src/systems/a380_systems/src/electrical/alternating_current.rs b/src/systems/a380_systems/src/electrical/alternating_current.rs new file mode 100644 index 000000000000..118a823401d5 --- /dev/null +++ b/src/systems/a380_systems/src/electrical/alternating_current.rs @@ -0,0 +1,535 @@ +use super::{ + A380AlternatingCurrentElectricalSystem, A380DirectCurrentElectricalSystem, + A380ElectricalOverheadPanel, A380EmergencyElectricalOverheadPanel, +}; +use std::time::Duration; +use systems::simulation::InitContext; +use systems::{ + electrical::{ + AlternatingCurrentElectricalSystem, Contactor, ElectricalBus, Electricity, + EmergencyGenerator, EngineGenerator, ExternalPowerSource, TransformerRectifier, + }, + shared::{ + AuxiliaryPowerUnitElectrical, DelayedTrueLogicGate, ElectricalBusType, EngineCorrectedN2, + EngineFirePushButtons, + }, + simulation::{SimulationElement, SimulationElementVisitor, UpdateContext}, +}; +use uom::si::{f64::*, velocity::knot}; + +pub(super) struct A380AlternatingCurrentElectrical { + main_power_sources: A380MainPowerSources, + ac_ess_feed_contactors: A380AcEssFeedContactors, + ac_bus_1: ElectricalBus, + ac_bus_2: ElectricalBus, + ac_ess_bus: ElectricalBus, + ac_ess_shed_bus: ElectricalBus, + ac_ess_shed_contactor: Contactor, + tr_1: TransformerRectifier, + tr_2: TransformerRectifier, + ac_bus_2_to_tr_2_contactor: Contactor, + tr_ess: TransformerRectifier, + ac_ess_to_tr_ess_contactor: Contactor, + emergency_gen_contactor: Contactor, + static_inv_to_ac_ess_bus_contactor: Contactor, + ac_stat_inv_bus: ElectricalBus, + ac_gnd_flt_service_bus: ElectricalBus, + ext_pwr_to_ac_gnd_flt_service_bus_and_tr_2_contactor: Contactor, +} +impl A380AlternatingCurrentElectrical { + pub fn new(context: &mut InitContext) -> Self { + A380AlternatingCurrentElectrical { + main_power_sources: A380MainPowerSources::new(context), + ac_ess_feed_contactors: A380AcEssFeedContactors::new(context), + ac_bus_1: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(1)), + ac_bus_2: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(2)), + ac_ess_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrentEssential), + ac_ess_shed_bus: ElectricalBus::new( + context, + ElectricalBusType::AlternatingCurrentEssentialShed, + ), + ac_ess_shed_contactor: Contactor::new(context, "8XH"), + tr_1: TransformerRectifier::new(context, 1), + tr_2: TransformerRectifier::new(context, 2), + ac_bus_2_to_tr_2_contactor: Contactor::new(context, "14PU"), + tr_ess: TransformerRectifier::new(context, 3), + ac_ess_to_tr_ess_contactor: Contactor::new(context, "15XE1"), + emergency_gen_contactor: Contactor::new(context, "2XE"), + static_inv_to_ac_ess_bus_contactor: Contactor::new(context, "15XE2"), + ac_stat_inv_bus: ElectricalBus::new( + context, + ElectricalBusType::AlternatingCurrentStaticInverter, + ), + ac_gnd_flt_service_bus: ElectricalBus::new( + context, + ElectricalBusType::AlternatingCurrentGndFltService, + ), + ext_pwr_to_ac_gnd_flt_service_bus_and_tr_2_contactor: Contactor::new(context, "12XN"), + } + } + + pub fn update_main_power_sources( + &mut self, + context: &UpdateContext, + electricity: &mut Electricity, + ext_pwr: &ExternalPowerSource, + overhead: &A380ElectricalOverheadPanel, + emergency_overhead: &A380EmergencyElectricalOverheadPanel, + apu: &impl AuxiliaryPowerUnitElectrical, + engine_fire_push_buttons: &impl EngineFirePushButtons, + engines: [&impl EngineCorrectedN2; 2], + ) { + self.main_power_sources.update( + context, + electricity, + ext_pwr, + overhead, + emergency_overhead, + apu, + engine_fire_push_buttons, + engines, + ); + + self.main_power_sources + .power_ac_bus_1(electricity, &self.ac_bus_1); + self.main_power_sources + .power_ac_bus_2(electricity, &self.ac_bus_2); + } + + pub fn update( + &mut self, + context: &UpdateContext, + electricity: &mut Electricity, + ext_pwr: &ExternalPowerSource, + overhead: &A380ElectricalOverheadPanel, + emergency_generator: &EmergencyGenerator, + ) { + self.ac_bus_2_to_tr_2_contactor + .close_when(electricity.is_powered(&self.ac_bus_2) && !self.tr_2.has_failed()); + electricity.flow(&self.ac_bus_2, &self.ac_bus_2_to_tr_2_contactor); + + // On the real aircraft there is a button inside the galley which is taken into + // account when determining whether to close this contactor or not. + // As we're not building a galley simulator, for now we assume the button is ON. + self.ext_pwr_to_ac_gnd_flt_service_bus_and_tr_2_contactor + .close_when( + !electricity.is_powered(&self.ac_bus_2) + && !self.tr_2.has_failed() + && electricity.is_powered(ext_pwr), + ); + electricity.flow( + ext_pwr, + &self.ext_pwr_to_ac_gnd_flt_service_bus_and_tr_2_contactor, + ); + + electricity.flow( + &self.ac_bus_2_to_tr_2_contactor, + &self.ac_gnd_flt_service_bus, + ); + electricity.flow( + &self.ext_pwr_to_ac_gnd_flt_service_bus_and_tr_2_contactor, + &self.ac_gnd_flt_service_bus, + ); + + electricity.flow(&self.ac_bus_1, &self.tr_1); + electricity.transform_in(&self.tr_1); + + electricity.flow(&self.ac_bus_2_to_tr_2_contactor, &self.tr_2); + electricity.flow( + &self.ext_pwr_to_ac_gnd_flt_service_bus_and_tr_2_contactor, + &self.tr_2, + ); + electricity.transform_in(&self.tr_2); + + self.ac_ess_feed_contactors.update( + context, + electricity, + &self.ac_bus_1, + &self.ac_bus_2, + overhead, + ); + + self.ac_ess_feed_contactors + .power_ac_ess_bus(electricity, &self.ac_ess_bus); + + self.emergency_gen_contactor.close_when( + !self.any_non_essential_bus_powered(electricity) + && emergency_generator.output_within_normal_parameters(), + ); + electricity.supplied_by(emergency_generator); + electricity.flow(emergency_generator, &self.emergency_gen_contactor); + + self.ac_ess_to_tr_ess_contactor.close_when( + (!self.tr_1_and_2_available(electricity) + && self.ac_ess_feed_contactors.provides_power(electricity)) + || electricity.is_powered(&self.emergency_gen_contactor), + ); + electricity.flow(&self.ac_ess_bus, &self.ac_ess_to_tr_ess_contactor); + electricity.flow( + &self.emergency_gen_contactor, + &self.ac_ess_to_tr_ess_contactor, + ); + + electricity.flow(&self.ac_ess_to_tr_ess_contactor, &self.ac_ess_bus); + + electricity.flow(&self.ac_ess_bus, &self.ac_ess_shed_contactor); + + electricity.flow(&self.ac_ess_to_tr_ess_contactor, &self.tr_ess); + electricity.flow(&self.emergency_gen_contactor, &self.tr_ess); + electricity.transform_in(&self.tr_ess); + + self.update_shedding(emergency_generator, electricity); + } + + pub fn update_after_direct_current( + &mut self, + context: &UpdateContext, + electricity: &mut Electricity, + emergency_generator: &EmergencyGenerator, + dc_state: &impl A380DirectCurrentElectricalSystem, + ) { + electricity.flow(dc_state.static_inverter(), &self.ac_stat_inv_bus); + + self.static_inv_to_ac_ess_bus_contactor + .close_when(self.should_close_15xe2_contactor( + context, + electricity, + emergency_generator, + )); + electricity.flow( + dc_state.static_inverter(), + &self.static_inv_to_ac_ess_bus_contactor, + ); + electricity.flow(&self.static_inv_to_ac_ess_bus_contactor, &self.ac_ess_bus); + } + + fn update_shedding( + &mut self, + emergency_generator: &EmergencyGenerator, + electricity: &mut Electricity, + ) { + let ac_bus_or_emergency_gen_provides_power = electricity.is_powered(&self.ac_bus_1) + || electricity.is_powered(&self.ac_bus_2) + || electricity.is_powered(emergency_generator); + self.ac_ess_shed_contactor + .close_when(ac_bus_or_emergency_gen_provides_power); + + electricity.flow(&self.ac_ess_shed_contactor, &self.ac_ess_shed_bus); + } + + /// Whether or not AC BUS 1 and AC BUS 2 are powered by a single engine + /// generator exclusively. Also returns true when one of the buses is + /// unpowered and the other bus is powered by an engine generator. + pub fn main_ac_buses_powered_by_single_engine_generator_only( + &self, + electricity: &Electricity, + ) -> bool { + let ac_bus_1_potential = electricity.output_of(&self.ac_bus_1); + let ac_bus_2_potential = electricity.output_of(&self.ac_bus_2); + + (ac_bus_1_potential.is_unpowered() + && ac_bus_2_potential.is_only_powered_by_single_engine_generator()) + || (ac_bus_2_potential.is_unpowered() + && ac_bus_1_potential.is_only_powered_by_single_engine_generator()) + || (ac_bus_1_potential.is_only_powered_by_single_engine_generator() + && ac_bus_1_potential.is_powered_by_same_single_source(ac_bus_2_potential)) + } + + /// Whether or not AC BUS 1 and AC BUS 2 are powered by the APU generator + /// exclusively. Also returns true when one of the buses is unpowered and + /// the other bus is powered by the APU generator. + pub fn main_ac_buses_powered_by_apu_generator_only(&self, electricity: &Electricity) -> bool { + let ac_bus_1_potential = electricity.output_of(&self.ac_bus_1); + let ac_bus_2_potential = electricity.output_of(&self.ac_bus_2); + + (ac_bus_1_potential.is_unpowered() && ac_bus_2_potential.is_only_powered_by_apu()) + || (ac_bus_2_potential.is_unpowered() && ac_bus_1_potential.is_only_powered_by_apu()) + || (ac_bus_1_potential.is_only_powered_by_apu() + && ac_bus_2_potential.is_only_powered_by_apu()) + } + + /// Determines if 15XE2 should be closed. 15XE2 is the contactor which connects + /// the static inverter to the AC ESS BUS. + fn should_close_15xe2_contactor( + &self, + context: &UpdateContext, + electricity: &Electricity, + emergency_generator: &EmergencyGenerator, + ) -> bool { + !self.any_non_essential_bus_powered(electricity) + && !electricity.is_powered(emergency_generator) + && context.indicated_airspeed() >= Velocity::new::(50.) + } + + pub fn debug_assert_invariants(&self) { + debug_assert!(self.static_inverter_or_emergency_gen_powers_ac_ess_bus()); + } + + fn static_inverter_or_emergency_gen_powers_ac_ess_bus(&self) -> bool { + !(self.static_inv_to_ac_ess_bus_contactor.is_closed() + && self.ac_ess_to_tr_ess_contactor.is_closed()) + } + + pub fn gen_contactor_open(&self, number: usize) -> bool { + self.main_power_sources.gen_contactor_open(number) + } + + pub fn emergency_generator_contactor_is_closed(&self) -> bool { + self.emergency_gen_contactor.is_closed() + } + + pub fn ac_ess_bus_is_powered(&self, electricity: &Electricity) -> bool { + electricity.is_powered(&self.ac_ess_bus) + } +} +impl A380AlternatingCurrentElectricalSystem for A380AlternatingCurrentElectrical { + fn ac_bus_2_powered(&self, electricity: &Electricity) -> bool { + electricity.is_powered(&self.ac_bus_2) + } + + fn tr_1_and_2_available(&self, electricity: &Electricity) -> bool { + electricity.is_powered(&self.tr_1) && electricity.is_powered(&self.tr_2) + } + + fn tr_1(&self) -> &TransformerRectifier { + &self.tr_1 + } + + fn tr_2(&self) -> &TransformerRectifier { + &self.tr_2 + } + + fn tr_ess(&self) -> &TransformerRectifier { + &self.tr_ess + } +} +impl AlternatingCurrentElectricalSystem for A380AlternatingCurrentElectrical { + fn any_non_essential_bus_powered(&self, electricity: &Electricity) -> bool { + electricity.is_powered(&self.ac_bus_1) || electricity.is_powered(&self.ac_bus_2) + } +} +impl SimulationElement for A380AlternatingCurrentElectrical { + fn accept(&mut self, visitor: &mut T) { + self.main_power_sources.accept(visitor); + self.ac_ess_feed_contactors.accept(visitor); + self.tr_1.accept(visitor); + self.tr_2.accept(visitor); + self.ac_bus_2_to_tr_2_contactor.accept(visitor); + self.tr_ess.accept(visitor); + + self.ac_ess_shed_contactor.accept(visitor); + self.ac_ess_to_tr_ess_contactor.accept(visitor); + self.emergency_gen_contactor.accept(visitor); + self.static_inv_to_ac_ess_bus_contactor.accept(visitor); + + self.ac_bus_1.accept(visitor); + self.ac_bus_2.accept(visitor); + self.ac_ess_bus.accept(visitor); + self.ac_ess_shed_bus.accept(visitor); + self.ac_stat_inv_bus.accept(visitor); + + self.ac_gnd_flt_service_bus.accept(visitor); + self.ext_pwr_to_ac_gnd_flt_service_bus_and_tr_2_contactor + .accept(visitor); + + visitor.visit(self); + } +} + +struct A380MainPowerSources { + engine_1_gen: EngineGenerator, + engine_2_gen: EngineGenerator, + engine_generator_contactors: [Contactor; 2], + bus_tie_1_contactor: Contactor, + bus_tie_2_contactor: Contactor, + apu_gen_contactor: Contactor, + ext_pwr_contactor: Contactor, +} +impl A380MainPowerSources { + fn new(context: &mut InitContext) -> Self { + A380MainPowerSources { + engine_1_gen: EngineGenerator::new(context, 1), + engine_2_gen: EngineGenerator::new(context, 2), + engine_generator_contactors: [ + Contactor::new(context, "9XU1"), + Contactor::new(context, "9XU2"), + ], + bus_tie_1_contactor: Contactor::new(context, "11XU1"), + bus_tie_2_contactor: Contactor::new(context, "11XU2"), + apu_gen_contactor: Contactor::new(context, "3XS"), + ext_pwr_contactor: Contactor::new(context, "3XG"), + } + } + + fn update( + &mut self, + context: &UpdateContext, + electricity: &mut Electricity, + ext_pwr: &ExternalPowerSource, + overhead: &A380ElectricalOverheadPanel, + emergency_overhead: &A380EmergencyElectricalOverheadPanel, + apu: &impl AuxiliaryPowerUnitElectrical, + engine_fire_push_buttons: &impl EngineFirePushButtons, + engines: [&impl EngineCorrectedN2; 2], + ) { + self.engine_1_gen + .update(context, engines[0], overhead, engine_fire_push_buttons); + electricity.supplied_by(&self.engine_1_gen); + + self.engine_2_gen + .update(context, engines[1], overhead, engine_fire_push_buttons); + electricity.supplied_by(&self.engine_2_gen); + + electricity.supplied_by(apu); + electricity.supplied_by(ext_pwr); + + let gen_1_provides_power = overhead.generator_is_on(1) + && emergency_overhead.generator_1_line_is_on() + && !engine_fire_push_buttons.is_released(1) + && self.engine_1_gen.output_within_normal_parameters(); + let gen_2_provides_power = overhead.generator_is_on(2) + && !engine_fire_push_buttons.is_released(2) + && self.engine_2_gen.output_within_normal_parameters(); + let only_one_engine_gen_is_powered = gen_1_provides_power ^ gen_2_provides_power; + let both_engine_gens_provide_power = gen_1_provides_power && gen_2_provides_power; + let ext_pwr_provides_power = overhead.external_power_is_on() + && ext_pwr.output_within_normal_parameters() + && !both_engine_gens_provide_power; + let apu_gen_provides_power = overhead.apu_generator_is_on() + && apu.output_within_normal_parameters() + && !ext_pwr_provides_power + && !both_engine_gens_provide_power; + + self.engine_generator_contactors[0].close_when(gen_1_provides_power); + self.engine_generator_contactors[1].close_when(gen_2_provides_power); + self.apu_gen_contactor.close_when(apu_gen_provides_power); + self.ext_pwr_contactor.close_when(ext_pwr_provides_power); + + let apu_or_ext_pwr_provides_power = ext_pwr_provides_power || apu_gen_provides_power; + self.bus_tie_1_contactor.close_when( + overhead.bus_tie_is_auto() + && ((only_one_engine_gen_is_powered && !apu_or_ext_pwr_provides_power) + || (apu_or_ext_pwr_provides_power && !gen_1_provides_power)), + ); + self.bus_tie_2_contactor.close_when( + overhead.bus_tie_is_auto() + && ((only_one_engine_gen_is_powered && !apu_or_ext_pwr_provides_power) + || (apu_or_ext_pwr_provides_power && !gen_2_provides_power)), + ); + + electricity.flow(apu, &self.apu_gen_contactor); + electricity.flow(ext_pwr, &self.ext_pwr_contactor); + + electricity.flow(&self.engine_1_gen, &self.engine_generator_contactors[0]); + electricity.flow( + &self.engine_generator_contactors[0], + &self.bus_tie_1_contactor, + ); + electricity.flow(&self.apu_gen_contactor, &self.bus_tie_1_contactor); + electricity.flow(&self.ext_pwr_contactor, &self.bus_tie_1_contactor); + + electricity.flow(&self.engine_2_gen, &self.engine_generator_contactors[1]); + electricity.flow( + &self.engine_generator_contactors[1], + &self.bus_tie_2_contactor, + ); + electricity.flow(&self.apu_gen_contactor, &self.bus_tie_2_contactor); + electricity.flow(&self.ext_pwr_contactor, &self.bus_tie_2_contactor); + + electricity.flow(&self.bus_tie_1_contactor, &self.bus_tie_2_contactor); + } + + fn power_ac_bus_1(&self, electricity: &mut Electricity, bus: &ElectricalBus) { + electricity.flow(&self.engine_generator_contactors[0], bus); + electricity.flow(&self.bus_tie_1_contactor, bus); + } + + fn power_ac_bus_2(&self, electricity: &mut Electricity, bus: &ElectricalBus) { + electricity.flow(&self.engine_generator_contactors[1], bus); + electricity.flow(&self.bus_tie_2_contactor, bus); + } + + pub fn gen_contactor_open(&self, number: usize) -> bool { + self.engine_generator_contactors[number - 1].is_open() + } +} +impl SimulationElement for A380MainPowerSources { + fn accept(&mut self, visitor: &mut T) { + self.engine_1_gen.accept(visitor); + self.engine_2_gen.accept(visitor); + self.engine_generator_contactors + .iter_mut() + .for_each(|contactor| { + contactor.accept(visitor); + }); + self.bus_tie_1_contactor.accept(visitor); + self.bus_tie_2_contactor.accept(visitor); + self.apu_gen_contactor.accept(visitor); + self.ext_pwr_contactor.accept(visitor); + + visitor.visit(self); + } +} + +pub(super) struct A380AcEssFeedContactors { + ac_ess_feed_contactor_1: Contactor, + ac_ess_feed_contactor_2: Contactor, + ac_ess_feed_contactor_delay_logic_gate: DelayedTrueLogicGate, +} +impl A380AcEssFeedContactors { + pub const AC_ESS_FEED_TO_AC_BUS_2_DELAY_IN_SECONDS: Duration = Duration::from_secs(3); + + fn new(context: &mut InitContext) -> Self { + A380AcEssFeedContactors { + ac_ess_feed_contactor_1: Contactor::new(context, "3XC1"), + ac_ess_feed_contactor_2: Contactor::new(context, "3XC2"), + ac_ess_feed_contactor_delay_logic_gate: DelayedTrueLogicGate::new( + A380AcEssFeedContactors::AC_ESS_FEED_TO_AC_BUS_2_DELAY_IN_SECONDS, + ), + } + } + + fn update( + &mut self, + context: &UpdateContext, + electricity: &mut Electricity, + ac_bus_1: &ElectricalBus, + ac_bus_2: &ElectricalBus, + overhead: &A380ElectricalOverheadPanel, + ) { + self.ac_ess_feed_contactor_delay_logic_gate + .update(context, !electricity.is_powered(ac_bus_1)); + + self.ac_ess_feed_contactor_1.close_when( + electricity.is_powered(ac_bus_1) + && (!self.ac_ess_feed_contactor_delay_logic_gate.output() + && overhead.ac_ess_feed_is_normal()), + ); + self.ac_ess_feed_contactor_2.close_when( + electricity.is_powered(ac_bus_2) + && (self.ac_ess_feed_contactor_delay_logic_gate.output() + || overhead.ac_ess_feed_is_altn()), + ); + + electricity.flow(ac_bus_1, &self.ac_ess_feed_contactor_1); + electricity.flow(ac_bus_2, &self.ac_ess_feed_contactor_2); + } + + fn power_ac_ess_bus(&self, electricity: &mut Electricity, ac_ess_bus: &ElectricalBus) { + electricity.flow(&self.ac_ess_feed_contactor_1, ac_ess_bus); + electricity.flow(&self.ac_ess_feed_contactor_2, ac_ess_bus); + } + + fn provides_power(&self, electricity: &Electricity) -> bool { + electricity.is_powered(&self.ac_ess_feed_contactor_1) + || electricity.is_powered(&self.ac_ess_feed_contactor_2) + } +} +impl SimulationElement for A380AcEssFeedContactors { + fn accept(&mut self, visitor: &mut T) { + self.ac_ess_feed_contactor_1.accept(visitor); + self.ac_ess_feed_contactor_2.accept(visitor); + + visitor.visit(self); + } +} diff --git a/src/systems/a380_systems/src/electrical/direct_current.rs b/src/systems/a380_systems/src/electrical/direct_current.rs new file mode 100644 index 000000000000..6ba5be608e9a --- /dev/null +++ b/src/systems/a380_systems/src/electrical/direct_current.rs @@ -0,0 +1,353 @@ +use super::{ + A380AlternatingCurrentElectricalSystem, A380DirectCurrentElectricalSystem, + A380ElectricalOverheadPanel, +}; +use systems::simulation::InitContext; +use systems::{ + electrical::{ + Battery, BatteryChargeLimiter, Contactor, ElectricalBus, Electricity, EmergencyElectrical, + EmergencyGenerator, StaticInverter, + }, + shared::{ + ApuMaster, ApuStart, AuxiliaryPowerUnitElectrical, ContactorSignal, ElectricalBusType, + LgciuWeightOnWheels, + }, + simulation::{SimulationElement, SimulationElementVisitor, UpdateContext}, +}; +use uom::si::{f64::*, velocity::knot}; + +pub(crate) const APU_START_MOTOR_BUS_TYPE: ElectricalBusType = ElectricalBusType::Sub("49-42-00"); + +pub(super) struct A380DirectCurrentElectrical { + dc_bus_1: ElectricalBus, + dc_bus_2: ElectricalBus, + dc_bus_1_tie_contactor: Contactor, + dc_bus_2_tie_contactor: Contactor, + dc_bat_bus: ElectricalBus, + dc_ess_bus: ElectricalBus, + dc_bat_bus_to_dc_ess_bus_contactor: Contactor, + dc_ess_shed_bus: ElectricalBus, + dc_ess_shed_contactor: Contactor, + battery_1: Battery, + battery_1_contactor: Contactor, + battery_1_charge_limiter: BatteryChargeLimiter, + battery_2: Battery, + battery_2_contactor: Contactor, + battery_2_charge_limiter: BatteryChargeLimiter, + hot_bus_2_to_dc_ess_bus_contactor: Contactor, + hot_bus_1_to_static_inv_contactor: Contactor, + static_inverter: StaticInverter, + hot_bus_1: ElectricalBus, + hot_bus_2: ElectricalBus, + tr_1_contactor: Contactor, + tr_2_contactor: Contactor, + tr_ess_contactor: Contactor, + apu_start_contactors: Contactor, + apu_start_motor_bus: ElectricalBus, + dc_gnd_flt_service_bus: ElectricalBus, + tr_2_to_dc_gnd_flt_service_bus_contactor: Contactor, + dc_bus_2_to_dc_gnd_flt_service_bus_contactor: Contactor, +} +impl A380DirectCurrentElectrical { + pub fn new(context: &mut InitContext) -> Self { + A380DirectCurrentElectrical { + dc_bus_1: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(1)), + dc_bus_1_tie_contactor: Contactor::new(context, "1PC1"), + dc_bus_2: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(2)), + dc_bus_2_tie_contactor: Contactor::new(context, "1PC2"), + dc_bat_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrentBattery), + dc_ess_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrentEssential), + dc_bat_bus_to_dc_ess_bus_contactor: Contactor::new(context, "4PC"), + dc_ess_shed_bus: ElectricalBus::new( + context, + ElectricalBusType::DirectCurrentEssentialShed, + ), + dc_ess_shed_contactor: Contactor::new(context, "8PH"), + battery_1: Battery::full(context, 1), + battery_1_contactor: Contactor::new(context, "6PB1"), + battery_1_charge_limiter: BatteryChargeLimiter::new(context, 1, "6PB1"), + battery_2: Battery::full(context, 2), + battery_2_contactor: Contactor::new(context, "6PB2"), + battery_2_charge_limiter: BatteryChargeLimiter::new(context, 2, "6PB2"), + hot_bus_2_to_dc_ess_bus_contactor: Contactor::new(context, "2XB2"), + hot_bus_1_to_static_inv_contactor: Contactor::new(context, "2XB1"), + static_inverter: StaticInverter::new(context), + hot_bus_1: ElectricalBus::new(context, ElectricalBusType::DirectCurrentHot(1)), + hot_bus_2: ElectricalBus::new(context, ElectricalBusType::DirectCurrentHot(2)), + tr_1_contactor: Contactor::new(context, "5PU1"), + tr_2_contactor: Contactor::new(context, "5PU2"), + tr_ess_contactor: Contactor::new(context, "3PE"), + apu_start_contactors: Contactor::new(context, "10KA_AND_5KA"), + apu_start_motor_bus: ElectricalBus::new(context, APU_START_MOTOR_BUS_TYPE), + dc_gnd_flt_service_bus: ElectricalBus::new( + context, + ElectricalBusType::DirectCurrentGndFltService, + ), + tr_2_to_dc_gnd_flt_service_bus_contactor: Contactor::new(context, "3PX"), + dc_bus_2_to_dc_gnd_flt_service_bus_contactor: Contactor::new(context, "8PN"), + } + } + + pub fn update( + &mut self, + context: &UpdateContext, + electricity: &mut Electricity, + overhead: &A380ElectricalOverheadPanel, + ac_state: &impl A380AlternatingCurrentElectricalSystem, + emergency_elec: &EmergencyElectrical, + emergency_generator: &EmergencyGenerator, + apu: &mut impl AuxiliaryPowerUnitElectrical, + apu_overhead: &(impl ApuMaster + ApuStart), + lgciu1: &impl LgciuWeightOnWheels, + ) { + self.tr_1_contactor + .close_when(electricity.is_powered(ac_state.tr_1())); + electricity.flow(ac_state.tr_1(), &self.tr_1_contactor); + + self.tr_2_contactor.close_when( + electricity.is_powered(ac_state.tr_2()) && ac_state.ac_bus_2_powered(electricity), + ); + electricity.flow(ac_state.tr_2(), &self.tr_2_contactor); + + self.tr_2_to_dc_gnd_flt_service_bus_contactor.close_when( + electricity.is_powered(ac_state.tr_2()) && !ac_state.ac_bus_2_powered(electricity), + ); + electricity.flow( + ac_state.tr_2(), + &self.tr_2_to_dc_gnd_flt_service_bus_contactor, + ); + + self.tr_ess_contactor.close_when( + !ac_state.tr_1_and_2_available(electricity) + && electricity.is_powered(ac_state.tr_ess()), + ); + electricity.flow(ac_state.tr_ess(), &self.tr_ess_contactor); + + electricity.flow(&self.tr_1_contactor, &self.dc_bus_1); + electricity.flow(&self.tr_2_contactor, &self.dc_bus_2); + + self.dc_bus_2_to_dc_gnd_flt_service_bus_contactor + .close_when( + electricity.is_powered(ac_state.tr_2()) && ac_state.ac_bus_2_powered(electricity), + ); + electricity.flow( + &self.dc_bus_2, + &self.dc_bus_2_to_dc_gnd_flt_service_bus_contactor, + ); + + electricity.flow( + &self.tr_2_to_dc_gnd_flt_service_bus_contactor, + &self.dc_gnd_flt_service_bus, + ); + electricity.flow( + &self.dc_bus_2_to_dc_gnd_flt_service_bus_contactor, + &self.dc_gnd_flt_service_bus, + ); + + electricity.flow(&self.dc_bus_1, &self.dc_bus_1_tie_contactor); + electricity.flow(&self.dc_bus_2, &self.dc_bus_2_tie_contactor); + + let dc_bus_1_is_powered = electricity.is_powered(&self.dc_bus_1); + let dc_bus_2_is_powered = electricity.is_powered(&self.dc_bus_2); + self.dc_bus_1_tie_contactor + .close_when(dc_bus_1_is_powered || dc_bus_2_is_powered); + self.dc_bus_2_tie_contactor.close_when( + (!dc_bus_1_is_powered && dc_bus_2_is_powered) + || (!dc_bus_2_is_powered && dc_bus_1_is_powered), + ); + + electricity.flow(&self.dc_bus_1_tie_contactor, &self.dc_bat_bus); + electricity.flow(&self.dc_bus_2_tie_contactor, &self.dc_bat_bus); + + electricity.supplied_by(&self.battery_1); + self.battery_1_charge_limiter.update( + context, + electricity, + emergency_elec, + emergency_generator, + &self.battery_1, + &self.dc_bat_bus, + lgciu1, + overhead, + apu, + apu_overhead, + ac_state, + ); + self.battery_1_contactor + .close_when(self.battery_1_charge_limiter.should_close_contactor()); + electricity.flow(&self.dc_bat_bus, &self.battery_1_contactor); + electricity.flow(&self.battery_1_contactor, &self.hot_bus_1); + electricity.flow(&self.hot_bus_1, &self.battery_1); + + electricity.supplied_by(&self.battery_2); + self.battery_2_charge_limiter.update( + context, + electricity, + emergency_elec, + emergency_generator, + &self.battery_2, + &self.dc_bat_bus, + lgciu1, + overhead, + apu, + apu_overhead, + ac_state, + ); + self.battery_2_contactor + .close_when(self.battery_2_charge_limiter.should_close_contactor()); + electricity.flow(&self.dc_bat_bus, &self.battery_2_contactor); + electricity.flow(&self.battery_2_contactor, &self.hot_bus_2); + electricity.flow(&self.hot_bus_2, &self.battery_2); + + self.apu_start_contactors.close_when( + self.battery_1_contactor.is_closed() + && self.battery_2_contactor.is_closed() + && matches!(apu.signal(), Some(ContactorSignal::Close)), + ); + electricity.flow(&self.dc_bat_bus, &self.apu_start_contactors); + electricity.flow(&self.apu_start_contactors, &self.apu_start_motor_bus); + + let should_close_2xb_contactor = + self.should_close_2xb_contactors(context, electricity, emergency_generator, ac_state); + self.hot_bus_1_to_static_inv_contactor + .close_when(should_close_2xb_contactor); + electricity.flow(&self.hot_bus_1, &self.hot_bus_1_to_static_inv_contactor); + electricity.flow( + &self.hot_bus_1_to_static_inv_contactor, + &self.static_inverter, + ); + electricity.transform_in(&self.static_inverter); + + self.hot_bus_2_to_dc_ess_bus_contactor + .close_when(should_close_2xb_contactor); + electricity.flow(&self.hot_bus_2, &self.hot_bus_2_to_dc_ess_bus_contactor); + + self.dc_bat_bus_to_dc_ess_bus_contactor + .close_when(ac_state.tr_1_and_2_available(electricity)); + electricity.flow(&self.dc_bat_bus, &self.dc_bat_bus_to_dc_ess_bus_contactor); + + electricity.flow(&self.dc_bat_bus_to_dc_ess_bus_contactor, &self.dc_ess_bus); + electricity.flow(&self.tr_ess_contactor, &self.dc_ess_bus); + electricity.flow(&self.hot_bus_2_to_dc_ess_bus_contactor, &self.dc_ess_bus); + + self.dc_ess_shed_contactor + .close_when(self.hot_bus_2_to_dc_ess_bus_contactor.is_open()); + electricity.flow(&self.dc_ess_bus, &self.dc_ess_shed_contactor); + + electricity.flow(&self.dc_ess_shed_contactor, &self.dc_ess_shed_bus); + } + + /// Determines if the 2XB contactors should be closed. 2XB are the two contactors + /// which connect BAT2 to DC ESS BUS; and BAT 1 to the static inverter. + fn should_close_2xb_contactors( + &self, + context: &UpdateContext, + electricity: &Electricity, + emergency_generator: &EmergencyGenerator, + ac_state: &impl A380AlternatingCurrentElectricalSystem, + ) -> bool { + !ac_state.any_non_essential_bus_powered(electricity) + && !electricity.is_powered(emergency_generator) + && ((context.indicated_airspeed() < Velocity::new::(50.) + && self.batteries_connected_to_bat_bus()) + || context.indicated_airspeed() >= Velocity::new::(50.)) + } + + fn batteries_connected_to_bat_bus(&self) -> bool { + self.battery_1_contactor.is_closed() && self.battery_2_contactor.is_closed() + } + + pub fn debug_assert_invariants(&self) { + debug_assert!(self.battery_never_powers_dc_ess_shed()); + debug_assert!(self.max_one_source_powers_dc_ess_bus()); + debug_assert!( + self.batteries_power_both_static_inv_and_dc_ess_bus_at_the_same_time_or_not_at_all() + ); + } + + fn battery_never_powers_dc_ess_shed(&self) -> bool { + !(self.hot_bus_2_to_dc_ess_bus_contactor.is_closed() + && self.dc_ess_shed_contactor.is_closed()) + } + + fn max_one_source_powers_dc_ess_bus(&self) -> bool { + (!self.hot_bus_2_to_dc_ess_bus_contactor.is_closed() + && !self.dc_bat_bus_to_dc_ess_bus_contactor.is_closed() + && !self.tr_ess_contactor.is_closed()) + || (self.hot_bus_2_to_dc_ess_bus_contactor.is_closed() + ^ self.dc_bat_bus_to_dc_ess_bus_contactor.is_closed() + ^ self.tr_ess_contactor.is_closed()) + } + + fn batteries_power_both_static_inv_and_dc_ess_bus_at_the_same_time_or_not_at_all( + &self, + ) -> bool { + self.hot_bus_1_to_static_inv_contactor.is_closed() + == self.hot_bus_2_to_dc_ess_bus_contactor.is_closed() + } + + #[cfg(test)] + pub fn battery_1(&self) -> &Battery { + &self.battery_1 + } + + #[cfg(test)] + pub fn battery_2(&self) -> &Battery { + &self.battery_2 + } + + #[cfg(test)] + pub fn empty_battery_1(&mut self) { + self.battery_1.set_empty_battery_charge(); + } + + #[cfg(test)] + pub fn empty_battery_2(&mut self) { + self.battery_2.set_empty_battery_charge(); + } +} +impl A380DirectCurrentElectricalSystem for A380DirectCurrentElectrical { + fn static_inverter(&self) -> &StaticInverter { + &self.static_inverter + } +} +impl SimulationElement for A380DirectCurrentElectrical { + fn accept(&mut self, visitor: &mut T) { + self.battery_1.accept(visitor); + self.battery_1_charge_limiter.accept(visitor); + self.battery_2.accept(visitor); + self.battery_2_charge_limiter.accept(visitor); + self.static_inverter.accept(visitor); + + self.dc_bus_1_tie_contactor.accept(visitor); + self.dc_bus_2_tie_contactor.accept(visitor); + self.dc_bat_bus_to_dc_ess_bus_contactor.accept(visitor); + self.dc_ess_shed_contactor.accept(visitor); + self.battery_1_contactor.accept(visitor); + self.battery_2_contactor.accept(visitor); + self.hot_bus_2_to_dc_ess_bus_contactor.accept(visitor); + self.hot_bus_1_to_static_inv_contactor.accept(visitor); + self.tr_1_contactor.accept(visitor); + self.tr_2_contactor.accept(visitor); + self.tr_ess_contactor.accept(visitor); + + self.dc_bus_1.accept(visitor); + self.dc_bus_2.accept(visitor); + self.dc_bat_bus.accept(visitor); + self.dc_ess_bus.accept(visitor); + self.dc_ess_shed_bus.accept(visitor); + self.hot_bus_1.accept(visitor); + self.hot_bus_2.accept(visitor); + + self.apu_start_contactors.accept(visitor); + self.apu_start_motor_bus.accept(visitor); + + self.dc_gnd_flt_service_bus.accept(visitor); + self.tr_2_to_dc_gnd_flt_service_bus_contactor + .accept(visitor); + self.dc_bus_2_to_dc_gnd_flt_service_bus_contactor + .accept(visitor); + + visitor.visit(self); + } +} diff --git a/src/systems/a380_systems/src/electrical/galley.rs b/src/systems/a380_systems/src/electrical/galley.rs new file mode 100644 index 000000000000..7eafcb2267ba --- /dev/null +++ b/src/systems/a380_systems/src/electrical/galley.rs @@ -0,0 +1,58 @@ +use super::{alternating_current::A380AlternatingCurrentElectrical, A380ElectricalOverheadPanel}; +use systems::{ + electrical::{AlternatingCurrentElectricalSystem, Electricity}, + simulation::UpdateContext, +}; + +pub(super) struct MainGalley { + is_shed: bool, +} +impl MainGalley { + pub fn new() -> Self { + Self { is_shed: false } + } + + pub fn is_shed(&self) -> bool { + self.is_shed + } + + pub fn update( + &mut self, + context: &UpdateContext, + electricity: &Electricity, + alternating_current: &A380AlternatingCurrentElectrical, + overhead: &A380ElectricalOverheadPanel, + ) { + self.is_shed = !alternating_current.any_non_essential_bus_powered(electricity) + || alternating_current + .main_ac_buses_powered_by_single_engine_generator_only(electricity) + || (alternating_current.main_ac_buses_powered_by_apu_generator_only(electricity) + && context.is_in_flight()) + || overhead.commercial_is_off() + || overhead.galy_and_cab_is_off(); + } +} + +pub(super) struct SecondaryGalley { + is_shed: bool, +} +impl SecondaryGalley { + pub fn new() -> Self { + Self { is_shed: false } + } + + pub fn is_shed(&self) -> bool { + self.is_shed + } + + pub fn update( + &mut self, + electricity: &Electricity, + alternating_current: &A380AlternatingCurrentElectrical, + overhead: &A380ElectricalOverheadPanel, + ) { + self.is_shed = !alternating_current.any_non_essential_bus_powered(electricity) + || overhead.commercial_is_off() + || overhead.galy_and_cab_is_off(); + } +} diff --git a/src/systems/a380_systems/src/electrical/mod.rs b/src/systems/a380_systems/src/electrical/mod.rs new file mode 100644 index 000000000000..11c9a15621b3 --- /dev/null +++ b/src/systems/a380_systems/src/electrical/mod.rs @@ -0,0 +1,2809 @@ +mod alternating_current; +mod direct_current; +mod galley; + +use self::{ + alternating_current::A380AlternatingCurrentElectrical, + direct_current::A380DirectCurrentElectrical, + galley::{MainGalley, SecondaryGalley}, +}; +pub(super) use direct_current::APU_START_MOTOR_BUS_TYPE; + +use uom::si::f64::*; + +#[cfg(test)] +use systems::electrical::Battery; + +use systems::simulation::VariableIdentifier; +use systems::{ + accept_iterable, + electrical::{ + AlternatingCurrentElectricalSystem, BatteryPushButtons, Electricity, EmergencyElectrical, + EmergencyGenerator, EngineGeneratorPushButtons, ExternalPowerSource, StaticInverter, + TransformerRectifier, + }, + overhead::{ + AutoOffFaultPushButton, FaultIndication, FaultReleasePushButton, MomentaryPushButton, + NormalAltnFaultPushButton, OnOffAvailablePushButton, OnOffFaultPushButton, + }, + shared::{ + ApuMaster, ApuStart, AuxiliaryPowerUnitElectrical, EmergencyElectricalRatPushButton, + EmergencyElectricalState, EmergencyGeneratorPower, EngineCorrectedN2, + EngineFirePushButtons, HydraulicGeneratorControlUnit, LgciuWeightOnWheels, + }, + simulation::{ + InitContext, SimulationElement, SimulationElementVisitor, SimulatorWriter, UpdateContext, + Write, + }, +}; + +pub(super) struct A380Electrical { + galley_is_shed_id: VariableIdentifier, + + alternating_current: A380AlternatingCurrentElectrical, + direct_current: A380DirectCurrentElectrical, + main_galley: MainGalley, + secondary_galley: SecondaryGalley, + emergency_elec: EmergencyElectrical, + emergency_gen: EmergencyGenerator, +} +impl A380Electrical { + pub fn new(context: &mut InitContext) -> A380Electrical { + A380Electrical { + galley_is_shed_id: context.get_identifier("ELEC_GALLEY_IS_SHED".to_owned()), + alternating_current: A380AlternatingCurrentElectrical::new(context), + direct_current: A380DirectCurrentElectrical::new(context), + main_galley: MainGalley::new(), + secondary_galley: SecondaryGalley::new(), + emergency_elec: EmergencyElectrical::new(), + emergency_gen: EmergencyGenerator::new(context), + } + } + + pub fn update( + &mut self, + context: &UpdateContext, + electricity: &mut Electricity, + ext_pwr: &ExternalPowerSource, + overhead: &A380ElectricalOverheadPanel, + emergency_overhead: &A380EmergencyElectricalOverheadPanel, + apu: &mut impl AuxiliaryPowerUnitElectrical, + apu_overhead: &(impl ApuMaster + ApuStart), + engine_fire_push_buttons: &impl EngineFirePushButtons, + engines: [&impl EngineCorrectedN2; 2], + gcu: &impl HydraulicGeneratorControlUnit, + lgciu1: &impl LgciuWeightOnWheels, + ) { + self.alternating_current.update_main_power_sources( + context, + electricity, + ext_pwr, + overhead, + emergency_overhead, + apu, + engine_fire_push_buttons, + engines, + ); + + self.emergency_elec + .update(context, electricity, &self.alternating_current); + + self.emergency_gen.update(gcu); + + self.alternating_current.update( + context, + electricity, + ext_pwr, + overhead, + &self.emergency_gen, + ); + + // Elec using LGCIU1 L&R compressed (14A output ASM 32_62_00) + self.direct_current.update( + context, + electricity, + overhead, + &self.alternating_current, + &self.emergency_elec, + &self.emergency_gen, + apu, + apu_overhead, + lgciu1, + ); + + self.alternating_current.update_after_direct_current( + context, + electricity, + &self.emergency_gen, + &self.direct_current, + ); + + self.main_galley + .update(context, electricity, &self.alternating_current, overhead); + self.secondary_galley + .update(electricity, &self.alternating_current, overhead); + + self.debug_assert_invariants(); + } + + fn emergency_generator_contactor_is_closed(&self) -> bool { + self.alternating_current + .emergency_generator_contactor_is_closed() + } + + fn ac_ess_bus_is_powered(&self, electricity: &Electricity) -> bool { + self.alternating_current.ac_ess_bus_is_powered(electricity) + } + + fn galley_is_shed(&self) -> bool { + self.main_galley.is_shed() || self.secondary_galley.is_shed() + } + + fn debug_assert_invariants(&self) { + self.alternating_current.debug_assert_invariants(); + self.direct_current.debug_assert_invariants(); + } + + #[cfg(test)] + fn tr_1(&self) -> &TransformerRectifier { + self.alternating_current.tr_1() + } + + #[cfg(test)] + fn tr_2(&self) -> &TransformerRectifier { + self.alternating_current.tr_2() + } + + #[cfg(test)] + fn tr_ess(&self) -> &TransformerRectifier { + self.alternating_current.tr_ess() + } + + #[cfg(test)] + fn battery_1(&self) -> &Battery { + self.direct_current.battery_1() + } + + #[cfg(test)] + fn battery_2(&self) -> &Battery { + self.direct_current.battery_2() + } + + #[cfg(test)] + pub fn empty_battery_1(&mut self) { + self.direct_current.empty_battery_1(); + } + + #[cfg(test)] + pub fn empty_battery_2(&mut self) { + self.direct_current.empty_battery_2(); + } + + pub fn gen_contactor_open(&self, number: usize) -> bool { + self.alternating_current.gen_contactor_open(number) + } + + pub fn in_emergency_elec(&self) -> bool { + self.emergency_elec.is_active() + } +} +impl SimulationElement for A380Electrical { + fn accept(&mut self, visitor: &mut T) { + self.alternating_current.accept(visitor); + self.direct_current.accept(visitor); + self.emergency_gen.accept(visitor); + + visitor.visit(self); + } + + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.galley_is_shed_id, self.galley_is_shed()) + } +} +impl EmergencyElectricalState for A380Electrical { + fn is_in_emergency_elec(&self) -> bool { + self.in_emergency_elec() + } +} +impl EmergencyGeneratorPower for A380Electrical { + fn generated_power(&self) -> Power { + self.emergency_gen.generated_power() + } +} + +trait A380DirectCurrentElectricalSystem { + fn static_inverter(&self) -> &StaticInverter; +} + +trait A380AlternatingCurrentElectricalSystem: AlternatingCurrentElectricalSystem { + fn ac_bus_2_powered(&self, electricity: &Electricity) -> bool; + fn tr_1_and_2_available(&self, electricity: &Electricity) -> bool; + fn tr_1(&self) -> &TransformerRectifier; + fn tr_2(&self) -> &TransformerRectifier; + fn tr_ess(&self) -> &TransformerRectifier; +} + +pub(super) struct A380ElectricalOverheadPanel { + batteries: [AutoOffFaultPushButton; 2], + idgs: [FaultReleasePushButton; 2], + generators: [OnOffFaultPushButton; 2], + apu_gen: OnOffFaultPushButton, + bus_tie: AutoOffFaultPushButton, + ac_ess_feed: NormalAltnFaultPushButton, + galy_and_cab: AutoOffFaultPushButton, + ext_pwr: OnOffAvailablePushButton, + commercial: OnOffFaultPushButton, +} +impl A380ElectricalOverheadPanel { + pub fn new(context: &mut InitContext) -> A380ElectricalOverheadPanel { + A380ElectricalOverheadPanel { + batteries: [ + AutoOffFaultPushButton::new_auto(context, "ELEC_BAT_1"), + AutoOffFaultPushButton::new_auto(context, "ELEC_BAT_2"), + ], + idgs: [ + FaultReleasePushButton::new_in(context, "ELEC_IDG_1"), + FaultReleasePushButton::new_in(context, "ELEC_IDG_2"), + ], + generators: [ + OnOffFaultPushButton::new_on(context, "ELEC_ENG_GEN_1"), + OnOffFaultPushButton::new_on(context, "ELEC_ENG_GEN_2"), + ], + apu_gen: OnOffFaultPushButton::new_on(context, "ELEC_APU_GEN"), + bus_tie: AutoOffFaultPushButton::new_auto(context, "ELEC_BUS_TIE"), + ac_ess_feed: NormalAltnFaultPushButton::new_normal(context, "ELEC_AC_ESS_FEED"), + galy_and_cab: AutoOffFaultPushButton::new_auto(context, "ELEC_GALY_AND_CAB"), + ext_pwr: OnOffAvailablePushButton::new_off(context, "ELEC_EXT_PWR"), + commercial: OnOffFaultPushButton::new_on(context, "ELEC_COMMERCIAL"), + } + } + + pub fn update_after_electrical( + &mut self, + electrical: &A380Electrical, + electricity: &Electricity, + ) { + self.ac_ess_feed + .set_fault(!electrical.ac_ess_bus_is_powered(electricity)); + + self.generators + .iter_mut() + .enumerate() + .for_each(|(index, gen)| { + gen.set_fault(electrical.gen_contactor_open(index + 1) && gen.is_on()); + }); + } + + fn generator_is_on(&self, number: usize) -> bool { + self.generators[number - 1].is_on() + } + + pub fn external_power_is_available(&self) -> bool { + self.ext_pwr.is_available() + } + + pub fn external_power_is_on(&self) -> bool { + self.ext_pwr.is_on() + } + + pub fn apu_generator_is_on(&self) -> bool { + self.apu_gen.is_on() + } + + fn bus_tie_is_auto(&self) -> bool { + self.bus_tie.is_auto() + } + + fn ac_ess_feed_is_normal(&self) -> bool { + self.ac_ess_feed.is_normal() + } + + fn ac_ess_feed_is_altn(&self) -> bool { + self.ac_ess_feed.is_altn() + } + + fn commercial_is_off(&self) -> bool { + self.commercial.is_off() + } + + fn galy_and_cab_is_off(&self) -> bool { + self.galy_and_cab.is_off() + } +} +impl EngineGeneratorPushButtons for A380ElectricalOverheadPanel { + fn engine_gen_push_button_is_on(&self, number: usize) -> bool { + self.generators[number - 1].is_on() + } + + fn idg_push_button_is_released(&self, number: usize) -> bool { + self.idgs[number - 1].is_released() + } +} +impl BatteryPushButtons for A380ElectricalOverheadPanel { + fn bat_is_auto(&self, number: usize) -> bool { + self.batteries[number - 1].is_auto() + } +} +impl SimulationElement for A380ElectricalOverheadPanel { + fn accept(&mut self, visitor: &mut T) { + accept_iterable!(self.batteries, visitor); + accept_iterable!(self.idgs, visitor); + accept_iterable!(self.generators, visitor); + + self.apu_gen.accept(visitor); + self.bus_tie.accept(visitor); + self.ac_ess_feed.accept(visitor); + self.galy_and_cab.accept(visitor); + self.ext_pwr.accept(visitor); + self.commercial.accept(visitor); + + visitor.visit(self); + } +} + +pub(super) struct A380EmergencyElectricalOverheadPanel { + // The GEN 1 line fault represents the SMOKE light illumination state. + gen_1_line: OnOffFaultPushButton, + rat_and_emergency_gen_fault: FaultIndication, + rat_and_emer_gen_man_on: MomentaryPushButton, +} +impl A380EmergencyElectricalOverheadPanel { + pub fn new(context: &mut InitContext) -> Self { + Self { + gen_1_line: OnOffFaultPushButton::new_on(context, "EMER_ELEC_GEN_1_LINE"), + rat_and_emergency_gen_fault: FaultIndication::new( + context, + "EMER_ELEC_RAT_AND_EMER_GEN", + ), + rat_and_emer_gen_man_on: MomentaryPushButton::new( + context, + "EMER_ELEC_RAT_AND_EMER_GEN", + ), + } + } + + pub fn update_after_electrical( + &mut self, + context: &UpdateContext, + electrical: &A380Electrical, + ) { + self.rat_and_emergency_gen_fault.set_fault( + electrical.in_emergency_elec() + && !electrical.emergency_generator_contactor_is_closed() + && !context.is_on_ground(), + ); + } + + fn generator_1_line_is_on(&self) -> bool { + self.gen_1_line.is_on() + } + + fn rat_and_emer_gen_man_on(&self) -> bool { + self.rat_and_emer_gen_man_on.is_pressed() + } +} +impl SimulationElement for A380EmergencyElectricalOverheadPanel { + fn accept(&mut self, visitor: &mut T) { + self.gen_1_line.accept(visitor); + self.rat_and_emergency_gen_fault.accept(visitor); + self.rat_and_emer_gen_man_on.accept(visitor); + + visitor.visit(self); + } +} +impl EmergencyElectricalRatPushButton for A380EmergencyElectricalOverheadPanel { + fn is_pressed(&self) -> bool { + self.rat_and_emer_gen_man_on() + } +} +#[cfg(test)] +mod a380_electrical { + use super::*; + use systems::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed}; + + #[test] + fn writes_its_state() { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(A380Electrical::new)); + + test_bed.run(); + + assert!(test_bed.contains_variable_with_name("ELEC_GALLEY_IS_SHED")); + } +} + +#[cfg(test)] +mod a380_electrical_circuit_tests { + use super::{alternating_current::A380AcEssFeedContactors, *}; + use rstest::rstest; + use std::{cell::Ref, time::Duration}; + use systems::{ + electrical::{ + ElectricalElement, ElectricalElementIdentifier, ElectricalElementIdentifierProvider, + Electricity, ElectricitySource, ExternalPowerSource, Potential, + INTEGRATED_DRIVE_GENERATOR_STABILIZATION_TIME_IN_MILLISECONDS, + }, + failures::FailureType, + shared::{ + ApuAvailable, ContactorSignal, ControllerSignal, ElectricalBusType, ElectricalBuses, + PotentialOrigin, + }, + simulation::{ + test::{ReadByName, SimulationTestBed, TestBed, WriteByName}, + Aircraft, + }, + }; + + use uom::si::{ + angular_velocity::revolution_per_minute, electric_potential::volt, length::foot, + power::watt, ratio::percent, velocity::knot, + }; + + #[test] + fn everything_off_batteries_empty() { + let test_bed = test_bed_with() + .bat_off(1) + .empty_battery_1() + .bat_off(2) + .empty_battery_2() + .and() + .airspeed(Velocity::new::(0.)) + .run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + assert!(test_bed.ac_ess_bus_output().is_unpowered()); + assert!(test_bed.ac_ess_shed_bus_output().is_unpowered()); + assert!(test_bed.static_inverter_input().is_unpowered()); + assert!(test_bed.ac_stat_inv_bus_output().is_unpowered()); + assert!(test_bed.ac_gnd_flt_service_bus_output().is_unpowered()); + assert!(test_bed.tr_1_input().is_unpowered()); + assert!(test_bed.tr_2_input().is_unpowered()); + assert!(test_bed.tr_ess_input().is_unpowered()); + assert!(test_bed.dc_bus_output(1).is_unpowered()); + assert!(test_bed.dc_bus_output(2).is_unpowered()); + assert!(test_bed.dc_bat_bus_output().is_unpowered()); + assert!(test_bed.dc_ess_bus_output().is_unpowered()); + assert!(test_bed.dc_ess_shed_bus_output().is_unpowered()); + assert!(test_bed.hot_bus_output(1).is_unpowered()); + assert!(test_bed.hot_bus_output(2).is_unpowered()); + assert!(test_bed.dc_gnd_flt_service_bus_output().is_unpowered()); + } + + #[test] + fn everything_off() { + let test_bed = test_bed_with() + .bat_off(1) + .bat_off(2) + .and() + .airspeed(Velocity::new::(0.)) + .run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + assert!(test_bed.ac_ess_bus_output().is_unpowered()); + assert!(test_bed.ac_ess_shed_bus_output().is_unpowered()); + assert!(test_bed.static_inverter_input().is_unpowered()); + assert!(test_bed.ac_stat_inv_bus_output().is_unpowered()); + assert!(test_bed.ac_gnd_flt_service_bus_output().is_unpowered()); + assert!(test_bed.tr_1_input().is_unpowered()); + assert!(test_bed.tr_2_input().is_unpowered()); + assert!(test_bed.tr_ess_input().is_unpowered()); + assert!(test_bed.dc_bus_output(1).is_unpowered()); + assert!(test_bed.dc_bus_output(2).is_unpowered()); + assert!(test_bed.dc_bat_bus_output().is_unpowered()); + assert!(test_bed.dc_ess_bus_output().is_unpowered()); + assert!(test_bed.dc_ess_shed_bus_output().is_unpowered()); + assert!(test_bed + .hot_bus_output(1) + .is_single(PotentialOrigin::Battery(1))); + assert!(test_bed + .hot_bus_output(2) + .is_single(PotentialOrigin::Battery(2))); + assert!(test_bed.dc_gnd_flt_service_bus_output().is_unpowered()); + } + + /// # Source + /// A320 manual electrical distribution table + #[test] + fn distribution_table_norm_conf() { + let test_bed = test_bed_with().running_engines().run(); + + assert!(test_bed + .ac_bus_output(1) + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .ac_bus_output(2) + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .ac_ess_shed_bus_output() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed.static_inverter_input().is_unpowered()); + assert!(test_bed.ac_stat_inv_bus_output().is_unpowered()); + assert!(test_bed + .ac_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed + .tr_1_input() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .tr_2_input() + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed.tr_ess_input().is_unpowered()); + assert!(test_bed + .dc_bus_output(1) + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_bus_output(2) + .is_single(PotentialOrigin::TransformerRectifier(2))); + assert!(test_bed + .dc_bat_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_ess_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_ess_shed_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .hot_bus_output(1) + .is_single(PotentialOrigin::Battery(1))); + assert!(test_bed + .hot_bus_output(2) + .is_single(PotentialOrigin::Battery(2))); + assert!(test_bed + .dc_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(2))); + } + + /// # Source + /// A320 manual electrical distribution table + #[test] + fn distribution_table_only_gen_1_available() { + let test_bed = test_bed_with().running_engine(1).run(); + + assert!(test_bed + .ac_bus_output(1) + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .ac_bus_output(2) + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .ac_ess_shed_bus_output() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed.static_inverter_input().is_unpowered()); + assert!(test_bed.ac_stat_inv_bus_output().is_unpowered()); + assert!(test_bed + .ac_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .tr_1_input() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .tr_2_input() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed.tr_ess_input().is_unpowered()); + assert!(test_bed + .dc_bus_output(1) + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_bus_output(2) + .is_single(PotentialOrigin::TransformerRectifier(2))); + assert!(test_bed + .dc_bat_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_ess_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_ess_shed_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .hot_bus_output(1) + .is_single(PotentialOrigin::Battery(1))); + assert!(test_bed + .hot_bus_output(2) + .is_single(PotentialOrigin::Battery(2))); + assert!(test_bed + .dc_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(2))); + } + + /// # Source + /// A320 manual electrical distribution table + #[test] + fn distribution_table_only_gen_2_available() { + let test_bed = test_bed_with().running_engine(2).run(); + + assert!(test_bed + .ac_bus_output(1) + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed + .ac_bus_output(2) + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed + .ac_ess_shed_bus_output() + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed.static_inverter_input().is_unpowered()); + assert!(test_bed.ac_stat_inv_bus_output().is_unpowered()); + assert!(test_bed + .ac_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed + .tr_1_input() + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed + .tr_2_input() + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed.tr_ess_input().is_unpowered()); + assert!(test_bed + .dc_bus_output(1) + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_bus_output(2) + .is_single(PotentialOrigin::TransformerRectifier(2))); + assert!(test_bed + .dc_bat_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_ess_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_ess_shed_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .hot_bus_output(1) + .is_single(PotentialOrigin::Battery(1))); + assert!(test_bed + .hot_bus_output(2) + .is_single(PotentialOrigin::Battery(2))); + assert!(test_bed + .dc_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(2))); + } + + /// # Source + /// A320 manual electrical distribution table + #[test] + fn distribution_table_only_apu_gen_available() { + let test_bed = test_bed_with().running_apu().run(); + + assert!(test_bed + .ac_bus_output(1) + .is_single(PotentialOrigin::ApuGenerator(1))); + assert!(test_bed + .ac_bus_output(2) + .is_single(PotentialOrigin::ApuGenerator(1))); + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::ApuGenerator(1))); + assert!(test_bed + .ac_ess_shed_bus_output() + .is_single(PotentialOrigin::ApuGenerator(1))); + assert!(test_bed.static_inverter_input().is_unpowered()); + assert!(test_bed.ac_stat_inv_bus_output().is_unpowered()); + assert!(test_bed + .ac_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::ApuGenerator(1))); + assert!(test_bed + .tr_1_input() + .is_single(PotentialOrigin::ApuGenerator(1))); + assert!(test_bed + .tr_2_input() + .is_single(PotentialOrigin::ApuGenerator(1))); + assert!(test_bed.tr_ess_input().is_unpowered()); + assert!(test_bed + .dc_bus_output(1) + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_bus_output(2) + .is_single(PotentialOrigin::TransformerRectifier(2))); + assert!(test_bed + .dc_bat_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_ess_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_ess_shed_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .hot_bus_output(1) + .is_single(PotentialOrigin::Battery(1))); + assert!(test_bed + .hot_bus_output(2) + .is_single(PotentialOrigin::Battery(2))); + assert!(test_bed + .dc_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(2))); + } + + /// # Source + /// Derived from A320 manual electrical distribution table + /// (doesn't list external power, but we'll assume it's the same as other generators). + #[test] + fn distribution_table_only_external_power_available_and_on() { + let test_bed = test_bed_with() + .connected_external_power() + .airspeed(Velocity::new::(0.)) + .on_the_ground() + .and() + .ext_pwr_on() + .run(); + + assert!(test_bed + .ac_bus_output(1) + .is_single(PotentialOrigin::External)); + assert!(test_bed + .ac_bus_output(2) + .is_single(PotentialOrigin::External)); + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::External)); + assert!(test_bed + .ac_ess_shed_bus_output() + .is_single(PotentialOrigin::External)); + assert!(test_bed.static_inverter_input().is_unpowered()); + assert!(test_bed.ac_stat_inv_bus_output().is_unpowered()); + assert!(test_bed + .ac_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::External)); + assert!(test_bed.tr_1_input().is_single(PotentialOrigin::External)); + assert!(test_bed.tr_2_input().is_single(PotentialOrigin::External)); + assert!(test_bed.tr_ess_input().is_unpowered()); + assert!(test_bed + .dc_bus_output(1) + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_bus_output(2) + .is_single(PotentialOrigin::TransformerRectifier(2))); + assert!(test_bed + .dc_bat_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_ess_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_ess_shed_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .hot_bus_output(1) + .is_single(PotentialOrigin::Battery(1))); + assert!(test_bed + .hot_bus_output(2) + .is_single(PotentialOrigin::Battery(2))); + assert!(test_bed + .dc_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(2))); + } + + /// # Source + /// A320 manual electrical distribution table + #[test] + fn distribution_table_emergency_config_before_emergency_gen_available() { + let test_bed = test_bed().run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + assert!(test_bed.ac_ess_shed_bus_output().is_unpowered()); + assert!(test_bed + .static_inverter_input() + .is_single(PotentialOrigin::Battery(1))); + assert!(test_bed + .ac_stat_inv_bus_output() + .is_single(PotentialOrigin::StaticInverter)); + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::StaticInverter)); + assert!(test_bed.ac_gnd_flt_service_bus_output().is_unpowered()); + assert!(test_bed.tr_1_input().is_unpowered()); + assert!(test_bed.tr_2_input().is_unpowered()); + assert!(test_bed.tr_ess_input().is_unpowered()); + assert!(test_bed.dc_bus_output(1).is_unpowered()); + assert!(test_bed.dc_bus_output(2).is_unpowered()); + assert!(test_bed.dc_bat_bus_output().is_unpowered()); + assert!(test_bed + .dc_ess_bus_output() + .is_single(PotentialOrigin::Battery(2))); + assert!(test_bed.dc_ess_shed_bus_output().is_unpowered()); + assert!(test_bed + .hot_bus_output(1) + .is_single(PotentialOrigin::Battery(1))); + assert!(test_bed + .hot_bus_output(2) + .is_single(PotentialOrigin::Battery(2))); + assert!(test_bed.dc_gnd_flt_service_bus_output().is_unpowered()); + } + + /// # Source + /// A320 manual electrical distribution table + #[test] + fn distribution_table_emergency_config_after_emergency_gen_available() { + let test_bed = test_bed_with().running_emergency_generator().run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::EmergencyGenerator)); + assert!(test_bed + .ac_ess_shed_bus_output() + .is_single(PotentialOrigin::EmergencyGenerator)); + assert!(test_bed.ac_gnd_flt_service_bus_output().is_unpowered()); + assert!(test_bed.tr_1_input().is_unpowered()); + assert!(test_bed.tr_2_input().is_unpowered()); + assert!(test_bed + .tr_ess_input() + .is_single(PotentialOrigin::EmergencyGenerator)); + assert!(test_bed.static_inverter_input().is_unpowered()); + assert!(test_bed.ac_stat_inv_bus_output().is_unpowered()); + assert!(test_bed.dc_bus_output(1).is_unpowered()); + assert!(test_bed.dc_bus_output(2).is_unpowered()); + assert!(test_bed.dc_bat_bus_output().is_unpowered()); + assert!(test_bed + .dc_ess_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(3))); + assert!(test_bed + .dc_ess_shed_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(3))); + assert!(test_bed + .hot_bus_output(1) + .is_single(PotentialOrigin::Battery(1))); + assert!(test_bed + .hot_bus_output(2) + .is_single(PotentialOrigin::Battery(2))); + assert!(test_bed.dc_gnd_flt_service_bus_output().is_unpowered()); + } + + /// # Source + /// A320 manual electrical distribution table + #[test] + fn distribution_table_tr_1_fault() { + let test_bed = test_bed_with().running_engines().and().failed_tr_1().run(); + + assert!(test_bed + .ac_bus_output(1) + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .ac_bus_output(2) + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .ac_ess_shed_bus_output() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed.static_inverter_input().is_unpowered()); + assert!(test_bed.ac_stat_inv_bus_output().is_unpowered()); + assert!(test_bed + .ac_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed + .tr_1_input() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .tr_2_input() + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed + .tr_ess_input() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .dc_bus_output(1) + .is_single(PotentialOrigin::TransformerRectifier(2))); + assert!(test_bed + .dc_bus_output(2) + .is_single(PotentialOrigin::TransformerRectifier(2))); + assert!(test_bed + .dc_bat_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(2))); + assert!(test_bed + .dc_ess_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(3))); + assert!(test_bed + .dc_ess_shed_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(3))); + assert!(test_bed + .hot_bus_output(1) + .is_single(PotentialOrigin::Battery(1))); + assert!(test_bed + .hot_bus_output(2) + .is_single(PotentialOrigin::Battery(2))); + assert!(test_bed + .dc_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(2))); + } + + /// # Source + /// A320 manual electrical distribution table + #[test] + fn distribution_table_tr_2_fault() { + let test_bed = test_bed_with().running_engines().and().failed_tr_2().run(); + + assert!(test_bed + .ac_bus_output(1) + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .ac_bus_output(2) + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .ac_ess_shed_bus_output() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed.static_inverter_input().is_unpowered()); + assert!(test_bed.ac_stat_inv_bus_output().is_unpowered()); + assert!(test_bed.ac_gnd_flt_service_bus_output().is_unpowered()); + assert!(test_bed + .tr_1_input() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed.tr_2_input().is_unpowered()); + assert!(test_bed + .tr_ess_input() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .dc_bus_output(1) + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_bus_output(2) + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_bat_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(1))); + assert!(test_bed + .dc_ess_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(3))); + assert!(test_bed + .dc_ess_shed_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(3))); + assert!(test_bed + .hot_bus_output(1) + .is_single(PotentialOrigin::Battery(1))); + assert!(test_bed + .hot_bus_output(2) + .is_single(PotentialOrigin::Battery(2))); + assert!(test_bed.dc_gnd_flt_service_bus_output().is_unpowered()); + } + + /// # Source + /// A320 manual electrical distribution table + #[test] + fn distribution_table_tr_1_and_2_fault() { + let test_bed = test_bed_with() + .running_engines() + .failed_tr_1() + .and() + .failed_tr_2() + .run(); + + assert!(test_bed + .ac_bus_output(1) + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .ac_bus_output(2) + .is_single(PotentialOrigin::EngineGenerator(2))); + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .ac_ess_shed_bus_output() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed.static_inverter_input().is_unpowered()); + assert!(test_bed.ac_stat_inv_bus_output().is_unpowered()); + assert!(test_bed.ac_gnd_flt_service_bus_output().is_unpowered()); + assert!(test_bed + .tr_1_input() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed.tr_2_input().is_unpowered()); + assert!(test_bed + .tr_ess_input() + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed.dc_bus_output(1).is_unpowered()); + assert!(test_bed.dc_bus_output(2).is_unpowered()); + assert!(test_bed.dc_bat_bus_output().is_unpowered()); + assert!(test_bed + .dc_ess_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(3))); + assert!(test_bed + .dc_ess_shed_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(3))); + assert!(test_bed + .hot_bus_output(1) + .is_single(PotentialOrigin::Battery(1))); + assert!(test_bed + .hot_bus_output(2) + .is_single(PotentialOrigin::Battery(2))); + assert!(test_bed.dc_gnd_flt_service_bus_output().is_unpowered()); + } + + /// # Source + /// A320 manual electrical distribution table + #[test] + fn distribution_table_on_ground_bat_and_emergency_gen_only_speed_above_100_knots() { + let test_bed = test_bed_with() + .running_emergency_generator() + .airspeed(Velocity::new::(101.)) + .and() + .on_the_ground() + .run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::EmergencyGenerator)); + assert!(test_bed + .ac_ess_shed_bus_output() + .is_single(PotentialOrigin::EmergencyGenerator)); + assert!(test_bed.ac_gnd_flt_service_bus_output().is_unpowered()); + assert!(test_bed.static_inverter_input().is_unpowered()); + assert!(test_bed.ac_stat_inv_bus_output().is_unpowered()); + assert!(test_bed.tr_1_input().is_unpowered()); + assert!(test_bed.tr_2_input().is_unpowered()); + assert!(test_bed + .tr_ess_input() + .is_single(PotentialOrigin::EmergencyGenerator)); + assert!(test_bed.dc_bus_output(1).is_unpowered()); + assert!(test_bed.dc_bus_output(2).is_unpowered()); + assert!(test_bed.dc_bat_bus_output().is_unpowered()); + assert!(test_bed + .dc_ess_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(3))); + assert!(test_bed + .dc_ess_shed_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(3))); + assert!(test_bed + .hot_bus_output(1) + .is_single(PotentialOrigin::Battery(1))); + assert!(test_bed + .hot_bus_output(2) + .is_single(PotentialOrigin::Battery(2))); + assert!(test_bed.dc_gnd_flt_service_bus_output().is_unpowered()); + } + + /// # Source + /// A320 manual electrical distribution table + #[test] + fn distribution_table_on_ground_bat_only_rat_stall_or_speed_between_50_to_100_knots() { + let test_bed = test_bed_with() + .running_emergency_generator() + .airspeed(Velocity::new::(50.0)) + .and() + .on_the_ground() + .run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::StaticInverter)); + assert!(test_bed.ac_ess_shed_bus_output().is_unpowered()); + assert!(test_bed.ac_gnd_flt_service_bus_output().is_unpowered()); + assert!(test_bed + .static_inverter_input() + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed + .ac_stat_inv_bus_output() + .is_single(PotentialOrigin::StaticInverter)); + assert!(test_bed.tr_1_input().is_unpowered()); + assert!(test_bed.tr_2_input().is_unpowered()); + assert!(test_bed.tr_ess_input().is_unpowered()); + assert!(test_bed.dc_bus_output(1).is_unpowered()); + assert!(test_bed.dc_bus_output(2).is_unpowered()); + assert!(test_bed + .dc_bat_bus_output() + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed + .dc_ess_bus_output() + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed.dc_ess_shed_bus_output().is_unpowered()); + assert!(test_bed + .hot_bus_output(1) + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed + .hot_bus_output(2) + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed.dc_gnd_flt_service_bus_output().is_unpowered()); + } + + /// # Source + /// A320 manual electrical distribution table + #[test] + fn distribution_table_on_ground_bat_only_speed_less_than_50_knots() { + let test_bed = test_bed_with() + .running_emergency_generator() + .airspeed(Velocity::new::(49.9)) + .and() + .on_the_ground() + .run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + assert!( + test_bed.ac_ess_bus_output().is_unpowered(), + "AC ESS BUS shouldn't be powered below 50 knots when on batteries only." + ); + assert!(test_bed.ac_ess_shed_bus_output().is_unpowered()); + assert!(test_bed + .static_inverter_input() + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed + .ac_stat_inv_bus_output() + .is_single(PotentialOrigin::StaticInverter)); + assert!(test_bed.ac_gnd_flt_service_bus_output().is_unpowered()); + assert!(test_bed.tr_1_input().is_unpowered()); + assert!(test_bed.tr_2_input().is_unpowered()); + assert!(test_bed.tr_ess_input().is_unpowered()); + assert!(test_bed.dc_bus_output(1).is_unpowered()); + assert!(test_bed.dc_bus_output(2).is_unpowered()); + assert!(test_bed + .dc_bat_bus_output() + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed + .dc_ess_bus_output() + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed.dc_ess_shed_bus_output().is_unpowered()); + assert!(test_bed + .hot_bus_output(1) + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed + .hot_bus_output(2) + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed.dc_gnd_flt_service_bus_output().is_unpowered()); + } + + #[test] + fn distribution_table_only_external_power_available_and_off() { + let test_bed = test_bed_with() + .connected_external_power() + .airspeed(Velocity::new::(0.)) + .and() + .on_the_ground() + .run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + assert!(test_bed.ac_ess_bus_output().is_unpowered()); + assert!(test_bed.ac_ess_shed_bus_output().is_unpowered()); + assert!(test_bed + .static_inverter_input() + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed + .ac_stat_inv_bus_output() + .is_single(PotentialOrigin::StaticInverter)); + assert!(test_bed + .ac_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::External)); + assert!(test_bed.tr_1_input().is_unpowered()); + assert!(test_bed.tr_2_input().is_single(PotentialOrigin::External)); + assert!(test_bed.tr_ess_input().is_unpowered()); + assert!(test_bed.dc_bus_output(1).is_unpowered()); + assert!(test_bed.dc_bus_output(2).is_unpowered()); + assert!(test_bed + .dc_bat_bus_output() + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed + .dc_ess_bus_output() + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed.dc_ess_shed_bus_output().is_unpowered()); + assert!(test_bed + .hot_bus_output(1) + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed + .hot_bus_output(2) + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + assert!(test_bed + .dc_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(2))); + } + + #[rstest] + #[case(1, 2)] + #[case(2, 1)] + fn when_single_engine_and_apu_running_apu_powers_other_ac_bus( + #[case] engine: usize, + #[case] ac_bus: u8, + ) { + let test_bed = test_bed_with() + .running_engine(engine) + .and() + .running_apu() + .run(); + + assert!(test_bed + .ac_bus_output(ac_bus) + .is_single(PotentialOrigin::ApuGenerator(1))); + } + + #[test] + fn when_only_apu_running_apu_powers_ac_bus_1_and_2() { + let test_bed = test_bed_with().running_apu().run(); + + assert!(test_bed + .ac_bus_output(1) + .is_single(PotentialOrigin::ApuGenerator(1))); + assert!(test_bed + .ac_bus_output(2) + .is_single(PotentialOrigin::ApuGenerator(1))); + } + + #[rstest] + #[case(1, 2)] + #[case(2, 1)] + fn when_single_engine_running_and_ext_pwr_connected_ext_pwr_powers_other_ac_bus( + #[case] engine: usize, + #[case] ac_bus: u8, + ) { + let test_bed = test_bed_with() + .running_engine(engine) + .connected_external_power() + .and() + .ext_pwr_on() + .run(); + + assert!(test_bed + .ac_bus_output(ac_bus) + .is_single(PotentialOrigin::External)); + } + + #[test] + fn when_only_external_power_connected_ext_pwr_powers_ac_bus_1_and_2() { + let test_bed = test_bed_with() + .connected_external_power() + .and() + .ext_pwr_on() + .run(); + + assert!(test_bed + .ac_bus_output(1) + .is_single(PotentialOrigin::External)); + assert!(test_bed + .ac_bus_output(2) + .is_single(PotentialOrigin::External)); + } + + #[test] + fn when_external_power_connected_and_apu_running_external_power_has_priority() { + let test_bed = test_bed_with() + .connected_external_power() + .ext_pwr_on() + .and() + .running_apu() + .run(); + + assert!(test_bed + .ac_bus_output(1) + .is_single(PotentialOrigin::External)); + assert!(test_bed + .ac_bus_output(2) + .is_single(PotentialOrigin::External)); + } + + #[test] + fn when_both_engines_running_and_external_power_connected_engines_power_ac_buses() { + let test_bed = test_bed_with() + .running_engines() + .and() + .connected_external_power() + .run(); + + assert!(test_bed + .ac_bus_output(1) + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .ac_bus_output(2) + .is_single(PotentialOrigin::EngineGenerator(2))); + } + + #[test] + fn when_both_engines_running_and_apu_running_engines_power_ac_buses() { + let test_bed = test_bed_with().running_engines().and().running_apu().run(); + + assert!(test_bed + .ac_bus_output(1) + .is_single(PotentialOrigin::EngineGenerator(1))); + assert!(test_bed + .ac_bus_output(2) + .is_single(PotentialOrigin::EngineGenerator(2))); + } + + #[test] + fn ac_bus_1_powers_ac_ess_bus_whenever_it_is_powered() { + let test_bed = test_bed_with().running_engines().run(); + + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::EngineGenerator(1))); + } + + #[test] + fn when_ac_bus_1_becomes_unpowered_but_ac_bus_2_powered_nothing_powers_ac_ess_bus_for_a_while() + { + let test_bed = test_bed_with() + .running_engine(2) + .and() + .bus_tie_off() + .run_waiting_until_just_before_ac_ess_feed_transition(); + + assert!(test_bed.static_inverter_input().is_unpowered()); + assert!(test_bed.ac_ess_bus_output().is_unpowered()); + } + + #[test] + fn when_ac_bus_1_becomes_unpowered_but_ac_bus_2_powered_nothing_powers_dc_ess_bus_for_a_while() + { + let test_bed = test_bed_with() + .running_engine(2) + .and() + .bus_tie_off() + .run_waiting_until_just_before_ac_ess_feed_transition(); + + assert!(test_bed.dc_ess_bus_output().is_unpowered()); + } + + #[test] + fn bat_only_low_airspeed_when_a_single_battery_contactor_closed_static_inverter_has_no_input() { + let test_bed = test_bed_with() + .bat_auto(1) + .bat_off(2) + .and() + .airspeed(Velocity::new::(49.)) + .run_waiting_for(Duration::from_secs(1_000)); + + assert!(test_bed.static_inverter_input().is_unpowered()); + } + + #[test] + fn bat_only_low_airspeed_static_inverter_has_input() { + let test_bed = test_bed_with() + .bat_auto(1) + .bat_auto(2) + .on_the_ground() + .and() + .airspeed(Velocity::new::(49.)) + .run_waiting_for(Duration::from_secs(1_000)); + + assert!(test_bed + .static_inverter_input() + .is_pair(PotentialOrigin::Battery(1), PotentialOrigin::Battery(2))); + } + + #[test] + fn when_airspeed_above_50_and_ac_bus_1_and_2_unpowered_and_emergency_gen_off_static_inverter_powers_ac_ess_bus( + ) { + let test_bed = test_bed_with() + .airspeed(Velocity::new::(51.)) + .run_waiting_for(Duration::from_secs(1_000)); + + assert!(test_bed + .static_inverter_input() + .is_single(PotentialOrigin::Battery(1))); + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::StaticInverter)); + } + + /// # Source + /// Discord (komp#1821): + /// > The fault light will extinguish after 3 seconds. That's the time delay before automatic switching is activated in case of AC BUS 1 loss. + #[test] + fn with_ac_bus_1_being_unpowered_after_a_delay_ac_bus_2_powers_ac_ess_bus() { + let test_bed = test_bed_with() + .running_engine(2) + .and() + .bus_tie_off() + .run_waiting_for_ac_ess_feed_transition(); + + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::EngineGenerator(2))); + } + + /// # Source + /// Discord (komp#1821): + /// > When AC BUS 1 is available again, it will switch back automatically without delay, unless the AC ESS FEED button is on ALTN. + #[test] + fn ac_bus_1_powers_ac_ess_bus_immediately_when_ac_bus_1_becomes_powered_after_ac_bus_2_was_powering_ac_ess_bus( + ) { + let test_bed = test_bed_with() + .running_engine(2) + .and() + .bus_tie_off() + .run_waiting_for_ac_ess_feed_transition() + .then_continue_with() + .running_engine(1) + .and() + .bus_tie_auto() + .run(); + + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::EngineGenerator(1))); + } + + #[rstest] + #[case(1)] + #[case(2)] + fn single_engine_with_generator_off_leaves_ac_buses_unpowered(#[case] engine: usize) { + let test_bed = test_bed_with() + .running_engine(engine) + .and() + .gen_off(engine) + .run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + } + + #[rstest] + #[case(1, 2)] + #[case(2, 1)] + fn when_generator_off_and_both_engines_running_the_other_generator_powers_ac_buses( + #[case] generator_off: usize, + #[case] supplying_generator: usize, + ) { + let test_bed = test_bed_with() + .running_engines() + .and() + .gen_off(generator_off) + .run(); + + assert!(test_bed + .ac_bus_output(1) + .is_single(PotentialOrigin::EngineGenerator(supplying_generator))); + assert!(test_bed + .ac_bus_output(2) + .is_single(PotentialOrigin::EngineGenerator(supplying_generator))); + } + + #[test] + fn when_ac_ess_feed_push_button_altn_engine_gen_2_powers_ac_ess_bus() { + let test_bed = test_bed_with() + .running_engines() + .and() + .ac_ess_feed_altn() + .run(); + + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::EngineGenerator(2))); + } + + #[test] + fn when_only_apu_running_but_apu_gen_push_button_off_nothing_powers_ac_bus_1_and_2() { + let test_bed = test_bed_with().running_apu().and().apu_gen_off().run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + } + + #[test] + fn when_only_external_power_connected_but_ext_pwr_push_button_off_nothing_powers_ac_bus_1_and_2( + ) { + let test_bed = test_bed_with() + .connected_external_power() + .and() + .ext_pwr_off() + .run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + } + + #[test] + fn when_ac_bus_1_and_ac_bus_2_are_lost_neither_ac_ess_feed_contactor_is_closed() { + let mut test_bed = test_bed_with().run(); + + assert!(test_bed.both_ac_ess_feed_contactors_open()); + } + + #[test] + fn when_battery_1_full_it_is_not_powered_by_dc_bat_bus() { + let test_bed = test_bed_with().running_engines().run(); + + assert!(test_bed + .battery_1_input() + .is_single(PotentialOrigin::Battery(1))); + } + + #[test] + fn when_battery_1_not_full_it_is_powered_by_dc_bat_bus() { + let test_bed = test_bed_with() + .running_engines() + .and() + .empty_battery_1() + .run(); + + assert!(test_bed.battery_1_input().is_powered()); + } + + #[test] + fn when_battery_1_not_full_and_button_off_it_is_not_powered_by_dc_bat_bus() { + let test_bed = test_bed_with() + .running_engines() + .empty_battery_1() + .and() + .bat_off(1) + .run(); + + assert!(test_bed.battery_1_input().is_unpowered()) + } + + #[test] + fn when_battery_1_has_charge_powers_hot_bus_1() { + let test_bed = test_bed().run(); + + assert!(test_bed.hot_bus_output(1).is_powered()); + } + + #[test] + fn when_battery_1_is_empty_and_dc_bat_bus_unpowered_hot_bus_1_unpowered() { + let test_bed = test_bed_with().empty_battery_1().run(); + + assert!(test_bed.hot_bus_output(1).is_unpowered()); + } + + #[test] + fn when_battery_1_is_empty_and_dc_bat_bus_powered_hot_bus_1_powered() { + let test_bed = test_bed_with() + .running_engines() + .and() + .empty_battery_1() + .run(); + + assert!(test_bed + .hot_bus_output(1) + .is_single(PotentialOrigin::TransformerRectifier(1))); + } + + #[test] + fn when_battery_2_full_it_is_not_powered_by_dc_bat_bus() { + let test_bed = test_bed_with().running_engines().run(); + + assert!(test_bed + .battery_2_input() + .is_single(PotentialOrigin::Battery(2))); + } + + #[test] + fn when_battery_2_not_full_it_is_powered_by_dc_bat_bus() { + let test_bed = test_bed_with() + .running_engines() + .and() + .empty_battery_2() + .run(); + + assert!(test_bed.battery_2_input().is_powered()); + } + + #[test] + fn when_battery_2_not_full_and_button_off_it_is_not_powered_by_dc_bat_bus() { + let test_bed = test_bed_with() + .running_engines() + .empty_battery_2() + .and() + .bat_off(2) + .run(); + + assert!(test_bed.battery_2_input().is_unpowered()) + } + + #[test] + fn when_battery_2_has_charge_powers_hot_bus_2() { + let test_bed = test_bed().run(); + + assert!(test_bed.hot_bus_output(2).is_powered()); + } + + #[test] + fn when_battery_2_is_empty_and_dc_bat_bus_unpowered_hot_bus_2_unpowered() { + let test_bed = test_bed_with().empty_battery_2().run(); + + assert!(test_bed.hot_bus_output(2).is_unpowered()); + } + + #[test] + fn when_battery_2_is_empty_and_dc_bat_bus_powered_hot_bus_2_powered() { + let test_bed = test_bed_with() + .running_engines() + .and() + .empty_battery_2() + .run(); + + assert!(test_bed + .hot_bus_output(2) + .is_single(PotentialOrigin::TransformerRectifier(1))); + } + + #[rstest] + #[case(1, 2)] + #[case(2, 1)] + fn when_bus_tie_off_engine_does_not_power_other_ac_bus( + #[case] engine: usize, + #[case] ac_bus: u8, + ) { + let test_bed = test_bed_with() + .running_engine(engine) + .and() + .bus_tie_off() + .run(); + + assert!(test_bed.ac_bus_output(ac_bus).is_unpowered()); + } + + #[test] + fn when_bus_tie_off_apu_does_not_power_ac_buses() { + let test_bed = test_bed_with().running_apu().and().bus_tie_off().run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + } + + #[test] + fn when_bus_tie_off_external_power_does_not_power_ac_buses() { + let test_bed = test_bed_with() + .connected_external_power() + .and() + .bus_tie_off() + .run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + } + + #[test] + fn when_dc_bus_1_and_dc_bus_2_unpowered_dc_bus_2_to_dc_bat_remains_open() { + let mut test_bed = test_bed().run(); + + assert!(test_bed.dc_bus_2_tie_contactor_is_open()); + } + + #[test] + fn when_ac_ess_bus_powered_ac_ess_feed_does_not_have_fault() { + let mut test_bed = test_bed_with().running_engines().run(); + + assert!(!test_bed.ac_ess_feed_has_fault()); + } + + #[test] + fn when_ac_ess_bus_is_unpowered_ac_ess_feed_has_fault() { + let mut test_bed = test_bed_with().airspeed(Velocity::new::(0.)).run(); + + assert!(test_bed.ac_ess_feed_has_fault()); + } + + #[test] + fn when_single_engine_and_apu_galley_is_not_shed() { + let mut test_bed = test_bed_with().running_engine(1).and().running_apu().run(); + + assert!(!test_bed.galley_is_shed()); + } + + #[rstest] + #[case(1)] + #[case(2)] + fn when_single_engine_gen_galley_is_shed(#[case] engine_number: usize) { + let mut test_bed = test_bed_with().running_engine(engine_number).run(); + + assert!(test_bed.galley_is_shed()); + } + + #[test] + fn when_on_ground_and_apu_gen_only_galley_is_not_shed() { + let mut test_bed = test_bed_with().running_apu().and().on_the_ground().run(); + + assert!(!test_bed.galley_is_shed()); + } + + #[test] + fn when_single_engine_gen_with_bus_tie_off_but_apu_running_galley_is_shed() { + let mut test_bed = test_bed_with() + .running_engine(1) + .running_apu() + .and() + .bus_tie_off() + .run(); + + assert!(test_bed.galley_is_shed()); + } + + #[test] + fn when_single_engine_gen_with_bus_tie_off_and_ext_pwr_on_galley_is_shed() { + let mut test_bed = test_bed_with() + .running_engine(1) + .connected_external_power() + .ext_pwr_on() + .and() + .bus_tie_off() + .run(); + + assert!(test_bed.galley_is_shed()); + } + + #[test] + fn when_on_ground_and_ext_pwr_only_galley_is_not_shed() { + let mut test_bed = test_bed_with() + .connected_external_power() + .ext_pwr_on() + .and() + .on_the_ground() + .run(); + + assert!(!test_bed.galley_is_shed()); + } + + #[test] + fn when_in_flight_and_apu_gen_only_galley_is_shed() { + let mut test_bed = test_bed_with().running_apu().run(); + + assert!(test_bed.galley_is_shed()); + } + + #[test] + fn when_in_flight_and_emer_gen_only_galley_is_shed() { + let mut test_bed = test_bed_with().running_emergency_generator().run(); + + assert!(test_bed.galley_is_shed()); + } + + #[test] + fn when_commercial_pb_off_galley_is_shed() { + let mut test_bed = test_bed_with() + .running_engines() + .and() + .commercial_off() + .run(); + + assert!(test_bed.galley_is_shed()); + } + + #[test] + fn when_galy_and_cab_pb_off_galley_is_shed() { + let mut test_bed = test_bed_with() + .running_engines() + .and() + .galy_and_cab_off() + .run(); + + assert!(test_bed.galley_is_shed()); + } + + #[test] + #[ignore = "Generator overloading is not yet supported."] + fn when_aircraft_on_the_ground_and_apu_gen_is_overloaded_galley_is_shed() {} + + #[rstest] + #[case(1)] + #[case(2)] + fn when_gen_contactor_open_gen_push_button_has_fault(#[case] gen_number: usize) { + let mut test_bed = test_bed_with().running_apu().run(); + + assert!(test_bed.gen_has_fault(gen_number)); + } + + #[rstest] + #[case(1)] + #[case(2)] + fn when_gen_contactor_open_and_gen_push_button_off_does_not_have_fault( + #[case] gen_number: usize, + ) { + let mut test_bed = test_bed_with() + .running_apu() + .and() + .gen_off(gen_number) + .run(); + + assert!(!test_bed.gen_has_fault(gen_number)); + } + + #[rstest] + #[case(1)] + #[case(2)] + fn when_gen_contactor_closed_gen_push_button_does_not_have_fault(#[case] gen_number: usize) { + let mut test_bed = test_bed_with().running_engine(gen_number).run(); + + assert!(!test_bed.gen_has_fault(gen_number)); + } + + #[rstest] + #[case(1)] + #[case(2)] + fn when_apu_start_with_battery_off_start_contactors_remain_open_and_motor_unpowered( + #[case] battery_number: usize, + ) { + let mut test_bed = test_bed_with() + .bat_off(battery_number) + .command_closing_of_start_contactors() + .and() + .run_for_start_contactor_test(); + + assert!(!test_bed.apu_start_contactors_closed()); + assert!(!test_bed.apu_start_motor_is_powered()); + } + + #[test] + fn when_apu_start_with_both_batteries_auto_and_closing_commanded_start_contactors_close_and_motor_is_powered( + ) { + let mut test_bed = test_bed_with() + .bat_auto(1) + .bat_auto(2) + .command_closing_of_start_contactors() + .and() + .run_for_start_contactor_test(); + + assert!(test_bed.apu_start_contactors_closed()); + assert!(test_bed.apu_start_motor_is_powered()); + } + + #[test] + fn when_apu_start_with_both_batteries_auto_and_closing_not_commanded_start_contactors_remain_open_and_motor_unpowered( + ) { + let mut test_bed = test_bed_with() + .bat_auto(1) + .bat_auto(2) + .and() + .run_for_start_contactor_test(); + + assert!(!test_bed.apu_start_contactors_closed()); + assert!(!test_bed.apu_start_motor_is_powered()); + } + + #[test] + fn transitions_between_gen_1_and_gen_2_without_interruption() { + // The current implementation shouldn't include power interruptions. + let mut test_bed = test_bed_with() + .running_engine(1) + .and() + .running_engine(2) + .run(); + assert!( + test_bed.ac_bus_output(1).is_powered(), + "Precondition: the test assumes the AC 1 bus is powered at this point." + ); + + test_bed = test_bed.then_continue_with().stopped_engine(1).run_once(); + + assert!(test_bed.ac_bus_output(1).is_powered()); + } + + #[test] + fn when_ac_2_bus_is_powered_it_has_priority_over_ext_pwr_gnd_flt_circuit() { + let test_bed = test_bed_with() + .running_engine(2) + .and() + .connected_external_power() + .run(); + + assert!(test_bed + .ac_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::EngineGenerator(2))); + } + + #[test] + fn when_ac_2_bus_is_unpowered_and_ac_1_is_powered_ext_pwr_powers_gnd_flt_buses() { + let test_bed = test_bed_with() + .running_engine(1) + .bus_tie_off() + .and() + .connected_external_power() + .run(); + + assert!(test_bed + .ac_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::External)); + assert!(test_bed + .dc_gnd_flt_service_bus_output() + .is_single(PotentialOrigin::TransformerRectifier(2))); + } + + #[test] + fn when_gen_1_line_off_and_only_engine_1_running_nothing_powers_ac_buses() { + let test_bed = test_bed_with() + .running_engine(1) + .and() + .gen_1_line_off() + .run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + } + + #[test] + fn when_gen_1_contactor_open_due_to_gen_1_line_being_off_gen_1_push_button_has_fault() { + let mut test_bed = test_bed_with() + .running_engine(1) + .and() + .gen_1_line_off() + .run(); + + assert!(test_bed.gen_has_fault(1)); + } + + #[test] + fn when_emergency_generator_not_supplying_while_ac_1_and_2_unavailable_in_flight_rat_and_emer_gen_has_fault( + ) { + let mut test_bed = test_bed_with() + .running_engines() + .gen_off(1) + .and() + .gen_off(2) + .run(); + + assert!(test_bed.rat_and_emer_gen_has_fault()); + } + + #[test] + fn when_emergency_generator_not_supplying_while_ac_1_and_2_unavailable_during_takeoff_rat_and_emer_gen_does_not_have_fault( + ) { + let mut test_bed = test_bed_with() + .running_engines() + .on_the_ground() + .gen_off(1) + .and() + .gen_off(2) + .run(); + + assert!(!test_bed.rat_and_emer_gen_has_fault()); + } + + #[test] + fn when_emergency_generator_not_supplying_while_ac_1_and_2_unavailable_during_low_speed_flight_rat_and_emer_gen_does_not_have_fault( + ) { + let mut test_bed = test_bed_with() + .running_engines() + .gen_off(1) + .gen_off(2) + .and() + .airspeed(Velocity::new::(99.)) + .run(); + + assert!(!test_bed.rat_and_emer_gen_has_fault()); + } + + #[test] + fn when_rat_and_emer_gen_man_on_push_button_is_pressed_at_an_earlier_time_in_case_of_ac_1_and_2_unavailable_emergency_generator_provides_power_immediately( + ) { + let test_bed = test_bed_with() + .running_engines() + .and() + .rat_and_emer_gen_man_on_pressed() + .run_waiting_for(Duration::from_secs(100)) + .then_continue_with() + .gen_off(1) + .and() + .gen_off(2) + .run(); + + assert!(test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::EmergencyGenerator)); + } + + #[test] + fn when_rat_and_emer_gen_man_on_push_button_is_pressed_in_case_of_ac_1_and_2_unavailable_emergency_generator_does_not_provide_power_immediately( + ) { + let test_bed = test_bed_with() + .running_engines() + .gen_off(1) + .gen_off(2) + .and() + .rat_and_emer_gen_man_on_pressed() + .run_waiting_for(Duration::from_secs(0)); + + assert!(!test_bed + .ac_ess_bus_output() + .is_single(PotentialOrigin::EmergencyGenerator)); + } + + #[rstest] + #[case(1)] + #[case(2)] + fn when_engine_fire_push_button_released_and_only_that_engine_is_running_nothing_powers_ac_buses( + #[case] number: usize, + ) { + let test_bed = test_bed_with() + .running_engine(number) + .and() + .released_engine_fire_push_button(number) + .run(); + + assert!(test_bed.ac_bus_output(1).is_unpowered()); + assert!(test_bed.ac_bus_output(2).is_unpowered()); + } + + #[rstest] + #[case(1)] + #[case(2)] + fn when_gen_contactor_open_due_to_engine_fire_push_button_released_gen_push_button_has_fault( + #[case] number: usize, + ) { + let mut test_bed = test_bed_with() + .running_engine(number) + .and() + .released_engine_fire_push_button(number) + .run(); + + assert!(test_bed.gen_has_fault(number)); + } + + fn test_bed_with() -> A380ElectricalTestBed { + test_bed() + } + + fn test_bed() -> A380ElectricalTestBed { + A380ElectricalTestBed::new() + } + + struct TestApu { + identifier: ElectricalElementIdentifier, + is_available: bool, + start_motor_is_powered: bool, + should_close_start_contactor: bool, + } + impl TestApu { + fn new(context: &mut InitContext) -> Self { + Self { + identifier: context.next_electrical_identifier(), + is_available: false, + start_motor_is_powered: false, + should_close_start_contactor: false, + } + } + + fn set_available(&mut self, available: bool) { + self.is_available = available; + } + + fn command_closing_of_start_contactors(&mut self) { + self.should_close_start_contactor = true; + } + + fn start_motor_is_powered(&self) -> bool { + self.start_motor_is_powered + } + } + impl SimulationElement for TestApu { + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.start_motor_is_powered = buses.is_powered(ElectricalBusType::Sub("49-42-00")); + } + } + impl AuxiliaryPowerUnitElectrical for TestApu { + fn output_within_normal_parameters(&self) -> bool { + self.is_available + } + } + impl ElectricitySource for TestApu { + fn output_potential(&self) -> Potential { + if self.is_available { + Potential::new( + PotentialOrigin::ApuGenerator(1), + ElectricPotential::new::(115.), + ) + } else { + Potential::none() + } + } + } + impl ElectricalElement for TestApu { + fn input_identifier(&self) -> systems::electrical::ElectricalElementIdentifier { + self.identifier + } + + fn output_identifier(&self) -> systems::electrical::ElectricalElementIdentifier { + self.identifier + } + + fn is_conductive(&self) -> bool { + true + } + } + impl ApuAvailable for TestApu { + fn is_available(&self) -> bool { + self.is_available + } + } + impl ControllerSignal for TestApu { + fn signal(&self) -> Option { + if self.should_close_start_contactor { + Some(ContactorSignal::Close) + } else { + Some(ContactorSignal::Open) + } + } + } + + struct TestEngineFirePushButtons { + is_released: [bool; 2], + } + impl TestEngineFirePushButtons { + fn new() -> Self { + Self { + is_released: [false, false], + } + } + + fn release(&mut self, engine_number: usize) { + self.is_released[engine_number - 1] = true; + } + } + impl EngineFirePushButtons for TestEngineFirePushButtons { + fn is_released(&self, engine_number: usize) -> bool { + self.is_released[engine_number - 1] + } + } + + struct TestEngine { + is_running: bool, + } + impl TestEngine { + fn new() -> Self { + Self { is_running: false } + } + + fn run(&mut self) { + self.is_running = true; + } + + fn stop(&mut self) { + self.is_running = false; + } + } + impl EngineCorrectedN2 for TestEngine { + fn corrected_n2(&self) -> Ratio { + Ratio::new::(if self.is_running { 80. } else { 0. }) + } + } + + struct TestApuOverhead { + master_sw_pb_on: bool, + start_pb_on: bool, + } + impl TestApuOverhead { + fn new() -> Self { + Self { + master_sw_pb_on: false, + start_pb_on: false, + } + } + + fn set_apu_master_sw_pb_on(&mut self) { + self.master_sw_pb_on = true; + } + + fn set_start_pb_on(&mut self) { + self.start_pb_on = true; + } + } + impl ApuMaster for TestApuOverhead { + fn master_sw_is_on(&self) -> bool { + self.master_sw_pb_on + } + } + impl ApuStart for TestApuOverhead { + fn start_is_on(&self) -> bool { + self.start_pb_on + } + } + + struct TestHydraulicSystem { + time_since_start: Duration, + starting_or_started: bool, + emergency_motor_speed: AngularVelocity, + } + impl TestHydraulicSystem { + fn new() -> Self { + Self { + time_since_start: Duration::from_secs(0), + starting_or_started: false, + emergency_motor_speed: AngularVelocity::new::(0.), + } + } + + fn update( + &mut self, + is_rat_hydraulic_loop_pressurised: bool, + is_emergency_elec: bool, + rat_man_on_pressed: bool, + context: &UpdateContext, + ) { + if (is_emergency_elec || rat_man_on_pressed) && is_rat_hydraulic_loop_pressurised { + self.starting_or_started = true; + } + + if self.starting_or_started { + self.time_since_start += context.delta(); + } + + if self.time_since_start > Duration::from_secs(8) && is_rat_hydraulic_loop_pressurised { + self.emergency_motor_speed = AngularVelocity::new::(12000.); + } else { + self.emergency_motor_speed = AngularVelocity::new::(0.); + } + } + } + impl HydraulicGeneratorControlUnit for TestHydraulicSystem { + fn max_allowed_power(&self) -> Power { + if self.emergency_motor_speed.get::() > 10000. { + Power::new::(5000.) + } else { + Power::new::(0.) + } + } + fn motor_speed(&self) -> AngularVelocity { + self.emergency_motor_speed + } + } + + struct TestLandingGear {} + impl TestLandingGear { + fn new() -> Self { + Self {} + } + } + impl LgciuWeightOnWheels for TestLandingGear { + fn right_gear_compressed(&self, _: bool) -> bool { + false + } + + fn right_gear_extended(&self, _: bool) -> bool { + true + } + + fn left_gear_compressed(&self, _: bool) -> bool { + false + } + + fn left_gear_extended(&self, _: bool) -> bool { + true + } + + fn left_and_right_gear_compressed(&self, _: bool) -> bool { + false + } + + fn left_and_right_gear_extended(&self, _: bool) -> bool { + true + } + + fn nose_gear_compressed(&self, _: bool) -> bool { + false + } + + fn nose_gear_extended(&self, _: bool) -> bool { + true + } + } + + struct A380ElectricalTestAircraft { + engines: [TestEngine; 2], + ext_pwr: ExternalPowerSource, + elec: A380Electrical, + overhead: A380ElectricalOverheadPanel, + emergency_overhead: A380EmergencyElectricalOverheadPanel, + apu: TestApu, + apu_overhead: TestApuOverhead, + engine_fire_push_buttons: TestEngineFirePushButtons, + hydraulics: TestHydraulicSystem, + force_run_emergency_gen: bool, + } + impl A380ElectricalTestAircraft { + fn new(context: &mut InitContext) -> Self { + Self { + engines: [TestEngine::new(), TestEngine::new()], + ext_pwr: ExternalPowerSource::new(context), + elec: A380Electrical::new(context), + overhead: A380ElectricalOverheadPanel::new(context), + emergency_overhead: A380EmergencyElectricalOverheadPanel::new(context), + apu: TestApu::new(context), + apu_overhead: TestApuOverhead::new(), + engine_fire_push_buttons: TestEngineFirePushButtons::new(), + hydraulics: TestHydraulicSystem::new(), + force_run_emergency_gen: false, + } + } + + fn running_engine(&mut self, number: usize) { + self.engines[number - 1].run(); + } + + fn stopped_engine(&mut self, number: usize) { + self.engines[number - 1].stop(); + } + + fn running_apu(&mut self) { + self.apu.set_available(true); + } + + fn set_apu_master_sw_pb_on(&mut self) { + self.apu_overhead.set_apu_master_sw_pb_on(); + } + + fn set_apu_start_pb_on(&mut self) { + self.apu_overhead.set_start_pb_on(); + } + + fn command_closing_of_start_contactors(&mut self) { + self.apu.command_closing_of_start_contactors(); + } + + fn apu_start_motor_is_powered(&self) -> bool { + self.apu.start_motor_is_powered() + } + + fn empty_battery_1(&mut self) { + self.elec.empty_battery_1(); + } + + fn empty_battery_2(&mut self) { + self.elec.empty_battery_2(); + } + + fn running_emergency_generator(&mut self) { + self.force_run_emergency_gen = true; + } + + fn static_inverter_input<'a>(&self, electricity: &'a Electricity) -> Ref<'a, Potential> { + electricity.input_of(self.elec.direct_current.static_inverter()) + } + + fn tr_1_input<'a>(&self, electricity: &'a Electricity) -> Ref<'a, Potential> { + electricity.input_of(self.elec.tr_1()) + } + + fn tr_2_input<'a>(&self, electricity: &'a Electricity) -> Ref<'a, Potential> { + electricity.input_of(self.elec.tr_2()) + } + + fn tr_ess_input<'a>(&self, electricity: &'a Electricity) -> Ref<'a, Potential> { + electricity.input_of(self.elec.tr_ess()) + } + + fn battery_1_input<'a>(&self, electricity: &'a Electricity) -> Ref<'a, Potential> { + electricity.input_of(self.elec.battery_1()) + } + + fn battery_2_input<'a>(&self, electricity: &'a Electricity) -> Ref<'a, Potential> { + electricity.input_of(self.elec.battery_2()) + } + + fn release_engine_fire_push_button(&mut self, engine_number: usize) { + self.engine_fire_push_buttons.release(engine_number); + } + } + impl Aircraft for A380ElectricalTestAircraft { + fn update_before_power_distribution( + &mut self, + context: &UpdateContext, + electricity: &mut Electricity, + ) { + self.hydraulics.update( + context.indicated_airspeed() > Velocity::new::(100.), + self.elec.in_emergency_elec(), + self.force_run_emergency_gen || self.emergency_overhead.is_pressed(), + context, + ); + + self.elec.update( + context, + electricity, + &self.ext_pwr, + &self.overhead, + &self.emergency_overhead, + &mut self.apu, + &self.apu_overhead, + &self.engine_fire_push_buttons, + [&self.engines[0], &self.engines[1]], + &self.hydraulics, + &TestLandingGear::new(), + ); + self.overhead + .update_after_electrical(&self.elec, electricity); + self.emergency_overhead + .update_after_electrical(context, &self.elec); + } + } + impl SimulationElement for A380ElectricalTestAircraft { + fn accept(&mut self, visitor: &mut T) { + self.ext_pwr.accept(visitor); + self.elec.accept(visitor); + self.overhead.accept(visitor); + self.emergency_overhead.accept(visitor); + self.apu.accept(visitor); + + visitor.visit(self); + } + } + + struct A380ElectricalTestBed { + test_bed: SimulationTestBed, + } + impl A380ElectricalTestBed { + fn new() -> Self { + Self { + test_bed: SimulationTestBed::new(A380ElectricalTestAircraft::new), + } + } + + fn running_engine(mut self, number: usize) -> Self { + self.command(|a| a.running_engine(number)); + + self = self.without_triggering_emergency_elec(|x| { + x.run_waiting_for(Duration::from_millis( + INTEGRATED_DRIVE_GENERATOR_STABILIZATION_TIME_IN_MILLISECONDS, + )) + }); + + self + } + + fn stopped_engine(mut self, number: usize) -> Self { + self.command(|a| a.stopped_engine(number)); + self + } + + fn running_engines(self) -> Self { + self.running_engine(1).and().running_engine(2) + } + + fn running_apu(mut self) -> Self { + self.command(|a| a.running_apu()); + self + } + + fn connected_external_power(mut self) -> Self { + self.write_by_name("EXTERNAL POWER AVAILABLE:1", true); + + self.without_triggering_emergency_elec(|x| x.run()) + } + + fn empty_battery_1(mut self) -> Self { + self.command(|a| a.empty_battery_1()); + self + } + + fn empty_battery_2(mut self) -> Self { + self.command(|a| a.empty_battery_2()); + self + } + + fn airspeed(mut self, ias: Velocity) -> Self { + self.set_indicated_airspeed(ias); + self + } + + fn on_the_ground(mut self) -> Self { + self.set_indicated_altitude(Length::new::(0.)); + self.set_on_ground(true); + self + } + + fn run_for_start_contactor_test(self) -> Self { + self.airspeed(Velocity::new::(0.)) + .on_the_ground() + .apu_master_sw_pb_on() + .and() + .apu_start_pb_on() + .run() + } + + fn and(self) -> Self { + self + } + + fn then_continue_with(self) -> Self { + self + } + + fn failed_tr_1(mut self) -> Self { + self.test_bed.fail(FailureType::TransformerRectifier(1)); + self + } + + fn failed_tr_2(mut self) -> Self { + self.test_bed.fail(FailureType::TransformerRectifier(2)); + self + } + + fn running_emergency_generator(mut self) -> Self { + self.command(|a| a.running_emergency_generator()); + self.run_waiting_for(Duration::from_secs(100)) + } + + fn gen_off(mut self, number: usize) -> Self { + self.write_by_name(&format!("OVHD_ELEC_ENG_GEN_{}_PB_IS_ON", number), false); + self + } + + fn released_engine_fire_push_button(mut self, engine_number: usize) -> Self { + self.command(|a| a.release_engine_fire_push_button(engine_number)); + self + } + + fn gen_1_line_off(mut self) -> Self { + self.write_by_name("OVHD_EMER_ELEC_GEN_1_LINE_PB_IS_ON", false); + self + } + + fn apu_gen_off(mut self) -> Self { + self.write_by_name("OVHD_ELEC_APU_GEN_PB_IS_ON", false); + self + } + + fn ext_pwr_on(mut self) -> Self { + self.write_by_name("OVHD_ELEC_EXT_PWR_PB_IS_ON", true); + self + } + + fn ext_pwr_off(mut self) -> Self { + self.write_by_name("OVHD_ELEC_EXT_PWR_PB_IS_ON", false); + self + } + + fn ac_ess_feed_altn(mut self) -> Self { + self.write_by_name("OVHD_ELEC_AC_ESS_FEED_PB_IS_NORMAL", false); + self + } + + fn bat_off(self, number: usize) -> Self { + self.bat(number, false) + } + + fn bat_auto(self, number: usize) -> Self { + self.bat(number, true) + } + + fn bat(mut self, number: usize, auto: bool) -> Self { + self.write_by_name(&format!("OVHD_ELEC_BAT_{}_PB_IS_AUTO", number), auto); + self + } + + fn bus_tie(mut self, auto: bool) -> Self { + self.write_by_name("OVHD_ELEC_BUS_TIE_PB_IS_AUTO", auto); + self + } + + fn bus_tie_auto(self) -> Self { + self.bus_tie(true) + } + + fn bus_tie_off(self) -> Self { + self.bus_tie(false) + } + + fn commercial_off(mut self) -> Self { + self.write_by_name("OVHD_ELEC_COMMERCIAL_PB_IS_ON", false); + self + } + + fn galy_and_cab_off(mut self) -> Self { + self.write_by_name("OVHD_ELEC_GALY_AND_CAB_PB_IS_AUTO", false); + self + } + + fn apu_master_sw_pb_on(mut self) -> Self { + self.command(|a| a.set_apu_master_sw_pb_on()); + self + } + + fn apu_start_pb_on(mut self) -> Self { + self.command(|a| a.set_apu_start_pb_on()); + self + } + + fn rat_and_emer_gen_man_on_pressed(mut self) -> Self { + self.write_by_name("OVHD_EMER_ELEC_RAT_AND_EMER_GEN_IS_PRESSED", true); + self + } + + fn command_closing_of_start_contactors(mut self) -> Self { + self.command(|a| a.command_closing_of_start_contactors()); + self + } + + fn apu_start_contactors_closed(&mut self) -> bool { + self.read_by_name("ELEC_CONTACTOR_10KA_AND_5KA_IS_CLOSED") + } + + fn apu_start_motor_is_powered(&self) -> bool { + self.query(|a| a.apu_start_motor_is_powered()) + } + + fn ac_bus_output(&self, number: u8) -> Ref { + self.query_elec_ref(|_, elec| { + elec.potential_of(ElectricalBusType::AlternatingCurrent(number)) + }) + } + + fn ac_ess_bus_output(&self) -> Ref { + self.query_elec_ref(|_, elec| { + elec.potential_of(ElectricalBusType::AlternatingCurrentEssential) + }) + } + + fn ac_ess_shed_bus_output(&self) -> Ref { + self.query_elec_ref(|_, elec| { + elec.potential_of(ElectricalBusType::AlternatingCurrentEssentialShed) + }) + } + + fn ac_stat_inv_bus_output(&self) -> Ref { + self.query_elec_ref(|_, elec| { + elec.potential_of(ElectricalBusType::AlternatingCurrentStaticInverter) + }) + } + + fn ac_gnd_flt_service_bus_output(&self) -> Ref { + self.query_elec_ref(|_, elec| { + elec.potential_of(ElectricalBusType::AlternatingCurrentGndFltService) + }) + } + + fn static_inverter_input(&self) -> Ref { + self.query_elec_ref(|a, elec| a.static_inverter_input(elec)) + } + + fn tr_1_input(&self) -> Ref { + self.query_elec_ref(|a, elec| a.tr_1_input(elec)) + } + + fn tr_2_input(&self) -> Ref { + self.query_elec_ref(|a, elec| a.tr_2_input(elec)) + } + + fn tr_ess_input(&self) -> Ref { + self.query_elec_ref(|a, elec| a.tr_ess_input(elec)) + } + + fn battery_1_input(&self) -> Ref { + self.query_elec_ref(|a, elec| a.battery_1_input(elec)) + } + + fn battery_2_input(&self) -> Ref { + self.query_elec_ref(|a, elec| a.battery_2_input(elec)) + } + + fn dc_bus_output(&self, number: u8) -> Ref { + self.query_elec_ref(|_, elec| { + elec.potential_of(ElectricalBusType::DirectCurrent(number)) + }) + } + + fn dc_bat_bus_output(&self) -> Ref { + self.query_elec_ref(|_, elec| { + elec.potential_of(ElectricalBusType::DirectCurrentBattery) + }) + } + + fn dc_ess_bus_output(&self) -> Ref { + self.query_elec_ref(|_, elec| { + elec.potential_of(ElectricalBusType::DirectCurrentEssential) + }) + } + + fn dc_ess_shed_bus_output(&self) -> Ref { + self.query_elec_ref(|_, elec| { + elec.potential_of(ElectricalBusType::DirectCurrentEssentialShed) + }) + } + + fn hot_bus_output(&self, number: u8) -> Ref { + self.query_elec_ref(|_, elec| { + elec.potential_of(ElectricalBusType::DirectCurrentHot(number)) + }) + } + + fn dc_gnd_flt_service_bus_output(&self) -> Ref { + self.query_elec_ref(|_, elec| { + elec.potential_of(ElectricalBusType::DirectCurrentGndFltService) + }) + } + + fn ac_ess_feed_has_fault(&mut self) -> bool { + self.read_by_name("OVHD_ELEC_AC_ESS_FEED_PB_HAS_FAULT") + } + + fn gen_has_fault(&mut self, number: usize) -> bool { + self.read_by_name(&format!("OVHD_ELEC_ENG_GEN_{}_PB_HAS_FAULT", number)) + } + + fn rat_and_emer_gen_has_fault(&mut self) -> bool { + self.read_by_name("OVHD_EMER_ELEC_RAT_AND_EMER_GEN_HAS_FAULT") + } + + fn galley_is_shed(&mut self) -> bool { + self.read_by_name("ELEC_GALLEY_IS_SHED") + } + + fn both_ac_ess_feed_contactors_open(&mut self) -> bool { + !ReadByName::::read_by_name( + self, + "ELEC_CONTACTOR_3XC1_IS_CLOSED", + ) && !ReadByName::::read_by_name( + self, + "ELEC_CONTACTOR_3XC2_IS_CLOSED", + ) + } + + fn dc_bus_2_tie_contactor_is_open(&mut self) -> bool { + !ReadByName::::read_by_name( + self, + "ELEC_CONTACTOR_1PC2_IS_CLOSED", + ) + } + + fn run(self) -> Self { + self.run_waiting_for(Duration::from_secs(1)) + } + + fn run_waiting_for(mut self, delta: Duration) -> Self { + self.run_with_delta(delta); + + // Sadly it's impossible for some electrical origins such as + // the generators to know their output potential before a single + // simulation tick has passed, as the output potential among other + // things depends on electrical load which is only known near the + // end of a tick. As the electrical system disallows e.g. an engine + // generator contactor to close when its electrical parameters are + // outside of normal parameters, we have to run a second tick before + // the potential has flown through the system in the way we expected. + self.run_with_delta(Duration::from_secs(0)); + + self + } + + /// Runs the simulation a single time with a delta of 1 second. + /// This particular is useful for tests that want to verify behaviour + /// which only occurs in a single tick and would be hidden by + /// run or run_waiting_for, which executes two ticks. + fn run_once(mut self) -> Self { + self.run_with_delta(Duration::from_secs(1)); + + self + } + + /// Tests assume they start at altitude with high velocity. + /// As power sources can take some time before they become available + /// by wrapping the functions that enable such a power sources we ensure it cannot + /// trigger the deployment of the RAT or start of EMER GEN. + fn without_triggering_emergency_elec(mut self, mut func: impl FnMut(Self) -> Self) -> Self { + let ias = self.indicated_airspeed(); + self.set_indicated_airspeed(Velocity::new::(0.)); + + self = func(self); + + self.set_indicated_airspeed(ias); + + self + } + + fn run_waiting_for_ac_ess_feed_transition(self) -> Self { + self.run_waiting_for(A380AcEssFeedContactors::AC_ESS_FEED_TO_AC_BUS_2_DELAY_IN_SECONDS) + } + + fn run_waiting_until_just_before_ac_ess_feed_transition(self) -> Self { + self.run_waiting_for( + A380AcEssFeedContactors::AC_ESS_FEED_TO_AC_BUS_2_DELAY_IN_SECONDS + - Duration::from_millis(1), + ) + } + } + impl TestBed for A380ElectricalTestBed { + type Aircraft = A380ElectricalTestAircraft; + + fn test_bed(&self) -> &SimulationTestBed { + &self.test_bed + } + + fn test_bed_mut(&mut self) -> &mut SimulationTestBed { + &mut self.test_bed + } + } +} diff --git a/src/systems/a380_systems/src/fuel.rs b/src/systems/a380_systems/src/fuel.rs new file mode 100644 index 000000000000..a1a3550e5662 --- /dev/null +++ b/src/systems/a380_systems/src/fuel.rs @@ -0,0 +1,34 @@ +use systems::simulation::{ + InitContext, Read, SimulationElement, SimulatorReader, VariableIdentifier, +}; +use uom::si::{f64::*, mass::kilogram}; + +pub struct A380Fuel { + unlimited_fuel_id: VariableIdentifier, + fuel_tank_left_main_quantity_id: VariableIdentifier, + + unlimited_fuel: bool, + left_inner_tank_fuel_quantity: Mass, +} +impl A380Fuel { + pub fn new(context: &mut InitContext) -> Self { + A380Fuel { + unlimited_fuel_id: context.get_identifier("UNLIMITED FUEL".to_owned()), + fuel_tank_left_main_quantity_id: context + .get_identifier("FUEL TANK LEFT MAIN QUANTITY".to_owned()), + + unlimited_fuel: false, + left_inner_tank_fuel_quantity: Mass::new::(0.), + } + } + + pub fn left_inner_tank_has_fuel_remaining(&self) -> bool { + self.unlimited_fuel || self.left_inner_tank_fuel_quantity > Mass::new::(0.) + } +} +impl SimulationElement for A380Fuel { + fn read(&mut self, reader: &mut SimulatorReader) { + self.unlimited_fuel = reader.read(&self.unlimited_fuel_id); + self.left_inner_tank_fuel_quantity = reader.read(&self.fuel_tank_left_main_quantity_id); + } +} diff --git a/src/systems/a380_systems/src/hydraulic/flaps_computer.rs b/src/systems/a380_systems/src/hydraulic/flaps_computer.rs new file mode 100644 index 000000000000..c446d4b1c643 --- /dev/null +++ b/src/systems/a380_systems/src/hydraulic/flaps_computer.rs @@ -0,0 +1,1527 @@ +use crate::systems::shared::arinc429::{Arinc429Word, SignStatus}; +use systems::shared::FeedbackPositionPickoffUnit; + +use systems::simulation::{ + InitContext, Read, SimulationElement, SimulationElementVisitor, SimulatorReader, + SimulatorWriter, UpdateContext, VariableIdentifier, Write, +}; + +use std::panic; +use uom::si::{angle::degree, f64::*, velocity::knot}; + +#[derive(Debug, Copy, Clone, PartialEq)] +enum FlapsConf { + Conf0, + Conf1, + Conf1F, + Conf2, + Conf3, + ConfFull, +} + +impl From for FlapsConf { + fn from(value: u8) -> Self { + match value { + 0 => FlapsConf::Conf0, + 1 => FlapsConf::Conf1, + 2 => FlapsConf::Conf1F, + 3 => FlapsConf::Conf2, + 4 => FlapsConf::Conf3, + 5 => FlapsConf::ConfFull, + i => panic!("Cannot convert from {} to FlapsConf.", i), + } + } +} + +/// A struct to read the handle position +struct FlapsHandle { + handle_position_id: VariableIdentifier, + position: u8, + previous_position: u8, +} + +impl FlapsHandle { + fn new(context: &mut InitContext) -> Self { + Self { + handle_position_id: context.get_identifier("FLAPS_HANDLE_INDEX".to_owned()), + position: 0, + previous_position: 0, + } + } + + fn position(&self) -> u8 { + self.position + } + + fn previous_position(&self) -> u8 { + self.previous_position + } +} + +impl SimulationElement for FlapsHandle { + fn read(&mut self, reader: &mut SimulatorReader) { + self.previous_position = self.position; + self.position = reader.read(&self.handle_position_id); + } +} + +struct SlatFlapControlComputer { + flaps_conf_index_id: VariableIdentifier, + slats_fppu_angle_id: VariableIdentifier, + flaps_fppu_angle_id: VariableIdentifier, + slat_flap_system_status_word_id: VariableIdentifier, + slat_flap_actual_position_word_id: VariableIdentifier, + slat_actual_position_word_id: VariableIdentifier, + flap_actual_position_word_id: VariableIdentifier, + + flaps_demanded_angle: Angle, + slats_demanded_angle: Angle, + flaps_feedback_angle: Angle, + slats_feedback_angle: Angle, + flaps_conf: FlapsConf, +} + +impl SlatFlapControlComputer { + const EQUAL_ANGLE_DELTA_DEGREE: f64 = 0.177; + const HANDLE_ONE_CONF_AIRSPEED_THRESHOLD_KNOTS: f64 = 100.; + const CONF1F_TO_CONF1_AIRSPEED_THRESHOLD_KNOTS: f64 = 210.; + + fn new(context: &mut InitContext) -> Self { + Self { + flaps_conf_index_id: context.get_identifier("FLAPS_CONF_INDEX".to_owned()), + slats_fppu_angle_id: context.get_identifier("SLATS_FPPU_ANGLE".to_owned()), + flaps_fppu_angle_id: context.get_identifier("FLAPS_FPPU_ANGLE".to_owned()), + slat_flap_system_status_word_id: context + .get_identifier("SFCC_SLAT_FLAP_SYSTEM_STATUS_WORD".to_owned()), + slat_flap_actual_position_word_id: context + .get_identifier("SFCC_SLAT_FLAP_ACTUAL_POSITION_WORD".to_owned()), + slat_actual_position_word_id: context + .get_identifier("SFCC_SLAT_ACTUAL_POSITION_WORD".to_owned()), + flap_actual_position_word_id: context + .get_identifier("SFCC_FLAP_ACTUAL_POSITION_WORD".to_owned()), + + flaps_demanded_angle: Angle::new::(0.), + slats_demanded_angle: Angle::new::(0.), + flaps_feedback_angle: Angle::new::(0.), + slats_feedback_angle: Angle::new::(0.), + flaps_conf: FlapsConf::Conf0, + } + } + + // Returns a flap demanded angle in FPPU reference degree (feedback sensor) + fn demanded_flaps_fppu_angle_from_conf(flap_conf: FlapsConf) -> Angle { + match flap_conf { + FlapsConf::Conf0 => Angle::new::(0.), + FlapsConf::Conf1 => Angle::new::(0.), + FlapsConf::Conf1F => Angle::new::(120.22), + FlapsConf::Conf2 => Angle::new::(145.51), + FlapsConf::Conf3 => Angle::new::(168.35), + FlapsConf::ConfFull => Angle::new::(251.97), + } + } + + // Returns a slat demanded angle in FPPU reference degree (feedback sensor) + fn demanded_slats_fppu_angle_from_conf(flap_conf: FlapsConf) -> Angle { + match flap_conf { + FlapsConf::Conf0 => Angle::new::(0.), + FlapsConf::Conf1 => Angle::new::(222.27), + FlapsConf::Conf1F => Angle::new::(222.27), + FlapsConf::Conf2 => Angle::new::(272.27), + FlapsConf::Conf3 => Angle::new::(272.27), + FlapsConf::ConfFull => Angle::new::(334.16), + } + } + + fn generate_configuration( + &self, + flaps_handle: &FlapsHandle, + context: &UpdateContext, + ) -> FlapsConf { + match (flaps_handle.previous_position(), flaps_handle.position()) { + (0, 1) + if context.indicated_airspeed().get::() + <= Self::HANDLE_ONE_CONF_AIRSPEED_THRESHOLD_KNOTS => + { + FlapsConf::Conf1F + } + (0, 1) => FlapsConf::Conf1, + (1, 1) + if context.indicated_airspeed().get::() + > Self::CONF1F_TO_CONF1_AIRSPEED_THRESHOLD_KNOTS => + { + FlapsConf::Conf1 + } + (1, 1) => self.flaps_conf, + (_, 1) + if context.indicated_airspeed().get::() + <= Self::CONF1F_TO_CONF1_AIRSPEED_THRESHOLD_KNOTS => + { + FlapsConf::Conf1F + } + (_, 1) => FlapsConf::Conf1, + (_, 0) => FlapsConf::Conf0, + (from, to) if from != to => FlapsConf::from(to + 1), + (_, _) => self.flaps_conf, + } + } + + fn surface_movement_required(demanded_angle: Angle, feedback_angle: Angle) -> bool { + (demanded_angle - feedback_angle).get::().abs() > Self::EQUAL_ANGLE_DELTA_DEGREE + } + + pub fn update( + &mut self, + context: &UpdateContext, + flaps_handle: &FlapsHandle, + flaps_feedback: &impl FeedbackPositionPickoffUnit, + slats_feedback: &impl FeedbackPositionPickoffUnit, + ) { + self.flaps_conf = self.generate_configuration(flaps_handle, context); + + self.flaps_demanded_angle = Self::demanded_flaps_fppu_angle_from_conf(self.flaps_conf); + self.slats_demanded_angle = Self::demanded_slats_fppu_angle_from_conf(self.flaps_conf); + self.flaps_feedback_angle = flaps_feedback.angle(); + self.slats_feedback_angle = slats_feedback.angle(); + } + + fn slat_flap_system_status_word(&self) -> Arinc429Word { + let mut word = Arinc429Word::new(0, SignStatus::NormalOperation); + + word.set_bit(11, false); + word.set_bit(12, false); + word.set_bit(13, false); + word.set_bit(14, false); + word.set_bit(15, false); + word.set_bit(16, false); + word.set_bit(17, self.flaps_conf == FlapsConf::Conf0); + word.set_bit( + 18, + self.flaps_conf == FlapsConf::Conf1 || self.flaps_conf == FlapsConf::Conf1F, + ); + word.set_bit(19, self.flaps_conf == FlapsConf::Conf2); + word.set_bit(20, self.flaps_conf == FlapsConf::Conf3); + word.set_bit(21, self.flaps_conf == FlapsConf::ConfFull); + word.set_bit(22, false); + word.set_bit(23, false); + word.set_bit(24, false); + word.set_bit(25, false); + word.set_bit(26, self.flaps_conf == FlapsConf::Conf1); + word.set_bit(27, false); + word.set_bit(28, true); + word.set_bit(29, true); + + word + } + + fn slat_flap_actual_position_word(&self) -> Arinc429Word { + let mut word = Arinc429Word::new(0, SignStatus::NormalOperation); + + word.set_bit(11, true); + word.set_bit( + 12, + self.slats_feedback_angle > Angle::new::(-5.0) + && self.slats_feedback_angle < Angle::new::(6.2), + ); + word.set_bit( + 13, + self.slats_feedback_angle > Angle::new::(210.4) + && self.slats_feedback_angle < Angle::new::(337.), + ); + word.set_bit( + 14, + self.slats_feedback_angle > Angle::new::(321.8) + && self.slats_feedback_angle < Angle::new::(337.), + ); + word.set_bit( + 15, + self.slats_feedback_angle > Angle::new::(327.4) + && self.slats_feedback_angle < Angle::new::(337.), + ); + word.set_bit(16, false); + word.set_bit(17, false); + word.set_bit(18, true); + word.set_bit( + 19, + self.flaps_feedback_angle > Angle::new::(-5.0) + && self.flaps_feedback_angle < Angle::new::(2.5), + ); + word.set_bit( + 20, + self.flaps_feedback_angle > Angle::new::(140.7) + && self.flaps_feedback_angle < Angle::new::(254.), + ); + word.set_bit( + 21, + self.flaps_feedback_angle > Angle::new::(163.7) + && self.flaps_feedback_angle < Angle::new::(254.), + ); + word.set_bit( + 22, + self.flaps_feedback_angle > Angle::new::(247.8) + && self.flaps_feedback_angle < Angle::new::(254.), + ); + word.set_bit( + 23, + self.flaps_feedback_angle > Angle::new::(250.) + && self.flaps_feedback_angle < Angle::new::(254.), + ); + word.set_bit(24, false); + word.set_bit(25, false); + word.set_bit(26, false); + word.set_bit(27, false); + word.set_bit(28, false); + word.set_bit(29, false); + + word + } + + fn slat_actual_position_word(&self) -> Arinc429Word { + Arinc429Word::new( + self.slats_feedback_angle.get::(), + SignStatus::NormalOperation, + ) + } + + fn flap_actual_position_word(&self) -> Arinc429Word { + Arinc429Word::new( + self.flaps_feedback_angle.get::(), + SignStatus::NormalOperation, + ) + } +} + +trait SlatFlapLane { + fn signal_demanded_angle(&self, surface_type: &str) -> Option; +} + +impl SlatFlapLane for SlatFlapControlComputer { + fn signal_demanded_angle(&self, surface_type: &str) -> Option { + match surface_type { + "FLAPS" + if Self::surface_movement_required( + self.flaps_demanded_angle, + self.flaps_feedback_angle, + ) => + { + Some(self.flaps_demanded_angle) + } + "SLATS" + if Self::surface_movement_required( + self.slats_demanded_angle, + self.slats_feedback_angle, + ) => + { + Some(self.slats_demanded_angle) + } + "FLAPS" | "SLATS" => None, + _ => panic!("Not a valid slat/flap surface"), + } + } +} + +impl SimulationElement for SlatFlapControlComputer { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.flaps_conf_index_id, self.flaps_conf as u8); + + writer.write(&self.slats_fppu_angle_id, self.slats_feedback_angle); + writer.write(&self.flaps_fppu_angle_id, self.flaps_feedback_angle); + + writer.write( + &self.slat_flap_system_status_word_id, + self.slat_flap_system_status_word(), + ); + writer.write( + &self.slat_flap_actual_position_word_id, + self.slat_flap_actual_position_word(), + ); + writer.write( + &self.slat_actual_position_word_id, + self.slat_actual_position_word(), + ); + writer.write( + &self.flap_actual_position_word_id, + self.flap_actual_position_word(), + ); + } +} + +pub struct SlatFlapComplex { + sfcc: SlatFlapControlComputer, + flaps_handle: FlapsHandle, +} + +impl SlatFlapComplex { + pub fn new(context: &mut InitContext) -> Self { + Self { + sfcc: SlatFlapControlComputer::new(context), + flaps_handle: FlapsHandle::new(context), + } + } + + pub fn update( + &mut self, + context: &UpdateContext, + flaps_feedback: &impl FeedbackPositionPickoffUnit, + slats_feedback: &impl FeedbackPositionPickoffUnit, + ) { + self.sfcc + .update(context, &self.flaps_handle, flaps_feedback, slats_feedback); + } + + pub fn flap_demand(&self) -> Option { + self.sfcc.signal_demanded_angle("FLAPS") + } + + pub fn slat_demand(&self) -> Option { + self.sfcc.signal_demanded_angle("SLATS") + } +} +impl SimulationElement for SlatFlapComplex { + fn accept(&mut self, visitor: &mut T) { + self.flaps_handle.accept(visitor); + self.sfcc.accept(visitor); + visitor.visit(self); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::Duration; + use systems::simulation::{ + test::{ReadByName, SimulationTestBed, TestBed, WriteByName}, + Aircraft, + }; + + use uom::si::{angular_velocity::degree_per_second, pressure::psi}; + + struct SlatFlapGear { + current_angle: Angle, + speed: AngularVelocity, + max_angle: Angle, + left_position_percent_id: VariableIdentifier, + right_position_percent_id: VariableIdentifier, + left_position_angle_id: VariableIdentifier, + right_position_angle_id: VariableIdentifier, + surface_type: String, + } + impl FeedbackPositionPickoffUnit for SlatFlapGear { + fn angle(&self) -> Angle { + self.current_angle + } + } + + impl SlatFlapGear { + const ANGLE_DELTA_DEGREE: f64 = 0.01; + + fn new( + context: &mut InitContext, + speed: AngularVelocity, + max_angle: Angle, + surface_type: &str, + ) -> Self { + Self { + current_angle: Angle::new::(0.), + speed, + max_angle, + + left_position_percent_id: context + .get_identifier(format!("LEFT_{}_POSITION_PERCENT", surface_type)), + right_position_percent_id: context + .get_identifier(format!("RIGHT_{}_POSITION_PERCENT", surface_type)), + + left_position_angle_id: context + .get_identifier(format!("LEFT_{}_ANGLE", surface_type)), + right_position_angle_id: context + .get_identifier(format!("RIGHT_{}_ANGLE", surface_type)), + + surface_type: surface_type.to_string(), + } + } + + fn update( + &mut self, + context: &UpdateContext, + sfcc: &impl SlatFlapLane, + hydraulic_pressure_left_side: Pressure, + hydraulic_pressure_right_side: Pressure, + ) { + if hydraulic_pressure_left_side.get::() > 1500. + || hydraulic_pressure_right_side.get::() > 1500. + { + if let Some(demanded_angle) = sfcc.signal_demanded_angle(&self.surface_type) { + let actual_minus_target_ffpu = demanded_angle - self.angle(); + + let fppu_angle = self.angle(); + + if actual_minus_target_ffpu.get::().abs() > Self::ANGLE_DELTA_DEGREE { + self.current_angle += Angle::new::( + actual_minus_target_ffpu.get::().signum() + * self.speed.get::() + * context.delta_as_secs_f64(), + ); + self.current_angle = self.current_angle.max(Angle::new::(0.)); + + let new_ffpu_angle = self.angle(); + // If demand was crossed between two frames: fixing to demand + if new_ffpu_angle > demanded_angle && fppu_angle < demanded_angle + || new_ffpu_angle < demanded_angle && fppu_angle > demanded_angle + { + self.current_angle = demanded_angle; + } + } + } + } + } + } + impl SimulationElement for SlatFlapGear { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write( + &self.left_position_percent_id, + self.current_angle / self.max_angle, + ); + writer.write( + &self.right_position_percent_id, + self.current_angle / self.max_angle, + ); + writer.write(&self.left_position_angle_id, self.current_angle); + writer.write(&self.right_position_angle_id, self.current_angle); + } + } + + struct A380FlapsTestAircraft { + green_hydraulic_pressure_id: VariableIdentifier, + blue_hydraulic_pressure_id: VariableIdentifier, + yellow_hydraulic_pressure_id: VariableIdentifier, + + flap_gear: SlatFlapGear, + slat_gear: SlatFlapGear, + slat_flap_complex: SlatFlapComplex, + + green_pressure: Pressure, + blue_pressure: Pressure, + yellow_pressure: Pressure, + } + + impl A380FlapsTestAircraft { + fn new(context: &mut InitContext) -> Self { + Self { + green_hydraulic_pressure_id: context + .get_identifier("HYD_GREEN_PRESSURE".to_owned()), + blue_hydraulic_pressure_id: context.get_identifier("HYD_BLUE_PRESSURE".to_owned()), + yellow_hydraulic_pressure_id: context + .get_identifier("HYD_YELLOW_PRESSURE".to_owned()), + + flap_gear: SlatFlapGear::new( + context, + AngularVelocity::new::(7.5), + Angle::new::(251.97), + "FLAPS", + ), + slat_gear: SlatFlapGear::new( + context, + AngularVelocity::new::(7.5), + Angle::new::(334.16), + "SLATS", + ), + + slat_flap_complex: SlatFlapComplex::new(context), + + green_pressure: Pressure::new::(0.), + blue_pressure: Pressure::new::(0.), + yellow_pressure: Pressure::new::(0.), + } + } + } + + impl Aircraft for A380FlapsTestAircraft { + fn update_after_power_distribution(&mut self, context: &UpdateContext) { + self.slat_flap_complex + .update(context, &self.flap_gear, &self.slat_gear); + self.flap_gear.update( + context, + &self.slat_flap_complex.sfcc, + self.green_pressure, + self.yellow_pressure, + ); + self.slat_gear.update( + context, + &self.slat_flap_complex.sfcc, + self.blue_pressure, + self.green_pressure, + ); + } + } + + impl SimulationElement for A380FlapsTestAircraft { + fn accept(&mut self, visitor: &mut T) { + self.slat_flap_complex.accept(visitor); + self.flap_gear.accept(visitor); + self.slat_gear.accept(visitor); + visitor.visit(self); + } + + fn read(&mut self, reader: &mut SimulatorReader) { + self.green_pressure = reader.read(&self.green_hydraulic_pressure_id); + self.blue_pressure = reader.read(&self.blue_hydraulic_pressure_id); + self.yellow_pressure = reader.read(&self.yellow_hydraulic_pressure_id); + } + } + + struct A380FlapsTestBed { + test_bed: SimulationTestBed, + } + + impl A380FlapsTestBed { + const HYD_TIME_STEP_MILLIS: u64 = 33; + + fn new() -> Self { + Self { + test_bed: SimulationTestBed::new(A380FlapsTestAircraft::new), + } + } + + fn run_one_tick(mut self) -> Self { + self.test_bed + .run_with_delta(Duration::from_millis(Self::HYD_TIME_STEP_MILLIS)); + self + } + + fn run_waiting_for(mut self, delta: Duration) -> Self { + self.test_bed.run_multiple_frames(delta); + self + } + + fn set_flaps_handle_position(mut self, pos: u8) -> Self { + self.write_by_name("FLAPS_HANDLE_INDEX", pos as f64); + self + } + + fn read_flaps_handle_position(&mut self) -> u8 { + self.read_by_name("FLAPS_HANDLE_INDEX") + } + + fn read_slat_flap_system_status_word(&mut self) -> Arinc429Word { + self.read_by_name("SFCC_SLAT_FLAP_SYSTEM_STATUS_WORD") + } + + fn read_slat_flap_actual_position_word(&mut self) -> Arinc429Word { + self.read_by_name("SFCC_SLAT_FLAP_ACTUAL_POSITION_WORD") + } + + fn set_indicated_airspeed(mut self, indicated_airspeed: f64) -> Self { + self.write_by_name("AIRSPEED INDICATED", indicated_airspeed); + self + } + + fn set_green_hyd_pressure(mut self) -> Self { + self.write_by_name("HYD_GREEN_PRESSURE", 2500.); + self + } + + fn set_blue_hyd_pressure(mut self) -> Self { + self.write_by_name("HYD_BLUE_PRESSURE", 2500.); + self + } + + fn set_yellow_hyd_pressure(mut self) -> Self { + self.write_by_name("HYD_YELLOW_PRESSURE", 2500.); + self + } + + fn get_flaps_demanded_angle(&self) -> f64 { + self.query(|a| { + a.slat_flap_complex + .sfcc + .flaps_demanded_angle + .get::() + }) + } + + fn get_slats_demanded_angle(&self) -> f64 { + self.query(|a| { + a.slat_flap_complex + .sfcc + .slats_demanded_angle + .get::() + }) + } + + fn get_flaps_conf(&self) -> FlapsConf { + self.query(|a| a.slat_flap_complex.sfcc.flaps_conf) + } + + fn get_flaps_fppu_feedback(&self) -> f64 { + self.query(|a| a.flap_gear.angle().get::()) + } + + fn get_slats_fppu_feedback(&self) -> f64 { + self.query(|a| a.slat_gear.angle().get::()) + } + + fn test_flap_conf( + &mut self, + handle_pos: u8, + flaps_demanded_angle: f64, + slats_demanded_angle: f64, + conf: FlapsConf, + angle_delta: f64, + ) { + assert_eq!(self.read_flaps_handle_position(), handle_pos); + assert!((self.get_flaps_demanded_angle() - flaps_demanded_angle).abs() < angle_delta); + assert!((self.get_slats_demanded_angle() - slats_demanded_angle).abs() < angle_delta); + assert_eq!(self.get_flaps_conf(), conf); + } + } + impl TestBed for A380FlapsTestBed { + type Aircraft = A380FlapsTestAircraft; + + fn test_bed(&self) -> &SimulationTestBed { + &self.test_bed + } + + fn test_bed_mut(&mut self) -> &mut SimulationTestBed { + &mut self.test_bed + } + } + + fn test_bed() -> A380FlapsTestBed { + A380FlapsTestBed::new() + } + + fn test_bed_with() -> A380FlapsTestBed { + test_bed() + } + + #[test] + fn flaps_simvars() { + let test_bed = test_bed_with().run_one_tick(); + + assert!(test_bed.contains_variable_with_name("LEFT_FLAPS_ANGLE")); + assert!(test_bed.contains_variable_with_name("RIGHT_FLAPS_ANGLE")); + assert!(test_bed.contains_variable_with_name("LEFT_FLAPS_POSITION_PERCENT")); + assert!(test_bed.contains_variable_with_name("RIGHT_FLAPS_POSITION_PERCENT")); + + assert!(test_bed.contains_variable_with_name("LEFT_SLATS_ANGLE")); + assert!(test_bed.contains_variable_with_name("RIGHT_SLATS_ANGLE")); + assert!(test_bed.contains_variable_with_name("LEFT_SLATS_POSITION_PERCENT")); + assert!(test_bed.contains_variable_with_name("RIGHT_SLATS_POSITION_PERCENT")); + + assert!(test_bed.contains_variable_with_name("FLAPS_CONF_INDEX")); + } + + #[test] + fn flaps_test_correct_bus_output_clean_config() { + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_yellow_hyd_pressure() + .set_blue_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(0) + .run_one_tick(); + + assert!(test_bed.read_slat_flap_system_status_word().get_bit(17)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(18)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(19)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(20)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(21)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(26)); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(10)); + + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(12)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(13)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(14)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(15)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(19)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(20)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(21)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(22)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(23)); + } + + #[test] + fn flaps_test_correct_bus_output_config_1() { + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_yellow_hyd_pressure() + .set_blue_hyd_pressure() + .set_indicated_airspeed(200.) + .set_flaps_handle_position(1) + .run_one_tick(); + + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(17)); + assert!(test_bed.read_slat_flap_system_status_word().get_bit(18)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(19)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(20)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(21)); + assert!(test_bed.read_slat_flap_system_status_word().get_bit(26)); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(31)); + + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(12)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(13)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(14)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(15)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(19)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(20)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(21)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(22)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(23)); + } + + #[test] + fn flaps_test_correct_bus_output_config_1_plus_f() { + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_yellow_hyd_pressure() + .set_blue_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(1) + .run_one_tick(); + + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(17)); + assert!(test_bed.read_slat_flap_system_status_word().get_bit(18)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(19)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(20)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(21)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(26)); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(31)); + + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(12)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(13)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(14)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(15)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(19)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(20)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(21)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(22)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(23)); + } + + #[test] + fn flaps_test_correct_bus_output_config_2() { + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_yellow_hyd_pressure() + .set_blue_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(2) + .run_one_tick(); + + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(17)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(18)); + assert!(test_bed.read_slat_flap_system_status_word().get_bit(19)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(20)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(21)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(26)); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(31)); + + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(12)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(13)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(14)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(15)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(19)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(20)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(21)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(22)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(23)); + } + + #[test] + fn flaps_test_correct_bus_output_config_3() { + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_yellow_hyd_pressure() + .set_blue_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(3) + .run_one_tick(); + + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(17)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(18)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(19)); + assert!(test_bed.read_slat_flap_system_status_word().get_bit(20)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(21)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(26)); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(31)); + + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(12)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(13)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(14)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(15)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(19)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(20)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(21)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(22)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(23)); + } + + #[test] + fn flaps_test_correct_bus_output_config_full() { + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_yellow_hyd_pressure() + .set_blue_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(4) + .run_one_tick(); + + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(17)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(18)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(19)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(20)); + assert!(test_bed.read_slat_flap_system_status_word().get_bit(21)); + assert!(!test_bed.read_slat_flap_system_status_word().get_bit(26)); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(45)); + + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(12)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(13)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(14)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(15)); + assert!(!test_bed.read_slat_flap_actual_position_word().get_bit(19)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(20)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(21)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(22)); + assert!(test_bed.read_slat_flap_actual_position_word().get_bit(23)); + } + + // Tests flaps configuration and angles for regular + // increasing handle transitions, i.e 0->1->2->3->4 in sequence + // below 100 knots + #[test] + fn flaps_test_regular_handle_increase_transitions_flaps_target_airspeed_below_100() { + let angle_delta: f64 = 0.1; + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(50.) + .run_one_tick(); + + test_bed.test_flap_conf(0, 0., 0., FlapsConf::Conf0, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + + test_bed.test_flap_conf(1, 120.22, 222.27, FlapsConf::Conf1F, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(2).run_one_tick(); + + test_bed.test_flap_conf(2, 145.51, 272.27, FlapsConf::Conf2, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); + + test_bed.test_flap_conf(3, 168.35, 272.27, FlapsConf::Conf3, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + + test_bed.test_flap_conf(4, 251.97, 334.16, FlapsConf::ConfFull, angle_delta); + } + + // Tests flaps configuration and angles for regular + // increasing handle transitions, i.e 0->1->2->3->4 in sequence + // above 100 knots + #[test] + fn flaps_test_regular_handle_increase_transitions_flaps_target_airspeed_above_100() { + let angle_delta: f64 = 0.1; + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(150.) + .run_one_tick(); + + test_bed.test_flap_conf(0, 0., 0., FlapsConf::Conf0, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + + test_bed.test_flap_conf(1, 0., 222.27, FlapsConf::Conf1, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(2).run_one_tick(); + + test_bed.test_flap_conf(2, 145.51, 272.27, FlapsConf::Conf2, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); + + test_bed.test_flap_conf(3, 168.35, 272.27, FlapsConf::Conf3, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + + test_bed.test_flap_conf(4, 251.97, 334.16, FlapsConf::ConfFull, angle_delta); + } + + //Tests regular transition 2->1 below and above 210 knots + #[test] + fn flaps_test_regular_handle_transition_pos_2_to_1() { + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(150.) + .set_flaps_handle_position(2) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf2); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1F); + + test_bed = test_bed + .set_indicated_airspeed(220.) + .set_flaps_handle_position(2) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf2); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1); + } + + //Tests transition between Conf1F to Conf1 above 210 knots + #[test] + fn flaps_test_regular_handle_transition_pos_1_to_1() { + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(50.) + .set_flaps_handle_position(1) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1F); + + test_bed = test_bed.set_indicated_airspeed(150.).run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1F); + + test_bed = test_bed.set_indicated_airspeed(220.).run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1); + } + + // Tests flaps configuration and angles for regular + // decreasing handle transitions, i.e 4->3->2->1->0 in sequence + // below 210 knots + #[test] + fn flaps_test_regular_decrease_handle_transition_flaps_target_airspeed_below_210() { + let angle_delta: f64 = 0.1; + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(150.) + .run_one_tick(); + + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + + test_bed.test_flap_conf(4, 251.97, 334.16, FlapsConf::ConfFull, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); + + test_bed.test_flap_conf(3, 168.35, 272.27, FlapsConf::Conf3, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(2).run_one_tick(); + + test_bed.test_flap_conf(2, 145.51, 272.27, FlapsConf::Conf2, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + + test_bed.test_flap_conf(1, 120.22, 222.27, FlapsConf::Conf1F, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(0).run_one_tick(); + + test_bed.test_flap_conf(0, 0., 0., FlapsConf::Conf0, angle_delta); + } + + // Tests flaps configuration and angles for regular + // decreasing handle transitions, i.e 4->3->2->1->0 in sequence + // above 210 knots + #[test] + fn flaps_test_regular_decrease_handle_transition_flaps_target_airspeed_above_210() { + let angle_delta: f64 = 0.1; + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(220.) + .run_one_tick(); + + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + + test_bed.test_flap_conf(4, 251.97, 334.16, FlapsConf::ConfFull, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); + + test_bed.test_flap_conf(3, 168.35, 272.27, FlapsConf::Conf3, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(2).run_one_tick(); + + test_bed.test_flap_conf(2, 145.51, 272.27, FlapsConf::Conf2, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + + test_bed.test_flap_conf(1, 0., 222.27, FlapsConf::Conf1, angle_delta); + + test_bed = test_bed.set_flaps_handle_position(0).run_one_tick(); + + test_bed.test_flap_conf(0, 0., 0., FlapsConf::Conf0, angle_delta); + } + + //The few tests that follow test irregular transitions + //e.g. direct from 0 to 3 or direct from 4 to 0. + //This is possible in the simulator, but obviously + //not possible in real life. An irregular transition from x = 2,3,4 + // to y = 0,1 should behave like a sequential transition. + #[test] + fn flaps_test_irregular_handle_transition_init_pos_0() { + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(0) + .run_one_tick(); + + test_bed = test_bed.set_flaps_handle_position(2).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf2); + + test_bed = test_bed.set_flaps_handle_position(0).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); + + test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); + + test_bed = test_bed.set_flaps_handle_position(0).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); + + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); + + test_bed = test_bed + .set_indicated_airspeed(110.) + .set_flaps_handle_position(0) + .run_one_tick(); + + test_bed = test_bed.set_flaps_handle_position(2).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf2); + + test_bed = test_bed.set_flaps_handle_position(0).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); + + test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); + + test_bed = test_bed.set_flaps_handle_position(0).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); + + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); + + test_bed = test_bed + .set_indicated_airspeed(220.) + .set_flaps_handle_position(0) + .run_one_tick(); + + test_bed = test_bed.set_flaps_handle_position(2).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf2); + + test_bed = test_bed.set_flaps_handle_position(0).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); + + test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); + + test_bed = test_bed.set_flaps_handle_position(0).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); + + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); + } + + #[test] + fn flaps_test_irregular_handle_transition_init_pos_1() { + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(1) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1F); + + test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1F); + + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); + + test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(110.) + .set_flaps_handle_position(1) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1); + + test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1F); + + test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(110.) + .set_flaps_handle_position(1) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1); + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1F); + + test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(220.) + .set_flaps_handle_position(1) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1); + + test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1); + + test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(220.) + .set_flaps_handle_position(1) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1); + + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1); + } + + #[test] + fn flaps_test_irregular_handle_transition_init_pos_2() { + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(2) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf2); + + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); + + test_bed = test_bed.set_flaps_handle_position(2).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf2); + + test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(2) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf2); + + test_bed = test_bed.set_flaps_handle_position(0).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); + + test_bed = test_bed.set_flaps_handle_position(2).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf2); + } + + #[test] + fn flaps_test_irregular_handle_transition_init_pos_3() { + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(150.) + .set_flaps_handle_position(3) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1F); + + test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); + + test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(220.) + .set_flaps_handle_position(3) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1); + + test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); + + test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(3) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); + + test_bed = test_bed.set_flaps_handle_position(0).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); + + test_bed = test_bed.set_flaps_handle_position(3).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); + } + + #[test] + fn flaps_test_irregular_handle_transition_init_pos_4() { + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(150.) + .set_flaps_handle_position(4) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1F); + + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); + + test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(220.) + .set_flaps_handle_position(4) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); + + test_bed = test_bed.set_flaps_handle_position(1).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1); + + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); + + test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(4) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); + + test_bed = test_bed.set_flaps_handle_position(0).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); + + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); + + test_bed = test_bed.set_flaps_handle_position(2).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf2); + + test_bed = test_bed.set_flaps_handle_position(4).run_one_tick(); + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::ConfFull); + } + + #[test] + fn flaps_test_movement_0_to_1f() { + let angle_delta = 0.2; + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(0) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); + + test_bed = test_bed + .set_flaps_handle_position(1) + .run_waiting_for(Duration::from_secs(20)); + + assert!( + (test_bed.get_flaps_fppu_feedback() - test_bed.get_flaps_demanded_angle()).abs() + <= angle_delta + ); + } + + #[test] + fn flaps_test_movement_1f_to_2() { + let angle_delta = 0.2; + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(1) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1F); + + test_bed = test_bed + .set_flaps_handle_position(2) + .run_waiting_for(Duration::from_secs(20)); + + assert!( + (test_bed.get_flaps_fppu_feedback() - test_bed.get_flaps_demanded_angle()).abs() + <= angle_delta + ); + } + + #[test] + fn flaps_test_movement_2_to_3() { + let angle_delta = 0.2; + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(2) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf2); + + test_bed = test_bed + .set_flaps_handle_position(3) + .run_waiting_for(Duration::from_secs(30)); + + assert!( + (test_bed.get_flaps_fppu_feedback() - test_bed.get_flaps_demanded_angle()).abs() + <= angle_delta + ); + } + + #[test] + fn flaps_test_movement_3_to_full() { + let angle_delta = 0.2; + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(3) + .run_waiting_for(Duration::from_secs(20)); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(20)); + + assert!( + (test_bed.get_flaps_fppu_feedback() - test_bed.get_flaps_demanded_angle()).abs() + <= angle_delta + ); + } + + #[test] + fn slats_test_movement_0_to_1f() { + let angle_delta = 0.2; + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(0) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); + + test_bed = test_bed + .set_flaps_handle_position(1) + .run_waiting_for(Duration::from_secs(30)); + + assert!( + (test_bed.get_slats_fppu_feedback() - test_bed.get_slats_demanded_angle()).abs() + <= angle_delta + ); + } + + #[test] + fn slats_and_flaps_test_movement_0_to_1() { + let angle_delta = 0.2; + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_blue_hyd_pressure() + .set_indicated_airspeed(220.) + .set_flaps_handle_position(0) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf0); + + test_bed = test_bed + .set_flaps_handle_position(1) + .run_waiting_for(Duration::from_secs(30)); + + assert!( + (test_bed.get_flaps_fppu_feedback() - test_bed.get_flaps_demanded_angle()).abs() + <= angle_delta + ); + assert!( + (test_bed.get_slats_fppu_feedback() - test_bed.get_slats_demanded_angle()).abs() + <= angle_delta + ); + } + + #[test] + fn slats_test_movement_1f_to_2() { + let angle_delta = 0.2; + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(1) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf1F); + + test_bed = test_bed + .set_flaps_handle_position(2) + .run_waiting_for(Duration::from_secs(40)); + + assert!( + (test_bed.get_slats_fppu_feedback() - test_bed.get_slats_demanded_angle()).abs() + <= angle_delta + ); + } + + #[test] + fn slats_test_movement_2_to_3() { + let angle_delta = 0.2; + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(2) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf2); + + test_bed = test_bed + .set_flaps_handle_position(3) + .run_waiting_for(Duration::from_secs(40)); + + assert!( + (test_bed.get_slats_fppu_feedback() - test_bed.get_slats_demanded_angle()).abs() + <= angle_delta + ); + } + + #[test] + fn slats_test_movement_3_to_full() { + let angle_delta = 0.2; + let mut test_bed = test_bed_with() + .set_green_hyd_pressure() + .set_indicated_airspeed(0.) + .set_flaps_handle_position(3) + .run_one_tick(); + + assert_eq!(test_bed.get_flaps_conf(), FlapsConf::Conf3); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(50)); + + assert!( + (test_bed.get_slats_fppu_feedback() - test_bed.get_slats_demanded_angle()).abs() + <= angle_delta + ); + } +} diff --git a/src/systems/a380_systems/src/hydraulic/mod.rs b/src/systems/a380_systems/src/hydraulic/mod.rs new file mode 100644 index 000000000000..8e774727740b --- /dev/null +++ b/src/systems/a380_systems/src/hydraulic/mod.rs @@ -0,0 +1,10858 @@ +use nalgebra::Vector3; + +use std::time::Duration; +use uom::si::{ + acceleration::meter_per_second_squared, + angle::{degree, radian}, + angular_velocity::{radian_per_second, revolution_per_minute}, + electric_current::ampere, + f64::*, + length::meter, + mass::kilogram, + pressure::psi, + ratio::{percent, ratio}, + velocity::knot, + volume::{cubic_inch, gallon, liter}, + volume_rate::gallon_per_second, +}; + +use systems::{ + engine::Engine, + hydraulic::{ + aerodynamic_model::AerodynamicModel, + brake_circuit::{ + AutobrakeDecelerationGovernor, AutobrakeMode, AutobrakePanel, BrakeCircuit, + BrakeCircuitController, + }, + electrical_generator::{GeneratorControlUnit, HydraulicGeneratorMotor}, + flap_slat::FlapSlatAssembly, + landing_gear::{GearGravityExtension, GearSystemController, HydraulicGearSystem}, + linear_actuator::{ + Actuator, BoundedLinearLength, HydraulicAssemblyController, + HydraulicLinearActuatorAssembly, HydraulicLocking, LinearActuatedRigidBodyOnHingeAxis, + LinearActuator, LinearActuatorCharacteristics, LinearActuatorMode, + }, + nose_steering::{ + Pushback, SteeringActuator, SteeringAngleLimiter, SteeringController, + SteeringRatioToAngle, + }, + pumps::PumpCharacteristics, + trimmable_horizontal_stabilizer::{ + ManualPitchTrimController, PitchTrimActuatorController, + TrimmableHorizontalStabilizerAssembly, + }, + ElectricPump, EngineDrivenPump, HydraulicCircuit, HydraulicCircuitController, + HydraulicPressureSensors, PowerTransferUnit, PowerTransferUnitCharacteristics, + PowerTransferUnitController, PressureSwitch, PressureSwitchType, PumpController, + RamAirTurbine, RamAirTurbineController, Reservoir, + }, + landing_gear::{GearSystemSensors, LandingGearControlInterfaceUnitSet}, + overhead::{ + AutoOffFaultPushButton, AutoOnFaultPushButton, MomentaryOnPushButton, MomentaryPushButton, + }, + shared::{ + interpolation, + low_pass_filter::LowPassFilter, + random_from_normal_distribution, random_from_range, + update_iterator::{FixedStepLoop, MaxStepLoop}, + AdirsDiscreteOutputs, DelayedFalseLogicGate, DelayedPulseTrueLogicGate, + DelayedTrueLogicGate, ElectricalBusType, ElectricalBuses, EmergencyElectricalRatPushButton, + EmergencyElectricalState, EmergencyGeneratorPower, EngineFirePushButtons, GearWheel, + HydraulicColor, HydraulicGeneratorControlUnit, LandingGearHandle, LgciuInterface, + LgciuWeightOnWheels, ReservoirAirPressure, SectionPressure, + }, + simulation::{ + InitContext, Read, Reader, SimulationElement, SimulationElementVisitor, SimulatorReader, + SimulatorWriter, StartState, UpdateContext, VariableIdentifier, Write, + }, +}; + +mod flaps_computer; +use flaps_computer::SlatFlapComplex; + +#[cfg(test)] +use systems::hydraulic::PressureSwitchState; + +struct A380HydraulicReservoirFactory {} +impl A380HydraulicReservoirFactory { + fn new_green_reservoir(context: &mut InitContext) -> Reservoir { + let reservoir_offset_when_gear_up = if context.start_gear_down() { + Volume::new::(0.) + } else { + Volume::new::(-1.3) + }; + + Reservoir::new( + context, + HydraulicColor::Green, + Volume::new::(23.), + Volume::new::(18.), + Volume::new::(3.6) + reservoir_offset_when_gear_up, + vec![PressureSwitch::new( + Pressure::new::(25.), + Pressure::new::(22.), + PressureSwitchType::Relative, + )], + Volume::new::(3.), + ) + } + + fn new_blue_reservoir(context: &mut InitContext) -> Reservoir { + Reservoir::new( + context, + HydraulicColor::Blue, + Volume::new::(10.), + Volume::new::(8.), + Volume::new::(1.56), + vec![PressureSwitch::new( + Pressure::new::(25.), + Pressure::new::(22.), + PressureSwitchType::Relative, + )], + Volume::new::(2.), + ) + } + + fn new_yellow_reservoir(context: &mut InitContext) -> Reservoir { + Reservoir::new( + context, + HydraulicColor::Yellow, + Volume::new::(20.), + Volume::new::(18.), + Volume::new::(3.6), + vec![PressureSwitch::new( + Pressure::new::(25.), + Pressure::new::(22.), + PressureSwitchType::Relative, + )], + Volume::new::(3.), + ) + } +} + +pub struct A380HydraulicCircuitFactory {} +impl A380HydraulicCircuitFactory { + const MIN_PRESS_EDP_SECTION_LO_HYST: f64 = 1740.0; + const MIN_PRESS_EDP_SECTION_HI_HYST: f64 = 2200.0; + const MIN_PRESS_BLUE_ELEC_PUMP_SECTION_LO_HYST: f64 = 1450.0; + const MIN_PRESS_BLUE_ELEC_PUMP_SECTION_HI_HYST: f64 = 1750.0; + const MIN_PRESS_PRESSURISED_LO_HYST: f64 = 1450.0; + const MIN_PRESS_PRESSURISED_HI_HYST: f64 = 1750.0; + + const YELLOW_GREEN_BLUE_PUMPS_INDEXES: usize = 0; + + const HYDRAULIC_TARGET_PRESSURE_PSI: f64 = 3000.; + + pub fn new_green_circuit(context: &mut InitContext) -> HydraulicCircuit { + let reservoir = A380HydraulicReservoirFactory::new_green_reservoir(context); + HydraulicCircuit::new( + context, + HydraulicColor::Green, + 1, + Ratio::new::(100.), + Volume::new::(10.), + reservoir, + Pressure::new::(Self::MIN_PRESS_PRESSURISED_LO_HYST), + Pressure::new::(Self::MIN_PRESS_PRESSURISED_HI_HYST), + Pressure::new::(Self::MIN_PRESS_EDP_SECTION_LO_HYST), + Pressure::new::(Self::MIN_PRESS_EDP_SECTION_HI_HYST), + true, + false, + false, + Pressure::new::(Self::HYDRAULIC_TARGET_PRESSURE_PSI), + ) + } + + pub fn new_blue_circuit(context: &mut InitContext) -> HydraulicCircuit { + let reservoir = A380HydraulicReservoirFactory::new_blue_reservoir(context); + HydraulicCircuit::new( + context, + HydraulicColor::Blue, + 1, + Ratio::new::(100.), + Volume::new::(8.), + reservoir, + Pressure::new::(Self::MIN_PRESS_PRESSURISED_LO_HYST), + Pressure::new::(Self::MIN_PRESS_PRESSURISED_HI_HYST), + Pressure::new::(Self::MIN_PRESS_BLUE_ELEC_PUMP_SECTION_LO_HYST), + Pressure::new::(Self::MIN_PRESS_BLUE_ELEC_PUMP_SECTION_HI_HYST), + false, + false, + false, + Pressure::new::(Self::HYDRAULIC_TARGET_PRESSURE_PSI), + ) + } + + pub fn new_yellow_circuit(context: &mut InitContext) -> HydraulicCircuit { + let reservoir = A380HydraulicReservoirFactory::new_yellow_reservoir(context); + HydraulicCircuit::new( + context, + HydraulicColor::Yellow, + 1, + Ratio::new::(100.), + Volume::new::(10.), + reservoir, + Pressure::new::(Self::MIN_PRESS_PRESSURISED_LO_HYST), + Pressure::new::(Self::MIN_PRESS_PRESSURISED_HI_HYST), + Pressure::new::(Self::MIN_PRESS_EDP_SECTION_LO_HYST), + Pressure::new::(Self::MIN_PRESS_EDP_SECTION_HI_HYST), + false, + true, + false, + Pressure::new::(Self::HYDRAULIC_TARGET_PRESSURE_PSI), + ) + } +} + +struct A380CargoDoorFactory {} +impl A380CargoDoorFactory { + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.05; + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.; + const FLOW_CONTROL_FORCE_GAIN: f64 = 200000.; + + fn a380_cargo_door_actuator( + bounded_linear_length: &impl BoundedLinearLength, + ) -> LinearActuator { + LinearActuator::new( + bounded_linear_length, + 2, + Length::new::(0.04422), + Length::new::(0.03366), + VolumeRate::new::(0.01), + 600000., + 15000., + 500., + 1000000., + Duration::from_millis(100), + [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], + [0., 0.2, 0.21, 0.79, 0.8, 1.], + Self::FLOW_CONTROL_PROPORTIONAL_GAIN, + Self::FLOW_CONTROL_INTEGRAL_GAIN, + Self::FLOW_CONTROL_FORCE_GAIN, + false, + false, + None, + ) + } + + /// Builds a cargo door body for A380-800 + fn a380_cargo_door_body(is_locked: bool) -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(100. / 1000., 1855. / 1000., 2025. / 1000.); + let cg_offset = Vector3::new(0., -size[1] / 2., 0.); + + let control_arm = Vector3::new(-0.1597, -0.1614, 0.); + let anchor = Vector3::new(-0.7596, -0.086, 0.); + let axis_direction = Vector3::new(0., 0., 1.); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(130.), + size, + cg_offset, + cg_offset, + control_arm, + anchor, + Angle::new::(-23.), + Angle::new::(136.), + Angle::new::(-23.), + 100., + is_locked, + axis_direction, + ) + } + + /// Builds a cargo door assembly consisting of the door physical rigid body and the hydraulic actuator connected + /// to it + fn a380_cargo_door_assembly() -> HydraulicLinearActuatorAssembly<1> { + let cargo_door_body = Self::a380_cargo_door_body(true); + let cargo_door_actuator = Self::a380_cargo_door_actuator(&cargo_door_body); + HydraulicLinearActuatorAssembly::new([cargo_door_actuator], cargo_door_body) + } + + fn new_a380_cargo_door(context: &mut InitContext, id: &str) -> CargoDoor { + let assembly = Self::a380_cargo_door_assembly(); + CargoDoor::new( + context, + id, + assembly, + Self::new_a380_cargo_door_aero_model(), + ) + } + + fn new_a380_cargo_door_aero_model() -> AerodynamicModel { + let body = Self::a380_cargo_door_body(false); + AerodynamicModel::new( + &body, + Some(Vector3::new(1., 0., 0.)), + Some(Vector3::new(0., 0., 1.)), + Some(Vector3::new(1., 0., 0.)), + Ratio::new::(1.), + ) + } +} + +struct A380AileronFactory {} +impl A380AileronFactory { + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.35; + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.; + const FLOW_CONTROL_FORCE_GAIN: f64 = 450000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 3500000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 1.; + + fn a380_aileron_actuator(bounded_linear_length: &impl BoundedLinearLength) -> LinearActuator { + let actuator_characteristics = LinearActuatorCharacteristics::new( + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING / 3., + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING, + VolumeRate::new::(0.055), + Ratio::new::(Self::MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + // Aileron actuator real data: + // Max force of 4700DaN @ 3000psi. Max flow 3.302 US gal/min thus 0.055033333 gal/s + // This gives a 0.00227225 squared meter of piston surface + // This gives piston diameter of 0.0537878 meters + // We use 0 as rod diameter as this is a symmetrical actuator so same surface each side + LinearActuator::new( + bounded_linear_length, + 1, + Length::new::(0.0537878), + Length::new::(0.), + actuator_characteristics.max_flow(), + 80000., + 1500., + 5000., + actuator_characteristics.slow_damping(), + Duration::from_millis(300), + [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], + [0., 0.2, 0.21, 0.79, 0.8, 1.], + Self::FLOW_CONTROL_PROPORTIONAL_GAIN, + Self::FLOW_CONTROL_INTEGRAL_GAIN, + Self::FLOW_CONTROL_FORCE_GAIN, + false, + false, + None, + ) + } + + /// Builds an aileron control surface body for A380-800 + fn a380_aileron_body(init_drooped_down: bool) -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(3.325, 0.16, 0.58); + + // CG at half the size + let cg_offset = Vector3::new(0., 0., -0.5 * size[2]); + let aero_center = Vector3::new(0., 0., -0.4 * size[2]); + + let control_arm = Vector3::new(0., -0.0525, 0.); + let anchor = Vector3::new(0., -0.0525, 0.33); + + let init_position = if init_drooped_down { + Angle::new::(-25.) + } else { + Angle::new::(0.) + }; + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(24.65), + size, + cg_offset, + aero_center, + control_arm, + anchor, + Angle::new::(-25.), + Angle::new::(50.), + init_position, + 1., + false, + Vector3::new(1., 0., 0.), + ) + } + + /// Builds an aileron assembly consisting of the aileron physical rigid body and two hydraulic actuators connected + /// to it + fn a380_aileron_assembly(init_drooped_down: bool) -> HydraulicLinearActuatorAssembly<2> { + let aileron_body = Self::a380_aileron_body(init_drooped_down); + + let aileron_actuator_outward = Self::a380_aileron_actuator(&aileron_body); + let aileron_actuator_inward = Self::a380_aileron_actuator(&aileron_body); + + HydraulicLinearActuatorAssembly::new( + [aileron_actuator_outward, aileron_actuator_inward], + aileron_body, + ) + } + + fn new_aileron(context: &mut InitContext, id: ActuatorSide) -> AileronAssembly { + let init_drooped_down = !context.is_in_flight(); + let assembly = Self::a380_aileron_assembly(init_drooped_down); + AileronAssembly::new(context, id, assembly, Self::new_a380_aileron_aero_model()) + } + + fn new_a380_aileron_aero_model() -> AerodynamicModel { + let body = Self::a380_aileron_body(true); + + // Aerodynamic object has a little rotation from horizontal direction so that at X° + // of wing AOA the aileron gets some X°+Y° AOA as the overwing pressure sucks the aileron up + AerodynamicModel::new( + &body, + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., 0.208, 0.978)), + Some(Vector3::new(0., 0.978, -0.208)), + Ratio::new::(1.), + ) + } +} + +struct A380SpoilerFactory {} +impl A380SpoilerFactory { + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.15; + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 2.; + const FLOW_CONTROL_FORCE_GAIN: f64 = 450000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 400000.; + + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 3.; + + fn a380_spoiler_actuator(bounded_linear_length: &impl BoundedLinearLength) -> LinearActuator { + let actuator_characteristics = LinearActuatorCharacteristics::new( + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING / 5., + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING, + VolumeRate::new::(0.03), + Ratio::new::(Self::MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + LinearActuator::new( + bounded_linear_length, + 1, + Length::new::(0.03), + Length::new::(0.), + actuator_characteristics.max_flow(), + 80000., + 1500., + 5000., + actuator_characteristics.slow_damping(), + Duration::from_millis(300), + [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], + [0., 0.2, 0.21, 0.79, 0.8, 1.], + Self::FLOW_CONTROL_PROPORTIONAL_GAIN, + Self::FLOW_CONTROL_INTEGRAL_GAIN, + Self::FLOW_CONTROL_FORCE_GAIN, + false, + true, + Some(( + AngularVelocity::new::(-10000.), + AngularVelocity::new::(0.), + )), + ) + } + + /// Builds a spoiler control surface body for A380-800 + fn a380_spoiler_body() -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(1.785, 0.1, 0.685); + let cg_offset = Vector3::new(0., 0., -0.5 * size[2]); + let aero_center = Vector3::new(0., 0., -0.4 * size[2]); + + let control_arm = Vector3::new(0., -0.067 * size[2], -0.26 * size[2]); + let anchor = Vector3::new(0., -0.26 * size[2], 0.26 * size[2]); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(16.), + size, + cg_offset, + aero_center, + control_arm, + anchor, + Angle::new::(-10.), + Angle::new::(40.), + Angle::new::(-10.), + 50., + false, + Vector3::new(1., 0., 0.), + ) + } + + /// Builds a spoiler assembly consisting of the spoiler physical rigid body and one hydraulic actuator + fn a380_spoiler_assembly() -> HydraulicLinearActuatorAssembly<1> { + let spoiler_body = Self::a380_spoiler_body(); + + let spoiler_actuator = Self::a380_spoiler_actuator(&spoiler_body); + + HydraulicLinearActuatorAssembly::new([spoiler_actuator], spoiler_body) + } + + fn new_a380_spoiler_group(context: &mut InitContext, id: ActuatorSide) -> SpoilerGroup { + let spoiler_1 = Self::new_a380_spoiler_element(context, id, 1); + let spoiler_2 = Self::new_a380_spoiler_element(context, id, 2); + let spoiler_3 = Self::new_a380_spoiler_element(context, id, 3); + let spoiler_4 = Self::new_a380_spoiler_element(context, id, 4); + let spoiler_5 = Self::new_a380_spoiler_element(context, id, 5); + + SpoilerGroup::new([spoiler_1, spoiler_2, spoiler_3, spoiler_4, spoiler_5]) + } + + fn new_a380_spoiler_element( + context: &mut InitContext, + id: ActuatorSide, + id_number: usize, + ) -> SpoilerElement { + let assembly = Self::a380_spoiler_assembly(); + SpoilerElement::new( + context, + id, + id_number, + assembly, + Self::new_a380_spoiler_aero_model(), + ) + } + + fn new_a380_spoiler_aero_model() -> AerodynamicModel { + let body = Self::a380_spoiler_body(); + + // Lift vector and normal are rotated 10° to acount for air supposedly following + // wing profile that is 10° from horizontal + // It means that with headwind and spoiler retracted (-10°), spoiler generates no lift + AerodynamicModel::new( + &body, + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., -0.174, 0.985)), + Some(Vector3::new(0., 0.985, 0.174)), + Ratio::new::(1.), + ) + } +} + +struct A380ElevatorFactory {} +impl A380ElevatorFactory { + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 1.; + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.; + const FLOW_CONTROL_FORCE_GAIN: f64 = 450000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 15000000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 1.; + + fn a380_elevator_actuator(bounded_linear_length: &impl BoundedLinearLength) -> LinearActuator { + let actuator_characteristics = LinearActuatorCharacteristics::new( + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING / 5., + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING, + VolumeRate::new::(0.029), + Ratio::new::(Self::MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + LinearActuator::new( + bounded_linear_length, + 1, + Length::new::(0.0407), + Length::new::(0.), + actuator_characteristics.max_flow(), + 80000., + 1500., + 20000., + actuator_characteristics.slow_damping(), + Duration::from_millis(300), + [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], + [0., 0.2, 0.21, 0.79, 0.8, 1.], + Self::FLOW_CONTROL_PROPORTIONAL_GAIN, + Self::FLOW_CONTROL_INTEGRAL_GAIN, + Self::FLOW_CONTROL_FORCE_GAIN, + false, + false, + None, + ) + } + + /// Builds an aileron control surface body for A380-800 + fn a380_elevator_body(init_drooped_down: bool) -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(6., 0.405, 1.125); + let cg_offset = Vector3::new(0., 0., -0.5 * size[2]); + let aero_center = Vector3::new(0., 0., -0.3 * size[2]); + + let control_arm = Vector3::new(0., -0.091, 0.); + let anchor = Vector3::new(0., -0.091, 0.41); + + let init_position = if init_drooped_down { + Angle::new::(-11.5) + } else { + Angle::new::(0.) + }; + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(58.6), + size, + cg_offset, + aero_center, + control_arm, + anchor, + Angle::new::(-11.5), + Angle::new::(27.5), + init_position, + 100., + false, + Vector3::new(1., 0., 0.), + ) + } + + /// Builds an aileron assembly consisting of the aileron physical rigid body and two hydraulic actuators connected + /// to it + fn a380_elevator_assembly(init_drooped_down: bool) -> HydraulicLinearActuatorAssembly<2> { + let elevator_body = Self::a380_elevator_body(init_drooped_down); + + let elevator_actuator_outboard = Self::a380_elevator_actuator(&elevator_body); + let elevator_actuator_inbord = Self::a380_elevator_actuator(&elevator_body); + + HydraulicLinearActuatorAssembly::new( + [elevator_actuator_outboard, elevator_actuator_inbord], + elevator_body, + ) + } + + fn new_elevator(context: &mut InitContext, id: ActuatorSide) -> ElevatorAssembly { + let init_drooped_down = !context.is_in_flight(); + let assembly = Self::a380_elevator_assembly(init_drooped_down); + ElevatorAssembly::new(context, id, assembly, Self::new_a380_elevator_aero_model()) + } + + fn new_a380_elevator_aero_model() -> AerodynamicModel { + let body = Self::a380_elevator_body(true); + AerodynamicModel::new( + &body, + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., 0., 1.)), + Some(Vector3::new(0., 1., 0.)), + Ratio::new::(0.8), + ) + } +} + +struct A380RudderFactory {} +impl A380RudderFactory { + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 1.5; + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 2.; + const FLOW_CONTROL_FORCE_GAIN: f64 = 350000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 1000000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 1.; + + fn a380_rudder_actuator(bounded_linear_length: &impl BoundedLinearLength) -> LinearActuator { + let actuator_characteristics = LinearActuatorCharacteristics::new( + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING / 4., + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING, + VolumeRate::new::(0.0792), + Ratio::new::(Self::MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + LinearActuator::new( + bounded_linear_length, + 1, + Length::new::(0.06), + Length::new::(0.), + actuator_characteristics.max_flow(), + 80000., + 1500., + 10000., + actuator_characteristics.slow_damping(), + Duration::from_millis(300), + [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], + [0., 0.2, 0.21, 0.79, 0.8, 1.], + Self::FLOW_CONTROL_PROPORTIONAL_GAIN, + Self::FLOW_CONTROL_INTEGRAL_GAIN, + Self::FLOW_CONTROL_FORCE_GAIN, + false, + false, + None, + ) + } + + /// Builds an aileron control surface body for A380-800 + fn a380_rudder_body(init_at_center: bool) -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(0.42, 6.65, 1.8); + let cg_offset = Vector3::new(0., 0.5 * size[1], -0.5 * size[2]); + let aero_center = Vector3::new(0., 0.5 * size[1], -0.3 * size[2]); + + let control_arm = Vector3::new(-0.144, 0., 0.); + let anchor = Vector3::new(-0.144, 0., 0.50); + + let randomized_init_position_angle_degree = if init_at_center { + 0. + } else { + random_from_range(-15., 15.) + }; + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(95.), + size, + cg_offset, + aero_center, + control_arm, + anchor, + Angle::new::(-25.), + Angle::new::(50.), + Angle::new::(randomized_init_position_angle_degree), + 100., + false, + Vector3::new(0., 1., 0.), + ) + } + + /// Builds an aileron assembly consisting of the aileron physical rigid body and two hydraulic actuators connected + /// to it + fn a380_rudder_assembly(init_at_center: bool) -> HydraulicLinearActuatorAssembly<3> { + let rudder_body = Self::a380_rudder_body(init_at_center); + + let rudder_actuator_green = Self::a380_rudder_actuator(&rudder_body); + let rudder_actuator_blue = Self::a380_rudder_actuator(&rudder_body); + let rudder_actuator_yellow = Self::a380_rudder_actuator(&rudder_body); + + HydraulicLinearActuatorAssembly::new( + [ + rudder_actuator_green, + rudder_actuator_blue, + rudder_actuator_yellow, + ], + rudder_body, + ) + } + + fn new_rudder(context: &mut InitContext) -> RudderAssembly { + let init_at_center = context.start_state() == StartState::Taxi + || context.start_state() == StartState::Runway + || context.is_in_flight(); + + let assembly = Self::a380_rudder_assembly(init_at_center); + RudderAssembly::new(context, assembly, Self::new_a380_rudder_aero_model()) + } + + fn new_a380_rudder_aero_model() -> AerodynamicModel { + let body = Self::a380_rudder_body(true); + AerodynamicModel::new( + &body, + Some(Vector3::new(1., 0., 0.)), + Some(Vector3::new(0., 0., 1.)), + Some(Vector3::new(1., 0., 0.)), + Ratio::new::(0.4), + ) + } +} + +struct A380GearDoorFactory {} +impl A380GearDoorFactory { + fn a380_nose_gear_door_aerodynamics() -> AerodynamicModel { + // Faking the single door by only considering right door aerodynamics. + // Will work with headwind, but will cause strange behaviour with massive crosswind. + AerodynamicModel::new( + &Self::a380_nose_gear_door_body(), + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., -0.2, 1.)), + Some(Vector3::new(0., -1., -0.2)), + Ratio::new::(0.7), + ) + } + + fn a380_left_gear_door_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a380_left_gear_door_body(), + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., -0.1, 1.)), + Some(Vector3::new(0., 1., 0.1)), + Ratio::new::(0.7), + ) + } + + fn a380_right_gear_door_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a380_right_gear_door_body(), + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., -0.1, 1.)), + Some(Vector3::new(0., 1., 0.1)), + Ratio::new::(0.7), + ) + } + + fn a380_nose_gear_door_actuator( + bounded_linear_length: &impl BoundedLinearLength, + ) -> LinearActuator { + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.; + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.15; + const FLOW_CONTROL_FORCE_GAIN: f64 = 200000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 28000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 3.; + + let actuator_characteristics = LinearActuatorCharacteristics::new( + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 0.98, + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 1.02, + VolumeRate::new::(0.027), + Ratio::new::(MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + LinearActuator::new( + bounded_linear_length, + 1, + Length::new::(0.0378), + Length::new::(0.023), + actuator_characteristics.max_flow(), + 20000., + 5000., + 2000., + actuator_characteristics.slow_damping(), + Duration::from_millis(100), + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], + [0., 0.15, 0.16, 0.84, 0.85, 1.], + FLOW_CONTROL_PROPORTIONAL_GAIN, + FLOW_CONTROL_INTEGRAL_GAIN, + FLOW_CONTROL_FORCE_GAIN, + true, + false, + None, + ) + } + + fn a380_main_gear_door_actuator( + bounded_linear_length: &impl BoundedLinearLength, + ) -> LinearActuator { + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.; + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.7; + const FLOW_CONTROL_FORCE_GAIN: f64 = 200000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 30000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 5.; + + let actuator_characteristics = LinearActuatorCharacteristics::new( + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 0.98, + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 1.02, + VolumeRate::new::(0.09), + Ratio::new::(MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + LinearActuator::new( + bounded_linear_length, + 1, + Length::new::(0.055), + Length::new::(0.03), + actuator_characteristics.max_flow(), + 200000., + 2500., + 2000., + actuator_characteristics.slow_damping(), + Duration::from_millis(100), + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], + [0., 0.07, 0.08, 0.9, 0.91, 1.], + FLOW_CONTROL_PROPORTIONAL_GAIN, + FLOW_CONTROL_INTEGRAL_GAIN, + FLOW_CONTROL_FORCE_GAIN, + true, + false, + None, + ) + } + + fn a380_left_gear_door_body() -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(-1.73, 0.02, 1.7); + let cg_offset = Vector3::new(2. / 3. * size[0], 0.1, 0.); + + let control_arm = Vector3::new(-0.76, 0., 0.); + let anchor = Vector3::new(-0.19, 0.23, 0.); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(50.), + size, + cg_offset, + cg_offset, + control_arm, + anchor, + Angle::new::(0.), + Angle::new::(85.), + Angle::new::(0.), + 150., + true, + Vector3::new(0., 0., 1.), + ) + } + + fn a380_right_gear_door_body() -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(1.73, 0.02, 1.7); + let cg_offset = Vector3::new(2. / 3. * size[0], 0.1, 0.); + + let control_arm = Vector3::new(0.76, 0., 0.); + let anchor = Vector3::new(0.19, 0.23, 0.); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(50.), + size, + cg_offset, + cg_offset, + control_arm, + anchor, + Angle::new::(-85.), + Angle::new::(85.), + Angle::new::(0.), + 150., + true, + Vector3::new(0., 0., 1.), + ) + } + + fn a380_nose_gear_door_body() -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(0.4, 0.02, 1.5); + let cg_offset = Vector3::new(-0.5 * size[0], 0., 0.); + + let control_arm = Vector3::new(-0.1465, 0., 0.); + let anchor = Vector3::new(-0.1465, 0.40, 0.); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(40.), + size, + cg_offset, + cg_offset, + control_arm, + anchor, + Angle::new::(0.), + Angle::new::(85.), + Angle::new::(0.), + 150., + true, + Vector3::new(0., 0., 1.), + ) + } + + fn a380_gear_door_assembly(wheel_id: GearWheel) -> HydraulicLinearActuatorAssembly<1> { + let gear_door_body = match wheel_id { + GearWheel::NOSE => Self::a380_nose_gear_door_body(), + GearWheel::LEFT => Self::a380_left_gear_door_body(), + GearWheel::RIGHT => Self::a380_right_gear_door_body(), + }; + let gear_door_actuator = match wheel_id { + GearWheel::NOSE => Self::a380_nose_gear_door_actuator(&gear_door_body), + GearWheel::LEFT | GearWheel::RIGHT => { + Self::a380_main_gear_door_actuator(&gear_door_body) + } + }; + + HydraulicLinearActuatorAssembly::new([gear_door_actuator], gear_door_body) + } +} + +struct A380GearFactory {} +impl A380GearFactory { + fn a380_nose_gear_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a380_nose_gear_body(true), + Some(Vector3::new(0., 0., 1.)), + None, + None, + Ratio::new::(0.25), + ) + } + + fn a380_right_gear_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a380_right_gear_body(true), + Some(Vector3::new(0., 0., 1.)), + Some(Vector3::new(0.3, 0., 1.)), + Some(Vector3::new(1., 0., -0.3)), + Ratio::new::(0.7), + ) + } + + fn a380_left_gear_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a380_left_gear_body(true), + Some(Vector3::new(0., 0., 1.)), + Some(Vector3::new(-0.3, 0., 1.)), + Some(Vector3::new(-1., 0., -0.3)), + Ratio::new::(0.7), + ) + } + + fn a380_nose_gear_actuator(bounded_linear_length: &impl BoundedLinearLength) -> LinearActuator { + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.; + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.3; + const FLOW_CONTROL_FORCE_GAIN: f64 = 250000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 900000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 3.; + + let actuator_characteristics = LinearActuatorCharacteristics::new( + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 0.98, + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 1.02, + VolumeRate::new::(0.053), + Ratio::new::(MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + LinearActuator::new( + bounded_linear_length, + 1, + Length::new::(0.0792), + Length::new::(0.035), + actuator_characteristics.max_flow(), + 800000., + 150000., + 50000., + actuator_characteristics.slow_damping(), + Duration::from_millis(100), + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], + [0., 0.1, 0.11, 0.89, 0.9, 1.], + FLOW_CONTROL_PROPORTIONAL_GAIN, + FLOW_CONTROL_INTEGRAL_GAIN, + FLOW_CONTROL_FORCE_GAIN, + true, + false, + None, + ) + } + + fn a380_main_gear_actuator(bounded_linear_length: &impl BoundedLinearLength) -> LinearActuator { + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.0; + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.3; + const FLOW_CONTROL_FORCE_GAIN: f64 = 250000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 2500000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 5.; + + let actuator_characteristics = LinearActuatorCharacteristics::new( + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 0.98, + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 1.02, + VolumeRate::new::(0.17), + Ratio::new::(MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + LinearActuator::new( + bounded_linear_length, + 1, + Length::new::(0.145), + Length::new::(0.105), + actuator_characteristics.max_flow(), + 800000., + 350000., + 50000., + actuator_characteristics.slow_damping(), + Duration::from_millis(100), + [1., 1., 1., 1., 0.5, 0.5], + [0.2, 0.4, 1., 1., 1., 1.], + [0., 0.13, 0.17, 0.95, 0.96, 1.], + FLOW_CONTROL_PROPORTIONAL_GAIN, + FLOW_CONTROL_INTEGRAL_GAIN, + FLOW_CONTROL_FORCE_GAIN, + true, + false, + None, + ) + } + + fn a380_left_gear_body(init_downlocked: bool) -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(0.3, 3.453, 0.3); + let cg_offset = Vector3::new(0., -3. / 4. * size[1], 0.); + + let control_arm = Vector3::new(0.1815, 0.15, 0.); + let anchor = Vector3::new(0.26, 0.15, 0.); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(700.), + size, + cg_offset, + cg_offset, + control_arm, + anchor, + Angle::new::(0.), + Angle::new::(80.), + if init_downlocked { + Angle::new::(0.) + } else { + Angle::new::(80.) + }, + 150., + true, + Vector3::new(0., 0., 1.), + ) + } + + fn a380_right_gear_body(init_downlocked: bool) -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(0.3, 3.453, 0.3); + let cg_offset = Vector3::new(0., -3. / 4. * size[1], 0.); + + let control_arm = Vector3::new(-0.1815, 0.15, 0.); + let anchor = Vector3::new(-0.26, 0.15, 0.); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(700.), + size, + cg_offset, + cg_offset, + control_arm, + anchor, + Angle::new::(-80.), + Angle::new::(80.), + if init_downlocked { + Angle::new::(0.) + } else { + Angle::new::(-80.) + }, + 150., + true, + Vector3::new(0., 0., 1.), + ) + } + + fn a380_nose_gear_body(init_downlocked: bool) -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(0.3, 2.453, 0.3); + let cg_offset = Vector3::new(0., -2. / 3. * size[1], 0.); + + let control_arm = Vector3::new(0., -0.093, 0.212); + let anchor = Vector3::new(0., 0.56, 0.); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(300.), + size, + cg_offset, + cg_offset, + control_arm, + anchor, + Angle::new::(-101.), + Angle::new::(92.), + if init_downlocked { + Angle::new::(-9.) + } else { + Angle::new::(-101.) + }, + 150., + true, + Vector3::new(1., 0., 0.), + ) + } + + fn a380_gear_assembly( + wheel_id: GearWheel, + init_downlocked: bool, + ) -> HydraulicLinearActuatorAssembly<1> { + let gear_body = match wheel_id { + GearWheel::NOSE => Self::a380_nose_gear_body(init_downlocked), + + GearWheel::LEFT => Self::a380_left_gear_body(init_downlocked), + + GearWheel::RIGHT => Self::a380_right_gear_body(init_downlocked), + }; + + let gear_actuator = match wheel_id { + GearWheel::NOSE => Self::a380_nose_gear_actuator(&gear_body), + + GearWheel::LEFT | GearWheel::RIGHT => Self::a380_main_gear_actuator(&gear_body), + }; + + HydraulicLinearActuatorAssembly::new([gear_actuator], gear_body) + } +} + +struct A380GearSystemFactory {} +impl A380GearSystemFactory { + fn a380_gear_system(context: &mut InitContext) -> HydraulicGearSystem { + let init_downlocked = context.start_gear_down(); + + HydraulicGearSystem::new( + context, + A380GearDoorFactory::a380_gear_door_assembly(GearWheel::NOSE), + A380GearDoorFactory::a380_gear_door_assembly(GearWheel::LEFT), + A380GearDoorFactory::a380_gear_door_assembly(GearWheel::RIGHT), + A380GearFactory::a380_gear_assembly(GearWheel::NOSE, init_downlocked), + A380GearFactory::a380_gear_assembly(GearWheel::LEFT, init_downlocked), + A380GearFactory::a380_gear_assembly(GearWheel::RIGHT, init_downlocked), + A380GearDoorFactory::a380_left_gear_door_aerodynamics(), + A380GearDoorFactory::a380_right_gear_door_aerodynamics(), + A380GearDoorFactory::a380_nose_gear_door_aerodynamics(), + A380GearFactory::a380_left_gear_aerodynamics(), + A380GearFactory::a380_right_gear_aerodynamics(), + A380GearFactory::a380_nose_gear_aerodynamics(), + ) + } +} +struct A380PowerTransferUnitCharacteristics { + efficiency: Ratio, + + deactivation_delta_pressure: Pressure, + activation_delta_pressure: Pressure, + + shot_to_shot_variability: Ratio, +} +impl A380PowerTransferUnitCharacteristics { + // Randomisation parameters + // As ptu wear parameters are non linear, for now we simulate two normal distributions: + // -Nominal distribution which is the PTU you'll find in wide majority of planes + // -Worn out distribution, which is the PTU which is still acceptable but has degraded behaviour + const MEAN_ACTIVATION_DELTA_PRESSURE_PSI: f64 = 500.; + const STD_DEV_ACTIVATION_DELTA_PRESSURE_PSI: f64 = 5.; + + // Ratio of worn PTU : 0.1 means 10% of cases we get a worn out ptu + const WORN_PTU_CASE_PROBABILITY: f64 = 0.15; + + const WORN_MEAN_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 20.; + const WORN_STD_DEV_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 5.; + const WORN_MIN_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 5.; + const WORN_MAX_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 30.; + + const NOMINAL_MEAN_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 90.; + const NOMINAL_STD_DEV_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 15.; + const NOMINAL_MIN_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 5.; + const NOMINAL_MAX_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 180.; + + const WORN_EFFICIENCY_MEAN: f64 = 0.6; + const WORN_EFFICIENCY_STD_DEV: f64 = 0.06; + const NOMINAL_EFFICIENCY_MEAN: f64 = 0.85; + const NOMINAL_EFFICIENCY_STD_DEV: f64 = 0.04; + const EFFICIENCY_MIN_ALLOWED: f64 = 0.5; + const EFFICIENCY_MAX: f64 = 0.9; + + const SHOT_TO_SHOT_VARIABILITY_PERCENT_RATIO: f64 = 0.05; + + fn new_randomized() -> Self { + let randomized_is_ptu_worn_out = Self::randomized_is_ptu_worn_out(); + + Self { + efficiency: Self::randomized_efficiency(randomized_is_ptu_worn_out), + + deactivation_delta_pressure: Self::randomized_deactivation_delta_pressure( + randomized_is_ptu_worn_out, + ), + + activation_delta_pressure: Pressure::new::(random_from_normal_distribution( + Self::MEAN_ACTIVATION_DELTA_PRESSURE_PSI, + Self::STD_DEV_ACTIVATION_DELTA_PRESSURE_PSI, + )), + + shot_to_shot_variability: Ratio::new::( + Self::SHOT_TO_SHOT_VARIABILITY_PERCENT_RATIO, + ), + } + } + + #[cfg(test)] + fn new_worst_part_acceptable() -> Self { + Self { + efficiency: Ratio::new::(Self::EFFICIENCY_MIN_ALLOWED), + + deactivation_delta_pressure: Pressure::new::( + Self::WORN_MIN_DEACTIVATION_DELTA_PRESSURE_PSI, + ), + + activation_delta_pressure: Pressure::new::( + Self::MEAN_ACTIVATION_DELTA_PRESSURE_PSI + + 5. * Self::STD_DEV_ACTIVATION_DELTA_PRESSURE_PSI, + ), + + shot_to_shot_variability: Ratio::new::( + Self::SHOT_TO_SHOT_VARIABILITY_PERCENT_RATIO, + ), + } + } + + fn randomized_is_ptu_worn_out() -> bool { + random_from_range(0., 1.) < Self::WORN_PTU_CASE_PROBABILITY + } + + fn randomized_efficiency(is_worn_out: bool) -> Ratio { + if is_worn_out { + Ratio::new::( + random_from_normal_distribution( + Self::WORN_EFFICIENCY_MEAN, + Self::WORN_EFFICIENCY_STD_DEV, + ) + .max(Self::EFFICIENCY_MIN_ALLOWED) + .min(Self::EFFICIENCY_MAX), + ) + } else { + Ratio::new::( + random_from_normal_distribution( + Self::NOMINAL_EFFICIENCY_MEAN, + Self::NOMINAL_EFFICIENCY_STD_DEV, + ) + .max(Self::EFFICIENCY_MIN_ALLOWED) + .min(Self::EFFICIENCY_MAX), + ) + } + } + + fn randomized_deactivation_delta_pressure(is_worn_out: bool) -> Pressure { + if is_worn_out { + Pressure::new::( + random_from_normal_distribution( + Self::WORN_MEAN_DEACTIVATION_DELTA_PRESSURE_PSI, + Self::WORN_STD_DEV_DEACTIVATION_DELTA_PRESSURE_PSI, + ) + .min(Self::WORN_MAX_DEACTIVATION_DELTA_PRESSURE_PSI) + .max(Self::WORN_MIN_DEACTIVATION_DELTA_PRESSURE_PSI), + ) + } else { + Pressure::new::( + random_from_normal_distribution( + Self::NOMINAL_MEAN_DEACTIVATION_DELTA_PRESSURE_PSI, + Self::NOMINAL_STD_DEV_DEACTIVATION_DELTA_PRESSURE_PSI, + ) + .min(Self::NOMINAL_MAX_DEACTIVATION_DELTA_PRESSURE_PSI) + .max(Self::NOMINAL_MIN_DEACTIVATION_DELTA_PRESSURE_PSI), + ) + } + } +} +impl PowerTransferUnitCharacteristics for A380PowerTransferUnitCharacteristics { + fn efficiency(&self) -> Ratio { + self.efficiency + } + + fn deactivation_delta_pressure(&self) -> Pressure { + self.deactivation_delta_pressure + } + + fn activation_delta_pressure(&self) -> Pressure { + self.activation_delta_pressure + } + + fn shot_to_shot_variability(&self) -> Ratio { + self.shot_to_shot_variability + } +} + +pub(super) struct A380Hydraulic { + hyd_ptu_ecam_memo_id: VariableIdentifier, + ptu_high_pitch_sound_id: VariableIdentifier, + ptu_continuous_mode_id: VariableIdentifier, + + nose_steering: SteeringActuator, + + core_hydraulic_updater: FixedStepLoop, + physics_updater: MaxStepLoop, + ultra_fast_physics_updater: MaxStepLoop, + + brake_steer_computer: A380HydraulicBrakeSteerComputerUnit, + + blue_circuit: HydraulicCircuit, + blue_circuit_controller: A380HydraulicCircuitController, + green_circuit: HydraulicCircuit, + green_circuit_controller: A380HydraulicCircuitController, + yellow_circuit: HydraulicCircuit, + yellow_circuit_controller: A380HydraulicCircuitController, + + engine_driven_pump_1: EngineDrivenPump, + engine_driven_pump_1_controller: A380EngineDrivenPumpController, + + engine_driven_pump_2: EngineDrivenPump, + engine_driven_pump_2_controller: A380EngineDrivenPumpController, + + blue_electric_pump: ElectricPump, + blue_electric_pump_controller: A380BlueElectricPumpController, + + yellow_electric_pump: ElectricPump, + yellow_electric_pump_controller: A380YellowElectricPumpController, + + pushback_tug: PushbackTug, + + ram_air_turbine: RamAirTurbine, + ram_air_turbine_controller: A380RamAirTurbineController, + + power_transfer_unit: PowerTransferUnit, + power_transfer_unit_controller: A380PowerTransferUnitController, + + braking_circuit_norm: BrakeCircuit, + braking_circuit_altn: BrakeCircuit, + braking_force: A380BrakingForce, + + flap_system: FlapSlatAssembly, + slat_system: FlapSlatAssembly, + slats_flaps_complex: SlatFlapComplex, + + gcu: GeneratorControlUnit<9>, + emergency_gen: HydraulicGeneratorMotor, + + forward_cargo_door: CargoDoor, + forward_cargo_door_controller: A380DoorController, + aft_cargo_door: CargoDoor, + aft_cargo_door_controller: A380DoorController, + + elac_computer: ElacComputer, + left_aileron: AileronAssembly, + right_aileron: AileronAssembly, + left_elevator: ElevatorAssembly, + right_elevator: ElevatorAssembly, + + fac_computer: FacComputer, + rudder: RudderAssembly, + + spoiler_computer: SpoilerComputer, + left_spoilers: SpoilerGroup, + right_spoilers: SpoilerGroup, + + gear_system_gravity_extension_controller: A380GravityExtension, + gear_system_hydraulic_controller: A380GearHydraulicController, + gear_system: HydraulicGearSystem, + + ptu_high_pitch_sound_active: DelayedFalseLogicGate, + + trim_controller: A380TrimInputController, + + trim_assembly: TrimmableHorizontalStabilizerAssembly, +} +impl A380Hydraulic { + const HIGH_PITCH_PTU_SOUND_DELTA_PRESS_THRESHOLD_PSI: f64 = 2400.; + const HIGH_PITCH_PTU_SOUND_DURATION: Duration = Duration::from_millis(3000); + + const FLAP_FPPU_TO_SURFACE_ANGLE_BREAKPTS: [f64; 12] = [ + 0., 35.66, 69.32, 89.7, 105.29, 120.22, 145.51, 168.35, 189.87, 210.69, 231.25, 251.97, + ]; + const FLAP_FPPU_TO_SURFACE_ANGLE_DEGREES: [f64; 12] = + [0., 0., 2.5, 5., 7.5, 10., 15., 20., 25., 30., 35., 40.]; + + const SLAT_FPPU_TO_SURFACE_ANGLE_BREAKPTS: [f64; 12] = [ + 0., 66.83, 167.08, 222.27, 272.27, 334.16, 334.16, 334.16, 334.16, 334.16, 334.16, 334.16, + ]; + const SLAT_FPPU_TO_SURFACE_ANGLE_DEGREES: [f64; 12] = + [0., 5.4, 13.5, 18., 22., 27., 27., 27., 27., 27., 27., 27.]; + + const FORWARD_CARGO_DOOR_ID: &'static str = "FWD"; + const AFT_CARGO_DOOR_ID: &'static str = "AFT"; + + const ELECTRIC_PUMP_MAX_CURRENT_AMPERE: f64 = 45.; + const BLUE_ELEC_PUMP_CONTROL_POWER_BUS: ElectricalBusType = + ElectricalBusType::DirectCurrentEssential; + const BLUE_ELEC_PUMP_SUPPLY_POWER_BUS: ElectricalBusType = + ElectricalBusType::AlternatingCurrent(1); + + const YELLOW_ELEC_PUMP_CONTROL_POWER_BUS: ElectricalBusType = + ElectricalBusType::DirectCurrent(2); + const YELLOW_ELEC_PUMP_CONTROL_FROM_CARGO_DOOR_OPERATION_POWER_BUS: ElectricalBusType = + ElectricalBusType::DirectCurrentGndFltService; + const YELLOW_ELEC_PUMP_SUPPLY_POWER_BUS: ElectricalBusType = + ElectricalBusType::AlternatingCurrentGndFltService; + + const YELLOW_EDP_CONTROL_POWER_BUS1: ElectricalBusType = ElectricalBusType::DirectCurrent(2); + const YELLOW_EDP_CONTROL_POWER_BUS2: ElectricalBusType = + ElectricalBusType::DirectCurrentEssential; + const GREEN_EDP_CONTROL_POWER_BUS1: ElectricalBusType = + ElectricalBusType::DirectCurrentEssential; + + const PTU_CONTROL_POWER_BUS: ElectricalBusType = ElectricalBusType::DirectCurrentGndFltService; + + const RAT_CONTROL_SOLENOID1_POWER_BUS: ElectricalBusType = + ElectricalBusType::DirectCurrentHot(1); + const RAT_CONTROL_SOLENOID2_POWER_BUS: ElectricalBusType = + ElectricalBusType::DirectCurrentHot(2); + + // Refresh rate of core hydraulic simulation + const HYDRAULIC_SIM_TIME_STEP: Duration = Duration::from_millis(33); + // Refresh rate of max fixed step loop for fast physics + const HYDRAULIC_SIM_MAX_TIME_STEP_MILLISECONDS: Duration = Duration::from_millis(33); + // Refresh rate of max fixed step loop for fastest flight controls physics needing super stability + // and fast reacting time + const HYDRAULIC_SIM_FLIGHT_CONTROLS_MAX_TIME_STEP_MILLISECONDS: Duration = + Duration::from_millis(10); + + pub(super) fn new(context: &mut InitContext) -> A380Hydraulic { + A380Hydraulic { + hyd_ptu_ecam_memo_id: context.get_identifier("HYD_PTU_ON_ECAM_MEMO".to_owned()), + ptu_high_pitch_sound_id: context.get_identifier("HYD_PTU_HIGH_PITCH_SOUND".to_owned()), + ptu_continuous_mode_id: context.get_identifier("HYD_PTU_CONTINUOUS_MODE".to_owned()), + + nose_steering: SteeringActuator::new( + context, + Angle::new::(75.), + AngularVelocity::new::(0.35), + Length::new::(0.075), + Ratio::new::(0.18), + ), + + core_hydraulic_updater: FixedStepLoop::new(Self::HYDRAULIC_SIM_TIME_STEP), + physics_updater: MaxStepLoop::new(Self::HYDRAULIC_SIM_MAX_TIME_STEP_MILLISECONDS), + ultra_fast_physics_updater: MaxStepLoop::new( + Self::HYDRAULIC_SIM_FLIGHT_CONTROLS_MAX_TIME_STEP_MILLISECONDS, + ), + + brake_steer_computer: A380HydraulicBrakeSteerComputerUnit::new(context), + + blue_circuit: A380HydraulicCircuitFactory::new_blue_circuit(context), + blue_circuit_controller: A380HydraulicCircuitController::new( + None, + HydraulicColor::Blue, + ), + green_circuit: A380HydraulicCircuitFactory::new_green_circuit(context), + green_circuit_controller: A380HydraulicCircuitController::new( + Some(1), + HydraulicColor::Green, + ), + yellow_circuit: A380HydraulicCircuitFactory::new_yellow_circuit(context), + yellow_circuit_controller: A380HydraulicCircuitController::new( + Some(2), + HydraulicColor::Yellow, + ), + + engine_driven_pump_1: EngineDrivenPump::new( + context, + "GREEN", + PumpCharacteristics::a380_edp(), + ), + engine_driven_pump_1_controller: A380EngineDrivenPumpController::new( + context, + 1, + vec![Self::GREEN_EDP_CONTROL_POWER_BUS1], + ), + + engine_driven_pump_2: EngineDrivenPump::new( + context, + "YELLOW", + PumpCharacteristics::a380_edp(), + ), + engine_driven_pump_2_controller: A380EngineDrivenPumpController::new( + context, + 2, + vec![ + Self::YELLOW_EDP_CONTROL_POWER_BUS1, + Self::YELLOW_EDP_CONTROL_POWER_BUS2, + ], + ), + + blue_electric_pump: ElectricPump::new( + context, + "BLUE", + Self::BLUE_ELEC_PUMP_SUPPLY_POWER_BUS, + ElectricCurrent::new::(Self::ELECTRIC_PUMP_MAX_CURRENT_AMPERE), + PumpCharacteristics::a320_electric_pump(), + ), + blue_electric_pump_controller: A380BlueElectricPumpController::new( + context, + Self::BLUE_ELEC_PUMP_CONTROL_POWER_BUS, + ), + + yellow_electric_pump: ElectricPump::new( + context, + "YELLOW", + Self::YELLOW_ELEC_PUMP_SUPPLY_POWER_BUS, + ElectricCurrent::new::(Self::ELECTRIC_PUMP_MAX_CURRENT_AMPERE), + PumpCharacteristics::a320_electric_pump(), + ), + yellow_electric_pump_controller: A380YellowElectricPumpController::new( + context, + Self::YELLOW_ELEC_PUMP_CONTROL_POWER_BUS, + Self::YELLOW_ELEC_PUMP_CONTROL_FROM_CARGO_DOOR_OPERATION_POWER_BUS, + ), + + pushback_tug: PushbackTug::new(context), + + ram_air_turbine: RamAirTurbine::new(context, PumpCharacteristics::a320_rat()), + ram_air_turbine_controller: A380RamAirTurbineController::new( + Self::RAT_CONTROL_SOLENOID1_POWER_BUS, + Self::RAT_CONTROL_SOLENOID2_POWER_BUS, + ), + + power_transfer_unit: PowerTransferUnit::new( + context, + &A380PowerTransferUnitCharacteristics::new_randomized(), + ), + power_transfer_unit_controller: A380PowerTransferUnitController::new( + context, + Self::PTU_CONTROL_POWER_BUS, + ), + + braking_circuit_norm: BrakeCircuit::new( + context, + "NORM", + Volume::new::(0.), + Volume::new::(0.), + Volume::new::(0.13), + Pressure::new::(A380HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ), + + // Alternate brakes accumulator in real A380 is 1.5 gal capacity. + // This is tuned down to 1.0 to match real world accumulator filling time + // as a faster accumulator response has too much unstability + braking_circuit_altn: BrakeCircuit::new( + context, + "ALTN", + Volume::new::(1.0), + Volume::new::(0.4), + Volume::new::(0.13), + Pressure::new::(A380HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ), + + braking_force: A380BrakingForce::new(context), + + flap_system: FlapSlatAssembly::new( + context, + "FLAPS", + Volume::new::(0.32), + AngularVelocity::new::(0.13), + Angle::new::(251.97), + Ratio::new::(140.), + Ratio::new::(16.632), + Ratio::new::(314.98), + Self::FLAP_FPPU_TO_SURFACE_ANGLE_BREAKPTS, + Self::FLAP_FPPU_TO_SURFACE_ANGLE_DEGREES, + Pressure::new::(A380HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ), + slat_system: FlapSlatAssembly::new( + context, + "SLATS", + Volume::new::(0.32), + AngularVelocity::new::(0.13), + Angle::new::(334.16), + Ratio::new::(140.), + Ratio::new::(16.632), + Ratio::new::(314.98), + Self::SLAT_FPPU_TO_SURFACE_ANGLE_BREAKPTS, + Self::SLAT_FPPU_TO_SURFACE_ANGLE_DEGREES, + Pressure::new::(A380HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ), + slats_flaps_complex: SlatFlapComplex::new(context), + + gcu: GeneratorControlUnit::new( + AngularVelocity::new::(12000.), + [ + 0., 1000., 6000., 9999., 10000., 12000., 14000., 14001., 30000., + ], + [0., 0., 0., 0., 1000., 6000., 1000., 0., 0.], + ), + + emergency_gen: HydraulicGeneratorMotor::new(context, Volume::new::(0.19)), + forward_cargo_door: A380CargoDoorFactory::new_a380_cargo_door( + context, + Self::FORWARD_CARGO_DOOR_ID, + ), + forward_cargo_door_controller: A380DoorController::new( + context, + Self::FORWARD_CARGO_DOOR_ID, + ), + + aft_cargo_door: A380CargoDoorFactory::new_a380_cargo_door( + context, + Self::AFT_CARGO_DOOR_ID, + ), + aft_cargo_door_controller: A380DoorController::new(context, Self::AFT_CARGO_DOOR_ID), + + elac_computer: ElacComputer::new(context), + left_aileron: A380AileronFactory::new_aileron(context, ActuatorSide::Left), + right_aileron: A380AileronFactory::new_aileron(context, ActuatorSide::Right), + left_elevator: A380ElevatorFactory::new_elevator(context, ActuatorSide::Left), + right_elevator: A380ElevatorFactory::new_elevator(context, ActuatorSide::Right), + + fac_computer: FacComputer::new(context), + rudder: A380RudderFactory::new_rudder(context), + + spoiler_computer: SpoilerComputer::new(context), + left_spoilers: A380SpoilerFactory::new_a380_spoiler_group(context, ActuatorSide::Left), + right_spoilers: A380SpoilerFactory::new_a380_spoiler_group( + context, + ActuatorSide::Right, + ), + + gear_system_gravity_extension_controller: A380GravityExtension::new(context), + gear_system_hydraulic_controller: A380GearHydraulicController::new(), + gear_system: A380GearSystemFactory::a380_gear_system(context), + + ptu_high_pitch_sound_active: DelayedFalseLogicGate::new( + Self::HIGH_PITCH_PTU_SOUND_DURATION, + ), + + trim_controller: A380TrimInputController::new(context), + trim_assembly: TrimmableHorizontalStabilizerAssembly::new( + context, + Angle::new::(360. * -1.4), + Angle::new::(360. * 6.13), + Angle::new::(360. * -1.87), + Angle::new::(360. * 8.19), // 1.87 rotations down 6.32 up, + AngularVelocity::new::(5000.), + Ratio::new::(2035. / 6.13), + Angle::new::(-4.), + Angle::new::(17.5), + ), + } + } + + pub(super) fn update( + &mut self, + context: &UpdateContext, + engine1: &impl Engine, + engine2: &impl Engine, + overhead_panel: &A380HydraulicOverheadPanel, + autobrake_panel: &AutobrakePanel, + engine_fire_push_buttons: &impl EngineFirePushButtons, + lgcius: &LandingGearControlInterfaceUnitSet, + rat_and_emer_gen_man_on: &impl EmergencyElectricalRatPushButton, + emergency_elec: &(impl EmergencyElectricalState + EmergencyGeneratorPower), + reservoir_pneumatics: &impl ReservoirAirPressure, + adirs: &impl AdirsDiscreteOutputs, + ) { + self.core_hydraulic_updater.update(context); + self.physics_updater.update(context); + self.ultra_fast_physics_updater.update(context); + + for cur_time_step in self.physics_updater { + self.update_fast_physics( + &context.with_delta(cur_time_step), + rat_and_emer_gen_man_on, + emergency_elec, + lgcius.lgciu1(), + lgcius.lgciu2(), + adirs, + ); + } + + self.update_with_sim_rate( + context, + overhead_panel, + autobrake_panel, + rat_and_emer_gen_man_on, + emergency_elec, + lgcius.lgciu1(), + lgcius.lgciu2(), + engine1, + engine2, + ); + + for cur_time_step in self.ultra_fast_physics_updater { + self.update_ultra_fast_physics(&context.with_delta(cur_time_step), lgcius); + } + + for cur_time_step in self.core_hydraulic_updater { + self.update_core_hydraulics( + &context.with_delta(cur_time_step), + engine1, + engine2, + overhead_panel, + engine_fire_push_buttons, + lgcius.lgciu1(), + lgcius.lgciu2(), + reservoir_pneumatics, + ); + } + + self.ptu_high_pitch_sound_active + .update(context, self.is_ptu_running_high_pitch_sound()); + } + + fn ptu_has_fault(&self) -> bool { + self.power_transfer_unit_controller + .has_air_pressure_low_fault() + || self.power_transfer_unit_controller.has_low_level_fault() + } + + fn green_edp_has_fault(&self) -> bool { + self.engine_driven_pump_1_controller + .has_pressure_low_fault() + || self + .engine_driven_pump_1_controller + .has_air_pressure_low_fault() + || self.engine_driven_pump_1_controller.has_low_level_fault() + } + + fn yellow_epump_has_fault(&self) -> bool { + self.yellow_electric_pump_controller + .has_pressure_low_fault() + || self + .yellow_electric_pump_controller + .has_air_pressure_low_fault() + || self.yellow_electric_pump_controller.has_low_level_fault() + } + + fn yellow_edp_has_fault(&self) -> bool { + self.engine_driven_pump_2_controller + .has_pressure_low_fault() + || self + .engine_driven_pump_2_controller + .has_air_pressure_low_fault() + || self.engine_driven_pump_2_controller.has_low_level_fault() + } + + fn blue_epump_has_fault(&self) -> bool { + self.blue_electric_pump_controller.has_pressure_low_fault() + || self + .blue_electric_pump_controller + .has_air_pressure_low_fault() + || self.blue_electric_pump_controller.has_low_level_fault() + } + + pub fn green_reservoir(&self) -> &Reservoir { + self.green_circuit.reservoir() + } + + pub fn blue_reservoir(&self) -> &Reservoir { + self.blue_circuit.reservoir() + } + + pub fn yellow_reservoir(&self) -> &Reservoir { + self.yellow_circuit.reservoir() + } + + #[cfg(test)] + fn should_pressurise_yellow_pump_for_cargo_door_operation(&self) -> bool { + self.yellow_electric_pump_controller + .should_pressurise_for_cargo_door_operation() + } + + #[cfg(test)] + fn nose_wheel_steering_pin_is_inserted(&self) -> bool { + self.pushback_tug.is_nose_wheel_steering_pin_inserted() + } + + #[cfg(test)] + fn is_blue_pressure_switch_pressurised(&self) -> bool { + self.blue_circuit.system_section_pressure_switch() == PressureSwitchState::Pressurised + } + + #[cfg(test)] + fn is_green_pressure_switch_pressurised(&self) -> bool { + self.green_circuit.system_section_pressure_switch() == PressureSwitchState::Pressurised + } + + #[cfg(test)] + fn is_yellow_pressure_switch_pressurised(&self) -> bool { + self.yellow_circuit.system_section_pressure_switch() == PressureSwitchState::Pressurised + } + + fn update_ultra_fast_physics( + &mut self, + context: &UpdateContext, + lgcius: &LandingGearControlInterfaceUnitSet, + ) { + self.left_aileron.update( + context, + self.elac_computer.left_controllers(), + self.blue_circuit.system_section(), + self.green_circuit.system_section(), + ); + + self.right_aileron.update( + context, + self.elac_computer.right_controllers(), + self.blue_circuit.system_section(), + self.green_circuit.system_section(), + ); + + self.left_elevator.update( + context, + self.elac_computer.left_elevator_controllers(), + self.blue_circuit.system_section(), + self.green_circuit.system_section(), + ); + + self.right_elevator.update( + context, + self.elac_computer.right_elevator_controllers(), + self.blue_circuit.system_section(), + self.yellow_circuit.system_section(), + ); + + self.rudder.update( + context, + self.fac_computer.rudder_controllers(), + self.green_circuit.system_section(), + self.blue_circuit.system_section(), + self.yellow_circuit.system_section(), + ); + + self.left_spoilers.update( + context, + self.spoiler_computer.left_controllers(), + self.green_circuit.system_section(), + self.blue_circuit.system_section(), + self.yellow_circuit.system_section(), + ); + + self.right_spoilers.update( + context, + self.spoiler_computer.right_controllers(), + self.green_circuit.system_section(), + self.blue_circuit.system_section(), + self.yellow_circuit.system_section(), + ); + + self.gear_system.update( + context, + &self.gear_system_hydraulic_controller, + lgcius.active_lgciu(), + self.green_circuit.system_section(), + ); + } + + // Updates at the same rate as the sim or at a fixed maximum time step if sim rate is too slow + fn update_fast_physics( + &mut self, + context: &UpdateContext, + rat_and_emer_gen_man_on: &impl EmergencyElectricalRatPushButton, + emergency_elec: &(impl EmergencyElectricalState + EmergencyGeneratorPower), + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + adirs: &impl AdirsDiscreteOutputs, + ) { + self.forward_cargo_door.update( + context, + &self.forward_cargo_door_controller, + self.yellow_circuit.system_section(), + ); + + self.aft_cargo_door.update( + context, + &self.aft_cargo_door_controller, + self.yellow_circuit.system_section(), + ); + + self.ram_air_turbine.update_physics( + &context.delta(), + context.indicated_airspeed(), + self.blue_circuit.system_section(), + ); + + self.gcu.update( + context, + &self.emergency_gen, + self.blue_circuit.system_section(), + emergency_elec, + rat_and_emer_gen_man_on, + lgciu1, + ); + + self.emergency_gen.update( + context, + self.blue_circuit.system_section(), + &self.gcu, + emergency_elec, + ); + + self.gear_system_hydraulic_controller.update( + adirs, + lgciu1, + lgciu2, + &self.gear_system_gravity_extension_controller, + ); + + self.trim_assembly.update( + context, + &self.trim_controller, + &self.trim_controller, + [ + self.green_circuit + .system_section() + .pressure_downstream_leak_valve(), + self.yellow_circuit + .system_section() + .pressure_downstream_leak_valve(), + ], + ); + } + + fn update_with_sim_rate( + &mut self, + context: &UpdateContext, + overhead_panel: &A380HydraulicOverheadPanel, + autobrake_panel: &AutobrakePanel, + rat_and_emer_gen_man_on: &impl EmergencyElectricalRatPushButton, + emergency_elec_state: &impl EmergencyElectricalState, + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + engine1: &impl Engine, + engine2: &impl Engine, + ) { + self.nose_steering.update( + context, + self.yellow_circuit.system_section(), + &self.brake_steer_computer, + &self.pushback_tug, + ); + + // Process brake logic (which circuit brakes) and send brake demands (how much) + self.brake_steer_computer.update( + context, + self.green_circuit.system_section(), + &self.braking_circuit_altn, + lgciu1, + lgciu2, + autobrake_panel, + engine1, + engine2, + ); + + // Updating rat stowed pos on all frames in case it's used for graphics + self.ram_air_turbine.update_position(&context.delta()); + + // Uses external conditions and momentary button: better to check each frame + self.ram_air_turbine_controller.update( + context, + overhead_panel, + rat_and_emer_gen_man_on, + emergency_elec_state, + ); + + self.pushback_tug.update(context); + + self.braking_force.update_forces( + context, + &self.braking_circuit_norm, + &self.braking_circuit_altn, + engine1, + engine2, + &self.pushback_tug, + ); + + self.slats_flaps_complex + .update(context, &self.flap_system, &self.slat_system); + + self.flap_system.update( + context, + self.slats_flaps_complex.flap_demand(), + self.slats_flaps_complex.flap_demand(), + self.green_circuit.system_section(), + self.yellow_circuit.system_section(), + ); + + self.slat_system.update( + context, + self.slats_flaps_complex.slat_demand(), + self.slats_flaps_complex.slat_demand(), + self.blue_circuit.system_section(), + self.green_circuit.system_section(), + ); + + self.forward_cargo_door_controller.update( + context, + &self.forward_cargo_door, + self.yellow_circuit.system_section(), + ); + + self.aft_cargo_door_controller.update( + context, + &self.aft_cargo_door, + self.yellow_circuit.system_section(), + ); + + self.elac_computer.update( + self.blue_circuit.system_section(), + self.green_circuit.system_section(), + self.yellow_circuit.system_section(), + ); + + self.slats_flaps_complex + .update(context, &self.flap_system, &self.slat_system); + + self.fac_computer.update( + self.green_circuit.system_section(), + self.blue_circuit.system_section(), + self.yellow_circuit.system_section(), + ); + } + + // For each hydraulic loop retrieves volumes from and to each actuator and pass it to the loops + fn update_actuators_volume(&mut self) { + self.update_green_actuators_volume(); + self.update_yellow_actuators_volume(); + self.update_blue_actuators_volume(); + } + + fn update_green_actuators_volume(&mut self) { + self.green_circuit + .update_system_actuator_volumes(&mut self.braking_circuit_norm); + + self.green_circuit.update_system_actuator_volumes( + self.left_aileron.actuator(AileronActuatorPosition::Inboard), + ); + self.green_circuit.update_system_actuator_volumes( + self.right_aileron + .actuator(AileronActuatorPosition::Inboard), + ); + + self.green_circuit.update_system_actuator_volumes( + self.left_elevator + .actuator(ElevatorActuatorPosition::Inboard), + ); + + self.green_circuit + .update_system_actuator_volumes(self.rudder.actuator(RudderActuatorPosition::Green)); + + self.green_circuit + .update_system_actuator_volumes(self.flap_system.left_motor()); + self.green_circuit + .update_system_actuator_volumes(self.slat_system.right_motor()); + + self.green_circuit + .update_system_actuator_volumes(self.left_spoilers.actuator(0)); + self.green_circuit + .update_system_actuator_volumes(self.left_spoilers.actuator(4)); + + self.green_circuit + .update_system_actuator_volumes(self.right_spoilers.actuator(0)); + self.green_circuit + .update_system_actuator_volumes(self.right_spoilers.actuator(4)); + + for actuator in self.gear_system.all_actuators() { + self.green_circuit.update_system_actuator_volumes(actuator); + } + + self.green_circuit + .update_system_actuator_volumes(self.trim_assembly.left_motor()); + } + + fn update_yellow_actuators_volume(&mut self) { + self.yellow_circuit + .update_system_actuator_volumes(&mut self.braking_circuit_altn); + + self.yellow_circuit + .update_system_actuator_volumes(self.flap_system.right_motor()); + + self.yellow_circuit + .update_system_actuator_volumes(self.forward_cargo_door.actuator()); + + self.yellow_circuit + .update_system_actuator_volumes(self.aft_cargo_door.actuator()); + + self.yellow_circuit + .update_system_actuator_volumes(&mut self.nose_steering); + + self.yellow_circuit.update_system_actuator_volumes( + self.right_elevator + .actuator(ElevatorActuatorPosition::Inboard), + ); + + self.yellow_circuit + .update_system_actuator_volumes(self.rudder.actuator(RudderActuatorPosition::Yellow)); + + self.yellow_circuit + .update_system_actuator_volumes(self.left_spoilers.actuator(1)); + self.yellow_circuit + .update_system_actuator_volumes(self.left_spoilers.actuator(3)); + + self.yellow_circuit + .update_system_actuator_volumes(self.right_spoilers.actuator(1)); + self.yellow_circuit + .update_system_actuator_volumes(self.right_spoilers.actuator(3)); + + self.yellow_circuit + .update_system_actuator_volumes(self.trim_assembly.right_motor()); + } + + fn update_blue_actuators_volume(&mut self) { + self.blue_circuit + .update_system_actuator_volumes(self.slat_system.left_motor()); + self.blue_circuit + .update_system_actuator_volumes(&mut self.emergency_gen); + + self.blue_circuit.update_system_actuator_volumes( + self.left_aileron + .actuator(AileronActuatorPosition::Outboard), + ); + self.blue_circuit.update_system_actuator_volumes( + self.right_aileron + .actuator(AileronActuatorPosition::Outboard), + ); + + self.blue_circuit.update_system_actuator_volumes( + self.left_elevator + .actuator(ElevatorActuatorPosition::Outboard), + ); + self.blue_circuit.update_system_actuator_volumes( + self.right_elevator + .actuator(ElevatorActuatorPosition::Outboard), + ); + + self.blue_circuit + .update_system_actuator_volumes(self.rudder.actuator(RudderActuatorPosition::Blue)); + + self.blue_circuit + .update_system_actuator_volumes(self.left_spoilers.actuator(2)); + + self.blue_circuit + .update_system_actuator_volumes(self.right_spoilers.actuator(2)); + } + + // All the core hydraulics updates that needs to be done at the slowest fixed step rate + fn update_core_hydraulics( + &mut self, + context: &UpdateContext, + engine1: &impl Engine, + engine2: &impl Engine, + overhead_panel: &A380HydraulicOverheadPanel, + engine_fire_push_buttons: &impl EngineFirePushButtons, + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + reservoir_pneumatics: &impl ReservoirAirPressure, + ) { + // First update what is currently consumed and given back by each actuator + // Todo: might have to split the actuator volumes by expected number of loops + self.update_actuators_volume(); + + self.power_transfer_unit_controller.update( + context, + overhead_panel, + &self.forward_cargo_door_controller, + &self.aft_cargo_door_controller, + &self.pushback_tug, + lgciu2, + self.green_circuit.reservoir(), + self.yellow_circuit.reservoir(), + ); + self.power_transfer_unit.update( + context, + self.green_circuit.system_section(), + self.yellow_circuit.system_section(), + &self.power_transfer_unit_controller, + ); + + self.engine_driven_pump_1_controller.update( + overhead_panel, + engine_fire_push_buttons, + engine1, + &self.green_circuit, + lgciu1, + self.green_circuit.reservoir(), + ); + + self.engine_driven_pump_1.update( + context, + self.green_circuit + .pump_section(A380HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES), + self.green_circuit.reservoir(), + engine1.hydraulic_pump_output_speed(), + &self.engine_driven_pump_1_controller, + ); + + self.engine_driven_pump_2_controller.update( + overhead_panel, + engine_fire_push_buttons, + engine2, + &self.yellow_circuit, + lgciu2, + self.yellow_circuit.reservoir(), + ); + + self.engine_driven_pump_2.update( + context, + self.yellow_circuit + .pump_section(A380HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES), + self.yellow_circuit.reservoir(), + engine2.hydraulic_pump_output_speed(), + &self.engine_driven_pump_2_controller, + ); + + self.blue_electric_pump_controller.update( + overhead_panel, + &self.blue_circuit, + engine1, + engine2, + lgciu1, + lgciu2, + self.blue_circuit.reservoir(), + ); + self.blue_electric_pump.update( + context, + self.blue_circuit + .pump_section(A380HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES), + self.blue_circuit.reservoir(), + &self.blue_electric_pump_controller, + ); + + self.yellow_electric_pump_controller.update( + context, + overhead_panel, + &self.forward_cargo_door_controller, + &self.aft_cargo_door_controller, + &self.yellow_circuit, + self.yellow_circuit.reservoir(), + ); + self.yellow_electric_pump.update( + context, + self.yellow_circuit.system_section(), + self.yellow_circuit.reservoir(), + &self.yellow_electric_pump_controller, + ); + + self.ram_air_turbine.update( + context, + self.blue_circuit.system_section(), + self.blue_circuit.reservoir(), + &self.ram_air_turbine_controller, + ); + + self.green_circuit_controller.update( + context, + engine_fire_push_buttons, + overhead_panel, + &self.yellow_electric_pump_controller, + ); + self.green_circuit.update( + context, + &mut vec![&mut self.engine_driven_pump_1], + None::<&mut ElectricPump>, + None::<&mut ElectricPump>, + Some(&self.power_transfer_unit), + &self.green_circuit_controller, + reservoir_pneumatics.green_reservoir_pressure(), + ); + + self.yellow_circuit_controller.update( + context, + engine_fire_push_buttons, + overhead_panel, + &self.yellow_electric_pump_controller, + ); + self.yellow_circuit.update( + context, + &mut vec![&mut self.engine_driven_pump_2], + Some(&mut self.yellow_electric_pump), + None::<&mut ElectricPump>, + Some(&self.power_transfer_unit), + &self.yellow_circuit_controller, + reservoir_pneumatics.yellow_reservoir_pressure(), + ); + + self.blue_circuit_controller.update( + context, + engine_fire_push_buttons, + overhead_panel, + &self.yellow_electric_pump_controller, + ); + self.blue_circuit.update( + context, + &mut vec![&mut self.blue_electric_pump], + Some(&mut self.ram_air_turbine), + None::<&mut ElectricPump>, + None, + &self.blue_circuit_controller, + reservoir_pneumatics.blue_reservoir_pressure(), + ); + + self.braking_circuit_norm.update( + context, + self.green_circuit.system_section(), + self.brake_steer_computer.norm_controller(), + ); + self.braking_circuit_altn.update( + context, + self.yellow_circuit.system_section(), + self.brake_steer_computer.alternate_controller(), + ); + } + + // Actual logic of HYD PTU memo computed here until done within FWS + fn should_show_hyd_ptu_message_on_ecam(&self) -> bool { + let ptu_valve_ctrol_off = !self.power_transfer_unit_controller.should_enable(); + let green_eng_pump_lo_pr = !self.green_circuit.pump_section_switch_pressurised( + A380HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES, + ); + + let yellow_sys_lo_pr = !self.yellow_circuit.system_section_switch_pressurised(); + + let yellow_sys_press_above_1450 = + self.yellow_circuit.system_section_pressure() > Pressure::new::(1450.); + + let green_sys_press_above_1450 = + self.green_circuit.system_section_pressure() > Pressure::new::(1450.); + + let green_sys_lo_pr = !self.green_circuit.system_section_switch_pressurised(); + + let yellow_eng_pump_lo_pr = !self.yellow_circuit.pump_section_switch_pressurised( + A380HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES, + ); + + let yellow_elec_pump_on = self.yellow_electric_pump_controller.should_pressurise(); + + let yellow_pump_state = yellow_eng_pump_lo_pr && !yellow_elec_pump_on; + + let yellow_press_node = yellow_sys_press_above_1450 || !yellow_sys_lo_pr; + let green_press_node = green_sys_press_above_1450 || !green_sys_lo_pr; + + let yellow_side_and = green_eng_pump_lo_pr && yellow_press_node && green_press_node; + let green_side_and = yellow_press_node && green_press_node && yellow_pump_state; + + !ptu_valve_ctrol_off && (yellow_side_and || green_side_and) + } + + // Function dedicated to sound so it triggers the high pitch PTU sound on specific PTU conditions + fn is_ptu_running_high_pitch_sound(&self) -> bool { + let is_ptu_rotating = self.power_transfer_unit.is_active_left_to_right() + || self.power_transfer_unit.is_active_right_to_left(); + + let absolute_delta_pressure = (self.green_circuit.system_section_pressure() + - self.yellow_circuit.system_section_pressure()) + .abs(); + + absolute_delta_pressure + > Pressure::new::(Self::HIGH_PITCH_PTU_SOUND_DELTA_PRESS_THRESHOLD_PSI) + && is_ptu_rotating + && !self.ptu_high_pitch_sound_active.output() + } + + pub fn gear_system(&self) -> &impl GearSystemSensors { + &self.gear_system + } +} +impl SimulationElement for A380Hydraulic { + fn accept(&mut self, visitor: &mut T) { + self.engine_driven_pump_1.accept(visitor); + self.engine_driven_pump_1_controller.accept(visitor); + + self.engine_driven_pump_2.accept(visitor); + self.engine_driven_pump_2_controller.accept(visitor); + + self.blue_electric_pump.accept(visitor); + self.blue_electric_pump_controller.accept(visitor); + + self.yellow_electric_pump.accept(visitor); + self.yellow_electric_pump_controller.accept(visitor); + + self.forward_cargo_door_controller.accept(visitor); + self.forward_cargo_door.accept(visitor); + + self.aft_cargo_door_controller.accept(visitor); + self.aft_cargo_door.accept(visitor); + + self.pushback_tug.accept(visitor); + + self.ram_air_turbine.accept(visitor); + self.ram_air_turbine_controller.accept(visitor); + + self.power_transfer_unit.accept(visitor); + self.power_transfer_unit_controller.accept(visitor); + + self.blue_circuit.accept(visitor); + self.green_circuit.accept(visitor); + self.yellow_circuit.accept(visitor); + + self.brake_steer_computer.accept(visitor); + + self.braking_circuit_norm.accept(visitor); + self.braking_circuit_altn.accept(visitor); + self.braking_force.accept(visitor); + + self.emergency_gen.accept(visitor); + self.nose_steering.accept(visitor); + self.slats_flaps_complex.accept(visitor); + self.flap_system.accept(visitor); + self.slat_system.accept(visitor); + + self.elac_computer.accept(visitor); + self.left_aileron.accept(visitor); + self.right_aileron.accept(visitor); + self.left_elevator.accept(visitor); + self.right_elevator.accept(visitor); + + self.fac_computer.accept(visitor); + self.rudder.accept(visitor); + + self.spoiler_computer.accept(visitor); + self.left_spoilers.accept(visitor); + self.right_spoilers.accept(visitor); + + self.gear_system_gravity_extension_controller + .accept(visitor); + self.gear_system.accept(visitor); + + self.trim_controller.accept(visitor); + self.trim_assembly.accept(visitor); + + visitor.visit(self); + } + + fn write(&self, writer: &mut SimulatorWriter) { + writer.write( + &self.hyd_ptu_ecam_memo_id, + self.should_show_hyd_ptu_message_on_ecam(), + ); + + writer.write( + &self.ptu_high_pitch_sound_id, + self.ptu_high_pitch_sound_active.output(), + ); + + // Two sound variables of ptu made exclusive with high pitch sound having priority + writer.write( + &self.ptu_continuous_mode_id, + self.power_transfer_unit.is_in_continuous_mode() + && !self.ptu_high_pitch_sound_active.output(), + ); + } +} +impl HydraulicGeneratorControlUnit for A380Hydraulic { + fn max_allowed_power(&self) -> Power { + self.gcu.max_allowed_power() + } + + fn motor_speed(&self) -> AngularVelocity { + self.gcu.motor_speed() + } +} + +struct A380GearHydraulicController { + safety_valve_should_open: bool, + cutoff_valve_should_open: bool, + vent_valves_should_open: bool, + doors_uplock_mechanical_release: bool, + gears_uplock_mechanical_release: bool, +} +impl A380GearHydraulicController { + fn new() -> Self { + Self { + safety_valve_should_open: true, + cutoff_valve_should_open: true, + vent_valves_should_open: false, + doors_uplock_mechanical_release: false, + gears_uplock_mechanical_release: false, + } + } + + fn update( + &mut self, + adirs: &impl AdirsDiscreteOutputs, + lgciu1: &(impl LgciuWeightOnWheels + LandingGearHandle), + lgciu2: &impl LgciuWeightOnWheels, + gear_gravity_extension: &impl GearGravityExtension, + ) { + self.update_safety_valve(adirs, lgciu1, lgciu2); + + self.update_safety_and_vent_valve(gear_gravity_extension); + + self.update_uplocks(gear_gravity_extension); + } + + fn update_uplocks(&mut self, gear_gravity_extension: &impl GearGravityExtension) { + self.doors_uplock_mechanical_release = + gear_gravity_extension.extension_handle_number_of_turns() >= 2; + self.gears_uplock_mechanical_release = + gear_gravity_extension.extension_handle_number_of_turns() >= 3; + } + + fn update_safety_and_vent_valve(&mut self, gear_gravity_extension: &impl GearGravityExtension) { + let one_or_more_handle_turns = + gear_gravity_extension.extension_handle_number_of_turns() >= 1; + + self.cutoff_valve_should_open = !one_or_more_handle_turns; + + self.vent_valves_should_open = one_or_more_handle_turns; + } + + fn update_safety_valve( + &mut self, + adirs: &impl AdirsDiscreteOutputs, + lgciu1: &(impl LgciuWeightOnWheels + LandingGearHandle), + lgciu2: &impl LgciuWeightOnWheels, + ) { + let speed_condition = + !adirs.low_speed_warning_4_260kts(1) || !adirs.low_speed_warning_4_260kts(3); + + let on_ground_condition = lgciu1.left_and_right_gear_compressed(true) + || lgciu2.left_and_right_gear_compressed(true); + + let self_maintained_gear_lever_condition = + self.safety_valve_should_open || lgciu1.gear_handle_is_down(); + + self.safety_valve_should_open = + (speed_condition || on_ground_condition) && self_maintained_gear_lever_condition; + } +} +impl GearSystemController for A380GearHydraulicController { + fn safety_valve_should_open(&self) -> bool { + self.safety_valve_should_open + } + + fn shut_off_valve_should_open(&self) -> bool { + self.cutoff_valve_should_open + } + + fn vent_valves_should_open(&self) -> bool { + self.vent_valves_should_open + } + + fn doors_uplocks_should_mechanically_unlock(&self) -> bool { + self.doors_uplock_mechanical_release + } + + fn gears_uplocks_should_mechanically_unlock(&self) -> bool { + self.gears_uplock_mechanical_release + } +} + +struct A380HydraulicCircuitController { + circuit_id: HydraulicColor, + engine_number: Option, + should_open_fire_shutoff_valve: bool, + should_open_leak_measurement_valve: bool, + cargo_door_in_use: DelayedFalseLogicGate, +} +impl A380HydraulicCircuitController { + const DELAY_TO_REOPEN_LEAK_VALVE_AFTER_CARGO_DOOR_USE: Duration = Duration::from_secs(15); + + fn new(engine_number: Option, circuit_id: HydraulicColor) -> Self { + Self { + circuit_id, + engine_number, + should_open_fire_shutoff_valve: true, + should_open_leak_measurement_valve: true, + cargo_door_in_use: DelayedFalseLogicGate::new( + Self::DELAY_TO_REOPEN_LEAK_VALVE_AFTER_CARGO_DOOR_USE, + ), + } + } + + fn update( + &mut self, + context: &UpdateContext, + engine_fire_push_buttons: &impl EngineFirePushButtons, + overhead_panel: &A380HydraulicOverheadPanel, + yellow_epump_controller: &A380YellowElectricPumpController, + ) { + self.cargo_door_in_use.update( + context, + yellow_epump_controller.should_pressurise_for_cargo_door_operation(), + ); + + if let Some(eng_number) = self.engine_number { + self.should_open_fire_shutoff_valve = !engine_fire_push_buttons.is_released(eng_number); + } + + self.update_leak_measurement_valve(context, overhead_panel); + } + + fn update_leak_measurement_valve( + &mut self, + context: &UpdateContext, + overhead_panel: &A380HydraulicOverheadPanel, + ) { + let measurement_valve_open_demand_raw = match &mut self.circuit_id { + HydraulicColor::Green => overhead_panel.green_leak_measurement_valve_is_on(), + HydraulicColor::Yellow => { + overhead_panel.yellow_leak_measurement_valve_is_on() + && !self.cargo_door_in_use.output() + } + HydraulicColor::Blue => overhead_panel.blue_leak_measurement_valve_is_on(), + }; + + self.should_open_leak_measurement_valve = measurement_valve_open_demand_raw + || self.plane_state_disables_leak_valve_closing(context); + } + + fn plane_state_disables_leak_valve_closing(&self, context: &UpdateContext) -> bool { + context.indicated_airspeed() >= Velocity::new::(100.) + } +} +impl HydraulicCircuitController for A380HydraulicCircuitController { + fn should_open_fire_shutoff_valve(&self, _: usize) -> bool { + // A380 only has one main pump per pump section thus index not useful + self.should_open_fire_shutoff_valve + } + + fn should_open_leak_measurement_valve(&self) -> bool { + self.should_open_leak_measurement_valve + } +} + +struct A380EngineDrivenPumpController { + green_pump_low_press_id: VariableIdentifier, + yellow_pump_low_press_id: VariableIdentifier, + + is_powered: bool, + powered_by: Vec, + engine_number: usize, + should_pressurise: bool, + has_pressure_low_fault: bool, + has_air_pressure_low_fault: bool, + has_low_level_fault: bool, + is_pressure_low: bool, +} +impl A380EngineDrivenPumpController { + fn new( + context: &mut InitContext, + engine_number: usize, + powered_by: Vec, + ) -> Self { + Self { + green_pump_low_press_id: context + .get_identifier("HYD_GREEN_EDPUMP_LOW_PRESS".to_owned()), + yellow_pump_low_press_id: context + .get_identifier("HYD_YELLOW_EDPUMP_LOW_PRESS".to_owned()), + + is_powered: false, + powered_by, + engine_number, + should_pressurise: true, + + has_pressure_low_fault: false, + has_air_pressure_low_fault: false, + has_low_level_fault: false, + + is_pressure_low: true, + } + } + + fn update_low_pressure( + &mut self, + engine: &impl Engine, + hydraulic_circuit: &impl HydraulicPressureSensors, + lgciu: &impl LgciuInterface, + ) { + self.is_pressure_low = self.should_pressurise() + && !hydraulic_circuit.pump_section_switch_pressurised( + A380HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES, + ); + + // Fault inhibited if on ground AND engine oil pressure is low (11KS1 elec relay) + self.has_pressure_low_fault = self.is_pressure_low + && (!engine.oil_pressure_is_low() + || !(lgciu.right_gear_compressed(false) && lgciu.left_gear_compressed(false))); + } + + fn update_low_air_pressure( + &mut self, + reservoir: &Reservoir, + overhead_panel: &A380HydraulicOverheadPanel, + ) { + self.has_air_pressure_low_fault = reservoir.is_low_air_pressure() + && overhead_panel.edp_push_button_is_auto(self.engine_number); + } + + fn update_low_level( + &mut self, + reservoir: &Reservoir, + overhead_panel: &A380HydraulicOverheadPanel, + ) { + self.has_low_level_fault = + reservoir.is_low_level() && overhead_panel.edp_push_button_is_auto(self.engine_number); + } + + fn update( + &mut self, + overhead_panel: &A380HydraulicOverheadPanel, + engine_fire_push_buttons: &impl EngineFirePushButtons, + engine: &impl Engine, + hydraulic_circuit: &impl HydraulicPressureSensors, + lgciu: &impl LgciuInterface, + reservoir: &Reservoir, + ) { + let mut should_pressurise_if_powered = false; + if overhead_panel.edp_push_button_is_auto(self.engine_number) + && !engine_fire_push_buttons.is_released(self.engine_number) + { + should_pressurise_if_powered = true; + } else if overhead_panel.edp_push_button_is_off(self.engine_number) + || engine_fire_push_buttons.is_released(self.engine_number) + { + should_pressurise_if_powered = false; + } + + // Inverted logic, no power means solenoid valve always leave pump in pressurise mode + self.should_pressurise = !self.is_powered || should_pressurise_if_powered; + + self.update_low_pressure(engine, hydraulic_circuit, lgciu); + + self.update_low_air_pressure(reservoir, overhead_panel); + + self.update_low_level(reservoir, overhead_panel); + } + + fn has_pressure_low_fault(&self) -> bool { + self.has_pressure_low_fault + } + + fn has_air_pressure_low_fault(&self) -> bool { + self.has_air_pressure_low_fault + } + + fn has_low_level_fault(&self) -> bool { + self.has_low_level_fault + } +} +impl PumpController for A380EngineDrivenPumpController { + fn should_pressurise(&self) -> bool { + self.should_pressurise + } +} +impl SimulationElement for A380EngineDrivenPumpController { + fn write(&self, writer: &mut SimulatorWriter) { + if self.engine_number == 1 { + writer.write(&self.green_pump_low_press_id, self.is_pressure_low); + } else if self.engine_number == 2 { + writer.write(&self.yellow_pump_low_press_id, self.is_pressure_low); + } else { + panic!("The A380 only supports two engines."); + } + } + + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.is_powered = buses.any_is_powered(&self.powered_by); + } +} + +struct A380BlueElectricPumpController { + low_press_id: VariableIdentifier, + + is_powered: bool, + powered_by: ElectricalBusType, + should_pressurise: bool, + has_pressure_low_fault: bool, + has_air_pressure_low_fault: bool, + has_low_level_fault: bool, + is_pressure_low: bool, +} +impl A380BlueElectricPumpController { + fn new(context: &mut InitContext, powered_by: ElectricalBusType) -> Self { + Self { + low_press_id: context.get_identifier("HYD_BLUE_EPUMP_LOW_PRESS".to_owned()), + + is_powered: false, + powered_by, + should_pressurise: false, + + has_pressure_low_fault: false, + has_air_pressure_low_fault: false, + has_low_level_fault: false, + + is_pressure_low: true, + } + } + + fn update( + &mut self, + overhead_panel: &A380HydraulicOverheadPanel, + hydraulic_circuit: &impl HydraulicPressureSensors, + engine1: &impl Engine, + engine2: &impl Engine, + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + reservoir: &Reservoir, + ) { + let mut should_pressurise_if_powered = false; + if overhead_panel.blue_epump_push_button.is_auto() { + if !lgciu1.nose_gear_compressed(false) + || engine1.is_above_minimum_idle() + || engine2.is_above_minimum_idle() + || overhead_panel.blue_epump_override_push_button_is_on() + { + should_pressurise_if_powered = true; + } else { + should_pressurise_if_powered = false; + } + } else if overhead_panel.blue_epump_push_button_is_off() { + should_pressurise_if_powered = false; + } + + self.should_pressurise = self.is_powered && should_pressurise_if_powered; + + self.update_low_pressure( + overhead_panel, + hydraulic_circuit, + engine1, + engine2, + lgciu1, + lgciu2, + ); + + self.update_low_air_pressure(reservoir, overhead_panel); + + self.update_low_level(reservoir, overhead_panel); + } + + fn update_low_pressure( + &mut self, + overhead_panel: &A380HydraulicOverheadPanel, + hydraulic_circuit: &impl HydraulicPressureSensors, + engine1: &impl Engine, + engine2: &impl Engine, + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + ) { + let is_both_engine_low_oil_pressure = + engine1.oil_pressure_is_low() && engine2.oil_pressure_is_low(); + + self.is_pressure_low = self.should_pressurise() + && !hydraulic_circuit.pump_section_switch_pressurised( + A380HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES, + ); + + self.has_pressure_low_fault = self.is_pressure_low + && (!is_both_engine_low_oil_pressure + || (!(lgciu1.left_gear_compressed(false) && lgciu1.right_gear_compressed(false)) + || !(lgciu2.left_gear_compressed(false) + && lgciu2.right_gear_compressed(false))) + || overhead_panel.blue_epump_override_push_button_is_on()); + } + + fn update_low_air_pressure( + &mut self, + reservoir: &Reservoir, + overhead_panel: &A380HydraulicOverheadPanel, + ) { + self.has_air_pressure_low_fault = + reservoir.is_low_air_pressure() && !overhead_panel.blue_epump_push_button_is_off(); + } + + fn update_low_level( + &mut self, + reservoir: &Reservoir, + overhead_panel: &A380HydraulicOverheadPanel, + ) { + self.has_low_level_fault = + reservoir.is_low_level() && !overhead_panel.blue_epump_push_button_is_off(); + } + + fn has_pressure_low_fault(&self) -> bool { + self.has_pressure_low_fault + } + + fn has_air_pressure_low_fault(&self) -> bool { + self.has_air_pressure_low_fault + } + + fn has_low_level_fault(&self) -> bool { + self.has_low_level_fault + } +} +impl PumpController for A380BlueElectricPumpController { + fn should_pressurise(&self) -> bool { + self.should_pressurise + } +} +impl SimulationElement for A380BlueElectricPumpController { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.low_press_id, self.is_pressure_low); + } + + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.is_powered = buses.is_powered(self.powered_by); + } +} + +struct A380YellowElectricPumpController { + low_press_id: VariableIdentifier, + + is_powered: bool, + powered_by: ElectricalBusType, + powered_by_when_cargo_door_operation: ElectricalBusType, + should_pressurise: bool, + has_pressure_low_fault: bool, + has_air_pressure_low_fault: bool, + has_low_level_fault: bool, + is_pressure_low: bool, + + is_required_for_cargo_door_operation: DelayedFalseLogicGate, + should_pressurise_for_cargo_door_operation: bool, + + low_pressure_hystereris: bool, +} +impl A380YellowElectricPumpController { + const DURATION_OF_YELLOW_PUMP_ACTIVATION_AFTER_CARGO_DOOR_OPERATION: Duration = + Duration::from_secs(20); + + const LOW_PRESS_HYSTERESIS_HIGH_PSI: f64 = 1750.; + const LOW_PRESS_HYSTERESIS_LOW_PSI: f64 = 1450.; + + fn new( + context: &mut InitContext, + powered_by: ElectricalBusType, + powered_by_when_cargo_door_operation: ElectricalBusType, + ) -> Self { + Self { + low_press_id: context.get_identifier("HYD_YELLOW_EPUMP_LOW_PRESS".to_owned()), + + is_powered: false, + powered_by, + powered_by_when_cargo_door_operation, + should_pressurise: false, + + has_pressure_low_fault: false, + has_air_pressure_low_fault: false, + has_low_level_fault: false, + + is_pressure_low: true, + is_required_for_cargo_door_operation: DelayedFalseLogicGate::new( + Self::DURATION_OF_YELLOW_PUMP_ACTIVATION_AFTER_CARGO_DOOR_OPERATION, + ), + should_pressurise_for_cargo_door_operation: false, + + low_pressure_hystereris: false, + } + } + + fn update( + &mut self, + context: &UpdateContext, + overhead_panel: &A380HydraulicOverheadPanel, + forward_cargo_door_controller: &A380DoorController, + aft_cargo_door_controller: &A380DoorController, + hydraulic_circuit: &impl HydraulicPressureSensors, + reservoir: &Reservoir, + ) { + self.update_cargo_door_logic( + context, + overhead_panel, + forward_cargo_door_controller, + aft_cargo_door_controller, + ); + + self.should_pressurise = (overhead_panel.yellow_epump_push_button.is_on() + || self.is_required_for_cargo_door_operation.output()) + && self.is_powered; + + self.update_low_pressure(hydraulic_circuit); + + self.update_low_air_pressure(reservoir, overhead_panel); + + self.update_low_level(reservoir, overhead_panel); + } + + fn update_low_pressure(&mut self, hydraulic_circuit: &impl HydraulicPressureSensors) { + self.update_low_pressure_hysteresis(hydraulic_circuit); + + self.is_pressure_low = self.should_pressurise() && !self.low_pressure_hystereris; + + self.has_pressure_low_fault = self.is_pressure_low; + } + + fn update_low_pressure_hysteresis( + &mut self, + hydraulic_circuit: &impl HydraulicPressureSensors, + ) { + if hydraulic_circuit + .system_section_pressure_transducer() + .get::() + > Self::LOW_PRESS_HYSTERESIS_HIGH_PSI + { + self.low_pressure_hystereris = true; + } else if hydraulic_circuit + .system_section_pressure_transducer() + .get::() + < Self::LOW_PRESS_HYSTERESIS_LOW_PSI + { + self.low_pressure_hystereris = false; + } + } + + fn update_cargo_door_logic( + &mut self, + context: &UpdateContext, + overhead_panel: &A380HydraulicOverheadPanel, + forward_cargo_door_controller: &A380DoorController, + aft_cargo_door_controller: &A380DoorController, + ) { + self.is_required_for_cargo_door_operation.update( + context, + forward_cargo_door_controller.should_pressurise_hydraulics() + || aft_cargo_door_controller.should_pressurise_hydraulics(), + ); + + self.should_pressurise_for_cargo_door_operation = + self.is_required_for_cargo_door_operation.output() + && !overhead_panel.yellow_epump_push_button.is_on(); + } + + fn update_low_air_pressure( + &mut self, + reservoir: &Reservoir, + overhead_panel: &A380HydraulicOverheadPanel, + ) { + self.has_air_pressure_low_fault = + reservoir.is_low_air_pressure() && !overhead_panel.yellow_epump_push_button_is_auto(); + } + + fn update_low_level( + &mut self, + reservoir: &Reservoir, + overhead_panel: &A380HydraulicOverheadPanel, + ) { + self.has_low_level_fault = + reservoir.is_low_level() && !overhead_panel.yellow_epump_push_button_is_auto(); + } + + fn has_pressure_low_fault(&self) -> bool { + self.has_pressure_low_fault + } + + fn has_air_pressure_low_fault(&self) -> bool { + self.has_air_pressure_low_fault + } + + fn has_low_level_fault(&self) -> bool { + self.has_low_level_fault + } + + fn should_pressurise_for_cargo_door_operation(&self) -> bool { + self.should_pressurise_for_cargo_door_operation + } +} +impl PumpController for A380YellowElectricPumpController { + fn should_pressurise(&self) -> bool { + self.should_pressurise + } +} +impl SimulationElement for A380YellowElectricPumpController { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.low_press_id, self.is_pressure_low); + } + + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + // Control of the pump is powered by dedicated bus OR manual operation of cargo door through another bus + self.is_powered = buses.is_powered(self.powered_by) + || (self.is_required_for_cargo_door_operation.output() + && buses.is_powered(self.powered_by_when_cargo_door_operation)) + } +} + +struct A380PowerTransferUnitController { + park_brake_lever_pos_id: VariableIdentifier, + general_eng_1_starter_active_id: VariableIdentifier, + general_eng_2_starter_active_id: VariableIdentifier, + + is_powered: bool, + powered_by: ElectricalBusType, + should_enable: bool, + should_inhibit_ptu_after_cargo_door_operation: DelayedFalseLogicGate, + + parking_brake_lever_pos: bool, + eng_1_master_on: bool, + eng_2_master_on: bool, + + has_air_pressure_low_fault: bool, + has_low_level_fault: bool, +} +impl A380PowerTransferUnitController { + const DURATION_OF_PTU_INHIBIT_AFTER_CARGO_DOOR_OPERATION: Duration = Duration::from_secs(40); + + fn new(context: &mut InitContext, powered_by: ElectricalBusType) -> Self { + Self { + park_brake_lever_pos_id: context.get_identifier("PARK_BRAKE_LEVER_POS".to_owned()), + general_eng_1_starter_active_id: context + .get_identifier("GENERAL ENG STARTER ACTIVE:1".to_owned()), + general_eng_2_starter_active_id: context + .get_identifier("GENERAL ENG STARTER ACTIVE:2".to_owned()), + + is_powered: false, + powered_by, + should_enable: false, + should_inhibit_ptu_after_cargo_door_operation: DelayedFalseLogicGate::new( + Self::DURATION_OF_PTU_INHIBIT_AFTER_CARGO_DOOR_OPERATION, + ), + + parking_brake_lever_pos: false, + eng_1_master_on: false, + eng_2_master_on: false, + + has_air_pressure_low_fault: false, + has_low_level_fault: false, + } + } + + fn update( + &mut self, + context: &UpdateContext, + overhead_panel: &A380HydraulicOverheadPanel, + forward_cargo_door_controller: &A380DoorController, + aft_cargo_door_controller: &A380DoorController, + pushback_tug: &PushbackTug, + lgciu2: &impl LgciuInterface, + reservoir_left_side: &Reservoir, + reservoir_right_side: &Reservoir, + ) { + self.should_inhibit_ptu_after_cargo_door_operation.update( + context, + forward_cargo_door_controller.should_pressurise_hydraulics() + || aft_cargo_door_controller.should_pressurise_hydraulics(), + ); + + let ptu_inhibited = self.should_inhibit_ptu_after_cargo_door_operation.output() + && overhead_panel.yellow_epump_push_button_is_auto(); + + let should_enable_if_powered = overhead_panel.ptu_push_button_is_auto() + && (!lgciu2.nose_gear_compressed(false) + || self.eng_1_master_on && self.eng_2_master_on + || !self.eng_1_master_on && !self.eng_2_master_on + || (!self.parking_brake_lever_pos + && !pushback_tug.is_nose_wheel_steering_pin_inserted())) + && !ptu_inhibited; + + // When there is no power, the PTU is always ON. + self.should_enable = !self.is_powered || should_enable_if_powered; + + self.update_low_air_pressure(reservoir_left_side, reservoir_right_side, overhead_panel); + + self.update_low_level(reservoir_left_side, reservoir_right_side, overhead_panel); + } + + fn update_low_air_pressure( + &mut self, + reservoir_left_side: &Reservoir, + reservoir_right_side: &Reservoir, + overhead_panel: &A380HydraulicOverheadPanel, + ) { + self.has_air_pressure_low_fault = (reservoir_left_side.is_low_air_pressure() + || reservoir_right_side.is_low_air_pressure()) + && overhead_panel.ptu_push_button_is_auto(); + } + + fn update_low_level( + &mut self, + reservoir_left_side: &Reservoir, + reservoir_right_side: &Reservoir, + overhead_panel: &A380HydraulicOverheadPanel, + ) { + self.has_low_level_fault = (reservoir_left_side.is_low_level() + || reservoir_right_side.is_low_level()) + && overhead_panel.ptu_push_button_is_auto(); + } + + fn has_air_pressure_low_fault(&self) -> bool { + self.has_air_pressure_low_fault + } + + fn has_low_level_fault(&self) -> bool { + self.has_low_level_fault + } +} +impl PowerTransferUnitController for A380PowerTransferUnitController { + fn should_enable(&self) -> bool { + self.should_enable + } +} +impl SimulationElement for A380PowerTransferUnitController { + fn read(&mut self, reader: &mut SimulatorReader) { + self.parking_brake_lever_pos = reader.read(&self.park_brake_lever_pos_id); + self.eng_1_master_on = reader.read(&self.general_eng_1_starter_active_id); + self.eng_2_master_on = reader.read(&self.general_eng_2_starter_active_id); + } + + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.is_powered = buses.is_powered(self.powered_by); + } +} + +struct A380RamAirTurbineController { + is_solenoid_1_powered: bool, + solenoid_1_bus: ElectricalBusType, + + is_solenoid_2_powered: bool, + solenoid_2_bus: ElectricalBusType, + + should_deploy: bool, +} +impl A380RamAirTurbineController { + fn new(solenoid_1_bus: ElectricalBusType, solenoid_2_bus: ElectricalBusType) -> Self { + Self { + is_solenoid_1_powered: false, + solenoid_1_bus, + + is_solenoid_2_powered: false, + solenoid_2_bus, + + should_deploy: false, + } + } + + fn update( + &mut self, + context: &UpdateContext, + overhead_panel: &A380HydraulicOverheadPanel, + rat_and_emer_gen_man_on: &impl EmergencyElectricalRatPushButton, + emergency_elec_state: &impl EmergencyElectricalState, + ) { + let solenoid_1_should_trigger_deployment_if_powered = + overhead_panel.rat_man_on_push_button_is_pressed(); + + let solenoid_2_should_trigger_deployment_if_powered = + emergency_elec_state.is_in_emergency_elec() || rat_and_emer_gen_man_on.is_pressed(); + + // due to initialization issues the RAT will not deployed in any case when simulation has just started + self.should_deploy = context.is_sim_ready() + && ((self.is_solenoid_1_powered && solenoid_1_should_trigger_deployment_if_powered) + || (self.is_solenoid_2_powered && solenoid_2_should_trigger_deployment_if_powered)); + } +} +impl RamAirTurbineController for A380RamAirTurbineController { + fn should_deploy(&self) -> bool { + self.should_deploy + } +} +impl SimulationElement for A380RamAirTurbineController { + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.is_solenoid_1_powered = buses.is_powered(self.solenoid_1_bus); + self.is_solenoid_2_powered = buses.is_powered(self.solenoid_2_bus); + } +} + +struct A380BrakeSystemOutputs { + left_demand: Ratio, + right_demand: Ratio, + pressure_limit: Pressure, +} +impl A380BrakeSystemOutputs { + fn new() -> Self { + Self { + left_demand: Ratio::new::(0.), + right_demand: Ratio::new::(0.), + pressure_limit: Pressure::new::(3000.), + } + } + + fn set_pressure_limit(&mut self, pressure_limit: Pressure) { + self.pressure_limit = pressure_limit; + } + + fn set_brake_demands(&mut self, left_demand: Ratio, right_demand: Ratio) { + self.left_demand = left_demand + .min(Ratio::new::(1.)) + .max(Ratio::new::(0.)); + self.right_demand = right_demand + .min(Ratio::new::(1.)) + .max(Ratio::new::(0.)); + } + + fn set_no_demands(&mut self) { + self.left_demand = Ratio::new::(0.); + self.right_demand = Ratio::new::(0.); + } + + fn set_max_demands(&mut self) { + self.left_demand = Ratio::new::(1.); + self.right_demand = Ratio::new::(1.); + } + + fn left_demand(&self) -> Ratio { + self.left_demand + } + + fn right_demand(&self) -> Ratio { + self.right_demand + } +} +impl BrakeCircuitController for A380BrakeSystemOutputs { + fn pressure_limit(&self) -> Pressure { + self.pressure_limit + } + + fn left_brake_demand(&self) -> Ratio { + self.left_demand + } + + fn right_brake_demand(&self) -> Ratio { + self.right_demand + } +} + +struct A380HydraulicBrakeSteerComputerUnit { + park_brake_lever_pos_id: VariableIdentifier, + + antiskid_brakes_active_id: VariableIdentifier, + left_brake_pedal_input_id: VariableIdentifier, + right_brake_pedal_input_id: VariableIdentifier, + + ground_speed_id: VariableIdentifier, + + rudder_pedal_input_id: VariableIdentifier, + tiller_handle_input_id: VariableIdentifier, + tiller_pedal_disconnect_id: VariableIdentifier, + autopilot_nosewheel_demand_id: VariableIdentifier, + + autobrake_controller: A380AutobrakeController, + parking_brake_demand: bool, + + left_brake_pilot_input: Ratio, + right_brake_pilot_input: Ratio, + + norm_brake_outputs: A380BrakeSystemOutputs, + alternate_brake_outputs: A380BrakeSystemOutputs, + + normal_brakes_available: bool, + should_disable_auto_brake_when_retracting: DelayedTrueLogicGate, + anti_skid_activated: bool, + + tiller_pedal_disconnect: bool, + tiller_handle_position: Ratio, + rudder_pedal_position: Ratio, + autopilot_nosewheel_demand: Ratio, + + pedal_steering_limiter: SteeringAngleLimiter<5>, + pedal_input_map: SteeringRatioToAngle<6>, + tiller_steering_limiter: SteeringAngleLimiter<5>, + tiller_input_map: SteeringRatioToAngle<6>, + final_steering_position_request: Angle, + + ground_speed: Velocity, +} +impl A380HydraulicBrakeSteerComputerUnit { + const RUDDER_PEDAL_INPUT_GAIN: f64 = 32.; + const RUDDER_PEDAL_INPUT_MAP: [f64; 6] = [0., 1., 2., 32., 32., 32.]; + const RUDDER_PEDAL_INPUT_CURVE_MAP: [f64; 6] = [0., 0., 2., 6.4, 6.4, 6.4]; + const MAX_RUDDER_INPUT_INCLUDING_AUTOPILOT_DEGREE: f64 = 6.; + + const SPEED_MAP_FOR_PEDAL_ACTION_KNOT: [f64; 5] = [0., 40., 130., 1500.0, 2800.0]; + const STEERING_ANGLE_FOR_PEDAL_ACTION_DEGREE: [f64; 5] = [1., 1., 0., 0., 0.]; + + const TILLER_INPUT_GAIN: f64 = 75.; + const TILLER_INPUT_MAP: [f64; 6] = [0., 1., 20., 40., 66., 75.]; + const TILLER_INPUT_CURVE_MAP: [f64; 6] = [0., 0., 4., 15., 45., 74.]; + + const AUTOPILOT_STEERING_INPUT_GAIN: f64 = 6.; + + const SPEED_MAP_FOR_TILLER_ACTION_KNOT: [f64; 5] = [0., 20., 70., 1500.0, 2800.0]; + const STEERING_ANGLE_FOR_TILLER_ACTION_DEGREE: [f64; 5] = [1., 1., 0., 0., 0.]; + + const MAX_STEERING_ANGLE_DEMAND_DEGREES: f64 = 74.; + + // Minimum pressure hysteresis on green until main switched on ALTN brakes + // Feedback by Cpt. Chaos — 25/04/2021 #pilot-feedback + const MIN_PRESSURE_BRAKE_ALTN_HYST_LO: f64 = 1305.; + const MIN_PRESSURE_BRAKE_ALTN_HYST_HI: f64 = 2176.; + + // Min pressure when parking brake enabled. Lower normal braking is allowed to use pilot input as emergency braking + // Feedback by avteknisyan — 25/04/2021 #pilot-feedback + const MIN_PRESSURE_PARK_BRAKE_EMERGENCY: f64 = 507.; + + const AUTOBRAKE_GEAR_RETRACTION_DURATION_S: f64 = 3.; + + const PILOT_INPUT_DETECTION_TRESHOLD: f64 = 0.2; + + fn new(context: &mut InitContext) -> Self { + Self { + park_brake_lever_pos_id: context.get_identifier("PARK_BRAKE_LEVER_POS".to_owned()), + antiskid_brakes_active_id: context.get_identifier("ANTISKID BRAKES ACTIVE".to_owned()), + left_brake_pedal_input_id: context.get_identifier("LEFT_BRAKE_PEDAL_INPUT".to_owned()), + right_brake_pedal_input_id: context + .get_identifier("RIGHT_BRAKE_PEDAL_INPUT".to_owned()), + + ground_speed_id: context.get_identifier("GPS GROUND SPEED".to_owned()), + rudder_pedal_input_id: context.get_identifier("RUDDER_PEDAL_POSITION_RATIO".to_owned()), + tiller_handle_input_id: context.get_identifier("TILLER_HANDLE_POSITION".to_owned()), + tiller_pedal_disconnect_id: context + .get_identifier("TILLER_PEDAL_DISCONNECT".to_owned()), + autopilot_nosewheel_demand_id: context + .get_identifier("AUTOPILOT_NOSEWHEEL_DEMAND".to_owned()), + + autobrake_controller: A380AutobrakeController::new(context), + + parking_brake_demand: true, + left_brake_pilot_input: Ratio::new::(0.0), + right_brake_pilot_input: Ratio::new::(0.0), + norm_brake_outputs: A380BrakeSystemOutputs::new(), + alternate_brake_outputs: A380BrakeSystemOutputs::new(), + normal_brakes_available: false, + should_disable_auto_brake_when_retracting: DelayedTrueLogicGate::new( + Duration::from_secs_f64(Self::AUTOBRAKE_GEAR_RETRACTION_DURATION_S), + ), + anti_skid_activated: true, + + tiller_pedal_disconnect: false, + tiller_handle_position: Ratio::new::(0.), + rudder_pedal_position: Ratio::new::(0.), + autopilot_nosewheel_demand: Ratio::new::(0.), + + pedal_steering_limiter: SteeringAngleLimiter::new( + Self::SPEED_MAP_FOR_PEDAL_ACTION_KNOT, + Self::STEERING_ANGLE_FOR_PEDAL_ACTION_DEGREE, + ), + pedal_input_map: SteeringRatioToAngle::new( + Ratio::new::(Self::RUDDER_PEDAL_INPUT_GAIN), + Self::RUDDER_PEDAL_INPUT_MAP, + Self::RUDDER_PEDAL_INPUT_CURVE_MAP, + ), + tiller_steering_limiter: SteeringAngleLimiter::new( + Self::SPEED_MAP_FOR_TILLER_ACTION_KNOT, + Self::STEERING_ANGLE_FOR_TILLER_ACTION_DEGREE, + ), + tiller_input_map: SteeringRatioToAngle::new( + Ratio::new::(Self::TILLER_INPUT_GAIN), + Self::TILLER_INPUT_MAP, + Self::TILLER_INPUT_CURVE_MAP, + ), + final_steering_position_request: Angle::new::(0.), + + ground_speed: Velocity::new::(0.), + } + } + + fn allow_autobrake_arming(&self) -> bool { + self.anti_skid_activated && self.normal_brakes_available + } + + fn update_normal_braking_availability(&mut self, normal_braking_circuit_pressure: Pressure) { + if normal_braking_circuit_pressure.get::() > Self::MIN_PRESSURE_BRAKE_ALTN_HYST_HI + && (self.left_brake_pilot_input.get::() < Self::PILOT_INPUT_DETECTION_TRESHOLD + && self.right_brake_pilot_input.get::() + < Self::PILOT_INPUT_DETECTION_TRESHOLD) + { + self.normal_brakes_available = true; + } else if normal_braking_circuit_pressure.get::() + < Self::MIN_PRESSURE_BRAKE_ALTN_HYST_LO + { + self.normal_brakes_available = false; + } + } + + fn update_brake_pressure_limitation(&mut self) { + let yellow_manual_braking_input = self.left_brake_pilot_input + > self.alternate_brake_outputs.left_demand() + Ratio::new::(0.2) + || self.right_brake_pilot_input + > self.alternate_brake_outputs.right_demand() + Ratio::new::(0.2); + + // Nominal braking from pedals is limited to 2538psi + self.norm_brake_outputs + .set_pressure_limit(Pressure::new::(2538.)); + + let alternate_brake_pressure_limit = Pressure::new::(if self.parking_brake_demand { + // If no pilot action, standard park brake pressure limit + if !yellow_manual_braking_input { + 2103. + } else { + // Else manual action limited to a higher max nominal pressure + 2538. + } + } else if !self.anti_skid_activated { + 1160. + } else { + // Else if any manual braking we use standard limit + 2538. + }); + + self.alternate_brake_outputs + .set_pressure_limit(alternate_brake_pressure_limit); + } + + /// Updates brakes and nose steering demands + fn update( + &mut self, + context: &UpdateContext, + current_pressure: &impl SectionPressure, + alternate_circuit: &BrakeCircuit, + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + autobrake_panel: &AutobrakePanel, + engine1: &impl Engine, + engine2: &impl Engine, + ) { + self.update_steering_demands(lgciu1, engine1, engine2); + + self.update_normal_braking_availability(current_pressure.pressure()); + self.update_brake_pressure_limitation(); + + self.autobrake_controller.update( + context, + autobrake_panel, + self.allow_autobrake_arming(), + self.left_brake_pilot_input, + self.right_brake_pilot_input, + lgciu1, + lgciu2, + ); + + let is_in_flight_gear_lever_up = !(lgciu1.left_and_right_gear_compressed(true) + || lgciu2.left_and_right_gear_compressed(true) + || lgciu1.gear_handle_is_down()); + + self.should_disable_auto_brake_when_retracting + .update(context, is_in_flight_gear_lever_up); + + if is_in_flight_gear_lever_up { + if self.should_disable_auto_brake_when_retracting.output() { + self.norm_brake_outputs.set_no_demands(); + } else { + // Slight brake pressure to stop the spinning wheels (have no pressure data available yet, 0.2 is random one) + self.norm_brake_outputs + .set_brake_demands(Ratio::new::(0.2), Ratio::new::(0.2)); + } + + self.alternate_brake_outputs.set_no_demands(); + } else { + let green_used_for_brakes = self.normal_brakes_available + && self.anti_skid_activated + && !self.parking_brake_demand; + + if green_used_for_brakes { + // Final output on normal brakes is max(pilot demand , autobrake demand) to allow pilot override autobrake demand + self.norm_brake_outputs.set_brake_demands( + self.left_brake_pilot_input + .max(self.autobrake_controller.brake_output()), + self.right_brake_pilot_input + .max(self.autobrake_controller.brake_output()), + ); + + self.alternate_brake_outputs.set_no_demands(); + } else { + self.norm_brake_outputs.set_no_demands(); + + if !self.parking_brake_demand { + // Normal braking but using alternate circuit + self.alternate_brake_outputs.set_brake_demands( + self.left_brake_pilot_input, + self.right_brake_pilot_input, + ); + } else { + // Else we just use parking brake + self.alternate_brake_outputs.set_max_demands(); + + // Special case: parking brake on but yellow can't provide enough brakes: green are allowed to brake for emergency + if alternate_circuit.left_brake_pressure().get::() + < Self::MIN_PRESSURE_PARK_BRAKE_EMERGENCY + || alternate_circuit.right_brake_pressure().get::() + < Self::MIN_PRESSURE_PARK_BRAKE_EMERGENCY + { + self.norm_brake_outputs.set_brake_demands( + self.left_brake_pilot_input, + self.right_brake_pilot_input, + ); + } + } + } + } + } + + fn update_steering_demands( + &mut self, + lgciu1: &impl LgciuInterface, + engine1: &impl Engine, + engine2: &impl Engine, + ) { + let steer_angle_from_autopilot = Angle::new::( + self.autopilot_nosewheel_demand.get::() * Self::AUTOPILOT_STEERING_INPUT_GAIN, + ); + + let steer_angle_from_pedals = if self.tiller_pedal_disconnect { + Angle::new::(0.) + } else { + self.pedal_input_map + .angle_demand_from_input_demand(self.rudder_pedal_position) + }; + + // TODO Here ground speed would be probably computed from wheel sensor logic + let final_steer_rudder_plus_autopilot = self.pedal_steering_limiter.angle_from_speed( + self.ground_speed, + (steer_angle_from_pedals + steer_angle_from_autopilot) + .min(Angle::new::( + Self::MAX_RUDDER_INPUT_INCLUDING_AUTOPILOT_DEGREE, + )) + .max(Angle::new::( + -Self::MAX_RUDDER_INPUT_INCLUDING_AUTOPILOT_DEGREE, + )), + ); + + let steer_angle_from_tiller = self.tiller_steering_limiter.angle_from_speed( + self.ground_speed, + self.tiller_input_map + .angle_demand_from_input_demand(self.tiller_handle_position), + ); + + let is_both_engine_low_oil_pressure = + engine1.oil_pressure_is_low() && engine2.oil_pressure_is_low(); + + self.final_steering_position_request = if !is_both_engine_low_oil_pressure + && self.anti_skid_activated + && lgciu1.nose_gear_compressed(false) + { + (final_steer_rudder_plus_autopilot + steer_angle_from_tiller) + .min(Angle::new::( + Self::MAX_STEERING_ANGLE_DEMAND_DEGREES, + )) + .max(Angle::new::( + -Self::MAX_STEERING_ANGLE_DEMAND_DEGREES, + )) + } else { + Angle::new::(0.) + }; + } + + fn norm_controller(&self) -> &impl BrakeCircuitController { + &self.norm_brake_outputs + } + + fn alternate_controller(&self) -> &impl BrakeCircuitController { + &self.alternate_brake_outputs + } +} +impl SimulationElement for A380HydraulicBrakeSteerComputerUnit { + fn accept(&mut self, visitor: &mut T) { + self.autobrake_controller.accept(visitor); + visitor.visit(self); + } + + fn read(&mut self, reader: &mut SimulatorReader) { + self.parking_brake_demand = reader.read(&self.park_brake_lever_pos_id); + + self.anti_skid_activated = reader.read(&self.antiskid_brakes_active_id); + self.left_brake_pilot_input = + Ratio::new::(reader.read(&self.left_brake_pedal_input_id)); + self.right_brake_pilot_input = + Ratio::new::(reader.read(&self.right_brake_pedal_input_id)); + + self.tiller_handle_position = + Ratio::new::(reader.read(&self.tiller_handle_input_id)); + self.rudder_pedal_position = Ratio::new::(reader.read(&self.rudder_pedal_input_id)); + self.tiller_pedal_disconnect = reader.read(&self.tiller_pedal_disconnect_id); + self.ground_speed = reader.read(&self.ground_speed_id); + + self.autopilot_nosewheel_demand = + Ratio::new::(reader.read(&self.autopilot_nosewheel_demand_id)); + } +} +impl SteeringController for A380HydraulicBrakeSteerComputerUnit { + fn requested_position(&self) -> Angle { + self.final_steering_position_request + } +} + +struct A380BrakingForce { + brake_left_force_factor_id: VariableIdentifier, + brake_right_force_factor_id: VariableIdentifier, + trailing_edge_flaps_left_percent_id: VariableIdentifier, + trailing_edge_flaps_right_percent_id: VariableIdentifier, + + enabled_chocks_id: VariableIdentifier, + light_beacon_on_id: VariableIdentifier, + + left_braking_force: f64, + right_braking_force: f64, + + flap_position: f64, + + is_chocks_enabled: bool, + is_light_beacon_on: bool, +} +impl A380BrakingForce { + const REFERENCE_PRESSURE_FOR_MAX_FORCE: f64 = 2538.; + + const FLAPS_BREAKPOINTS: [f64; 3] = [0., 50., 100.]; + const FLAPS_PENALTY_PERCENT: [f64; 3] = [5., 5., 0.]; + + pub fn new(context: &mut InitContext) -> Self { + A380BrakingForce { + brake_left_force_factor_id: context + .get_identifier("BRAKE LEFT FORCE FACTOR".to_owned()), + brake_right_force_factor_id: context + .get_identifier("BRAKE RIGHT FORCE FACTOR".to_owned()), + trailing_edge_flaps_left_percent_id: context + .get_identifier("LEFT_FLAPS_POSITION_PERCENT".to_owned()), + trailing_edge_flaps_right_percent_id: context + .get_identifier("RIGHT_FLAPS_POSITION_PERCENT".to_owned()), + + enabled_chocks_id: context.get_identifier("MODEL_WHEELCHOCKS_ENABLED".to_owned()), + light_beacon_on_id: context.get_identifier("LIGHT BEACON ON".to_owned()), + + left_braking_force: 0., + right_braking_force: 0., + + flap_position: 0., + + is_chocks_enabled: false, + is_light_beacon_on: false, + } + } + + pub fn update_forces( + &mut self, + context: &UpdateContext, + norm_brakes: &BrakeCircuit, + altn_brakes: &BrakeCircuit, + engine1: &impl Engine, + engine2: &impl Engine, + pushback_tug: &PushbackTug, + ) { + // Base formula for output force is output_force[0:1] = 50 * sqrt(current_pressure) / Max_brake_pressure + // This formula gives a bit more punch for lower brake pressures (like 1000 psi alternate braking), as linear formula + // gives really too low brake force for 1000psi + + let left_force_norm = 50. * norm_brakes.left_brake_pressure().get::().sqrt() + / Self::REFERENCE_PRESSURE_FOR_MAX_FORCE; + let left_force_altn = 50. * altn_brakes.left_brake_pressure().get::().sqrt() + / Self::REFERENCE_PRESSURE_FOR_MAX_FORCE; + self.left_braking_force = left_force_norm + left_force_altn; + self.left_braking_force = self.left_braking_force.max(0.).min(1.); + + let right_force_norm = 50. * norm_brakes.right_brake_pressure().get::().sqrt() + / Self::REFERENCE_PRESSURE_FOR_MAX_FORCE; + let right_force_altn = 50. * altn_brakes.right_brake_pressure().get::().sqrt() + / Self::REFERENCE_PRESSURE_FOR_MAX_FORCE; + self.right_braking_force = right_force_norm + right_force_altn; + self.right_braking_force = self.right_braking_force.max(0.).min(1.); + + self.correct_with_flaps_state(context); + + self.update_chocks_braking(context, engine1, engine2, pushback_tug); + } + + fn correct_with_flaps_state(&mut self, context: &UpdateContext) { + let flap_correction = Ratio::new::(interpolation( + &Self::FLAPS_BREAKPOINTS, + &Self::FLAPS_PENALTY_PERCENT, + self.flap_position, + )); + + // Using airspeed with formula 0.1 * sqrt(airspeed) to get a 0 to 1 ratio to use our flap correction + // This way the less airspeed, the less our correction is used as it is an aerodynamic effect on brakes + let mut airspeed_corrective_factor = + 0.1 * context.indicated_airspeed().get::().abs().sqrt(); + airspeed_corrective_factor = airspeed_corrective_factor.min(1.0); + + let final_flaps_correction_with_speed = flap_correction * airspeed_corrective_factor; + + self.left_braking_force = self.left_braking_force + - (self.left_braking_force * final_flaps_correction_with_speed.get::()); + + self.right_braking_force = self.right_braking_force + - (self.right_braking_force * final_flaps_correction_with_speed.get::()); + } + + fn update_chocks_braking( + &mut self, + context: &UpdateContext, + engine1: &impl Engine, + engine2: &impl Engine, + pushback_tug: &PushbackTug, + ) { + let chocks_on_wheels = context.is_on_ground() + && engine1.corrected_n1().get::() < 3.5 + && engine2.corrected_n1().get::() < 3.5 + && !pushback_tug.is_nose_wheel_steering_pin_inserted() + && !self.is_light_beacon_on; + + if self.is_chocks_enabled && chocks_on_wheels { + self.left_braking_force = 1.; + self.right_braking_force = 1.; + } + } +} + +impl SimulationElement for A380BrakingForce { + fn write(&self, writer: &mut SimulatorWriter) { + // BRAKE XXXX FORCE FACTOR is the actual braking force we want the plane to generate in the simulator + writer.write(&self.brake_left_force_factor_id, self.left_braking_force); + writer.write(&self.brake_right_force_factor_id, self.right_braking_force); + } + + fn read(&mut self, reader: &mut SimulatorReader) { + let left_flap: f64 = reader.read(&self.trailing_edge_flaps_left_percent_id); + let right_flap: f64 = reader.read(&self.trailing_edge_flaps_right_percent_id); + self.flap_position = (left_flap + right_flap) / 2.; + + self.is_chocks_enabled = reader.read(&self.enabled_chocks_id); + self.is_light_beacon_on = reader.read(&self.light_beacon_on_id); + } +} + +#[derive(PartialEq, Clone, Copy)] +enum DoorControlState { + DownLocked = 0, + NoControl = 1, + HydControl = 2, + UpLocked = 3, +} + +struct A380DoorController { + requested_position_id: VariableIdentifier, + + control_state: DoorControlState, + + position_requested: Ratio, + + duration_in_no_control: Duration, + duration_in_hyd_control: Duration, + + should_close_valves: bool, + control_position_request: Ratio, + should_unlock: bool, +} +impl A380DoorController { + // Duration which the hydraulic valves sends a open request when request is closing (this is done on real aircraft so uplock can be easily unlocked without friction) + const UP_CONTROL_TIME_BEFORE_DOWN_CONTROL: Duration = Duration::from_millis(200); + + // Delay from the ground crew unlocking the door to the time they start requiring up movement in control panel + const DELAY_UNLOCK_TO_HYDRAULIC_CONTROL: Duration = Duration::from_secs(5); + + fn new(context: &mut InitContext, id: &str) -> Self { + Self { + requested_position_id: context.get_identifier(format!("{}_DOOR_CARGO_OPEN_REQ", id)), + control_state: DoorControlState::DownLocked, + position_requested: Ratio::new::(0.), + + duration_in_no_control: Duration::from_secs(0), + duration_in_hyd_control: Duration::from_secs(0), + + should_close_valves: true, + control_position_request: Ratio::new::(0.), + should_unlock: false, + } + } + + fn update( + &mut self, + context: &UpdateContext, + door: &CargoDoor, + current_pressure: &impl SectionPressure, + ) { + self.control_state = + self.determine_control_state_and_lock_action(door, current_pressure.pressure()); + self.update_timers(context); + self.update_actions_from_state(); + } + + fn update_timers(&mut self, context: &UpdateContext) { + if self.control_state == DoorControlState::NoControl { + self.duration_in_no_control += context.delta(); + } else { + self.duration_in_no_control = Duration::from_secs(0); + } + + if self.control_state == DoorControlState::HydControl { + self.duration_in_hyd_control += context.delta(); + } else { + self.duration_in_hyd_control = Duration::from_secs(0); + } + } + + fn update_actions_from_state(&mut self) { + match self.control_state { + DoorControlState::DownLocked => {} + DoorControlState::NoControl => { + self.should_close_valves = true; + } + DoorControlState::HydControl => { + self.should_close_valves = false; + self.control_position_request = if self.position_requested > Ratio::new::(0.) + || self.duration_in_hyd_control < Self::UP_CONTROL_TIME_BEFORE_DOWN_CONTROL + { + Ratio::new::(1.0) + } else { + Ratio::new::(-0.1) + } + } + DoorControlState::UpLocked => { + self.should_close_valves = true; + } + } + } + + fn determine_control_state_and_lock_action( + &mut self, + door: &CargoDoor, + current_pressure: Pressure, + ) -> DoorControlState { + match self.control_state { + DoorControlState::DownLocked if self.position_requested > Ratio::new::(0.) => { + self.should_unlock = true; + DoorControlState::NoControl + } + DoorControlState::NoControl + if self.duration_in_no_control > Self::DELAY_UNLOCK_TO_HYDRAULIC_CONTROL => + { + self.should_unlock = false; + DoorControlState::HydControl + } + DoorControlState::HydControl if door.is_locked() => { + self.should_unlock = false; + DoorControlState::DownLocked + } + DoorControlState::HydControl + if door.position() > Ratio::new::(0.9) + && self.position_requested > Ratio::new::(0.5) => + { + self.should_unlock = false; + DoorControlState::UpLocked + } + DoorControlState::UpLocked + if self.position_requested < Ratio::new::(1.) + && current_pressure > Pressure::new::(1000.) => + { + DoorControlState::HydControl + } + _ => self.control_state, + } + } + + fn should_pressurise_hydraulics(&self) -> bool { + (self.control_state == DoorControlState::UpLocked + && self.position_requested < Ratio::new::(1.)) + || self.control_state == DoorControlState::HydControl + } +} +impl HydraulicAssemblyController for A380DoorController { + fn requested_mode(&self) -> LinearActuatorMode { + if self.should_close_valves { + LinearActuatorMode::ClosedValves + } else { + LinearActuatorMode::PositionControl + } + } + + fn requested_position(&self) -> Ratio { + self.control_position_request + } + + fn should_lock(&self) -> bool { + !self.should_unlock + } + + fn requested_lock_position(&self) -> Ratio { + Ratio::new::(0.) + } +} +impl HydraulicLocking for A380DoorController {} +impl SimulationElement for A380DoorController { + fn read(&mut self, reader: &mut SimulatorReader) { + self.position_requested = Ratio::new::(reader.read(&self.requested_position_id)); + } +} + +struct CargoDoor { + hydraulic_assembly: HydraulicLinearActuatorAssembly<1>, + + position_id: VariableIdentifier, + locked_id: VariableIdentifier, + position: Ratio, + + is_locked: bool, + + aerodynamic_model: AerodynamicModel, +} +impl CargoDoor { + fn new( + context: &mut InitContext, + id: &str, + hydraulic_assembly: HydraulicLinearActuatorAssembly<1>, + aerodynamic_model: AerodynamicModel, + ) -> Self { + Self { + hydraulic_assembly, + position_id: context.get_identifier(format!("{}_DOOR_CARGO_POSITION", id)), + locked_id: context.get_identifier(format!("{}_DOOR_CARGO_LOCKED", id)), + + position: Ratio::new::(0.), + + is_locked: true, + + aerodynamic_model, + } + } + + fn position(&self) -> Ratio { + self.position + } + + fn is_locked(&self) -> bool { + self.is_locked + } + + fn actuator(&mut self) -> &mut impl Actuator { + self.hydraulic_assembly.actuator(0) + } + + fn update( + &mut self, + context: &UpdateContext, + cargo_door_controller: &(impl HydraulicAssemblyController + HydraulicLocking), + current_pressure: &impl SectionPressure, + ) { + self.aerodynamic_model + .update_body(context, self.hydraulic_assembly.body()); + self.hydraulic_assembly.update( + context, + std::slice::from_ref(cargo_door_controller), + [current_pressure.pressure()], + ); + + self.position = self.hydraulic_assembly.position_normalized(); + self.is_locked = self.hydraulic_assembly.is_locked(); + } +} +impl SimulationElement for CargoDoor { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.position_id, self.position()); + writer.write(&self.locked_id, self.is_locked()); + } +} + +struct PushbackTug { + nw_strg_disc_memo_id: VariableIdentifier, + state_id: VariableIdentifier, + steer_angle_id: VariableIdentifier, + + steering_angle_raw: Angle, + steering_angle: LowPassFilter, + + // Type of pushback: + // 0 = Straight + // 1 = Left + // 2 = Right + // 3 = Assumed to be no pushback + // 4 = might be finishing pushback, to confirm + state: f64, + nose_wheel_steering_pin_inserted: DelayedFalseLogicGate, +} +impl PushbackTug { + const DURATION_AFTER_WHICH_NWS_PIN_IS_REMOVED_AFTER_PUSHBACK: Duration = + Duration::from_secs(15); + + const STATE_NO_PUSHBACK: f64 = 3.; + + const STEERING_ANGLE_FILTER_TIME_CONSTANT: Duration = Duration::from_millis(1500); + + fn new(context: &mut InitContext) -> Self { + Self { + nw_strg_disc_memo_id: context.get_identifier("HYD_NW_STRG_DISC_ECAM_MEMO".to_owned()), + state_id: context.get_identifier("PUSHBACK STATE".to_owned()), + steer_angle_id: context.get_identifier("PUSHBACK ANGLE".to_owned()), + + steering_angle_raw: Angle::default(), + steering_angle: LowPassFilter::new(Self::STEERING_ANGLE_FILTER_TIME_CONSTANT), + + state: Self::STATE_NO_PUSHBACK, + nose_wheel_steering_pin_inserted: DelayedFalseLogicGate::new( + Self::DURATION_AFTER_WHICH_NWS_PIN_IS_REMOVED_AFTER_PUSHBACK, + ), + } + } + + fn update(&mut self, context: &UpdateContext) { + self.nose_wheel_steering_pin_inserted + .update(context, self.is_pushing()); + + if self.is_pushing() { + self.steering_angle + .update(context.delta(), self.steering_angle_raw); + } else { + self.steering_angle.reset(Angle::default()); + } + } + + fn is_pushing(&self) -> bool { + (self.state - PushbackTug::STATE_NO_PUSHBACK).abs() > f64::EPSILON + } +} +impl Pushback for PushbackTug { + fn is_nose_wheel_steering_pin_inserted(&self) -> bool { + self.nose_wheel_steering_pin_inserted.output() + } + + fn steering_angle(&self) -> Angle { + self.steering_angle.output() + } +} +impl SimulationElement for PushbackTug { + fn read(&mut self, reader: &mut SimulatorReader) { + self.state = reader.read(&self.state_id); + + self.steering_angle_raw = Angle::new::(reader.read(&self.steer_angle_id)); + } + + fn write(&self, writer: &mut SimulatorWriter) { + writer.write( + &self.nw_strg_disc_memo_id, + self.is_nose_wheel_steering_pin_inserted(), + ); + } +} + +/// Autobrake controller computes the state machine of the autobrake logic, and the deceleration target +/// that we expect for the plane +pub struct A380AutobrakeController { + armed_mode_id: VariableIdentifier, + armed_mode_id_set: VariableIdentifier, + decel_light_id: VariableIdentifier, + active_id: VariableIdentifier, + spoilers_ground_spoilers_active_id: VariableIdentifier, + external_disarm_event_id: VariableIdentifier, + + deceleration_governor: AutobrakeDecelerationGovernor, + + target: Acceleration, + mode: AutobrakeMode, + + arming_is_allowed_by_bcu: bool, + left_brake_pedal_input: Ratio, + right_brake_pedal_input: Ratio, + + ground_spoilers_are_deployed: bool, + last_ground_spoilers_are_deployed: bool, + + should_disarm_after_time_in_flight: DelayedPulseTrueLogicGate, + should_reject_max_mode_after_time_in_flight: DelayedTrueLogicGate, + + external_disarm_event: bool, +} +impl A380AutobrakeController { + const DURATION_OF_FLIGHT_TO_DISARM_AUTOBRAKE_SECS: f64 = 10.; + + // Dynamic decel target map versus time for any mode that needs it + const LOW_MODE_DECEL_PROFILE_ACCEL_MS2: [f64; 4] = [4., 4., 0., -2.]; + const LOW_MODE_DECEL_PROFILE_TIME_S: [f64; 4] = [0., 1.99, 2., 4.5]; + + const MED_MODE_DECEL_PROFILE_ACCEL_MS2: [f64; 5] = [4., 4., 0., -2., -3.]; + const MED_MODE_DECEL_PROFILE_TIME_S: [f64; 5] = [0., 1.99, 2., 2.5, 4.]; + + const MAX_MODE_DECEL_TARGET_MS2: f64 = -6.; + const OFF_MODE_DECEL_TARGET_MS2: f64 = 5.; + + const MARGIN_PERCENT_TO_TARGET_TO_SHOW_DECEL_IN_LO_MED: f64 = 80.; + const TARGET_TO_SHOW_DECEL_IN_MAX_MS2: f64 = -2.7; + + fn new(context: &mut InitContext) -> A380AutobrakeController { + A380AutobrakeController { + armed_mode_id: context.get_identifier("AUTOBRAKES_ARMED_MODE".to_owned()), + armed_mode_id_set: context.get_identifier("AUTOBRAKES_ARMED_MODE_SET".to_owned()), + decel_light_id: context.get_identifier("AUTOBRAKES_DECEL_LIGHT".to_owned()), + active_id: context.get_identifier("AUTOBRAKES_ACTIVE".to_owned()), + spoilers_ground_spoilers_active_id: context + .get_identifier("SPOILERS_GROUND_SPOILERS_ACTIVE".to_owned()), + external_disarm_event_id: context.get_identifier("AUTOBRAKE_DISARM".to_owned()), + + deceleration_governor: AutobrakeDecelerationGovernor::new(), + target: Acceleration::new::(0.), + mode: AutobrakeMode::NONE, + arming_is_allowed_by_bcu: context.is_in_flight(), + left_brake_pedal_input: Ratio::new::(0.), + right_brake_pedal_input: Ratio::new::(0.), + ground_spoilers_are_deployed: false, + last_ground_spoilers_are_deployed: false, + should_disarm_after_time_in_flight: DelayedPulseTrueLogicGate::new( + Duration::from_secs_f64(Self::DURATION_OF_FLIGHT_TO_DISARM_AUTOBRAKE_SECS), + ) + .starting_as(context.is_in_flight(), false), + should_reject_max_mode_after_time_in_flight: DelayedTrueLogicGate::new( + Duration::from_secs_f64(Self::DURATION_OF_FLIGHT_TO_DISARM_AUTOBRAKE_SECS), + ) + .starting_as(context.is_in_flight()), + external_disarm_event: false, + } + } + + fn spoilers_retracted_during_this_update(&self) -> bool { + !self.ground_spoilers_are_deployed && self.last_ground_spoilers_are_deployed + } + + fn brake_output(&self) -> Ratio { + Ratio::new::(self.deceleration_governor.output()) + } + + fn determine_mode( + &mut self, + context: &UpdateContext, + autobrake_panel: &AutobrakePanel, + ) -> AutobrakeMode { + if self.should_disarm(context) { + AutobrakeMode::NONE + } else { + match autobrake_panel.pressed_mode() { + Some(mode) if self.mode == mode => AutobrakeMode::NONE, + Some(mode) + if mode != AutobrakeMode::MAX + || !self.should_reject_max_mode_after_time_in_flight.output() => + { + mode + } + Some(_) | None => self.mode, + } + } + } + + fn should_engage_deceleration_governor(&self, context: &UpdateContext) -> bool { + self.is_armed() && self.ground_spoilers_are_deployed && !self.should_disarm(context) + } + + fn is_armed(&self) -> bool { + self.mode != AutobrakeMode::NONE + } + + fn is_decelerating(&self) -> bool { + match self.mode { + AutobrakeMode::NONE => false, + AutobrakeMode::LOW | AutobrakeMode::MED => { + self.deceleration_demanded() + && self + .deceleration_governor + .is_on_target(Ratio::new::( + Self::MARGIN_PERCENT_TO_TARGET_TO_SHOW_DECEL_IN_LO_MED, + )) + } + _ => { + self.deceleration_demanded() + && self.deceleration_governor.decelerating_at_or_above_rate( + Acceleration::new::( + Self::TARGET_TO_SHOW_DECEL_IN_MAX_MS2, + ), + ) + } + } + } + + fn deceleration_demanded(&self) -> bool { + self.deceleration_governor.is_engaged() + && self.target.get::() < 0. + } + + fn should_disarm_due_to_pedal_input(&self) -> bool { + match self.mode { + AutobrakeMode::NONE => false, + AutobrakeMode::LOW | AutobrakeMode::MED => { + self.left_brake_pedal_input > Ratio::new::(53.) + || self.right_brake_pedal_input > Ratio::new::(53.) + || (self.left_brake_pedal_input > Ratio::new::(11.) + && self.right_brake_pedal_input > Ratio::new::(11.)) + } + AutobrakeMode::MAX => { + self.left_brake_pedal_input > Ratio::new::(77.) + || self.right_brake_pedal_input > Ratio::new::(77.) + || (self.left_brake_pedal_input > Ratio::new::(53.) + && self.right_brake_pedal_input > Ratio::new::(53.)) + } + _ => false, + } + } + + fn should_disarm(&self, context: &UpdateContext) -> bool { + // when a simulation is started in flight, some values need to be ignored for a certain time to ensure + // an unintended disarm is not happening + (self.deceleration_governor.is_engaged() && self.should_disarm_due_to_pedal_input()) + || (context.is_sim_ready() && !self.arming_is_allowed_by_bcu) + || self.spoilers_retracted_during_this_update() + || self.should_disarm_after_time_in_flight.output() + || self.external_disarm_event + || (self.mode == AutobrakeMode::MAX + && self.should_reject_max_mode_after_time_in_flight.output()) + } + + fn calculate_target(&mut self) -> Acceleration { + Acceleration::new::(match self.mode { + AutobrakeMode::NONE => Self::OFF_MODE_DECEL_TARGET_MS2, + AutobrakeMode::LOW => interpolation( + &Self::LOW_MODE_DECEL_PROFILE_TIME_S, + &Self::LOW_MODE_DECEL_PROFILE_ACCEL_MS2, + self.deceleration_governor.time_engaged().as_secs_f64(), + ), + AutobrakeMode::MED => interpolation( + &Self::MED_MODE_DECEL_PROFILE_TIME_S, + &Self::MED_MODE_DECEL_PROFILE_ACCEL_MS2, + self.deceleration_governor.time_engaged().as_secs_f64(), + ), + AutobrakeMode::MAX => Self::MAX_MODE_DECEL_TARGET_MS2, + _ => Self::OFF_MODE_DECEL_TARGET_MS2, + }) + } + + fn update_input_conditions( + &mut self, + context: &UpdateContext, + allow_arming: bool, + pedal_input_left: Ratio, + pedal_input_right: Ratio, + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + ) { + let in_flight_lgciu1 = + !lgciu1.right_gear_compressed(false) && !lgciu1.left_gear_compressed(false); + let in_flight_lgciu2 = + !lgciu2.right_gear_compressed(false) && !lgciu2.left_gear_compressed(false); + + self.should_disarm_after_time_in_flight + .update(context, in_flight_lgciu1 && in_flight_lgciu2); + self.should_reject_max_mode_after_time_in_flight + .update(context, in_flight_lgciu1 && in_flight_lgciu2); + + self.arming_is_allowed_by_bcu = allow_arming; + self.left_brake_pedal_input = pedal_input_left; + self.right_brake_pedal_input = pedal_input_right; + } + + fn update( + &mut self, + context: &UpdateContext, + autobrake_panel: &AutobrakePanel, + allow_arming: bool, + pedal_input_left: Ratio, + pedal_input_right: Ratio, + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + ) { + self.update_input_conditions( + context, + allow_arming, + pedal_input_left, + pedal_input_right, + lgciu1, + lgciu2, + ); + self.mode = self.determine_mode(context, autobrake_panel); + + self.deceleration_governor + .engage_when(self.should_engage_deceleration_governor(context)); + + self.target = self.calculate_target(); + self.deceleration_governor.update(context, self.target); + } +} +impl SimulationElement for A380AutobrakeController { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.armed_mode_id, self.mode as u8 as f64); + writer.write(&self.armed_mode_id_set, -1.); + writer.write(&self.decel_light_id, self.is_decelerating()); + writer.write(&self.active_id, self.deceleration_demanded()); + } + + fn read(&mut self, reader: &mut SimulatorReader) { + self.last_ground_spoilers_are_deployed = self.ground_spoilers_are_deployed; + self.ground_spoilers_are_deployed = reader.read(&self.spoilers_ground_spoilers_active_id); + self.external_disarm_event = reader.read(&self.external_disarm_event_id); + + // Reading current mode in sim to initialize correct mode if sim changes it (from .FLT files for example) + let readed_mode = reader.read_f64(&self.armed_mode_id_set); + if readed_mode >= 0.0 { + self.mode = readed_mode.into(); + } + } +} + +pub(super) struct A380HydraulicOverheadPanel { + edp1_push_button: AutoOffFaultPushButton, + edp2_push_button: AutoOffFaultPushButton, + blue_epump_push_button: AutoOffFaultPushButton, + ptu_push_button: AutoOffFaultPushButton, + rat_push_button: MomentaryPushButton, + yellow_epump_push_button: AutoOnFaultPushButton, + blue_epump_override_push_button: MomentaryOnPushButton, + + green_leak_measurement_push_button: AutoOffFaultPushButton, + blue_leak_measurement_push_button: AutoOffFaultPushButton, + yellow_leak_measurement_push_button: AutoOffFaultPushButton, +} +impl A380HydraulicOverheadPanel { + pub(super) fn new(context: &mut InitContext) -> A380HydraulicOverheadPanel { + A380HydraulicOverheadPanel { + edp1_push_button: AutoOffFaultPushButton::new_auto(context, "HYD_ENG_1_PUMP"), + edp2_push_button: AutoOffFaultPushButton::new_auto(context, "HYD_ENG_2_PUMP"), + blue_epump_push_button: AutoOffFaultPushButton::new_auto(context, "HYD_EPUMPB"), + ptu_push_button: AutoOffFaultPushButton::new_auto(context, "HYD_PTU"), + rat_push_button: MomentaryPushButton::new(context, "HYD_RAT_MAN_ON"), + yellow_epump_push_button: AutoOnFaultPushButton::new_auto(context, "HYD_EPUMPY"), + blue_epump_override_push_button: MomentaryOnPushButton::new(context, "HYD_EPUMPY_OVRD"), + + green_leak_measurement_push_button: AutoOffFaultPushButton::new_auto( + context, + "HYD_LEAK_MEASUREMENT_G", + ), + blue_leak_measurement_push_button: AutoOffFaultPushButton::new_auto( + context, + "HYD_LEAK_MEASUREMENT_B", + ), + yellow_leak_measurement_push_button: AutoOffFaultPushButton::new_auto( + context, + "HYD_LEAK_MEASUREMENT_Y", + ), + } + } + + fn update_blue_override_state(&mut self) { + if self.blue_epump_push_button.is_off() { + self.blue_epump_override_push_button.turn_off(); + } + } + + pub(super) fn update(&mut self, hyd: &A380Hydraulic) { + self.edp1_push_button.set_fault(hyd.green_edp_has_fault()); + self.edp2_push_button.set_fault(hyd.yellow_edp_has_fault()); + self.blue_epump_push_button + .set_fault(hyd.blue_epump_has_fault()); + self.yellow_epump_push_button + .set_fault(hyd.yellow_epump_has_fault()); + self.ptu_push_button.set_fault(hyd.ptu_has_fault()); + + self.update_blue_override_state(); + } + + fn yellow_epump_push_button_is_auto(&self) -> bool { + self.yellow_epump_push_button.is_auto() + } + + fn ptu_push_button_is_auto(&self) -> bool { + self.ptu_push_button.is_auto() + } + + fn edp_push_button_is_auto(&self, number: usize) -> bool { + match number { + 1 => self.edp1_push_button.is_auto(), + 2 => self.edp2_push_button.is_auto(), + _ => panic!("The A380 only supports two engines."), + } + } + + fn edp_push_button_is_off(&self, number: usize) -> bool { + match number { + 1 => self.edp1_push_button.is_off(), + 2 => self.edp2_push_button.is_off(), + _ => panic!("The A380 only supports two engines."), + } + } + + fn blue_epump_override_push_button_is_on(&self) -> bool { + self.blue_epump_override_push_button.is_on() + } + + fn blue_epump_push_button_is_off(&self) -> bool { + self.blue_epump_push_button.is_off() + } + + fn rat_man_on_push_button_is_pressed(&self) -> bool { + self.rat_push_button.is_pressed() + } + + fn blue_leak_measurement_valve_is_on(&self) -> bool { + self.blue_leak_measurement_push_button.is_auto() + } + + fn green_leak_measurement_valve_is_on(&self) -> bool { + self.green_leak_measurement_push_button.is_auto() + } + + fn yellow_leak_measurement_valve_is_on(&self) -> bool { + self.yellow_leak_measurement_push_button.is_auto() + } +} +impl SimulationElement for A380HydraulicOverheadPanel { + 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.green_leak_measurement_push_button.accept(visitor); + self.blue_leak_measurement_push_button.accept(visitor); + self.yellow_leak_measurement_push_button.accept(visitor); + + visitor.visit(self); + } + + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + if !buses.is_powered(A380Hydraulic::BLUE_ELEC_PUMP_CONTROL_POWER_BUS) + || !buses.is_powered(A380Hydraulic::BLUE_ELEC_PUMP_SUPPLY_POWER_BUS) + { + self.blue_epump_override_push_button.turn_off(); + } + } +} + +struct AileronController { + mode: LinearActuatorMode, + requested_position: Ratio, +} +impl AileronController { + fn new() -> Self { + Self { + mode: LinearActuatorMode::ClosedCircuitDamping, + + requested_position: Ratio::new::(0.), + } + } + + fn set_mode(&mut self, mode: LinearActuatorMode) { + self.mode = mode; + } + + /// Receives a [0;1] position request, 0 is down 1 is up + fn set_requested_position(&mut self, requested_position: Ratio) { + self.requested_position = requested_position + .min(Ratio::new::(1.)) + .max(Ratio::new::(0.)); + } +} +impl HydraulicAssemblyController for AileronController { + fn requested_mode(&self) -> LinearActuatorMode { + self.mode + } + + fn requested_position(&self) -> Ratio { + self.requested_position + } + + fn should_lock(&self) -> bool { + false + } + + fn requested_lock_position(&self) -> Ratio { + Ratio::default() + } +} +impl HydraulicLocking for AileronController {} + +enum AileronHydConfiguration { + GB, + G, + B, + NoHyd, +} +impl AileronHydConfiguration { + fn from_hyd_state( + green_circuit_available: bool, + blue_circuit_available: bool, + ) -> AileronHydConfiguration { + if green_circuit_available && blue_circuit_available { + AileronHydConfiguration::GB + } else if green_circuit_available { + AileronHydConfiguration::G + } else if blue_circuit_available { + AileronHydConfiguration::B + } else { + AileronHydConfiguration::NoHyd + } + } +} + +enum RightElevatorHydConfiguration { + YB, + Y, + B, + NoHyd, +} +impl RightElevatorHydConfiguration { + fn from_hyd_state( + blue_circuit_available: bool, + yellow_circuit_available: bool, + ) -> RightElevatorHydConfiguration { + if yellow_circuit_available && blue_circuit_available { + RightElevatorHydConfiguration::YB + } else if yellow_circuit_available { + RightElevatorHydConfiguration::Y + } else if blue_circuit_available { + RightElevatorHydConfiguration::B + } else { + RightElevatorHydConfiguration::NoHyd + } + } +} +enum LeftElevatorHydConfiguration { + GB, + G, + B, + NoHyd, +} +impl LeftElevatorHydConfiguration { + fn from_hyd_state( + green_circuit_available: bool, + blue_circuit_available: bool, + ) -> LeftElevatorHydConfiguration { + if green_circuit_available && blue_circuit_available { + LeftElevatorHydConfiguration::GB + } else if green_circuit_available { + LeftElevatorHydConfiguration::G + } else if blue_circuit_available { + LeftElevatorHydConfiguration::B + } else { + LeftElevatorHydConfiguration::NoHyd + } + } +} + +/// Implements a placeholder elac computer logic commanding correct hydraulic modes depending +/// on pressure state. +/// TODO: Receive each actuator mode and commands directly from a FBW Elac implementation +struct ElacComputer { + left_aileron_requested_position_id: VariableIdentifier, + right_aileron_requested_position_id: VariableIdentifier, + elevator_requested_position_id: VariableIdentifier, + + left_aileron_requested_position: Ratio, + right_aileron_requested_position: Ratio, + elevator_requested_position: Ratio, + + left_aileron_controllers: [AileronController; 2], + right_aileron_controllers: [AileronController; 2], + + left_elevator_controllers: [AileronController; 2], + right_elevator_controllers: [AileronController; 2], + + green_circuit_available: bool, + blue_circuit_available: bool, + yellow_circuit_available: bool, + + is_powered: bool, +} +impl ElacComputer { + const PRESSURE_AVAILABLE_HIGH_HYSTERESIS_PSI: f64 = 1450.; + const PRESSURE_AVAILABLE_LOW_HYSTERESIS_PSI: f64 = 800.; + + //TODO hot busses are in reality sub busses 703pp and 704pp + const ALL_POWER_BUSES: [ElectricalBusType; 4] = [ + ElectricalBusType::DirectCurrentEssential, + ElectricalBusType::DirectCurrent(2), + ElectricalBusType::DirectCurrentHot(1), + ElectricalBusType::DirectCurrentHot(2), + ]; + + fn new(context: &mut InitContext) -> Self { + Self { + left_aileron_requested_position_id: context + .get_identifier("HYD_AILERON_LEFT_DEMAND".to_owned()), + right_aileron_requested_position_id: context + .get_identifier("HYD_AILERON_RIGHT_DEMAND".to_owned()), + elevator_requested_position_id: context + .get_identifier("HYD_ELEVATOR_DEMAND".to_owned()), + + left_aileron_requested_position: Ratio::default(), + right_aileron_requested_position: Ratio::default(), + elevator_requested_position: Ratio::default(), + + // Controllers are in outward->inward order, so for aileron [Blue circuit, Green circuit] + left_aileron_controllers: [AileronController::new(), AileronController::new()], + right_aileron_controllers: [AileronController::new(), AileronController::new()], + + // Controllers are in outboard->inboard order + left_elevator_controllers: [AileronController::new(), AileronController::new()], + right_elevator_controllers: [AileronController::new(), AileronController::new()], + + green_circuit_available: false, + blue_circuit_available: false, + yellow_circuit_available: false, + + is_powered: false, + } + } + + fn update_aileron_requested_position(&mut self) { + for controller in &mut self.left_aileron_controllers { + controller.set_requested_position(self.left_aileron_requested_position); + } + + for controller in &mut self.right_aileron_controllers { + controller.set_requested_position(self.right_aileron_requested_position); + } + } + + fn update_elevator_requested_position(&mut self) { + for controller in &mut self.left_elevator_controllers { + controller.set_requested_position(self.elevator_requested_position); + } + + for controller in &mut self.right_elevator_controllers { + controller.set_requested_position(self.elevator_requested_position); + } + } + + fn set_aileron_no_position_control(&mut self) { + for controller in &mut self.left_aileron_controllers { + controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + + for controller in &mut self.right_aileron_controllers { + controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + } + + fn set_elevator_no_position_control(&mut self) { + for controller in &mut self.left_elevator_controllers { + controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + + for controller in &mut self.right_elevator_controllers { + controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + } + + fn set_left_aileron_position_control( + &mut self, + hydraulic_configuration: AileronHydConfiguration, + ) { + match hydraulic_configuration { + AileronHydConfiguration::GB | AileronHydConfiguration::B => { + self.left_aileron_controllers[AileronActuatorPosition::Outboard as usize] + .set_mode(LinearActuatorMode::PositionControl); + self.left_aileron_controllers[AileronActuatorPosition::Inboard as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + } + + AileronHydConfiguration::G => { + self.left_aileron_controllers[AileronActuatorPosition::Outboard as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + self.left_aileron_controllers[AileronActuatorPosition::Inboard as usize] + .set_mode(LinearActuatorMode::PositionControl); + } + AileronHydConfiguration::NoHyd => { + self.left_aileron_controllers[AileronActuatorPosition::Outboard as usize] + .set_mode(LinearActuatorMode::ClosedCircuitDamping); + self.left_aileron_controllers[AileronActuatorPosition::Inboard as usize] + .set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + } + } + + fn set_right_aileron_position_control( + &mut self, + hydraulic_configuration: AileronHydConfiguration, + ) { + match hydraulic_configuration { + AileronHydConfiguration::GB | AileronHydConfiguration::G => { + self.right_aileron_controllers[AileronActuatorPosition::Outboard as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + self.right_aileron_controllers[AileronActuatorPosition::Inboard as usize] + .set_mode(LinearActuatorMode::PositionControl); + } + + AileronHydConfiguration::B => { + self.right_aileron_controllers[AileronActuatorPosition::Outboard as usize] + .set_mode(LinearActuatorMode::PositionControl); + self.right_aileron_controllers[AileronActuatorPosition::Inboard as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + } + _ => { + self.right_aileron_controllers[AileronActuatorPosition::Outboard as usize] + .set_mode(LinearActuatorMode::ClosedCircuitDamping); + self.right_aileron_controllers[AileronActuatorPosition::Inboard as usize] + .set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + } + } + + fn set_left_elevator_position_control( + &mut self, + hydraulic_configuration: LeftElevatorHydConfiguration, + ) { + match hydraulic_configuration { + LeftElevatorHydConfiguration::GB => { + if self.elevator_requested_position > Ratio::new::(0.8) { + self.left_elevator_controllers[LeftElevatorActuatorCircuit::Blue as usize] + .set_mode(LinearActuatorMode::PositionControl); + } else { + self.left_elevator_controllers[LeftElevatorActuatorCircuit::Blue as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + } + self.left_elevator_controllers[LeftElevatorActuatorCircuit::Green as usize] + .set_mode(LinearActuatorMode::PositionControl); + } + LeftElevatorHydConfiguration::G => { + self.left_elevator_controllers[LeftElevatorActuatorCircuit::Blue as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + self.left_elevator_controllers[LeftElevatorActuatorCircuit::Green as usize] + .set_mode(LinearActuatorMode::PositionControl); + } + LeftElevatorHydConfiguration::B => { + self.left_elevator_controllers[LeftElevatorActuatorCircuit::Blue as usize] + .set_mode(LinearActuatorMode::PositionControl); + self.left_elevator_controllers[LeftElevatorActuatorCircuit::Green as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + } + LeftElevatorHydConfiguration::NoHyd => { + self.left_elevator_controllers[LeftElevatorActuatorCircuit::Blue as usize] + .set_mode(LinearActuatorMode::ClosedCircuitDamping); + self.left_elevator_controllers[LeftElevatorActuatorCircuit::Green as usize] + .set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + } + } + + fn set_right_elevator_position_control( + &mut self, + hydraulic_configuration: RightElevatorHydConfiguration, + ) { + match hydraulic_configuration { + RightElevatorHydConfiguration::YB => { + if self.elevator_requested_position > Ratio::new::(0.8) { + self.right_elevator_controllers[RightElevatorActuatorCircuit::Blue as usize] + .set_mode(LinearActuatorMode::PositionControl); + } else { + self.right_elevator_controllers[RightElevatorActuatorCircuit::Blue as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + } + self.right_elevator_controllers[RightElevatorActuatorCircuit::Yellow as usize] + .set_mode(LinearActuatorMode::PositionControl); + } + RightElevatorHydConfiguration::Y => { + self.right_elevator_controllers[RightElevatorActuatorCircuit::Blue as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + self.right_elevator_controllers[RightElevatorActuatorCircuit::Yellow as usize] + .set_mode(LinearActuatorMode::PositionControl); + } + RightElevatorHydConfiguration::B => { + self.right_elevator_controllers[RightElevatorActuatorCircuit::Blue as usize] + .set_mode(LinearActuatorMode::PositionControl); + self.right_elevator_controllers[RightElevatorActuatorCircuit::Yellow as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + } + RightElevatorHydConfiguration::NoHyd => { + self.right_elevator_controllers[RightElevatorActuatorCircuit::Blue as usize] + .set_mode(LinearActuatorMode::ClosedCircuitDamping); + self.right_elevator_controllers[RightElevatorActuatorCircuit::Yellow as usize] + .set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + } + } + + fn update_elevator(&mut self) { + if self.is_powered { + self.set_left_elevator_position_control(LeftElevatorHydConfiguration::from_hyd_state( + self.green_circuit_available, + self.blue_circuit_available, + )); + + self.set_right_elevator_position_control( + RightElevatorHydConfiguration::from_hyd_state( + self.blue_circuit_available, + self.yellow_circuit_available, + ), + ); + } else { + self.set_elevator_no_position_control(); + } + } + + fn update_aileron(&mut self) { + if self.is_powered { + self.set_right_aileron_position_control(AileronHydConfiguration::from_hyd_state( + self.green_circuit_available, + self.blue_circuit_available, + )); + self.set_left_aileron_position_control(AileronHydConfiguration::from_hyd_state( + self.green_circuit_available, + self.blue_circuit_available, + )); + } else { + self.set_aileron_no_position_control(); + } + } + + fn circuit_is_available(pressure: Pressure, current_availability: bool) -> bool { + if pressure.get::() > Self::PRESSURE_AVAILABLE_HIGH_HYSTERESIS_PSI { + true + } else if pressure.get::() < Self::PRESSURE_AVAILABLE_LOW_HYSTERESIS_PSI { + false + } else { + current_availability + } + } + + fn update( + &mut self, + blue_pressure: &impl SectionPressure, + green_pressure: &impl SectionPressure, + yellow_pressure: &impl SectionPressure, + ) { + self.update_aileron_requested_position(); + self.update_elevator_requested_position(); + + self.blue_circuit_available = Self::circuit_is_available( + blue_pressure.pressure_downstream_leak_valve(), + self.blue_circuit_available, + ); + self.green_circuit_available = Self::circuit_is_available( + green_pressure.pressure_downstream_leak_valve(), + self.green_circuit_available, + ); + self.yellow_circuit_available = Self::circuit_is_available( + yellow_pressure.pressure_downstream_leak_valve(), + self.yellow_circuit_available, + ); + + self.update_aileron(); + + self.update_elevator(); + } + + fn left_elevator_controllers(&self) -> &[impl HydraulicAssemblyController + HydraulicLocking] { + &self.left_elevator_controllers[..] + } + + fn right_elevator_controllers(&self) -> &[impl HydraulicAssemblyController + HydraulicLocking] { + &self.right_elevator_controllers[..] + } + + fn left_controllers(&self) -> &[impl HydraulicAssemblyController + HydraulicLocking] { + &self.left_aileron_controllers[..] + } + + fn right_controllers(&self) -> &[impl HydraulicAssemblyController + HydraulicLocking] { + &self.right_aileron_controllers[..] + } +} +impl SimulationElement for ElacComputer { + fn read(&mut self, reader: &mut SimulatorReader) { + self.left_aileron_requested_position = + Ratio::new::(reader.read(&self.left_aileron_requested_position_id)); + self.right_aileron_requested_position = + Ratio::new::(reader.read(&self.right_aileron_requested_position_id)); + + self.elevator_requested_position = + Ratio::new::(reader.read(&self.elevator_requested_position_id)); + } + + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.is_powered = buses.any_is_powered(&Self::ALL_POWER_BUSES); + } +} + +/// Implements a placeholder fac computer logic commanding correct hydraulic modes depending +/// on pressure state. +/// TODO: Receive each actuator mode and commands directly from a FBW fac implementation +struct FacComputer { + requested_rudder_position_id: VariableIdentifier, + + rudder_position_requested: Ratio, + + rudder_controllers: [AileronController; 3], + + is_powered: bool, +} +impl FacComputer { + //TODO hot busses of FAC to check + const ALL_POWER_BUSES: [ElectricalBusType; 4] = [ + ElectricalBusType::DirectCurrentEssential, + ElectricalBusType::DirectCurrent(2), + ElectricalBusType::DirectCurrentHot(1), + ElectricalBusType::DirectCurrentHot(2), + ]; + + fn new(context: &mut InitContext) -> Self { + Self { + requested_rudder_position_id: context.get_identifier("HYD_RUDDER_DEMAND".to_owned()), + + rudder_position_requested: Ratio::default(), + + // Controllers are in [ Green circuit, Blue circuit, Yellow circuit] order + rudder_controllers: [ + AileronController::new(), + AileronController::new(), + AileronController::new(), + ], + + is_powered: false, + } + } + + fn update_rudder_requested_position(&mut self) { + for controller in &mut self.rudder_controllers { + controller.set_requested_position(self.rudder_position_requested); + } + } + + fn set_rudder_no_position_control(&mut self) { + for controller in &mut self.rudder_controllers { + controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + } + + fn set_rudder_position_control( + &mut self, + green_circuit_available: bool, + blue_circuit_available: bool, + yellow_circuit_available: bool, + ) { + if green_circuit_available { + self.rudder_controllers[RudderActuatorPosition::Green as usize] + .set_mode(LinearActuatorMode::PositionControl); + } else { + self.rudder_controllers[RudderActuatorPosition::Green as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + } + + if blue_circuit_available { + self.rudder_controllers[RudderActuatorPosition::Blue as usize] + .set_mode(LinearActuatorMode::PositionControl); + } else { + self.rudder_controllers[RudderActuatorPosition::Blue as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + } + + if yellow_circuit_available { + self.rudder_controllers[RudderActuatorPosition::Yellow as usize] + .set_mode(LinearActuatorMode::PositionControl); + } else { + self.rudder_controllers[RudderActuatorPosition::Yellow as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + } + } + + fn update_rudder( + &mut self, + green_circuit_available: bool, + blue_circuit_available: bool, + yellow_circuit_available: bool, + ) { + let no_hydraulics = + !green_circuit_available && !blue_circuit_available && !yellow_circuit_available; + + if self.is_powered && !no_hydraulics { + self.set_rudder_position_control( + green_circuit_available, + blue_circuit_available, + yellow_circuit_available, + ); + } else { + self.set_rudder_no_position_control(); + } + } + + fn update( + &mut self, + green_pressure: &impl SectionPressure, + blue_pressure: &impl SectionPressure, + yellow_pressure: &impl SectionPressure, + ) { + self.update_rudder_requested_position(); + + let blue_circuit_available = + blue_pressure.pressure_downstream_leak_valve().get::() > 1500.; + let green_circuit_available = + green_pressure.pressure_downstream_leak_valve().get::() > 1500.; + let yellow_circuit_available = yellow_pressure + .pressure_downstream_leak_valve() + .get::() + > 1500.; + + self.update_rudder( + green_circuit_available, + blue_circuit_available, + yellow_circuit_available, + ); + } + + fn rudder_controllers(&self) -> &[impl HydraulicAssemblyController + HydraulicLocking] { + &self.rudder_controllers[..] + } +} +impl SimulationElement for FacComputer { + fn read(&mut self, reader: &mut SimulatorReader) { + self.rudder_position_requested = + Ratio::new::(reader.read(&self.requested_rudder_position_id)); + } + + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.is_powered = buses.any_is_powered(&Self::ALL_POWER_BUSES); + } +} + +#[derive(PartialEq, Clone, Copy)] +enum ActuatorSide { + Left, + Right, +} + +#[derive(PartialEq, Clone, Copy)] +enum AileronActuatorPosition { + Outboard = 0, + Inboard = 1, +} + +#[derive(PartialEq, Clone, Copy)] +enum ElevatorActuatorPosition { + Outboard = 0, + Inboard = 1, +} + +enum RudderActuatorPosition { + Green = 0, + Blue = 1, + Yellow = 2, +} + +enum LeftElevatorActuatorCircuit { + Blue = 0, + Green = 1, +} + +enum RightElevatorActuatorCircuit { + Blue = 0, + Yellow = 1, +} + +struct AileronAssembly { + hydraulic_assembly: HydraulicLinearActuatorAssembly<2>, + + position_id: VariableIdentifier, + + position: Ratio, + + aerodynamic_model: AerodynamicModel, +} +impl AileronAssembly { + fn new( + context: &mut InitContext, + id: ActuatorSide, + hydraulic_assembly: HydraulicLinearActuatorAssembly<2>, + aerodynamic_model: AerodynamicModel, + ) -> Self { + Self { + hydraulic_assembly, + position_id: match id { + ActuatorSide::Left => context.get_identifier("HYD_AIL_LEFT_DEFLECTION".to_owned()), + ActuatorSide::Right => { + context.get_identifier("HYD_AIL_RIGHT_DEFLECTION".to_owned()) + } + }, + position: Ratio::new::(0.), + aerodynamic_model, + } + } + + fn actuator(&mut self, circuit_position: AileronActuatorPosition) -> &mut impl Actuator { + self.hydraulic_assembly.actuator(circuit_position as usize) + } + + fn update( + &mut self, + context: &UpdateContext, + aileron_controllers: &[impl HydraulicAssemblyController + HydraulicLocking], + current_pressure_outward: &impl SectionPressure, + current_pressure_inward: &impl SectionPressure, + ) { + self.aerodynamic_model + .update_body(context, self.hydraulic_assembly.body()); + self.hydraulic_assembly.update( + context, + aileron_controllers, + [ + current_pressure_outward.pressure_downstream_leak_valve(), + current_pressure_inward.pressure_downstream_leak_valve(), + ], + ); + + self.position = self.hydraulic_assembly.position_normalized(); + } +} +impl SimulationElement for AileronAssembly { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.position_id, self.position.get::()); + } +} + +struct ElevatorAssembly { + hydraulic_assembly: HydraulicLinearActuatorAssembly<2>, + + position_id: VariableIdentifier, + + position: Ratio, + + aerodynamic_model: AerodynamicModel, +} +impl ElevatorAssembly { + fn new( + context: &mut InitContext, + id: ActuatorSide, + hydraulic_assembly: HydraulicLinearActuatorAssembly<2>, + aerodynamic_model: AerodynamicModel, + ) -> Self { + Self { + hydraulic_assembly, + position_id: match id { + ActuatorSide::Left => context.get_identifier("HYD_ELEV_LEFT_DEFLECTION".to_owned()), + ActuatorSide::Right => { + context.get_identifier("HYD_ELEV_RIGHT_DEFLECTION".to_owned()) + } + }, + position: Ratio::new::(0.), + aerodynamic_model, + } + } + + fn actuator(&mut self, circuit_position: ElevatorActuatorPosition) -> &mut impl Actuator { + self.hydraulic_assembly.actuator(circuit_position as usize) + } + + fn update( + &mut self, + context: &UpdateContext, + aileron_controllers: &[impl HydraulicAssemblyController + HydraulicLocking], + current_pressure_outward: &impl SectionPressure, + current_pressure_inward: &impl SectionPressure, + ) { + self.aerodynamic_model + .update_body(context, self.hydraulic_assembly.body()); + self.hydraulic_assembly.update( + context, + aileron_controllers, + [ + current_pressure_outward.pressure_downstream_leak_valve(), + current_pressure_inward.pressure_downstream_leak_valve(), + ], + ); + + self.position = self.hydraulic_assembly.position_normalized(); + } +} +impl SimulationElement for ElevatorAssembly { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.position_id, self.position.get::()); + } +} + +struct RudderAssembly { + hydraulic_assembly: HydraulicLinearActuatorAssembly<3>, + name_id: VariableIdentifier, + + position: Ratio, + + aerodynamic_model: AerodynamicModel, +} +impl RudderAssembly { + fn new( + context: &mut InitContext, + hydraulic_assembly: HydraulicLinearActuatorAssembly<3>, + aerodynamic_model: AerodynamicModel, + ) -> Self { + Self { + hydraulic_assembly, + + name_id: context.get_identifier("HYD_RUD_DEFLECTION".to_owned()), + + position: Ratio::new::(0.5), + + aerodynamic_model, + } + } + + fn actuator(&mut self, circuit_position: RudderActuatorPosition) -> &mut impl Actuator { + self.hydraulic_assembly.actuator(circuit_position as usize) + } + + fn update( + &mut self, + context: &UpdateContext, + rudder_controllers: &[impl HydraulicAssemblyController + HydraulicLocking], + current_pressure_green: &impl SectionPressure, + current_pressure_blue: &impl SectionPressure, + current_pressure_yellow: &impl SectionPressure, + ) { + self.aerodynamic_model + .update_body(context, self.hydraulic_assembly.body()); + + self.hydraulic_assembly.update( + context, + rudder_controllers, + [ + current_pressure_green.pressure_downstream_leak_valve(), + current_pressure_blue.pressure_downstream_leak_valve(), + current_pressure_yellow.pressure_downstream_leak_valve(), + ], + ); + + self.position = self.hydraulic_assembly.position_normalized(); + } +} +impl SimulationElement for RudderAssembly { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.name_id, self.position.get::()); + } +} + +struct SpoilerElement { + hydraulic_assembly: HydraulicLinearActuatorAssembly<1>, + + position_id: VariableIdentifier, + + position: Ratio, + + aerodynamic_model: AerodynamicModel, +} +impl SpoilerElement { + fn new( + context: &mut InitContext, + id: ActuatorSide, + id_num: usize, + hydraulic_assembly: HydraulicLinearActuatorAssembly<1>, + aerodynamic_model: AerodynamicModel, + ) -> Self { + Self { + hydraulic_assembly, + position_id: match id { + ActuatorSide::Left => { + context.get_identifier(format!("HYD_SPOIL_{}_LEFT_DEFLECTION", id_num)) + } + ActuatorSide::Right => { + context.get_identifier(format!("HYD_SPOIL_{}_RIGHT_DEFLECTION", id_num)) + } + }, + position: Ratio::new::(0.), + aerodynamic_model, + } + } + + fn actuator(&mut self) -> &mut impl Actuator { + self.hydraulic_assembly.actuator(0) + } + + fn update( + &mut self, + context: &UpdateContext, + spoiler_controller: &(impl HydraulicAssemblyController + HydraulicLocking), + current_pressure: Pressure, + ) { + self.aerodynamic_model + .update_body(context, self.hydraulic_assembly.body()); + self.hydraulic_assembly.update( + context, + std::slice::from_ref(spoiler_controller), + [current_pressure], + ); + + self.position = self.hydraulic_assembly.position_normalized(); + } +} +impl SimulationElement for SpoilerElement { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.position_id, self.position.get::()); + } +} + +struct SpoilerGroup { + spoilers: [SpoilerElement; 5], +} +impl SpoilerGroup { + fn new(spoilers: [SpoilerElement; 5]) -> Self { + Self { spoilers } + } + + fn update( + &mut self, + context: &UpdateContext, + spoiler_controllers: &[impl HydraulicAssemblyController + HydraulicLocking], + green_pressure: &impl SectionPressure, + blue_pressure: &impl SectionPressure, + yellow_pressure: &impl SectionPressure, + ) { + self.spoilers[0].update( + context, + &spoiler_controllers[0], + green_pressure.pressure_downstream_leak_valve(), + ); + self.spoilers[1].update( + context, + &spoiler_controllers[1], + yellow_pressure.pressure_downstream_leak_valve(), + ); + self.spoilers[2].update( + context, + &spoiler_controllers[2], + blue_pressure.pressure_downstream_leak_valve(), + ); + self.spoilers[3].update( + context, + &spoiler_controllers[3], + yellow_pressure.pressure_downstream_leak_valve(), + ); + self.spoilers[4].update( + context, + &spoiler_controllers[4], + green_pressure.pressure_downstream_leak_valve(), + ); + } + + fn actuator(&mut self, spoiler_id: usize) -> &mut impl Actuator { + self.spoilers[spoiler_id].actuator() + } +} +impl SimulationElement for SpoilerGroup { + fn accept(&mut self, visitor: &mut T) { + for spoiler in &mut self.spoilers { + spoiler.accept(visitor); + } + + visitor.visit(self); + } +} + +#[derive(PartialEq, Clone, Copy)] +struct SpoilerController { + mode: LinearActuatorMode, + requested_position: Ratio, +} +impl SpoilerController { + fn new() -> Self { + Self { + mode: LinearActuatorMode::PositionControl, + + requested_position: Ratio::new::(0.), + } + } + + /// Receives a [0;1] position request, 0 is down 1 is up + fn set_requested_position(&mut self, requested_position: Ratio) { + self.requested_position = requested_position + .min(Ratio::new::(1.)) + .max(Ratio::new::(0.)); + } +} +impl HydraulicAssemblyController for SpoilerController { + fn requested_mode(&self) -> LinearActuatorMode { + LinearActuatorMode::PositionControl + } + + fn requested_position(&self) -> Ratio { + self.requested_position + } + + fn should_lock(&self) -> bool { + false + } + + fn requested_lock_position(&self) -> Ratio { + Ratio::default() + } +} +impl HydraulicLocking for SpoilerController {} + +struct SpoilerComputer { + requested_position_left_1_id: VariableIdentifier, + requested_position_left_2_id: VariableIdentifier, + requested_position_left_3_id: VariableIdentifier, + requested_position_left_4_id: VariableIdentifier, + requested_position_left_5_id: VariableIdentifier, + + requested_position_right_1_id: VariableIdentifier, + requested_position_right_2_id: VariableIdentifier, + requested_position_right_3_id: VariableIdentifier, + requested_position_right_4_id: VariableIdentifier, + requested_position_right_5_id: VariableIdentifier, + + left_positions_requested: [Ratio; 5], + right_positions_requested: [Ratio; 5], + + left_controllers: [SpoilerController; 5], + right_controllers: [SpoilerController; 5], +} +impl SpoilerComputer { + fn new(context: &mut InitContext) -> Self { + Self { + requested_position_left_1_id: context + .get_identifier("HYD_SPOILER_1_LEFT_DEMAND".to_owned()), + requested_position_left_2_id: context + .get_identifier("HYD_SPOILER_2_LEFT_DEMAND".to_owned()), + requested_position_left_3_id: context + .get_identifier("HYD_SPOILER_3_LEFT_DEMAND".to_owned()), + requested_position_left_4_id: context + .get_identifier("HYD_SPOILER_4_LEFT_DEMAND".to_owned()), + requested_position_left_5_id: context + .get_identifier("HYD_SPOILER_5_LEFT_DEMAND".to_owned()), + + requested_position_right_1_id: context + .get_identifier("HYD_SPOILER_1_RIGHT_DEMAND".to_owned()), + requested_position_right_2_id: context + .get_identifier("HYD_SPOILER_2_RIGHT_DEMAND".to_owned()), + requested_position_right_3_id: context + .get_identifier("HYD_SPOILER_3_RIGHT_DEMAND".to_owned()), + requested_position_right_4_id: context + .get_identifier("HYD_SPOILER_4_RIGHT_DEMAND".to_owned()), + requested_position_right_5_id: context + .get_identifier("HYD_SPOILER_5_RIGHT_DEMAND".to_owned()), + + left_positions_requested: [Ratio::default(); 5], + right_positions_requested: [Ratio::default(); 5], + + // Controllers are in inward->outward order + left_controllers: [SpoilerController::new(); 5], + right_controllers: [SpoilerController::new(); 5], + } + } + + fn update_spoilers_requested_position(&mut self) { + for (idx, controller) in &mut self.left_controllers.iter_mut().enumerate() { + controller.set_requested_position(self.left_positions_requested[idx]); + } + + for (idx, controller) in &mut self.right_controllers.iter_mut().enumerate() { + controller.set_requested_position(self.right_positions_requested[idx]); + } + } + + fn left_controllers(&self) -> &[impl HydraulicAssemblyController + HydraulicLocking] { + &self.left_controllers[..] + } + + fn right_controllers(&self) -> &[impl HydraulicAssemblyController + HydraulicLocking] { + &self.right_controllers[..] + } +} +impl SimulationElement for SpoilerComputer { + fn read(&mut self, reader: &mut SimulatorReader) { + self.left_positions_requested = [ + Ratio::new::(reader.read(&self.requested_position_left_1_id)), + Ratio::new::(reader.read(&self.requested_position_left_2_id)), + Ratio::new::(reader.read(&self.requested_position_left_3_id)), + Ratio::new::(reader.read(&self.requested_position_left_4_id)), + Ratio::new::(reader.read(&self.requested_position_left_5_id)), + ]; + self.right_positions_requested = [ + Ratio::new::(reader.read(&self.requested_position_right_1_id)), + Ratio::new::(reader.read(&self.requested_position_right_2_id)), + Ratio::new::(reader.read(&self.requested_position_right_3_id)), + Ratio::new::(reader.read(&self.requested_position_right_4_id)), + Ratio::new::(reader.read(&self.requested_position_right_5_id)), + ]; + + self.update_spoilers_requested_position(); + } +} + +struct A380GravityExtension { + gear_gravity_extension_handle_position_id: VariableIdentifier, + + handle_angle: Angle, +} +impl A380GravityExtension { + fn new(context: &mut InitContext) -> Self { + Self { + gear_gravity_extension_handle_position_id: context + .get_identifier("GRAVITYGEAR_ROTATE_PCT".to_owned()), + + handle_angle: Angle::default(), + } + } +} +impl GearGravityExtension for A380GravityExtension { + fn extension_handle_number_of_turns(&self) -> u8 { + (self.handle_angle.get::() / 360.).floor() as u8 + } +} +impl SimulationElement for A380GravityExtension { + fn read(&mut self, reader: &mut SimulatorReader) { + let handle_percent: f64 = reader.read(&self.gear_gravity_extension_handle_position_id); + + self.handle_angle = Angle::new::(handle_percent * 3.6) + .max(Angle::new::(0.)) + .min(Angle::new::(360. * 3.)); + } +} + +struct A380TrimInputController { + motor1_active_id: VariableIdentifier, + motor2_active_id: VariableIdentifier, + motor3_active_id: VariableIdentifier, + + motor1_position_id: VariableIdentifier, + motor2_position_id: VariableIdentifier, + motor3_position_id: VariableIdentifier, + + manual_control_active_id: VariableIdentifier, + manual_control_speed_id: VariableIdentifier, + + motor_active: [bool; 3], + motor_position: [Angle; 3], + + manual_control: bool, + manual_control_speed: AngularVelocity, +} +impl A380TrimInputController { + fn new(context: &mut InitContext) -> Self { + Self { + motor1_active_id: context.get_identifier("THS_1_ACTIVE_MODE_COMMANDED".to_owned()), + motor2_active_id: context.get_identifier("THS_2_ACTIVE_MODE_COMMANDED".to_owned()), + motor3_active_id: context.get_identifier("THS_3_ACTIVE_MODE_COMMANDED".to_owned()), + + motor1_position_id: context.get_identifier("THS_1_COMMANDED_POSITION".to_owned()), + motor2_position_id: context.get_identifier("THS_2_COMMANDED_POSITION".to_owned()), + motor3_position_id: context.get_identifier("THS_3_COMMANDED_POSITION".to_owned()), + + manual_control_active_id: context + .get_identifier("THS_MANUAL_CONTROL_ACTIVE".to_owned()), + manual_control_speed_id: context.get_identifier("THS_MANUAL_CONTROL_SPEED".to_owned()), + + motor_active: [false; 3], + motor_position: [Angle::default(); 3], + + manual_control: false, + manual_control_speed: AngularVelocity::default(), + } + } +} +impl PitchTrimActuatorController for A380TrimInputController { + fn commanded_position(&self) -> Angle { + for (idx, motor_active) in self.motor_active.iter().enumerate() { + if *motor_active { + return self.motor_position[idx]; + } + } + + Angle::default() + } + + fn energised_motor(&self) -> [bool; 3] { + self.motor_active + } +} +impl ManualPitchTrimController for A380TrimInputController { + fn is_manually_moved(&self) -> bool { + self.manual_control || self.manual_control_speed.get::() != 0. + } + + fn moving_speed(&self) -> AngularVelocity { + self.manual_control_speed + } +} +impl SimulationElement for A380TrimInputController { + fn read(&mut self, reader: &mut SimulatorReader) { + self.motor_active[0] = reader.read(&self.motor1_active_id); + self.motor_active[1] = reader.read(&self.motor2_active_id); + self.motor_active[2] = reader.read(&self.motor3_active_id); + + self.motor_position[0] = reader.read(&self.motor1_position_id); + self.motor_position[1] = reader.read(&self.motor2_position_id); + self.motor_position[2] = reader.read(&self.motor3_position_id); + + self.manual_control = reader.read(&self.manual_control_active_id); + self.manual_control_speed = reader.read(&self.manual_control_speed_id); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod a380_hydraulics { + use super::*; + use systems::{ + electrical::{ + test::TestElectricitySource, ElectricalBus, Electricity, ElectricitySource, + ExternalPowerSource, + }, + engine::{leap_engine::LeapEngine, EngineFireOverheadPanel}, + hydraulic::electrical_generator::TestGenerator, + landing_gear::{GearSystemState, LandingGear, LandingGearControlInterfaceUnitSet}, + shared::{EmergencyElectricalState, HydraulicGeneratorControlUnit, PotentialOrigin}, + simulation::{ + test::{ReadByName, SimulationTestBed, TestBed, WriteByName}, + Aircraft, InitContext, + }, + }; + + use uom::si::{ + angle::degree, + electric_potential::volt, + length::foot, + ratio::{percent, ratio}, + volume::liter, + }; + + struct A380TestEmergencyElectricalOverheadPanel { + rat_and_emer_gen_man_on: MomentaryPushButton, + } + + impl A380TestEmergencyElectricalOverheadPanel { + pub fn new(context: &mut InitContext) -> Self { + A380TestEmergencyElectricalOverheadPanel { + rat_and_emer_gen_man_on: MomentaryPushButton::new( + context, + "EMER_ELEC_RAT_AND_EMER_GEN", + ), + } + } + } + impl SimulationElement for A380TestEmergencyElectricalOverheadPanel { + fn accept(&mut self, visitor: &mut T) { + self.rat_and_emer_gen_man_on.accept(visitor); + + visitor.visit(self); + } + } + impl EmergencyElectricalRatPushButton for A380TestEmergencyElectricalOverheadPanel { + fn is_pressed(&self) -> bool { + self.rat_and_emer_gen_man_on.is_pressed() + } + } + + #[derive(Default)] + struct A380TestAdirus { + airspeed: Velocity, + } + impl A380TestAdirus { + fn update(&mut self, context: &UpdateContext) { + self.airspeed = context.true_airspeed() + } + } + impl AdirsDiscreteOutputs for A380TestAdirus { + fn low_speed_warning_1_104kts(&self, _: usize) -> bool { + self.airspeed.get::() > 104. + } + + fn low_speed_warning_2_54kts(&self, _: usize) -> bool { + self.airspeed.get::() > 54. + } + + fn low_speed_warning_3_159kts(&self, _: usize) -> bool { + self.airspeed.get::() > 159. + } + + fn low_speed_warning_4_260kts(&self, _: usize) -> bool { + self.airspeed.get::() > 260. + } + } + + struct A380TestPneumatics { + pressure: Pressure, + } + impl A380TestPneumatics { + pub fn new() -> Self { + Self { + pressure: Pressure::new::(50.), + } + } + + fn set_nominal_air_pressure(&mut self) { + self.pressure = Pressure::new::(50.); + } + + fn set_low_air_pressure(&mut self) { + self.pressure = Pressure::new::(1.); + } + } + impl ReservoirAirPressure for A380TestPneumatics { + fn green_reservoir_pressure(&self) -> Pressure { + self.pressure + } + + fn blue_reservoir_pressure(&self) -> Pressure { + self.pressure + } + + fn yellow_reservoir_pressure(&self) -> Pressure { + self.pressure + } + } + + struct A380TestElectrical { + airspeed: Velocity, + all_ac_lost: bool, + emergency_generator: TestGenerator, + } + impl A380TestElectrical { + pub fn new() -> Self { + A380TestElectrical { + airspeed: Velocity::new::(100.), + all_ac_lost: false, + emergency_generator: TestGenerator::default(), + } + } + + fn update( + &mut self, + gcu: &impl HydraulicGeneratorControlUnit, + context: &UpdateContext, + ) { + self.airspeed = context.indicated_airspeed(); + self.emergency_generator.update(gcu); + } + } + impl EmergencyElectricalState for A380TestElectrical { + fn is_in_emergency_elec(&self) -> bool { + self.all_ac_lost && self.airspeed >= Velocity::new::(100.) + } + } + impl EmergencyGeneratorPower for A380TestElectrical { + fn generated_power(&self) -> Power { + self.emergency_generator.generated_power() + } + } + impl SimulationElement for A380TestElectrical { + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.all_ac_lost = !buses.is_powered(ElectricalBusType::AlternatingCurrent(1)) + && !buses.is_powered(ElectricalBusType::AlternatingCurrent(2)); + } + } + struct A380HydraulicsTestAircraft { + pneumatics: A380TestPneumatics, + engine_1: LeapEngine, + engine_2: LeapEngine, + hydraulics: A380Hydraulic, + overhead: A380HydraulicOverheadPanel, + autobrake_panel: AutobrakePanel, + emergency_electrical_overhead: A380TestEmergencyElectricalOverheadPanel, + engine_fire_overhead: EngineFireOverheadPanel<2>, + landing_gear: LandingGear, + lgcius: LandingGearControlInterfaceUnitSet, + adirus: A380TestAdirus, + electrical: A380TestElectrical, + ext_pwr: ExternalPowerSource, + + powered_source_ac: TestElectricitySource, + ac_ground_service_bus: ElectricalBus, + dc_ground_service_bus: ElectricalBus, + ac_1_bus: ElectricalBus, + ac_2_bus: ElectricalBus, + dc_1_bus: ElectricalBus, + dc_2_bus: ElectricalBus, + dc_ess_bus: ElectricalBus, + dc_hot_1_bus: ElectricalBus, + dc_hot_2_bus: ElectricalBus, + + // Electric buses states to be able to kill them dynamically + is_ac_ground_service_powered: bool, + is_dc_ground_service_powered: bool, + is_ac_1_powered: bool, + is_ac_2_powered: bool, + is_dc_1_powered: bool, + is_dc_2_powered: bool, + is_dc_ess_powered: bool, + is_dc_hot_1_powered: bool, + is_dc_hot_2_powered: bool, + } + impl A380HydraulicsTestAircraft { + fn new(context: &mut InitContext) -> Self { + Self { + pneumatics: A380TestPneumatics::new(), + engine_1: LeapEngine::new(context, 1), + engine_2: LeapEngine::new(context, 2), + hydraulics: A380Hydraulic::new(context), + overhead: A380HydraulicOverheadPanel::new(context), + autobrake_panel: AutobrakePanel::new(context), + emergency_electrical_overhead: A380TestEmergencyElectricalOverheadPanel::new( + context, + ), + engine_fire_overhead: EngineFireOverheadPanel::new(context), + landing_gear: LandingGear::new(context), + lgcius: LandingGearControlInterfaceUnitSet::new( + context, + ElectricalBusType::DirectCurrentEssential, + ElectricalBusType::DirectCurrentGndFltService, + ), + adirus: A380TestAdirus::default(), + electrical: A380TestElectrical::new(), + ext_pwr: ExternalPowerSource::new(context), + powered_source_ac: TestElectricitySource::powered( + context, + PotentialOrigin::EngineGenerator(1), + ), + ac_ground_service_bus: ElectricalBus::new( + context, + ElectricalBusType::AlternatingCurrentGndFltService, + ), + dc_ground_service_bus: ElectricalBus::new( + context, + ElectricalBusType::DirectCurrentGndFltService, + ), + ac_1_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(1)), + ac_2_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(2)), + dc_1_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(1)), + dc_2_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(2)), + dc_ess_bus: ElectricalBus::new( + context, + ElectricalBusType::DirectCurrentEssential, + ), + dc_hot_1_bus: ElectricalBus::new( + context, + ElectricalBusType::DirectCurrentHot(1), + ), + dc_hot_2_bus: ElectricalBus::new( + context, + ElectricalBusType::DirectCurrentHot(2), + ), + is_ac_ground_service_powered: true, + is_dc_ground_service_powered: true, + is_ac_1_powered: true, + is_ac_2_powered: true, + is_dc_1_powered: true, + is_dc_2_powered: true, + is_dc_ess_powered: true, + is_dc_hot_1_powered: true, + is_dc_hot_2_powered: true, + } + } + + fn is_rat_commanded_to_deploy(&self) -> bool { + self.hydraulics.ram_air_turbine_controller.should_deploy() + } + + fn is_emergency_gen_at_nominal_speed(&self) -> bool { + self.hydraulics.gcu.is_at_nominal_speed() + } + + fn is_green_edp_commanded_on(&self) -> bool { + self.hydraulics + .engine_driven_pump_1_controller + .should_pressurise() + } + + fn is_yellow_edp_commanded_on(&self) -> bool { + self.hydraulics + .engine_driven_pump_2_controller + .should_pressurise() + } + + fn get_yellow_brake_accumulator_fluid_volume(&self) -> Volume { + self.hydraulics + .braking_circuit_altn + .accumulator_fluid_volume() + } + + fn is_nws_pin_inserted(&self) -> bool { + self.hydraulics.nose_wheel_steering_pin_is_inserted() + } + + fn is_cargo_powering_yellow_epump(&self) -> bool { + self.hydraulics + .should_pressurise_yellow_pump_for_cargo_door_operation() + } + + fn is_yellow_epump_controller_pressurising(&self) -> bool { + self.hydraulics + .yellow_electric_pump_controller + .should_pressurise() + } + + fn is_blue_epump_controller_pressurising(&self) -> bool { + self.hydraulics + .blue_electric_pump_controller + .should_pressurise() + } + + fn is_edp1_green_pump_controller_pressurising(&self) -> bool { + self.hydraulics + .engine_driven_pump_1_controller + .should_pressurise() + } + + fn is_edp2_yellow_pump_controller_pressurising(&self) -> bool { + self.hydraulics + .engine_driven_pump_2_controller + .should_pressurise() + } + + fn is_ptu_controller_activating_ptu(&self) -> bool { + self.hydraulics + .power_transfer_unit_controller + .should_enable() + } + + fn is_ptu_enabled(&self) -> bool { + self.hydraulics.power_transfer_unit.is_enabled() + } + + fn is_blue_pressure_switch_pressurised(&self) -> bool { + self.hydraulics.is_blue_pressure_switch_pressurised() + } + + fn is_green_pressure_switch_pressurised(&self) -> bool { + self.hydraulics.is_green_pressure_switch_pressurised() + } + + fn is_yellow_pressure_switch_pressurised(&self) -> bool { + self.hydraulics.is_yellow_pressure_switch_pressurised() + } + + fn is_yellow_leak_meas_valve_commanded_open(&self) -> bool { + self.hydraulics + .yellow_circuit_controller + .should_open_leak_measurement_valve() + } + + fn is_blue_leak_meas_valve_commanded_open(&self) -> bool { + self.hydraulics + .blue_circuit_controller + .should_open_leak_measurement_valve() + } + + fn is_green_leak_meas_valve_commanded_open(&self) -> bool { + self.hydraulics + .green_circuit_controller + .should_open_leak_measurement_valve() + } + + fn nose_steering_position(&self) -> Angle { + self.hydraulics.nose_steering.position_feedback() + } + + fn is_cargo_fwd_door_locked_up(&self) -> bool { + self.hydraulics.forward_cargo_door_controller.control_state + == DoorControlState::UpLocked + } + + fn set_ac_bus_1_is_powered(&mut self, bus_is_alive: bool) { + self.is_ac_1_powered = bus_is_alive; + } + + fn set_ac_bus_2_is_powered(&mut self, bus_is_alive: bool) { + self.is_ac_2_powered = bus_is_alive; + } + + fn set_dc_ground_service_is_powered(&mut self, bus_is_alive: bool) { + self.is_dc_ground_service_powered = bus_is_alive; + } + + fn set_ac_ground_service_is_powered(&mut self, bus_is_alive: bool) { + self.is_ac_ground_service_powered = bus_is_alive; + } + + fn set_dc_bus_2_is_powered(&mut self, bus_is_alive: bool) { + self.is_dc_2_powered = bus_is_alive; + } + + fn set_dc_ess_is_powered(&mut self, bus_is_alive: bool) { + self.is_dc_ess_powered = bus_is_alive; + } + + fn use_worst_case_ptu(&mut self) { + self.hydraulics.power_transfer_unit.update_characteristics( + &A380PowerTransferUnitCharacteristics::new_worst_part_acceptable(), + ); + } + } + + impl Aircraft for A380HydraulicsTestAircraft { + fn update_before_power_distribution( + &mut self, + _: &UpdateContext, + electricity: &mut Electricity, + ) { + self.powered_source_ac + .power_with_potential(ElectricPotential::new::(115.)); + electricity.supplied_by(&self.powered_source_ac); + + if self.is_ac_1_powered { + electricity.flow(&self.powered_source_ac, &self.ac_1_bus); + } + + if self.is_ac_2_powered { + electricity.flow(&self.powered_source_ac, &self.ac_2_bus); + } + + if self.is_ac_ground_service_powered { + electricity.flow(&self.powered_source_ac, &self.ac_ground_service_bus); + } + + if self.is_dc_ground_service_powered { + electricity.flow(&self.powered_source_ac, &self.dc_ground_service_bus); + } + + if self.is_dc_1_powered { + electricity.flow(&self.powered_source_ac, &self.dc_1_bus); + } + + if self.is_dc_2_powered { + electricity.flow(&self.powered_source_ac, &self.dc_2_bus); + } + + if self.is_dc_ess_powered { + electricity.flow(&self.powered_source_ac, &self.dc_ess_bus); + } + + if self.is_dc_hot_1_powered { + electricity.flow(&self.powered_source_ac, &self.dc_hot_1_bus); + } + + if self.is_dc_hot_2_powered { + electricity.flow(&self.powered_source_ac, &self.dc_hot_2_bus); + } + } + + fn update_after_power_distribution(&mut self, context: &UpdateContext) { + self.electrical.update(&self.hydraulics.gcu, context); + + self.adirus.update(context); + + self.lgcius.update( + context, + &self.landing_gear, + &self.hydraulics.gear_system, + self.ext_pwr.output_potential().is_powered(), + ); + + self.hydraulics.update( + context, + &self.engine_1, + &self.engine_2, + &self.overhead, + &self.autobrake_panel, + &self.engine_fire_overhead, + &self.lgcius, + &self.emergency_electrical_overhead, + &self.electrical, + &self.pneumatics, + &self.adirus, + ); + + self.overhead.update(&self.hydraulics); + } + } + impl SimulationElement for A380HydraulicsTestAircraft { + fn accept(&mut self, visitor: &mut T) { + self.engine_1.accept(visitor); + self.engine_2.accept(visitor); + self.landing_gear.accept(visitor); + self.lgcius.accept(visitor); + self.hydraulics.accept(visitor); + self.autobrake_panel.accept(visitor); + self.overhead.accept(visitor); + self.engine_fire_overhead.accept(visitor); + self.emergency_electrical_overhead.accept(visitor); + self.electrical.accept(visitor); + self.ext_pwr.accept(visitor); + + visitor.visit(self); + } + } + + struct A380HydraulicsTestBed { + test_bed: SimulationTestBed, + } + impl A380HydraulicsTestBed { + fn new_with_start_state(start_state: StartState) -> Self { + Self { + test_bed: SimulationTestBed::new_with_start_state( + start_state, + A380HydraulicsTestAircraft::new, + ), + } + } + + fn run_one_tick(mut self) -> Self { + self.run_with_delta(A380Hydraulic::HYDRAULIC_SIM_TIME_STEP); + self + } + + fn run_waiting_for(mut self, delta: Duration) -> Self { + self.test_bed.run_multiple_frames(delta); + self + } + + fn is_green_edp_commanded_on(&self) -> bool { + self.query(|a| a.is_green_edp_commanded_on()) + } + + fn is_yellow_edp_commanded_on(&self) -> bool { + self.query(|a| a.is_yellow_edp_commanded_on()) + } + + fn is_ptu_enabled(&self) -> bool { + self.query(|a| a.is_ptu_enabled()) + } + + fn is_blue_pressure_switch_pressurised(&self) -> bool { + self.query(|a| a.is_blue_pressure_switch_pressurised()) + } + + fn is_green_pressure_switch_pressurised(&self) -> bool { + self.query(|a| a.is_green_pressure_switch_pressurised()) + } + + fn is_yellow_pressure_switch_pressurised(&self) -> bool { + self.query(|a| a.is_yellow_pressure_switch_pressurised()) + } + + fn is_flaps_moving(&mut self) -> bool { + self.read_by_name("IS_FLAPS_MOVING") + } + + fn is_slats_moving(&mut self) -> bool { + self.read_by_name("IS_SLATS_MOVING") + } + + fn nose_steering_position(&self) -> Angle { + self.query(|a| a.nose_steering_position()) + } + + fn is_cargo_fwd_door_locked_down(&mut self) -> bool { + self.read_by_name("FWD_DOOR_CARGO_LOCKED") + } + + fn is_cargo_fwd_door_locked_up(&self) -> bool { + self.query(|a| a.is_cargo_fwd_door_locked_up()) + } + + fn cargo_fwd_door_position(&mut self) -> f64 { + self.read_by_name("FWD_DOOR_CARGO_POSITION") + } + + fn cargo_aft_door_position(&mut self) -> f64 { + self.read_by_name("AFT_DOOR_CARGO_POSITION") + } + + fn green_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_GREEN_SYSTEM_1_SECTION_PRESSURE") + } + + fn blue_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_BLUE_SYSTEM_1_SECTION_PRESSURE") + } + + fn yellow_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_YELLOW_SYSTEM_1_SECTION_PRESSURE") + } + + fn get_yellow_reservoir_volume(&mut self) -> Volume { + self.read_by_name("HYD_YELLOW_RESERVOIR_LEVEL") + } + + fn is_green_edp_press_low(&mut self) -> bool { + self.read_by_name("HYD_GREEN_EDPUMP_LOW_PRESS") + } + + fn green_edp_has_fault(&mut self) -> bool { + self.read_by_name("OVHD_HYD_ENG_1_PUMP_PB_HAS_FAULT") + } + + fn yellow_edp_has_fault(&mut self) -> bool { + self.read_by_name("OVHD_HYD_ENG_2_PUMP_PB_HAS_FAULT") + } + + fn is_yellow_edp_press_low(&mut self) -> bool { + self.read_by_name("HYD_YELLOW_EDPUMP_LOW_PRESS") + } + + fn is_yellow_epump_press_low(&mut self) -> bool { + self.read_by_name("HYD_YELLOW_EPUMP_LOW_PRESS") + } + + fn is_blue_epump_press_low(&mut self) -> bool { + self.read_by_name("HYD_BLUE_EPUMP_LOW_PRESS") + } + + fn blue_epump_has_fault(&mut self) -> bool { + self.read_by_name("OVHD_HYD_EPUMPB_PB_HAS_FAULT") + } + + fn yellow_epump_has_fault(&mut self) -> bool { + self.read_by_name("OVHD_HYD_EPUMPY_PB_HAS_FAULT") + } + + fn ptu_has_fault(&mut self) -> bool { + self.read_by_name("OVHD_HYD_PTU_PB_HAS_FAULT") + } + + fn blue_epump_override_is_on(&mut self) -> bool { + self.read_by_name("OVHD_HYD_EPUMPY_OVRD_IS_ON") + } + + fn get_brake_left_yellow_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_BRAKE_ALTN_LEFT_PRESS") + } + + fn get_brake_right_yellow_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_BRAKE_ALTN_RIGHT_PRESS") + } + + fn get_green_reservoir_volume(&mut self) -> Volume { + self.read_by_name("HYD_GREEN_RESERVOIR_LEVEL") + } + + fn get_blue_reservoir_volume(&mut self) -> Volume { + self.read_by_name("HYD_BLUE_RESERVOIR_LEVEL") + } + + fn autobrake_mode(&mut self) -> AutobrakeMode { + ReadByName::::read_by_name( + self, + "AUTOBRAKES_ARMED_MODE", + ) + .into() + } + + fn get_brake_left_green_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_BRAKE_NORM_LEFT_PRESS") + } + + fn get_brake_right_green_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_BRAKE_NORM_RIGHT_PRESS") + } + + fn get_brake_yellow_accumulator_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_BRAKE_ALTN_ACC_PRESS") + } + + fn get_brake_yellow_accumulator_fluid_volume(&self) -> Volume { + self.query(|a| a.get_yellow_brake_accumulator_fluid_volume()) + } + + fn get_rat_position(&mut self) -> f64 { + self.read_by_name("HYD_RAT_STOW_POSITION") + } + + fn get_rat_rpm(&mut self) -> f64 { + self.read_by_name("A32NX_HYD_RAT_RPM") + } + + fn get_left_aileron_position(&mut self) -> Ratio { + Ratio::new::(self.read_by_name("HYD_AIL_LEFT_DEFLECTION")) + } + + fn get_right_aileron_position(&mut self) -> Ratio { + Ratio::new::(self.read_by_name("HYD_AIL_RIGHT_DEFLECTION")) + } + + fn get_left_elevator_position(&mut self) -> Ratio { + Ratio::new::(self.read_by_name("HYD_ELEV_LEFT_DEFLECTION")) + } + + fn get_right_elevator_position(&mut self) -> Ratio { + Ratio::new::(self.read_by_name("HYD_ELEV_RIGHT_DEFLECTION")) + } + + fn get_rudder_position(&mut self) -> Ratio { + Ratio::new::(self.read_by_name("HYD_RUD_DEFLECTION")) + } + + fn get_nose_steering_ratio(&mut self) -> Ratio { + Ratio::new::(self.read_by_name("NOSE_WHEEL_POSITION_RATIO")) + } + + fn rat_deploy_commanded(&self) -> bool { + self.query(|a| a.is_rat_commanded_to_deploy()) + } + + fn is_emergency_gen_at_nominal_speed(&self) -> bool { + self.query(|a| a.is_emergency_gen_at_nominal_speed()) + } + + fn is_fire_valve_eng1_closed(&mut self) -> bool { + !ReadByName::::read_by_name( + self, + "HYD_GREEN_PUMP_1_FIRE_VALVE_OPENED", + ) && !self.query(|a| { + a.hydraulics.green_circuit.is_fire_shutoff_valve_open( + A380HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES, + ) + }) + } + + fn is_fire_valve_eng2_closed(&mut self) -> bool { + !ReadByName::::read_by_name( + self, + "HYD_YELLOW_PUMP_1_FIRE_VALVE_OPENED", + ) && !self.query(|a| { + a.hydraulics.green_circuit.is_fire_shutoff_valve_open( + A380HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES, + ) + }) + } + + fn is_yellow_leak_meas_valve_commanded_open(&mut self) -> bool { + self.query(|a| a.is_yellow_leak_meas_valve_commanded_open()) + } + + fn is_green_leak_meas_valve_commanded_open(&mut self) -> bool { + self.query(|a| a.is_green_leak_meas_valve_commanded_open()) + } + + fn is_blue_leak_meas_valve_commanded_open(&mut self) -> bool { + self.query(|a| a.is_blue_leak_meas_valve_commanded_open()) + } + + fn green_leak_meas_valve_closed(mut self) -> Self { + self.write_by_name("OVHD_HYD_LEAK_MEASUREMENT_G_PB_IS_AUTO", false); + self + } + + fn blue_leak_meas_valve_closed(mut self) -> Self { + self.write_by_name("OVHD_HYD_LEAK_MEASUREMENT_B_PB_IS_AUTO", false); + self + } + + fn yellow_leak_meas_valve_closed(mut self) -> Self { + self.write_by_name("OVHD_HYD_LEAK_MEASUREMENT_Y_PB_IS_AUTO", false); + self + } + + fn engines_off(self) -> Self { + self.stop_eng1().stop_eng2() + } + + fn external_power(mut self, is_connected: bool) -> Self { + self.write_by_name("EXTERNAL POWER AVAILABLE:1", is_connected); + + if is_connected { + self = self.on_the_ground(); + } + self + } + + fn with_worst_case_ptu(mut self) -> Self { + self.command(|a| a.use_worst_case_ptu()); + self + } + + fn on_the_ground(mut self) -> Self { + self.set_indicated_altitude(Length::new::(0.)); + self.set_on_ground(true); + self.set_indicated_airspeed(Velocity::new::(5.)); + self + } + + fn on_the_ground_after_touchdown(mut self) -> Self { + self.set_indicated_altitude(Length::new::(0.)); + self.set_on_ground(true); + self.set_indicated_airspeed(Velocity::new::(100.)); + self + } + + fn air_press_low(mut self) -> Self { + self.command(|a| a.pneumatics.set_low_air_pressure()); + self + } + + fn air_press_nominal(mut self) -> Self { + self.command(|a| a.pneumatics.set_nominal_air_pressure()); + self + } + + fn rotates_on_runway(mut self) -> Self { + self.set_indicated_altitude(Length::new::(0.)); + self.set_on_ground(false); + self.set_indicated_airspeed(Velocity::new::(135.)); + self.write_by_name( + LandingGear::GEAR_CENTER_COMPRESSION, + Ratio::new::(0.5), + ); + self.write_by_name(LandingGear::GEAR_LEFT_COMPRESSION, Ratio::new::(0.8)); + self.write_by_name( + LandingGear::GEAR_RIGHT_COMPRESSION, + Ratio::new::(0.8), + ); + self + } + + fn in_flight(mut self) -> Self { + self.set_on_ground(false); + self.set_indicated_altitude(Length::new::(2500.)); + self.set_indicated_airspeed(Velocity::new::(180.)); + self.start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .set_gear_lever_up() + .set_park_brake(false) + .external_power(false) + } + + fn sim_not_ready(mut self) -> Self { + self.set_sim_is_ready(false); + self + } + + fn sim_ready(mut self) -> Self { + self.set_sim_is_ready(true); + self + } + + fn set_tiller_demand(mut self, steering_ratio: Ratio) -> Self { + self.write_by_name("TILLER_HANDLE_POSITION", steering_ratio.get::()); + self + } + + fn set_autopilot_steering_demand(mut self, steering_ratio: Ratio) -> Self { + self.write_by_name("AUTOPILOT_NOSEWHEEL_DEMAND", steering_ratio.get::()); + self + } + + fn set_eng1_fire_button(mut self, is_active: bool) -> Self { + self.write_by_name("FIRE_BUTTON_ENG1", is_active); + self + } + + fn set_eng2_fire_button(mut self, is_active: bool) -> Self { + self.write_by_name("FIRE_BUTTON_ENG2", is_active); + self + } + + fn open_fwd_cargo_door(mut self) -> Self { + self.write_by_name("FWD_DOOR_CARGO_OPEN_REQ", 1.); + self + } + + fn close_fwd_cargo_door(mut self) -> Self { + self.write_by_name("FWD_DOOR_CARGO_OPEN_REQ", 0.); + self + } + + fn set_pushback_state(mut self, is_pushed_back: bool) -> Self { + if is_pushed_back { + self.write_by_name("PUSHBACK STATE", 0.); + } else { + self.write_by_name("PUSHBACK STATE", 3.); + } + self + } + + fn set_pushback_angle(mut self, angle: Angle) -> Self { + self.write_by_name("PUSHBACK ANGLE", angle.get::()); + self + } + + fn is_nw_disc_memo_shown(&mut self) -> bool { + self.read_by_name("HYD_NW_STRG_DISC_ECAM_MEMO") + } + + fn is_ptu_running_high_pitch_sound(&mut self) -> bool { + self.read_by_name("HYD_PTU_HIGH_PITCH_SOUND") + } + + fn start_eng1(mut self, n2: Ratio) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:1", true); + self.write_by_name("ENGINE_N2:1", n2); + + self + } + + fn start_eng2(mut self, n2: Ratio) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:2", true); + self.write_by_name("ENGINE_N2:2", n2); + + self + } + + fn stop_eng1(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:1", false); + self.write_by_name("ENGINE_N2:1", 0.); + + self + } + + fn stopping_eng1(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:1", false); + self.write_by_name("ENGINE_N2:1", 25.); + + self + } + + fn stop_eng2(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:2", false); + self.write_by_name("ENGINE_N2:2", 0.); + + self + } + + fn stopping_eng2(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:2", false); + self.write_by_name("ENGINE_N2:2", 25.); + + self + } + + fn set_park_brake(mut self, is_set: bool) -> Self { + self.write_by_name("PARK_BRAKE_LEVER_POS", is_set); + self + } + + fn set_gear_lever_up(mut self) -> Self { + // One tick is needed so lever up can be evaluated + self.write_by_name("GEAR_LEVER_POSITION_REQUEST", false); + self = self.run_one_tick(); + + self + } + + fn set_gear_lever_down(mut self) -> Self { + self.write_by_name("GEAR_LEVER_POSITION_REQUEST", true); + + self + } + + fn set_anti_skid(mut self, is_set: bool) -> Self { + self.write_by_name("ANTISKID BRAKES ACTIVE", is_set); + self + } + + fn set_yellow_e_pump(mut self, is_auto: bool) -> Self { + self.write_by_name("OVHD_HYD_EPUMPY_PB_IS_AUTO", is_auto); + self + } + + fn set_blue_e_pump(mut self, is_auto: bool) -> Self { + self.write_by_name("OVHD_HYD_EPUMPB_PB_IS_AUTO", is_auto); + self + } + + fn set_blue_e_pump_ovrd_pressed(mut self, is_pressed: bool) -> Self { + self.write_by_name("OVHD_HYD_EPUMPY_OVRD_IS_PRESSED", is_pressed); + self + } + + fn set_green_ed_pump(mut self, is_auto: bool) -> Self { + self.write_by_name("OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO", is_auto); + self + } + + fn set_yellow_ed_pump(mut self, is_auto: bool) -> Self { + self.write_by_name("OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO", is_auto); + self + } + + fn set_ptu_state(mut self, is_auto: bool) -> Self { + self.write_by_name("OVHD_HYD_PTU_PB_IS_AUTO", is_auto); + self + } + + fn set_spoiler_position_demand( + mut self, + left_demand: Ratio, + right_demand: Ratio, + ) -> Self { + self.write_by_name("HYD_SPOILER_1_RIGHT_DEMAND", right_demand.get::()); + self.write_by_name("HYD_SPOILER_2_RIGHT_DEMAND", right_demand.get::()); + self.write_by_name("HYD_SPOILER_3_RIGHT_DEMAND", right_demand.get::()); + self.write_by_name("HYD_SPOILER_4_RIGHT_DEMAND", right_demand.get::()); + self.write_by_name("HYD_SPOILER_5_RIGHT_DEMAND", right_demand.get::()); + + self.write_by_name("HYD_SPOILER_1_LEFT_DEMAND", left_demand.get::()); + self.write_by_name("HYD_SPOILER_2_LEFT_DEMAND", left_demand.get::()); + self.write_by_name("HYD_SPOILER_3_LEFT_DEMAND", left_demand.get::()); + self.write_by_name("HYD_SPOILER_4_LEFT_DEMAND", left_demand.get::()); + self.write_by_name("HYD_SPOILER_5_LEFT_DEMAND", left_demand.get::()); + + self + } + + fn get_spoiler_left_mean_position(&mut self) -> Ratio { + (Ratio::new::(self.read_by_name("HYD_SPOIL_1_LEFT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOIL_2_LEFT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOIL_3_LEFT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOIL_4_LEFT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOIL_5_LEFT_DEFLECTION"))) + / 5. + } + + fn get_spoiler_right_mean_position(&mut self) -> Ratio { + (Ratio::new::(self.read_by_name("HYD_SPOIL_1_RIGHT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOIL_2_RIGHT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOIL_3_RIGHT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOIL_4_RIGHT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOIL_5_RIGHT_DEFLECTION"))) + / 5. + } + + fn set_flaps_handle_position(mut self, pos: u8) -> Self { + self.write_by_name("FLAPS_HANDLE_INDEX", pos as f64); + self + } + + fn get_flaps_left_position_percent(&mut self) -> f64 { + self.read_by_name("LEFT_FLAPS_POSITION_PERCENT") + } + + fn get_flaps_right_position_percent(&mut self) -> f64 { + self.read_by_name("RIGHT_FLAPS_POSITION_PERCENT") + } + + fn get_slats_left_position_percent(&mut self) -> f64 { + self.read_by_name("LEFT_SLATS_POSITION_PERCENT") + } + + fn get_slats_right_position_percent(&mut self) -> f64 { + self.read_by_name("RIGHT_SLATS_POSITION_PERCENT") + } + + fn get_real_gear_position(&mut self, wheel_id: GearWheel) -> Ratio { + match wheel_id { + GearWheel::NOSE => self.read_by_name("GEAR_CENTER_POSITION"), + GearWheel::LEFT => self.read_by_name("GEAR_LEFT_POSITION"), + GearWheel::RIGHT => self.read_by_name("GEAR_RIGHT_POSITION"), + } + } + + fn get_real_gear_door_position(&mut self, wheel_id: GearWheel) -> Ratio { + match wheel_id { + GearWheel::NOSE => self.read_by_name("GEAR_DOOR_CENTER_POSITION"), + GearWheel::LEFT => self.read_by_name("GEAR_DOOR_LEFT_POSITION"), + GearWheel::RIGHT => self.read_by_name("GEAR_DOOR_RIGHT_POSITION"), + } + } + + fn is_all_gears_really_up(&mut self) -> bool { + self.get_real_gear_position(GearWheel::NOSE) <= Ratio::new::(0.01) + && self.get_real_gear_position(GearWheel::LEFT) <= Ratio::new::(0.01) + && self.get_real_gear_position(GearWheel::RIGHT) <= Ratio::new::(0.01) + } + + fn is_all_gears_really_down(&mut self) -> bool { + self.get_real_gear_position(GearWheel::NOSE) >= Ratio::new::(0.99) + && self.get_real_gear_position(GearWheel::LEFT) >= Ratio::new::(0.99) + && self.get_real_gear_position(GearWheel::RIGHT) >= Ratio::new::(0.99) + } + + fn is_all_doors_really_up(&mut self) -> bool { + self.get_real_gear_door_position(GearWheel::NOSE) <= Ratio::new::(0.01) + && self.get_real_gear_door_position(GearWheel::LEFT) + <= Ratio::new::(0.01) + && self.get_real_gear_door_position(GearWheel::RIGHT) + <= Ratio::new::(0.01) + } + + fn is_all_doors_really_down(&mut self) -> bool { + self.get_real_gear_door_position(GearWheel::NOSE) >= Ratio::new::(0.9) + && self.get_real_gear_door_position(GearWheel::LEFT) >= Ratio::new::(0.9) + && self.get_real_gear_door_position(GearWheel::RIGHT) + >= Ratio::new::(0.9) + } + + fn ac_bus_1_lost(mut self) -> Self { + self.command(|a| a.set_ac_bus_1_is_powered(false)); + self + } + + fn ac_bus_2_lost(mut self) -> Self { + self.command(|a| a.set_ac_bus_2_is_powered(false)); + self + } + + fn dc_ground_service_lost(mut self) -> Self { + self.command(|a| a.set_dc_ground_service_is_powered(false)); + self + } + fn dc_ground_service_avail(mut self) -> Self { + self.command(|a| a.set_dc_ground_service_is_powered(true)); + self + } + + fn ac_ground_service_lost(mut self) -> Self { + self.command(|a| a.set_ac_ground_service_is_powered(false)); + self + } + + fn dc_bus_2_lost(mut self) -> Self { + self.command(|a| a.set_dc_bus_2_is_powered(false)); + self + } + + fn dc_ess_lost(mut self) -> Self { + self.command(|a| a.set_dc_ess_is_powered(false)); + self + } + + fn dc_ess_active(mut self) -> Self { + self.command(|a| a.set_dc_ess_is_powered(true)); + self + } + + fn set_cold_dark_inputs(self) -> Self { + self.set_eng1_fire_button(false) + .set_eng2_fire_button(false) + .set_blue_e_pump(true) + .set_yellow_e_pump(true) + .set_green_ed_pump(true) + .set_yellow_ed_pump(true) + .set_ptu_state(true) + .set_park_brake(true) + .set_anti_skid(true) + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .set_gear_lever_down() + .set_pushback_state(false) + .air_press_nominal() + .set_ailerons_neutral() + .set_elevator_neutral() + } + + fn set_left_brake(mut self, position: Ratio) -> Self { + self.write_by_name("LEFT_BRAKE_PEDAL_INPUT", position); + self + } + + fn set_right_brake(mut self, position: Ratio) -> Self { + self.write_by_name("RIGHT_BRAKE_PEDAL_INPUT", position); + self + } + + fn set_autobrake_disarmed_with_set_variable(mut self) -> Self { + self.write_by_name("AUTOBRAKES_ARMED_MODE_SET", 0); + self + } + + fn set_autobrake_low_with_set_variable(mut self) -> Self { + self.write_by_name("AUTOBRAKES_ARMED_MODE_SET", 1); + self + } + + fn set_autobrake_med_with_set_variable(mut self) -> Self { + self.write_by_name("AUTOBRAKES_ARMED_MODE_SET", 2); + self + } + + fn set_autobrake_max_with_set_variable(mut self) -> Self { + self.write_by_name("AUTOBRAKES_ARMED_MODE_SET", 3); + self + } + + fn set_autobrake_low(mut self) -> Self { + self.write_by_name("OVHD_AUTOBRK_LOW_ON_IS_PRESSED", true); + self = self.run_one_tick(); + self.write_by_name("OVHD_AUTOBRK_LOW_ON_IS_PRESSED", false); + self + } + + fn set_autobrake_med(mut self) -> Self { + self.write_by_name("OVHD_AUTOBRK_MED_ON_IS_PRESSED", true); + self = self.run_one_tick(); + self.write_by_name("OVHD_AUTOBRK_MED_ON_IS_PRESSED", false); + self + } + + fn set_autobrake_max(mut self) -> Self { + self.write_by_name("OVHD_AUTOBRK_MAX_ON_IS_PRESSED", true); + self = self.run_one_tick(); + self.write_by_name("OVHD_AUTOBRK_MAX_ON_IS_PRESSED", false); + self + } + + fn set_deploy_spoilers(mut self) -> Self { + self.write_by_name("SPOILERS_GROUND_SPOILERS_ACTIVE", true); + self + } + + fn set_retract_spoilers(mut self) -> Self { + self.write_by_name("SPOILERS_GROUND_SPOILERS_ACTIVE", false); + self + } + + fn set_ailerons_neutral(mut self) -> Self { + self.write_by_name("HYD_AILERON_LEFT_DEMAND", 0.5); + self.write_by_name("HYD_AILERON_RIGHT_DEMAND", 0.5); + self + } + + fn set_elevator_neutral(mut self) -> Self { + self.write_by_name("HYD_ELEVATOR_DEMAND", 0.5); + self + } + + fn set_ailerons_left_turn(mut self) -> Self { + self.write_by_name("HYD_AILERON_LEFT_DEMAND", 1.); + self.write_by_name("HYD_AILERON_RIGHT_DEMAND", 0.); + self + } + + fn set_ailerons_right_turn(mut self) -> Self { + self.write_by_name("HYD_AILERON_LEFT_DEMAND", 0.); + self.write_by_name("HYD_AILERON_RIGHT_DEMAND", 1.); + self + } + + fn gear_system_state(&self) -> GearSystemState { + self.query(|a| a.lgcius.active_lgciu().gear_system_state()) + } + + fn empty_brake_accumulator_using_park_brake(mut self) -> Self { + self = self + .set_park_brake(true) + .run_waiting_for(Duration::from_secs(1)); + + let mut number_of_loops = 0; + while self + .get_brake_yellow_accumulator_fluid_volume() + .get::() + > 0.001 + { + self = self + .set_park_brake(false) + .run_waiting_for(Duration::from_secs(1)) + .set_park_brake(true) + .run_waiting_for(Duration::from_secs(1)); + number_of_loops += 1; + assert!(number_of_loops < 20); + } + + self = self + .set_park_brake(false) + .run_waiting_for(Duration::from_secs(1)) + .set_park_brake(true) + .run_waiting_for(Duration::from_secs(1)); + + self + } + + fn empty_brake_accumulator_using_pedal_brake(mut self) -> Self { + let mut number_of_loops = 0; + while self + .get_brake_yellow_accumulator_fluid_volume() + .get::() + > 0.001 + { + self = self + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)) + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + number_of_loops += 1; + assert!(number_of_loops < 50); + } + + self = self + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)) + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + self + } + + fn turn_emergency_gear_extension_n_turns(mut self, number_of_turns: u8) -> Self { + self.write_by_name("GRAVITYGEAR_ROTATE_PCT", number_of_turns as f64 * 100.); + self + } + + fn stow_emergency_gear_extension(mut self) -> Self { + self.write_by_name("GRAVITYGEAR_ROTATE_PCT", 0.); + self + } + + fn press_blue_epump_override_button_once(self) -> Self { + self.set_blue_e_pump_ovrd_pressed(true) + .run_one_tick() + .set_blue_e_pump_ovrd_pressed(false) + .run_one_tick() + } + } + impl TestBed for A380HydraulicsTestBed { + type Aircraft = A380HydraulicsTestAircraft; + + fn test_bed(&self) -> &SimulationTestBed { + &self.test_bed + } + + fn test_bed_mut(&mut self) -> &mut SimulationTestBed { + &mut self.test_bed + } + } + + fn test_bed_on_ground() -> A380HydraulicsTestBed { + A380HydraulicsTestBed::new_with_start_state(StartState::Apron) + } + + fn test_bed_in_flight() -> A380HydraulicsTestBed { + A380HydraulicsTestBed::new_with_start_state(StartState::Cruise) + } + + fn test_bed_on_ground_with() -> A380HydraulicsTestBed { + test_bed_on_ground() + } + + fn test_bed_in_flight_with() -> A380HydraulicsTestBed { + test_bed_in_flight() + } + + #[test] + fn pressure_state_at_init_one_simulation_step() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.is_ptu_enabled()); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(50.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn pressure_state_after_5s() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_secs(5)); + + assert!(test_bed.is_ptu_enabled()); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(50.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn ptu_inhibited_by_overhead_off_push_button() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Enabled on cold start + assert!(test_bed.is_ptu_enabled()); + + // Ptu push button disables PTU accordingly + test_bed = test_bed.set_ptu_state(false).run_one_tick(); + assert!(!test_bed.is_ptu_enabled()); + test_bed = test_bed.set_ptu_state(true).run_one_tick(); + assert!(test_bed.is_ptu_enabled()); + } + + #[test] + fn ptu_inhibited_on_ground_when_only_one_engine_on_and_park_brake_on() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + + assert!(!test_bed.is_ptu_enabled()); + + test_bed = test_bed.set_park_brake(false).run_one_tick(); + assert!(test_bed.is_ptu_enabled()); + + test_bed = test_bed.set_park_brake(true).run_one_tick(); + assert!(!test_bed.is_ptu_enabled()); + } + + #[test] + fn ptu_inhibited_on_ground_is_activated_when_nose_gear_in_air() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + + assert!(!test_bed.is_ptu_enabled()); + + test_bed = test_bed.rotates_on_runway().run_one_tick(); + assert!(test_bed.is_ptu_enabled()); + } + + #[test] + fn ptu_unpowered_cant_inhibit() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Enabled on cold start + assert!(test_bed.is_ptu_enabled()); + + // Ptu push button disables PTU accordingly + test_bed = test_bed.set_ptu_state(false).run_one_tick(); + assert!(!test_bed.is_ptu_enabled()); + + // No power on closing valve : ptu become active + test_bed = test_bed.dc_ground_service_lost().run_one_tick(); + assert!(test_bed.is_ptu_enabled()); + + test_bed = test_bed.dc_ground_service_avail().run_one_tick(); + assert!(!test_bed.is_ptu_enabled()); + } + + #[test] + fn ptu_cargo_operation_inhibit() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Enabled on cold start + assert!(test_bed.is_ptu_enabled()); + + // Ptu disabled from cargo operation + test_bed = test_bed.open_fwd_cargo_door().run_waiting_for( + Duration::from_secs(1) + A380DoorController::DELAY_UNLOCK_TO_HYDRAULIC_CONTROL, + ); + + assert!(!test_bed.is_ptu_enabled()); + test_bed = test_bed.run_waiting_for( + Duration::from_secs(25) + A380PowerTransferUnitController::DURATION_OF_PTU_INHIBIT_AFTER_CARGO_DOOR_OPERATION, + ); // Should re enabled after 40s + assert!(test_bed.is_ptu_enabled()); + } + + #[test] + fn nose_wheel_pin_detection() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(!test_bed.query(|a| a.is_nws_pin_inserted())); + assert!(!test_bed.is_nw_disc_memo_shown()); + + test_bed = test_bed.set_pushback_state(true).run_one_tick(); + assert!(test_bed.query(|a| a.is_nws_pin_inserted())); + assert!(test_bed.is_nw_disc_memo_shown()); + + test_bed = test_bed + .set_pushback_state(false) + .run_waiting_for(Duration::from_secs(1)); + assert!(test_bed.query(|a| a.is_nws_pin_inserted())); + assert!(test_bed.is_nw_disc_memo_shown()); + + test_bed = test_bed.set_pushback_state(false).run_waiting_for( + PushbackTug::DURATION_AFTER_WHICH_NWS_PIN_IS_REMOVED_AFTER_PUSHBACK, + ); + + assert!(!test_bed.query(|a| a.is_nws_pin_inserted())); + assert!(!test_bed.is_nw_disc_memo_shown()); + } + + #[test] + fn cargo_door_yellow_epump_powering() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(!test_bed.query(|a| a.is_cargo_powering_yellow_epump())); + + // Need to wait for operator to first unlock, then activate hydraulic control + test_bed = test_bed.open_fwd_cargo_door().run_waiting_for( + Duration::from_secs(1) + A380DoorController::DELAY_UNLOCK_TO_HYDRAULIC_CONTROL, + ); + assert!(test_bed.query(|a| a.is_cargo_powering_yellow_epump())); + + // Wait for the door to fully open + test_bed = test_bed.run_waiting_for(Duration::from_secs(25)); + assert!(test_bed.is_cargo_fwd_door_locked_up()); + + test_bed = test_bed.run_waiting_for( + A380YellowElectricPumpController::DURATION_OF_YELLOW_PUMP_ACTIVATION_AFTER_CARGO_DOOR_OPERATION, + ); + + assert!(!test_bed.query(|a| a.is_cargo_powering_yellow_epump())); + } + + #[test] + fn ptu_pressurise_green_from_yellow_epump() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Enabled on cold start + assert!(test_bed.is_ptu_enabled()); + + // Yellow epump ON / Waiting 25s + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(25)); + + assert!(test_bed.is_ptu_enabled()); + + // Now we should have pressure in yellow and green + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.green_pressure() < Pressure::new::(3100.)); + + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(50.)); + assert!(test_bed.blue_pressure() > Pressure::new::(-50.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2000.)); + assert!(test_bed.yellow_pressure() < Pressure::new::(3100.)); + + // Ptu push button disables PTU / green press should fall + test_bed = test_bed + .set_ptu_state(false) + .run_waiting_for(Duration::from_secs(20)); + assert!(!test_bed.is_ptu_enabled()); + + // Now we should have pressure in yellow only + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(500.)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(50.)); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2000.)); + } + + #[test] + fn ptu_pressurise_green_from_yellow_epump_and_edp2() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .start_eng2(Ratio::new::(100.)) + .set_park_brake(false) + .set_yellow_e_pump(false) + .set_yellow_ed_pump(true) // Else Ptu inhibited by parking brake + .run_waiting_for(Duration::from_secs(25)); + + assert!(test_bed.is_ptu_enabled()); + + // Now we should have pressure in yellow and green + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.green_pressure() < Pressure::new::(3100.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2000.)); + assert!(test_bed.yellow_pressure() < Pressure::new::(3100.)); + } + + #[test] + fn green_edp_buildup() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Starting eng 1 + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_one_tick(); + + // ALMOST No pressure + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(1000.)); + + // Blue is auto run from engine master switches logic + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(1000.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(1000.)); + + // Waiting for 5s pressure should be at 3000 psi + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(5)); + + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2900.)); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(50.)); + + // Stoping engine, pressure should fall in 20s + test_bed = test_bed + .stop_eng1() + .run_waiting_for(Duration::from_secs(20)); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(500.)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(200.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn green_edp_no_fault_on_ground_eng_off() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_millis(500)); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_green_edp_commanded_on()); + // EDP should have no fault + assert!(!test_bed.green_edp_has_fault()); + } + + #[test] + fn green_edp_fault_not_on_ground_eng_off() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .engines_off() + .run_one_tick(); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_green_edp_commanded_on()); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + // EDP should have a fault as we are in flight + assert!(test_bed.green_edp_has_fault()); + } + + #[test] + fn green_edp_fault_on_ground_eng_starting() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_millis(500)); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_green_edp_commanded_on()); + // EDP should have no fault + assert!(!test_bed.green_edp_has_fault()); + + test_bed = test_bed + .start_eng1(Ratio::new::(3.)) + .run_one_tick(); + + assert!(!test_bed.green_edp_has_fault()); + + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_one_tick(); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_edp_has_fault()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(10)); + + // When finally pressurised no fault + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(!test_bed.green_edp_has_fault()); + } + + #[test] + fn yellow_edp_no_fault_on_ground_eng_off() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_millis(500)); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_yellow_edp_commanded_on()); + // EDP should have no fault + assert!(!test_bed.yellow_edp_has_fault()); + } + + #[test] + fn yellow_edp_fault_not_on_ground_eng_off() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .engines_off() + .run_one_tick(); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_yellow_edp_commanded_on()); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + // EDP should have a fault as we are in flight + assert!(test_bed.yellow_edp_has_fault()); + } + + #[test] + fn yellow_edp_fault_on_ground_eng_starting() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_millis(500)); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_yellow_edp_commanded_on()); + // EDP should have no fault + assert!(!test_bed.yellow_edp_has_fault()); + + test_bed = test_bed + .start_eng2(Ratio::new::(3.)) + .run_one_tick(); + + assert!(!test_bed.yellow_edp_has_fault()); + + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_edp_has_fault()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(10)); + + // When finally pressurised no fault + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(!test_bed.yellow_edp_has_fault()); + } + + #[test] + fn blue_epump_no_fault_on_ground_eng_starting() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_millis(500)); + + // Blue epump should have no fault + assert!(!test_bed.blue_epump_has_fault()); + + test_bed = test_bed + .start_eng2(Ratio::new::(3.)) + .run_one_tick(); + + assert!(!test_bed.blue_epump_has_fault()); + + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_epump_has_fault()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(10)); + + // When finally pressurised no fault + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(!test_bed.blue_epump_has_fault()); + } + + #[test] + fn blue_epump_fault_on_ground_using_override() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_millis(500)); + + // Blue epump should have no fault + assert!(!test_bed.blue_epump_has_fault()); + + test_bed = test_bed.press_blue_epump_override_button_once(); + assert!(test_bed.blue_epump_override_is_on()); + + // As we use override, this bypasses eng off fault inhibit so we have a fault + assert!(test_bed.blue_epump_has_fault()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(10)); + + // When finally pressurised no fault + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(!test_bed.blue_epump_has_fault()); + } + + #[test] + fn green_edp_press_low_engine_off_to_on() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_millis(500)); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_green_edp_commanded_on()); + + // EDP should be LOW pressure state + assert!(test_bed.is_green_edp_press_low()); + + // Starting eng 1 N2 is low at start + test_bed = test_bed + .start_eng1(Ratio::new::(3.)) + .run_one_tick(); + + // Engine commanded on but pressure couldn't rise enough: we are in fault low + assert!(test_bed.is_green_edp_press_low()); + + // Waiting for 5s pressure should be at 3000 psi + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(25)); + + // No more fault LOW expected + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2900.)); + assert!(!test_bed.is_green_edp_press_low()); + + // Stoping pump, no fault expected + test_bed = test_bed + .set_green_ed_pump(false) + .run_waiting_for(Duration::from_secs(1)); + assert!(!test_bed.is_green_edp_press_low()); + } + + #[test] + fn green_edp_press_low_engine_on_to_off() { + let mut test_bed = test_bed_on_ground_with() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(75.)) + .run_waiting_for(Duration::from_secs(5)); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_green_edp_commanded_on()); + assert!(test_bed.is_green_pressure_switch_pressurised()); + // EDP should not be in fault low when engine running and pressure is ok + assert!(!test_bed.is_green_edp_press_low()); + + // Stoping eng 1 with N2 still turning + test_bed = test_bed.stopping_eng1().run_one_tick(); + + // Edp should still be in pressurized mode but as engine just stopped no fault + assert!(test_bed.is_green_edp_commanded_on()); + assert!(!test_bed.is_green_edp_press_low()); + + // Waiting for 25s pressure should drop and still no fault + test_bed = test_bed + .stop_eng1() + .run_waiting_for(Duration::from_secs(25)); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(500.)); + assert!(test_bed.is_green_edp_press_low()); + } + + #[test] + fn yellow_edp_press_low_engine_on_to_off() { + let mut test_bed = test_bed_on_ground_with() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng2(Ratio::new::(75.)) + .run_waiting_for(Duration::from_secs(5)); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_yellow_edp_commanded_on()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + // EDP should not be in fault low when engine running and pressure is ok + assert!(!test_bed.is_yellow_edp_press_low()); + + // Stoping eng 2 with N2 still turning + test_bed = test_bed.stopping_eng2().run_one_tick(); + + // Edp should still be in pressurized mode but as engine just stopped no fault + assert!(test_bed.is_yellow_edp_commanded_on()); + assert!(!test_bed.is_yellow_edp_press_low()); + + // Waiting for 25s pressure should drop and still no fault + test_bed = test_bed + .stop_eng2() + .run_waiting_for(Duration::from_secs(25)); + + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(500.)); + assert!(test_bed.is_yellow_edp_press_low()); + } + + #[test] + fn yellow_edp_press_low_engine_off_to_on() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_yellow_edp_commanded_on()); + + // EDP should be LOW pressure state + assert!(test_bed.is_yellow_edp_press_low()); + + // Starting eng 2 N2 is low at start + test_bed = test_bed + .start_eng2(Ratio::new::(3.)) + .run_one_tick(); + + // Engine commanded on but pressure couldn't rise enough: we are in fault low + assert!(test_bed.is_yellow_edp_press_low()); + + // Waiting for 5s pressure should be at 3000 psi + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(5)); + + // No more fault LOW expected + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + assert!(!test_bed.is_yellow_edp_press_low()); + + // Stoping pump, no fault expected + test_bed = test_bed + .set_yellow_ed_pump(false) + .run_waiting_for(Duration::from_secs(1)); + assert!(!test_bed.is_yellow_edp_press_low()); + } + + #[test] + fn yellow_edp_press_low_engine_off_to_on_with_e_pump() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(false) + .set_yellow_e_pump(false) + .run_one_tick(); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_yellow_edp_commanded_on()); + + // EDP should be LOW pressure state + assert!(test_bed.is_yellow_edp_press_low()); + + // Waiting for 20s pressure should be at 3000 psi + test_bed = test_bed.run_waiting_for(Duration::from_secs(20)); + + // Yellow pressurised but edp still off, we expect fault LOW press + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + assert!(test_bed.is_yellow_edp_press_low()); + + // Starting eng 2 N2 is low at start + test_bed = test_bed + .start_eng2(Ratio::new::(3.)) + .run_one_tick(); + + // Engine commanded on but pressure couldn't rise enough: we are in fault low + assert!(test_bed.is_yellow_edp_press_low()); + + // Waiting for 5s pressure should be at 3000 psi in EDP section + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(5)); + + // No more fault LOW expected + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + assert!(!test_bed.is_yellow_edp_press_low()); + } + + #[test] + fn green_edp_press_low_engine_off_to_on_with_ptu() { + let mut test_bed = test_bed_on_ground_with() + .on_the_ground() + .set_cold_dark_inputs() + .set_park_brake(false) + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + + // EDP should be LOW pressure state + assert!(test_bed.is_green_edp_press_low()); + + // Waiting for 20s pressure should be at 2300+ psi thanks to ptu + test_bed = test_bed.run_waiting_for(Duration::from_secs(20)); + + // Yellow pressurised by engine2, green presurised from ptu we expect fault LOW press on EDP1 + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2800.)); + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2300.)); + assert!(test_bed.is_green_edp_press_low()); + + // Starting eng 1 N2 is low at start + test_bed = test_bed + .start_eng1(Ratio::new::(3.)) + .run_one_tick(); + + // Engine commanded on but pressure couldn't rise enough: we are in fault low + assert!(test_bed.is_green_edp_press_low()); + + // Waiting for 5s pressure should be at 3000 psi in EDP section + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(5)); + + // No more fault LOW expected + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2900.)); + assert!(!test_bed.is_green_edp_press_low()); + } + + #[test] + fn yellow_epump_press_low_at_pump_on() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // EDP should not be in fault low when cold start + assert!(!test_bed.is_yellow_epump_press_low()); + + // Starting epump + test_bed = test_bed.set_yellow_e_pump(false).run_one_tick(); + + // Pump commanded on but pressure couldn't rise enough: we are in fault low + assert!(test_bed.is_yellow_epump_press_low()); + + // Waiting for 20s pressure should be at 3000 psi + test_bed = test_bed.run_waiting_for(Duration::from_secs(20)); + + // No more fault LOW expected + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2500.)); + assert!(!test_bed.is_yellow_epump_press_low()); + + // Stoping epump, no fault expected + test_bed = test_bed + .set_yellow_e_pump(true) + .run_waiting_for(Duration::from_secs(1)); + assert!(!test_bed.is_yellow_epump_press_low()); + } + + #[test] + fn blue_epump_press_low_at_pump_on() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // EDP should not be in fault low when cold start + assert!(!test_bed.is_blue_epump_press_low()); + + // Starting epump + test_bed = test_bed.press_blue_epump_override_button_once(); + assert!(test_bed.blue_epump_override_is_on()); + + // Pump commanded on but pressure couldn't rise enough: we are in fault low + assert!(test_bed.is_blue_epump_press_low()); + + // Waiting for 10s pressure should be at 3000 psi + test_bed = test_bed.run_waiting_for(Duration::from_secs(10)); + + // No more fault LOW expected + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() > Pressure::new::(2900.)); + assert!(!test_bed.is_blue_epump_press_low()); + + // Stoping epump, no fault expected + test_bed = test_bed.press_blue_epump_override_button_once(); + assert!(!test_bed.blue_epump_override_is_on()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(1)); + assert!(!test_bed.is_blue_epump_press_low()); + } + + #[test] + fn blue_epump_override_switches_to_off_when_losing_relay_power_and_stays_off() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Starting epump + test_bed = test_bed + .press_blue_epump_override_button_once() + .run_waiting_for(Duration::from_secs(10)); + assert!(test_bed.blue_epump_override_is_on()); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + // Killing the bus corresponding to the latching relay of blue pump override push button + // It should set the override state back to off without touching the push button + test_bed = test_bed.dc_ess_lost().run_one_tick(); + assert!(!test_bed.blue_epump_override_is_on()); + + // Stays off even powered back + test_bed = test_bed.dc_ess_active().run_one_tick(); + assert!(!test_bed.blue_epump_override_is_on()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(10)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + } + + #[test] + fn blue_epump_override_switches_to_off_when_pump_forced_off_on_hyd_panel() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Starting epump + test_bed = test_bed + .press_blue_epump_override_button_once() + .run_waiting_for(Duration::from_secs(10)); + assert!(test_bed.blue_epump_override_is_on()); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + test_bed = test_bed.set_blue_e_pump(false).run_one_tick(); + assert!(!test_bed.blue_epump_override_is_on()); + } + + #[test] + fn edp_deactivation() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(false) + .run_one_tick(); + + // Starting eng 1 and eng 2 + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + + // ALMOST No pressure + assert!(test_bed.green_pressure() < Pressure::new::(1000.)); + assert!(test_bed.yellow_pressure() < Pressure::new::(1000.)); + + // Waiting for 5s pressure should be at 3000 psi + test_bed = test_bed.run_waiting_for(Duration::from_secs(5)); + + assert!(test_bed.green_pressure() > Pressure::new::(2900.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + + // Stoping edp1, pressure should fall in 20s + test_bed = test_bed + .set_green_ed_pump(false) + .run_waiting_for(Duration::from_secs(20)); + + assert!(test_bed.green_pressure() < Pressure::new::(500.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + + // Stoping edp2, pressure should fall in 20s + test_bed = test_bed + .set_yellow_ed_pump(false) + .run_waiting_for(Duration::from_secs(20)); + + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(test_bed.yellow_pressure() < Pressure::new::(500.)); + } + + #[test] + fn yellow_edp_buildup() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Starting eng 1 + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + // ALMOST No pressure + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + + // Blue is auto run + assert!(test_bed.blue_pressure() < Pressure::new::(1000.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(1000.)); + + // Waiting for 5s pressure should be at 3000 psi + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(5)); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2800.)); + + // Stoping engine, pressure should fall in 20s + test_bed = test_bed + .stop_eng2() + .run_waiting_for(Duration::from_secs(20)); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(200.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(500.)); + } + + #[test] + fn when_yellow_edp_solenoid_main_power_bus_unavailable_backup_bus_keeps_pump_in_unpressurised_state( + ) { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(15)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + // Stoping EDP manually + test_bed = test_bed + .set_yellow_ed_pump(false) + .run_waiting_for(Duration::from_secs(15)); + + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .dc_bus_2_lost() + .run_waiting_for(Duration::from_secs(15)); + + // Yellow solenoid has backup power from DC ESS BUS + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + } + + #[test] + fn when_yellow_edp_solenoid_both_bus_unpowered_yellow_hydraulic_system_is_pressurised() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(15)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + // Stoping EDP manually + test_bed = test_bed + .set_yellow_ed_pump(false) + .run_waiting_for(Duration::from_secs(15)); + + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .dc_ess_lost() + .dc_bus_2_lost() + .run_waiting_for(Duration::from_secs(15)); + + // Now solenoid defaults to pressurised without power + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + } + + #[test] + fn when_green_edp_solenoid_unpowered_yellow_hydraulic_system_is_pressurised() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(15)); + + assert!(test_bed.is_green_pressure_switch_pressurised()); + + // Stoping EDP manually + test_bed = test_bed + .set_green_ed_pump(false) + .run_waiting_for(Duration::from_secs(15)); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + + test_bed = test_bed + .dc_ess_lost() + .run_waiting_for(Duration::from_secs(15)); + + // Now solenoid defaults to pressurised + assert!(test_bed.is_green_pressure_switch_pressurised()); + } + + #[test] + #[ignore] + // Checks numerical stability of reservoir level: level should remain after multiple pressure cycles + fn yellow_circuit_reservoir_coherency() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(false) + // Park brake off to not use fluid in brakes + .set_park_brake(false) + .run_one_tick(); + + // Starting epump wait for pressure rise to make sure system is primed including brake accumulator + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(20)); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(3500.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(2500.)); + + // Shutdown and wait for pressure stabilisation + test_bed = test_bed + .set_yellow_e_pump(true) + .run_waiting_for(Duration::from_secs(50)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(-50.)); + + let reservoir_level_after_priming = test_bed.get_yellow_reservoir_volume(); + + let total_fluid_res_plus_accumulator_before_loops = reservoir_level_after_priming + + test_bed.get_brake_yellow_accumulator_fluid_volume(); + + // Now doing cycles of pressurisation on EDP and ePump + for _ in 1..6 { + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(50)); + + assert!(test_bed.yellow_pressure() < Pressure::new::(3100.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(2500.)); + + let mut current_res_level = test_bed.get_yellow_reservoir_volume(); + assert!(current_res_level < reservoir_level_after_priming); + + test_bed = test_bed + .stop_eng2() + .run_waiting_for(Duration::from_secs(50)); + assert!(test_bed.yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(-50.)); + + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(50)); + + assert!(test_bed.yellow_pressure() < Pressure::new::(3500.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(2500.)); + + current_res_level = test_bed.get_yellow_reservoir_volume(); + assert!(current_res_level < reservoir_level_after_priming); + + test_bed = test_bed + .set_yellow_e_pump(true) + .run_waiting_for(Duration::from_secs(50)); + assert!(test_bed.yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(-50.)); + } + let total_fluid_res_plus_accumulator_after_loops = test_bed + .get_yellow_reservoir_volume() + + test_bed.get_brake_yellow_accumulator_fluid_volume(); + + let total_fluid_difference = total_fluid_res_plus_accumulator_before_loops + - total_fluid_res_plus_accumulator_after_loops; + + // Make sure no more deviation than 0.001 gallon is lost after full pressure and unpressurized states + assert!(total_fluid_difference.get::().abs() < 0.001); + } + + #[test] + #[ignore] + // Checks numerical stability of reservoir level: level should remain after multiple pressure cycles + fn green_circuit_reservoir_coherency() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(false) + .run_one_tick(); + + // Starting EDP wait for pressure rise to make sure system is primed + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(20)); + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(3500.)); + assert!(test_bed.green_pressure() > Pressure::new::(2500.)); + + // Shutdown and wait for pressure stabilisation + test_bed = test_bed + .stop_eng1() + .run_waiting_for(Duration::from_secs(50)); + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(test_bed.green_pressure() > Pressure::new::(-50.)); + + let reservoir_level_after_priming = test_bed.get_green_reservoir_volume(); + + // Now doing cycles of pressurisation on EDP + for _ in 1..6 { + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(50)); + + assert!(test_bed.green_pressure() < Pressure::new::(3500.)); + assert!(test_bed.green_pressure() > Pressure::new::(2500.)); + + let current_res_level = test_bed.get_green_reservoir_volume(); + assert!(current_res_level < reservoir_level_after_priming); + + test_bed = test_bed + .stop_eng1() + .run_waiting_for(Duration::from_secs(50)); + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(test_bed.green_pressure() > Pressure::new::(-50.)); + } + + let total_fluid_difference = + reservoir_level_after_priming - test_bed.get_green_reservoir_volume(); + + // Make sure no more deviation than 0.001 gallon is lost after full pressure and unpressurized states + assert!(total_fluid_difference.get::().abs() < 0.001); + } + + #[test] + #[ignore] + // Checks numerical stability of reservoir level: level should remain after multiple pressure cycles + fn blue_circuit_reservoir_coherency() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Starting blue_epump wait for pressure rise to make sure system is primed + test_bed = test_bed.press_blue_epump_override_button_once(); + assert!(test_bed.blue_epump_override_is_on()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(20)); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(3500.)); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + + // Shutdown and wait for pressure stabilisation + test_bed = test_bed.press_blue_epump_override_button_once(); + assert!(!test_bed.blue_epump_override_is_on()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(50)); + + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(50.)); + assert!(test_bed.blue_pressure() > Pressure::new::(-50.)); + + let reservoir_level_after_priming = test_bed.get_blue_reservoir_volume(); + + // Now doing cycles of pressurisation on epump relying on auto run of epump when eng is on + for _ in 1..6 { + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(50)); + + assert!(test_bed.blue_pressure() < Pressure::new::(3500.)); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + + let current_res_level = test_bed.get_blue_reservoir_volume(); + assert!(current_res_level < reservoir_level_after_priming); + + test_bed = test_bed + .stop_eng1() + .run_waiting_for(Duration::from_secs(50)); + assert!(test_bed.blue_pressure() < Pressure::new::(50.)); + assert!(test_bed.blue_pressure() > Pressure::new::(-50.)); + + // Now engine 2 is used + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(50)); + + assert!(test_bed.blue_pressure() < Pressure::new::(3500.)); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + + let current_res_level = test_bed.get_blue_reservoir_volume(); + assert!(current_res_level < reservoir_level_after_priming); + + test_bed = test_bed + .stop_eng2() + .run_waiting_for(Duration::from_secs(50)); + assert!(test_bed.blue_pressure() < Pressure::new::(50.)); + assert!(test_bed.blue_pressure() > Pressure::new::(-50.)); + } + + let total_fluid_difference = + reservoir_level_after_priming - test_bed.get_blue_reservoir_volume(); + + // Make sure no more deviation than 0.001 gallon is lost after full pressure and unpressurized states + assert!(total_fluid_difference.get::().abs() < 0.001); + } + + #[test] + fn yellow_green_edp_firevalve() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // PTU would mess up the test + test_bed = test_bed.set_ptu_state(false).run_one_tick(); + assert!(!test_bed.is_ptu_enabled()); + + assert!(!test_bed.is_fire_valve_eng1_closed()); + assert!(!test_bed.is_fire_valve_eng2_closed()); + + // Starting eng 1 + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(5)); + + // Waiting for 5s pressure should be at 3000 psi + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2900.)); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2800.)); + + assert!(!test_bed.is_fire_valve_eng1_closed()); + assert!(!test_bed.is_fire_valve_eng2_closed()); + + // Green shutoff valve + test_bed = test_bed + .set_eng1_fire_button(true) + .run_waiting_for(Duration::from_secs(20)); + + assert!(test_bed.is_fire_valve_eng1_closed()); + assert!(!test_bed.is_fire_valve_eng2_closed()); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(500.)); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + + // Yellow shutoff valve + test_bed = test_bed + .set_eng2_fire_button(true) + .run_waiting_for(Duration::from_secs(20)); + + assert!(test_bed.is_fire_valve_eng1_closed()); + assert!(test_bed.is_fire_valve_eng2_closed()); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(500.)); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(500.)); + } + + #[test] + fn yellow_brake_accumulator() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Getting accumulator pressure on cold start + let mut accumulator_pressure = test_bed.get_brake_yellow_accumulator_pressure(); + + // No brakes on green, no more pressure than in accumulator on yellow + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!( + test_bed.get_brake_left_yellow_pressure() + < accumulator_pressure + Pressure::new::(50.) + ); + assert!( + test_bed.get_brake_right_yellow_pressure() + < accumulator_pressure + Pressure::new::(50.) + ); + + // No brakes even if we brake on green, no more than accumulator pressure on yellow + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(5)); + + accumulator_pressure = test_bed.get_brake_yellow_accumulator_pressure(); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!( + test_bed.get_brake_left_yellow_pressure() + < accumulator_pressure + Pressure::new::(50.) + ); + assert!( + test_bed.get_brake_right_yellow_pressure() + < accumulator_pressure + Pressure::new::(50.) + ); + assert!( + test_bed.get_brake_yellow_accumulator_pressure() + < accumulator_pressure + Pressure::new::(50.) + ); + + // Park brake off, loading accumulator, we expect no brake pressure but accumulator loaded + test_bed = test_bed + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .set_park_brake(false) + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(30)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2500.)); + assert!(test_bed.yellow_pressure() < Pressure::new::(3500.)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + assert!(test_bed.get_brake_yellow_accumulator_pressure() > Pressure::new::(2500.)); + + // Park brake on, loaded accumulator, we expect brakes on yellow side only + test_bed = test_bed + .set_park_brake(true) + .run_waiting_for(Duration::from_secs(3)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() > Pressure::new::(2000.)); + assert!(test_bed.get_brake_right_yellow_pressure() > Pressure::new::(2000.)); + + assert!(test_bed.get_brake_yellow_accumulator_pressure() > Pressure::new::(2500.)); + } + + #[test] + fn norm_brake_vs_altn_brake() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Getting accumulator pressure on cold start + let accumulator_pressure = test_bed.get_brake_yellow_accumulator_pressure(); + + // No brakes + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!( + test_bed.get_brake_left_yellow_pressure() + < accumulator_pressure + Pressure::new::(50.) + ); + assert!( + test_bed.get_brake_right_yellow_pressure() + < accumulator_pressure + Pressure::new::(50.) + ); + + test_bed = test_bed + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .set_park_brake(false) + .run_waiting_for(Duration::from_secs(5)); + + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + // No brakes if we don't brake + test_bed = test_bed + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + // Braking cause green braking system to rise + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(3500.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(3500.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + // Disabling Askid causes alternate braking to work and release green brakes + test_bed = test_bed + .set_anti_skid(false) + .run_waiting_for(Duration::from_secs(2)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() > Pressure::new::(950.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(3500.)); + assert!(test_bed.get_brake_right_yellow_pressure() > Pressure::new::(950.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(3500.)); + } + + #[test] + fn no_brake_inversion() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .set_park_brake(false) + .run_waiting_for(Duration::from_secs(5)); + + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + // Braking left + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + // Braking right + test_bed = test_bed + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + // Disabling Askid causes alternate braking to work and release green brakes + test_bed = test_bed + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(100.)) + .set_anti_skid(false) + .run_waiting_for(Duration::from_secs(2)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() > Pressure::new::(950.)); + + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(2)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() > Pressure::new::(950.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn auto_brake_at_gear_retraction() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .set_park_brake(false) + .run_waiting_for(Duration::from_secs(15)); + + // No brake inputs + test_bed = test_bed + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + // Positive climb, gear up + test_bed = test_bed + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(1)); + + // Check auto brake is active + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(50.)); + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(1500.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(1500.)); + + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + // Check no more autobrakes after 3s + test_bed = test_bed.run_waiting_for(Duration::from_secs(3)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn alternate_brake_accumulator_is_emptying_while_braking() { + let mut test_bed = test_bed_on_ground_with() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .set_park_brake(false) + .run_waiting_for(Duration::from_secs(15)); + + // Check we got yellow pressure and brake accumulator loaded + assert!(test_bed.yellow_pressure() >= Pressure::new::(2500.)); + assert!( + test_bed.get_brake_yellow_accumulator_pressure() >= Pressure::new::(2500.) + ); + + // Disabling green and yellow side so accumulator stop being able to reload + test_bed = test_bed + .set_ptu_state(false) + .set_yellow_ed_pump(false) + .set_green_ed_pump(false) + .set_yellow_e_pump(true) + .run_waiting_for(Duration::from_secs(30)); + + assert!(test_bed.yellow_pressure() <= Pressure::new::(100.)); + assert!(test_bed.green_pressure() <= Pressure::new::(100.)); + assert!( + test_bed.get_brake_yellow_accumulator_pressure() >= Pressure::new::(2500.) + ); + + // Now using brakes and check accumulator gets empty + test_bed = test_bed + .empty_brake_accumulator_using_pedal_brake() + .run_waiting_for(Duration::from_secs(1)); + + assert!( + test_bed.get_brake_yellow_accumulator_pressure() <= Pressure::new::(1000.) + ); + assert!( + test_bed.get_brake_yellow_accumulator_fluid_volume() <= Volume::new::(0.01) + ); + } + + #[test] + fn brakes_inactive_in_flight() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(10)); + + // No brake inputs + test_bed = test_bed + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + // Now full brakes + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)); + + // Check no action on brakes + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn brakes_norm_active_in_flight_gear_down() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(10)); + + // Now full brakes gear down + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs(1)); + + // Brakes norm should work normally + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(50.)); + + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn brakes_alternate_active_in_flight_gear_down() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(10)); + + // Now full brakes gear down + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .set_gear_lever_down() + .set_anti_skid(false) + .run_waiting_for(Duration::from_secs(1)); + + // Brakes norm should work normally + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + + assert!(test_bed.get_brake_left_yellow_pressure() > Pressure::new::(900.)); + assert!(test_bed.get_brake_right_yellow_pressure() > Pressure::new::(900.)); + } + + #[test] + // Testing that green for brakes is only available if park brake is on while altn pressure is at too low level + fn brake_logic_green_backup_emergency() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Setting on ground with yellow side hydraulics off + // This should prevent yellow accumulator to fill + test_bed = test_bed + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .set_park_brake(true) + .set_ptu_state(false) + .set_yellow_e_pump(true) + .set_yellow_ed_pump(false) + .run_waiting_for(Duration::from_secs(15)); + + // Braking but park is on: no output on green brakes expected + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() > Pressure::new::(500.)); + assert!(test_bed.get_brake_right_yellow_pressure() > Pressure::new::(500.)); + + // With no more fluid in yellow accumulator, green should work as emergency + test_bed = test_bed + .empty_brake_accumulator_using_park_brake() + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn autobrakes_arms_in_flight_lo_or_med() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(12)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + + test_bed = test_bed + .set_autobrake_low() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::LOW); + + test_bed = test_bed + .set_autobrake_med() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + } + + #[test] + fn autobrakes_arming_according_to_set_variable() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(10)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + + // set autobrake to LOW + test_bed = test_bed + .set_autobrake_low_with_set_variable() + .run_waiting_for(Duration::from_secs(1)); + assert!(test_bed.autobrake_mode() == AutobrakeMode::LOW); + + // using the set variable again is still resulting in LOW + // and not disarming + test_bed = test_bed + .set_autobrake_low_with_set_variable() + .run_waiting_for(Duration::from_secs(1)); + assert!(test_bed.autobrake_mode() == AutobrakeMode::LOW); + + // set autobrake to MED + test_bed = test_bed + .set_autobrake_med_with_set_variable() + .run_waiting_for(Duration::from_secs(1)); + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + + // set autobrake to MAX + test_bed = test_bed + .set_autobrake_max_with_set_variable() + .run_waiting_for(Duration::from_secs(1)); + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + + // set autobrake to DISARMED + test_bed = test_bed + .set_autobrake_disarmed_with_set_variable() + .run_waiting_for(Duration::from_secs(1)); + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + } + + #[test] + fn autobrakes_disarms_if_green_pressure_low() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(12)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + + test_bed = test_bed + .set_autobrake_low() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::LOW); + + test_bed = test_bed + .set_ptu_state(false) + .stop_eng1() + .run_waiting_for(Duration::from_secs(20)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + } + + #[test] + fn autobrakes_does_not_disarm_if_askid_off_but_sim_not_ready() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .sim_not_ready() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(12)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + + test_bed = test_bed + .set_autobrake_med() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + + // sim is not ready --> no disarm + test_bed = test_bed + .set_anti_skid(false) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + + // sim is now ready --> disarm expected + test_bed = test_bed.sim_ready().run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + } + + #[test] + fn autobrakes_disarms_if_askid_off() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(12)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + + test_bed = test_bed + .set_autobrake_med() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + + test_bed = test_bed + .set_anti_skid(false) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + } + + #[test] + fn autobrakes_max_wont_arm_in_flight() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(15)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + + test_bed = test_bed + .set_autobrake_max() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + + // using the set variable should also not work + test_bed = test_bed + .set_autobrake_max_with_set_variable() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + } + + #[test] + fn autobrakes_taxiing_wont_disarm_when_braking() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .start_eng1(Ratio::new::(60.)) + .start_eng2(Ratio::new::(60.)) + .run_waiting_for(Duration::from_secs(10)); + + test_bed = test_bed + .set_autobrake_max() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + + test_bed = test_bed + .set_right_brake(Ratio::new::(100.)) + .set_left_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + } + + #[test] + fn autobrakes_activates_on_ground_on_spoiler_deploy() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(10)); + + test_bed = test_bed + .set_autobrake_max() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + + test_bed = test_bed + .set_deploy_spoilers() + .run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn autobrakes_disengage_on_spoiler_retract() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(10)); + + test_bed = test_bed + .set_autobrake_max() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + + test_bed = test_bed + .set_deploy_spoilers() + .run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + + test_bed = test_bed + .set_retract_spoilers() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + } + + #[test] + // Should disable with one pedal > 61° over max range of 79.4° thus 77% + fn autobrakes_max_disengage_at_77_on_one_pedal_input() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(10)); + + test_bed = test_bed + .set_autobrake_max() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + + test_bed = test_bed + .set_deploy_spoilers() + .run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_left_brake(Ratio::new::(70.)) + .run_waiting_for(Duration::from_secs(1)) + .set_left_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_left_brake(Ratio::new::(78.)) + .run_waiting_for(Duration::from_secs(1)) + .set_left_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + } + + #[test] + fn autobrakes_max_disengage_at_52_on_both_pedal_input() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(10)); + + test_bed = test_bed + .set_autobrake_max() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + + test_bed = test_bed + .set_deploy_spoilers() + .run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_left_brake(Ratio::new::(55.)) + .run_waiting_for(Duration::from_secs(1)) + .set_left_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_left_brake(Ratio::new::(55.)) + .set_right_brake(Ratio::new::(55.)) + .run_waiting_for(Duration::from_secs(1)) + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + } + + #[test] + // Should disable with one pedals > 42° over max range of 79.4° thus 52% + fn autobrakes_med_disengage_at_52_on_one_pedal_input() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(10)); + + test_bed = test_bed + .set_autobrake_med() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + + test_bed = test_bed + .set_deploy_spoilers() + .run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_right_brake(Ratio::new::(50.)) + .run_waiting_for(Duration::from_secs(1)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_right_brake(Ratio::new::(55.)) + .run_waiting_for(Duration::from_secs(1)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + } + + #[test] + fn autobrakes_med_disengage_at_11_on_both_pedal_input() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(10)); + + test_bed = test_bed + .set_autobrake_med() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + + test_bed = test_bed + .set_deploy_spoilers() + .run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_right_brake(Ratio::new::(15.)) + .run_waiting_for(Duration::from_secs(1)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_right_brake(Ratio::new::(15.)) + .set_left_brake(Ratio::new::(15.)) + .run_waiting_for(Duration::from_secs(1)) + .set_right_brake(Ratio::new::(0.)) + .set_left_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + } + + #[test] + fn autobrakes_max_disarm_after_10s_in_flight() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(10)); + + test_bed = test_bed + .set_autobrake_max() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + + test_bed = test_bed.in_flight().run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + + test_bed = test_bed.in_flight().run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + } + + #[test] + fn autobrakes_does_not_disarm_after_10s_when_started_in_flight() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs(1)); + + test_bed = test_bed + .set_autobrake_med_with_set_variable() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + + test_bed = test_bed.in_flight().run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + + test_bed = test_bed.in_flight().run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + } + + #[test] + fn controller_blue_epump_activates_when_no_weight_on_nose_wheel() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(!test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + + test_bed = test_bed.rotates_on_runway().run_one_tick(); + + assert!(test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + } + + #[test] + fn controller_blue_epump_split_engine_states() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(!test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + + test_bed = test_bed + .start_eng1(Ratio::new::(65.)) + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + + test_bed = test_bed + .start_eng2(Ratio::new::(65.)) + .stop_eng1() + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + } + + #[test] + fn controller_blue_epump_on_off_engines() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(65.)) + .start_eng2(Ratio::new::(65.)) + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + + test_bed = test_bed.stop_eng1().stop_eng2().run_one_tick(); + + assert!(!test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + } + + #[test] + fn controller_blue_epump_override() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .press_blue_epump_override_button_once() + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + + test_bed = test_bed + .press_blue_epump_override_button_once() + .run_one_tick(); + + assert!(!test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + } + + #[test] + fn controller_blue_epump_override_without_power_shall_not_run_blue_pump() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(65.)) + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + + test_bed = test_bed.dc_ess_lost().run_one_tick(); + + assert!(!test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + } + + #[test] + fn controller_yellow_epump_is_activated_by_overhead_button() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(!test_bed.query(|a| a.is_yellow_epump_controller_pressurising())); + + test_bed = test_bed.set_yellow_e_pump(false).run_one_tick(); + + assert!(test_bed.query(|a| a.is_yellow_epump_controller_pressurising())); + + test_bed = test_bed.set_yellow_e_pump(true).run_one_tick(); + + assert!(!test_bed.query(|a| a.is_yellow_epump_controller_pressurising())); + } + + #[test] + fn controller_yellow_epump_unpowered_cant_command_pump() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_yellow_e_pump(false) + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_yellow_epump_controller_pressurising())); + + test_bed = test_bed.dc_bus_2_lost().run_one_tick(); + + assert!(!test_bed.query(|a| a.is_yellow_epump_controller_pressurising())); + } + + #[test] + fn controller_yellow_epump_can_operate_from_cargo_door_without_main_control_power_bus() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(!test_bed.query(|a| a.is_cargo_powering_yellow_epump())); + + test_bed = test_bed + .dc_ground_service_lost() + .open_fwd_cargo_door() + .run_waiting_for( + Duration::from_secs(1) + A380DoorController::DELAY_UNLOCK_TO_HYDRAULIC_CONTROL, + ); + + assert!(test_bed.query(|a| a.is_cargo_powering_yellow_epump())); + } + + #[test] + fn controller_engine_driven_pump1_overhead_button_logic_with_eng_on() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_edp1_green_pump_controller_pressurising())); + + test_bed = test_bed + .start_eng1(Ratio::new::(65.)) + .run_one_tick(); + assert!(test_bed.query(|a| a.is_edp1_green_pump_controller_pressurising())); + + test_bed = test_bed.set_green_ed_pump(false).run_one_tick(); + assert!(!test_bed.query(|a| a.is_edp1_green_pump_controller_pressurising())); + + test_bed = test_bed.set_green_ed_pump(true).run_one_tick(); + assert!(test_bed.query(|a| a.is_edp1_green_pump_controller_pressurising())); + } + + #[test] + fn controller_engine_driven_pump1_fire_overhead_released_stops_pump() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(65.)) + .start_eng2(Ratio::new::(65.)) + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_edp1_green_pump_controller_pressurising())); + + test_bed = test_bed.set_eng1_fire_button(true).run_one_tick(); + assert!(!test_bed.query(|a| a.is_edp1_green_pump_controller_pressurising())); + } + + #[test] + fn controller_engine_driven_pump2_overhead_button_logic_with_eng_on() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_edp2_yellow_pump_controller_pressurising())); + + test_bed = test_bed + .start_eng2(Ratio::new::(65.)) + .run_one_tick(); + assert!(test_bed.query(|a| a.is_edp2_yellow_pump_controller_pressurising())); + + test_bed = test_bed.set_yellow_ed_pump(false).run_one_tick(); + assert!(!test_bed.query(|a| a.is_edp2_yellow_pump_controller_pressurising())); + + test_bed = test_bed.set_yellow_ed_pump(true).run_one_tick(); + assert!(test_bed.query(|a| a.is_edp2_yellow_pump_controller_pressurising())); + } + + #[test] + fn controller_engine_driven_pump2_fire_overhead_released_stops_pump() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(65.)) + .start_eng2(Ratio::new::(65.)) + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_edp2_yellow_pump_controller_pressurising())); + + test_bed = test_bed.set_eng2_fire_button(true).run_one_tick(); + assert!(!test_bed.query(|a| a.is_edp2_yellow_pump_controller_pressurising())); + } + + #[test] + fn controller_ptu_on_off_with_overhead_pushbutton() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + + test_bed = test_bed.set_ptu_state(false).run_one_tick(); + + assert!(!test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + + test_bed = test_bed.set_ptu_state(true).run_one_tick(); + + assert!(test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + } + + #[test] + fn controller_ptu_off_when_cargo_door_is_moved() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + + test_bed = test_bed.open_fwd_cargo_door().run_waiting_for( + Duration::from_secs(1) + A380DoorController::DELAY_UNLOCK_TO_HYDRAULIC_CONTROL, + ); + + assert!(!test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + + // Ptu should reactivate after door fully opened + 40s + test_bed = test_bed.run_waiting_for(Duration::from_secs(25) + Duration::from_secs(41)); + + assert!(test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + } + + #[test] + fn controller_ptu_disabled_when_tug_attached() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + + test_bed = test_bed + .start_eng1(Ratio::new::(65.)) + .set_park_brake(false) + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + + test_bed = test_bed.set_pushback_state(true).run_one_tick(); + + assert!(!test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + + // Ptu should reactivate after 15ish seconds + test_bed = test_bed + .set_pushback_state(false) + .run_waiting_for(Duration::from_secs(16)); + + assert!(test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + } + + #[test] + fn rat_does_not_deploy_on_ground_at_eng_off() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(10)); + + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.get_rat_position() <= 0.); + assert!(test_bed.get_rat_rpm() <= 1.); + + test_bed = test_bed + .ac_bus_1_lost() + .ac_bus_2_lost() + .run_waiting_for(Duration::from_secs(2)); + + // RAT has not deployed + assert!(test_bed.get_rat_position() <= 0.); + assert!(test_bed.get_rat_rpm() <= 1.); + } + + #[test] + fn rat_does_not_deploy_when_sim_not_ready() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .sim_not_ready() + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(10)); + + // AC off, sim not ready -> RAT should not deploy + test_bed = test_bed + .ac_bus_1_lost() + .ac_bus_2_lost() + .run_waiting_for(Duration::from_secs(2)); + + assert!(!test_bed.rat_deploy_commanded()); + + // AC off, sim ready -> RAT should deploy + test_bed = test_bed.sim_ready().run_waiting_for(Duration::from_secs(2)); + + assert!(test_bed.rat_deploy_commanded()); + } + + #[test] + fn rat_deploys_on_both_ac_lost() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(10)); + + assert!(!test_bed.rat_deploy_commanded()); + + test_bed = test_bed + .ac_bus_1_lost() + .run_waiting_for(Duration::from_secs(2)); + + assert!(!test_bed.rat_deploy_commanded()); + + // Now all AC off should deploy RAT in flight + test_bed = test_bed + .ac_bus_1_lost() + .ac_bus_2_lost() + .run_waiting_for(Duration::from_secs(2)); + + assert!(test_bed.rat_deploy_commanded()); + } + + #[test] + fn blue_epump_unavailable_if_unpowered() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(10)); + + // Blue epump working + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + test_bed = test_bed + .ac_bus_2_lost() + .run_waiting_for(Duration::from_secs(25)); + + // Blue epump still working as it's not plugged on AC2 + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + test_bed = test_bed + .ac_bus_1_lost() + .run_waiting_for(Duration::from_secs(25)); + + // Blue epump has stopped + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + } + + #[test] + fn yellow_epump_unavailable_if_unpowered() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(10)); + + // Yellow epump working + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .ac_bus_2_lost() + .ac_bus_1_lost() + .run_waiting_for(Duration::from_secs(25)); + + // Yellow epump still working as not plugged on AC2 or AC1 + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .ac_ground_service_lost() + .run_waiting_for(Duration::from_secs(25)); + + // Yellow epump has stopped + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + } + + #[test] + fn flaps_and_slats_declare_moving() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .set_yellow_e_pump(false) + .set_ptu_state(false) + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(5)); + + // Only yellow press so only flaps can move + assert!(test_bed.is_flaps_moving()); + assert!(!test_bed.is_slats_moving()); + + // Now slats can move through ptu + test_bed = test_bed + .set_ptu_state(true) + .run_waiting_for(Duration::from_secs(5)); + + assert!(test_bed.is_flaps_moving()); + assert!(test_bed.is_slats_moving()); + } + + #[test] + fn yellow_epump_can_deploy_flaps_and_slats() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(10)); + + // Yellow epump working + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(80)); + + assert!(test_bed.get_flaps_left_position_percent() > 99.); + assert!(test_bed.get_flaps_right_position_percent() > 99.); + assert!(test_bed.get_slats_left_position_percent() > 99.); + assert!(test_bed.get_slats_right_position_percent() > 99.); + } + + #[test] + fn yellow_epump_can_deploy_flaps_and_slats_on_worst_case_ptu() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .with_worst_case_ptu() + .run_one_tick(); + + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(10)); + + // Yellow epump working + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(90)); + + assert!(test_bed.get_flaps_left_position_percent() > 99.); + assert!(test_bed.get_flaps_right_position_percent() > 99.); + assert!(test_bed.get_slats_left_position_percent() > 99.); + assert!(test_bed.get_slats_right_position_percent() > 99.); + } + + #[test] + fn yellow_epump_no_ptu_can_deploy_flaps_less_33s() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(false) + .run_one_tick(); + + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(20)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(32)); + + assert!(test_bed.get_flaps_left_position_percent() > 99.); + assert!(test_bed.get_flaps_right_position_percent() > 99.); + assert!(test_bed.get_slats_left_position_percent() < 1.); + assert!(test_bed.get_slats_right_position_percent() < 1.); + } + + #[test] + fn blue_epump_can_deploy_slats_in_less_50_s_and_no_flaps() { + let mut test_bed = test_bed_on_ground_with() + .on_the_ground() + .set_cold_dark_inputs() + .set_blue_e_pump_ovrd_pressed(true) + .run_waiting_for(Duration::from_secs(5)); + + // Blue epump is on + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(50)); + + assert!(test_bed.get_flaps_left_position_percent() <= 1.); + assert!(test_bed.get_flaps_right_position_percent() <= 1.); + assert!(test_bed.get_slats_left_position_percent() > 99.); + assert!(test_bed.get_slats_right_position_percent() > 99.); + } + + #[test] + fn blue_epump_cannot_deploy_slats_in_less_28_s_and_no_flaps() { + let mut test_bed = test_bed_on_ground_with() + .on_the_ground() + .set_cold_dark_inputs() + .set_blue_e_pump_ovrd_pressed(true) + .run_waiting_for(Duration::from_secs(5)); + + // Blue epump is on + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(28)); + + assert!(test_bed.get_flaps_left_position_percent() <= 1.); + assert!(test_bed.get_flaps_right_position_percent() <= 1.); + assert!(test_bed.get_slats_left_position_percent() < 99.); + assert!(test_bed.get_slats_right_position_percent() < 99.); + } + + #[test] + fn yellow_plus_blue_epumps_can_deploy_flaps_and_slats() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .set_yellow_e_pump(false) + .set_blue_e_pump_ovrd_pressed(true) + .run_waiting_for(Duration::from_secs(15)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(45)); + + assert!(test_bed.get_flaps_left_position_percent() > 99.); + assert!(test_bed.get_flaps_right_position_percent() > 99.); + assert!(test_bed.get_slats_left_position_percent() > 99.); + assert!(test_bed.get_slats_right_position_percent() > 99.); + } + + #[test] + fn no_pressure_no_flap_slats() { + let mut test_bed = test_bed_on_ground_with() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_secs(5)); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(10)); + + assert!(test_bed.get_flaps_left_position_percent() <= 1.); + assert!(test_bed.get_flaps_right_position_percent() <= 1.); + assert!(test_bed.get_slats_left_position_percent() <= 1.); + assert!(test_bed.get_slats_right_position_percent() <= 1.); + + assert!(!test_bed.is_slats_moving()); + assert!(!test_bed.is_flaps_moving()); + } + + #[test] + fn emergency_gen_is_started_on_both_ac_lost_in_flight() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(10)); + + assert!(!test_bed.is_emergency_gen_at_nominal_speed()); + + test_bed = test_bed + .ac_bus_1_lost() + .run_waiting_for(Duration::from_secs(2)); + + assert!(!test_bed.is_emergency_gen_at_nominal_speed()); + + // Now all AC off should deploy RAT in flight + test_bed = test_bed + .ac_bus_1_lost() + .ac_bus_2_lost() + .run_waiting_for(Duration::from_secs(8)); + + assert!(test_bed.is_emergency_gen_at_nominal_speed()); + } + + #[test] + fn cargo_door_stays_closed_at_init() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() == 0.); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(15.)); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() == 0.); + } + + #[test] + fn cargo_door_unlocks_when_commanded() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() == 0.); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() == 0.); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(!test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() >= 0.); + } + + #[test] + fn cargo_door_controller_opens_the_door() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() == 0.); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(!test_bed.is_cargo_fwd_door_locked_down()); + + let current_position_unlocked = test_bed.cargo_fwd_door_position(); + + test_bed = test_bed.open_fwd_cargo_door().run_waiting_for( + A380DoorController::DELAY_UNLOCK_TO_HYDRAULIC_CONTROL + Duration::from_secs(1), + ); + + assert!(test_bed.cargo_fwd_door_position() > current_position_unlocked); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(30.)); + + assert!(test_bed.cargo_fwd_door_position() > 0.85); + } + + #[test] + fn fwd_cargo_door_controller_opens_fwd_door_only() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() == 0.); + assert!(test_bed.cargo_aft_door_position() == 0.); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(30.)); + + assert!(test_bed.cargo_fwd_door_position() > 0.85); + assert!(test_bed.cargo_aft_door_position() == 0.); + } + + #[test] + fn cargo_door_opened_uses_correct_reservoir_amount() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_yellow_e_pump(false) + .set_ptu_state(false) + .run_waiting_for(Duration::from_secs_f64(20.)); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + let pressurised_yellow_level_door_closed = test_bed.get_yellow_reservoir_volume(); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(40.)); + + assert!(!test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() > 0.85); + + let pressurised_yellow_level_door_opened = test_bed.get_yellow_reservoir_volume(); + + let volume_used_liter = (pressurised_yellow_level_door_closed + - pressurised_yellow_level_door_opened) + .get::(); + + // For one cargo door we expect losing between 0.6 to 0.8 liter of fluid into the two actuators + assert!((0.6..=0.8).contains(&volume_used_liter)); + } + + #[test] + fn cargo_door_controller_closes_the_door() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(30.)); + + assert!(!test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() > 0.85); + + test_bed = test_bed + .close_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(60.)); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() <= 0.); + } + + #[test] + fn cargo_door_controller_closes_the_door_after_yellow_pump_auto_shutdown() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(30.)); + + assert!(!test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() > 0.85); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(30.)); + + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .close_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(30.)); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() <= 0.); + } + + #[test] + fn nose_steering_responds_to_tiller_demand_if_yellow_pressure_and_engines() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_yellow_e_pump(false) + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + + test_bed = test_bed + .set_tiller_demand(Ratio::new::(1.)) + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.nose_steering_position().get::() >= 73.9); + assert!(test_bed.nose_steering_position().get::() <= 74.1); + + test_bed = test_bed + .set_tiller_demand(Ratio::new::(-1.)) + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.nose_steering_position().get::() <= -73.9); + assert!(test_bed.nose_steering_position().get::() >= -74.1); + } + + #[test] + fn nose_steering_does_not_move_if_yellow_pressure_but_no_engine() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_yellow_e_pump(false) + .run_one_tick(); + + test_bed = test_bed + .set_tiller_demand(Ratio::new::(1.)) + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.nose_steering_position().get::() <= 0.1); + assert!(test_bed.nose_steering_position().get::() >= -0.1); + + test_bed = test_bed + .set_tiller_demand(Ratio::new::(-1.)) + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.nose_steering_position().get::() <= 0.1); + assert!(test_bed.nose_steering_position().get::() >= -0.1); + } + + #[test] + fn nose_steering_does_not_move_when_a_skid_off() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_yellow_e_pump(false) + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .set_anti_skid(false) + .run_one_tick(); + + test_bed = test_bed + .set_tiller_demand(Ratio::new::(1.)) + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.nose_steering_position().get::() >= -0.1); + assert!(test_bed.nose_steering_position().get::() <= 0.1); + } + + #[test] + fn nose_steering_centers_itself_when_a_skid_off() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_yellow_e_pump(false) + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .set_anti_skid(true) + .run_one_tick(); + + test_bed = test_bed + .set_tiller_demand(Ratio::new::(1.)) + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.nose_steering_position().get::() >= 70.); + + test_bed = test_bed + .set_tiller_demand(Ratio::new::(1.)) + .set_anti_skid(false) + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.nose_steering_position().get::() <= 0.1); + assert!(test_bed.nose_steering_position().get::() >= -0.1); + } + + #[test] + fn nose_steering_responds_to_autopilot_demand() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + + test_bed = test_bed + .set_autopilot_steering_demand(Ratio::new::(1.5)) + .run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.nose_steering_position().get::() >= 5.9); + assert!(test_bed.nose_steering_position().get::() <= 6.1); + + test_bed = test_bed + .set_autopilot_steering_demand(Ratio::new::(-1.8)) + .run_waiting_for(Duration::from_secs_f64(4.)); + + assert!(test_bed.nose_steering_position().get::() <= -5.9); + assert!(test_bed.nose_steering_position().get::() >= -6.1); + } + + #[test] + fn ptu_pressurise_green_from_yellow_edp() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .start_eng2(Ratio::new::(60.)) + .set_park_brake(false) + .set_ptu_state(false) + .run_waiting_for(Duration::from_secs(25)); + + assert!(!test_bed.is_ptu_enabled()); + + test_bed = test_bed + .set_ptu_state(true) + .run_waiting_for(Duration::from_secs(10)); + + // Now we should have pressure in yellow and green + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.green_pressure() < Pressure::new::(3100.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2000.)); + assert!(test_bed.yellow_pressure() < Pressure::new::(3100.)); + } + + #[test] + fn ptu_pressurise_yellow_from_green_edp() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .start_eng1(Ratio::new::(60.)) + .set_park_brake(false) + .set_ptu_state(false) + .run_waiting_for(Duration::from_secs(25)); + + assert!(!test_bed.is_ptu_enabled()); + + test_bed = test_bed + .set_ptu_state(true) + .run_waiting_for(Duration::from_secs(25)); + + // Now we should have pressure in yellow and green + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.green_pressure() < Pressure::new::(3100.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2000.)); + assert!(test_bed.yellow_pressure() < Pressure::new::(3100.)); + } + + #[test] + fn yellow_epump_has_cavitation_at_low_air_press() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(false) + .run_one_tick(); + + test_bed = test_bed + .air_press_nominal() + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.yellow_pressure().get::() > 2900.); + + test_bed = test_bed + .air_press_low() + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.yellow_pressure().get::() < 2000.); + } + + #[test] + fn low_air_press_fault_causes_ptu_fault() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_millis(500)); + + assert!(!test_bed.ptu_has_fault()); + assert!(!test_bed.green_edp_has_fault()); + assert!(!test_bed.yellow_edp_has_fault()); + + test_bed = test_bed + .air_press_low() + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.green_edp_has_fault()); + assert!(test_bed.yellow_edp_has_fault()); + assert!(test_bed.ptu_has_fault()); + } + + #[test] + fn low_air_press_fault_causes_yellow_blue_epump_fault() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_millis(5000)); + + assert!(!test_bed.blue_epump_has_fault()); + assert!(!test_bed.yellow_epump_has_fault()); + + test_bed = test_bed + .air_press_low() + .run_waiting_for(Duration::from_secs_f64(10.)); + + // Blue pump is on but yellow is off: only blue fault expected + assert!(test_bed.blue_epump_has_fault()); + assert!(!test_bed.yellow_epump_has_fault()); + + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.yellow_epump_has_fault()); + } + + #[test] + fn no_yellow_epump_fault_after_brake_accumulator_is_filled() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_millis(8000)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(!test_bed.yellow_epump_has_fault()); + } + + #[test] + fn ailerons_are_dropped_down_in_cold_and_dark() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.get_left_aileron_position().get::() < 0.1); + assert!(test_bed.get_right_aileron_position().get::() < 0.1); + } + + #[test] + fn ailerons_do_not_respond_in_cold_and_dark() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .set_ailerons_left_turn() + .run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.get_left_aileron_position().get::() < 0.1); + assert!(test_bed.get_right_aileron_position().get::() < 0.1); + + test_bed = test_bed + .set_ailerons_right_turn() + .run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.get_left_aileron_position().get::() < 0.1); + assert!(test_bed.get_right_aileron_position().get::() < 0.1); + } + + #[test] + fn ailerons_do_not_respond_if_only_yellow_pressure() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(false) + .set_yellow_e_pump(false) + .run_one_tick(); + + test_bed = test_bed + .set_ailerons_left_turn() + .run_waiting_for(Duration::from_secs_f64(6.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_aileron_position().get::() < 0.1); + assert!(test_bed.get_right_aileron_position().get::() < 0.1); + } + + #[test] + fn ailerons_respond_if_green_pressure() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(true) + .set_yellow_e_pump(false) + .run_one_tick(); + + test_bed = test_bed + .set_ailerons_left_turn() + .run_waiting_for(Duration::from_secs_f64(6.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_aileron_position().get::() > 0.9); + assert!(test_bed.get_right_aileron_position().get::() < 0.1); + + test_bed = test_bed + .set_ailerons_right_turn() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_aileron_position().get::() < 0.1); + assert!(test_bed.get_right_aileron_position().get::() > 0.9); + } + + #[test] + fn ailerons_droop_down_after_pressure_is_off() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(true) + .set_yellow_e_pump(false) + .run_one_tick(); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(8.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_aileron_position().get::() > 0.45); + assert!(test_bed.get_right_aileron_position().get::() > 0.45); + + test_bed = test_bed + .set_ptu_state(false) + .set_yellow_e_pump(true) + .run_waiting_for(Duration::from_secs_f64(50.)); + + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_aileron_position().get::() < 0.42); + assert!(test_bed.get_right_aileron_position().get::() < 0.42); + } + + #[test] + fn elevators_droop_down_after_pressure_is_off() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(true) + .set_yellow_e_pump(false) + .run_one_tick(); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(8.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_elevator_position().get::() > 0.45); + assert!(test_bed.get_right_elevator_position().get::() > 0.45); + + test_bed = test_bed + .set_ptu_state(false) + .set_yellow_e_pump(true) + .run_waiting_for(Duration::from_secs_f64(75.)); + + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_elevator_position().get::() < 0.4); + assert!(test_bed.get_right_elevator_position().get::() < 0.4); + } + + #[test] + fn cargo_door_operation_closes_yellow_leak_meas_valve() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.yellow_pressure().get::() > 500.); + assert!(!test_bed.is_yellow_leak_meas_valve_commanded_open()); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + } + + #[test] + fn cargo_door_operation_but_yellow_epump_on_opens_yellow_leak_meas_valve() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .open_fwd_cargo_door() + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.yellow_pressure().get::() > 500.); + assert!(test_bed.is_yellow_leak_meas_valve_commanded_open()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + } + + #[test] + fn leak_meas_valve_cant_be_closed_in_flight() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .in_flight() + .green_leak_meas_valve_closed() + .blue_leak_meas_valve_closed() + .yellow_leak_meas_valve_closed() + .run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(test_bed.is_yellow_leak_meas_valve_commanded_open()); + assert!(test_bed.is_blue_leak_meas_valve_commanded_open()); + assert!(test_bed.is_green_leak_meas_valve_commanded_open()); + } + + #[test] + fn leak_meas_valve_can_be_closed_on_ground() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .green_leak_meas_valve_closed() + .blue_leak_meas_valve_closed() + .yellow_leak_meas_valve_closed() + .run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(!test_bed.is_yellow_leak_meas_valve_commanded_open()); + assert!(!test_bed.is_blue_leak_meas_valve_commanded_open()); + assert!(!test_bed.is_green_leak_meas_valve_commanded_open()); + } + + #[test] + fn nose_wheel_steers_with_pushback_tug() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .set_pushback_state(true) + .set_pushback_angle(Angle::new::(80.)) + .run_waiting_for(Duration::from_secs_f64(0.5)); + + // Do not turn instantly in 0.5s + assert!( + test_bed.get_nose_steering_ratio() > Ratio::new::(0.) + && test_bed.get_nose_steering_ratio() < Ratio::new::(0.5) + ); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(5.)); + + // Has turned fully after 5s + assert!(test_bed.get_nose_steering_ratio() > Ratio::new::(0.9)); + + // Going left + test_bed = test_bed + .set_pushback_state(true) + .set_pushback_angle(Angle::new::(-80.)) + .run_waiting_for(Duration::from_secs_f64(0.5)); + + assert!(test_bed.get_nose_steering_ratio() > Ratio::new::(0.2)); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(5.)); + + // Has turned fully left after 5s + assert!(test_bed.get_nose_steering_ratio() < Ratio::new::(-0.9)); + } + + #[test] + fn high_pitch_ptu_simvar_on_ptu_first_start() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .start_eng2(Ratio::new::(60.)) + .run_waiting_for(Duration::from_secs(10)); + + assert!(!test_bed.is_ptu_enabled()); + assert!(test_bed.green_pressure() < Pressure::new::(100.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + + test_bed = test_bed.set_park_brake(false).run_one_tick(); + + assert!(test_bed.is_ptu_enabled()); + assert!(test_bed.is_ptu_running_high_pitch_sound()); + } + + #[test] + fn nominal_gear_retraction_extension_cycles_in_flight() { + let mut test_bed = test_bed_on_ground_with().set_cold_dark_inputs().in_flight(); + + assert!(test_bed.gear_system_state() == GearSystemState::AllDownLocked); + + test_bed = test_bed + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(25.)); + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + + test_bed = test_bed + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(25.)); + assert!(test_bed.gear_system_state() == GearSystemState::AllDownLocked); + + test_bed = test_bed + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(25.)); + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + } + + #[test] + fn gear_retracts_using_yellow_epump_plus_ptu() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .with_worst_case_ptu() + .set_gear_lever_down() + .set_green_ed_pump(false) + .set_yellow_ed_pump(false) + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs_f64(15.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.gear_system_state() == GearSystemState::AllDownLocked); + + test_bed = test_bed.set_gear_lever_up(); + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(80.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + } + + #[test] + fn emergency_gear_extension_at_2_turns_open_doors() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .turn_emergency_gear_extension_n_turns(1) + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.is_all_doors_really_up()); + + test_bed = test_bed + .turn_emergency_gear_extension_n_turns(2) + .run_waiting_for(Duration::from_secs_f64(25.)); + + assert!(test_bed.is_all_doors_really_down()); + } + + #[test] + fn emergency_gear_extension_at_3_turns_release_gear() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(25.)); + + assert!(test_bed.is_all_doors_really_up()); + assert!(test_bed.is_all_gears_really_up()); + + test_bed = test_bed + .set_green_ed_pump(false) + .set_ptu_state(false) + .turn_emergency_gear_extension_n_turns(3) + .run_waiting_for(Duration::from_secs_f64(35.)); + + assert!(test_bed.is_all_doors_really_down()); + assert!(test_bed.is_all_gears_really_down()); + } + + #[test] + fn complete_gear_cycle_do_not_change_fluid_volume() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllDownLocked); + + let initial_fluid_quantity = test_bed.get_green_reservoir_volume(); + + test_bed = test_bed + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(20.)); + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + assert!(test_bed.is_all_doors_really_up()); + + let uplocked_fluid_quantity = test_bed.get_green_reservoir_volume(); + + assert!(initial_fluid_quantity - uplocked_fluid_quantity > Volume::new::(1.)); + assert!(initial_fluid_quantity - uplocked_fluid_quantity < Volume::new::(2.)); + + test_bed = test_bed + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(20.)); + assert!(test_bed.gear_system_state() == GearSystemState::AllDownLocked); + assert!(test_bed.is_all_doors_really_up()); + + let downlocked_fluid_quantity = test_bed.get_green_reservoir_volume(); + assert!( + (initial_fluid_quantity - downlocked_fluid_quantity).abs() + < Volume::new::(0.01) + ); + } + + #[test] + fn reverting_emergency_extension_do_not_change_fluid_volume() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllDownLocked); + + test_bed = test_bed + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(20.)); + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + assert!(test_bed.is_all_doors_really_up()); + + let initial_uplocked_fluid_quantity = test_bed.get_green_reservoir_volume(); + + test_bed = test_bed + .set_gear_lever_down() + .turn_emergency_gear_extension_n_turns(3) + .run_waiting_for(Duration::from_secs_f64(30.)); + assert!(test_bed.is_all_gears_really_down()); + assert!(test_bed.is_all_doors_really_down()); + + test_bed = test_bed + .stow_emergency_gear_extension() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(20.)); + assert!(test_bed.is_all_gears_really_up()); + assert!(test_bed.is_all_doors_really_up()); + + let final_uplocked_fluid_quantity = test_bed.get_green_reservoir_volume(); + + assert!( + (initial_uplocked_fluid_quantity - final_uplocked_fluid_quantity).abs() + < Volume::new::(0.01) + ); + } + + #[test] + fn spoilers_move_to_requested_position() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs(10)); + + assert!(test_bed.green_pressure() > Pressure::new::(2900.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + assert!(test_bed.blue_pressure() > Pressure::new::(2900.)); + + test_bed = test_bed + .set_spoiler_position_demand(Ratio::new::(0.7), Ratio::new::(0.5)) + .run_waiting_for(Duration::from_secs(2)); + + assert!(test_bed.get_spoiler_left_mean_position().get::() > 0.68); + assert!(test_bed.get_spoiler_left_mean_position().get::() < 0.72); + + assert!(test_bed.get_spoiler_right_mean_position().get::() > 0.48); + assert!(test_bed.get_spoiler_right_mean_position().get::() < 0.52); + } + + #[test] + fn gear_init_up_if_spawning_in_air() { + let test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_one_tick(); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + } + + #[test] + fn gear_gravity_extension_reverted_has_correct_sequence() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .with_worst_case_ptu() + .in_flight() + .run_one_tick(); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + + test_bed = test_bed + .turn_emergency_gear_extension_n_turns(3) + .run_waiting_for(Duration::from_secs_f64(35.)); + + assert!(test_bed.is_all_doors_really_down()); + assert!(test_bed.is_all_gears_really_down()); + + test_bed = test_bed + .stow_emergency_gear_extension() + .run_waiting_for(Duration::from_secs_f64(5.)); + + // After 5 seconds we expect gear being retracted and doors still down + assert!(test_bed.gear_system_state() == GearSystemState::Retracting); + assert!(test_bed.is_all_doors_really_down()); + assert!(!test_bed.is_all_gears_really_down()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(15.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + assert!(test_bed.is_all_doors_really_up()); + assert!(test_bed.is_all_gears_really_up()); + } + + #[test] + fn aileron_init_centered_if_spawning_in_air() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_one_tick(); + + assert!(test_bed.get_left_aileron_position().get::() < 0.51); + assert!(test_bed.get_right_aileron_position().get::() < 0.51); + assert!(test_bed.get_left_aileron_position().get::() > 0.49); + assert!(test_bed.get_right_aileron_position().get::() > 0.49); + } + + #[test] + fn rudder_init_centered_if_spawning_in_air() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_one_tick(); + + assert!(test_bed.get_rudder_position().get::() > 0.49); + assert!(test_bed.get_rudder_position().get::() < 0.51); + } + + #[test] + fn elevator_init_centered_if_spawning_in_air() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_one_tick(); + + // Elevator deflection is assymetrical so middle is below 0.5 + assert!(test_bed.get_left_elevator_position().get::() < 0.45); + assert!(test_bed.get_right_elevator_position().get::() < 0.45); + assert!(test_bed.get_left_elevator_position().get::() > 0.35); + assert!(test_bed.get_right_elevator_position().get::() > 0.35); + } + + #[test] + fn leak_meas_valve_opens_after_yellow_pump_auto_shutdown_plus_a_delay_and_elevators_stay_drooped_down( + ) { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(45.)); + + // Cargo door no more powering yellow epump yet valve is still closed + assert!(!test_bed.is_yellow_leak_meas_valve_commanded_open()); + assert!(!test_bed.query(|a| a.is_cargo_powering_yellow_epump())); + + // Only reopens after a delay + test_bed = test_bed.run_waiting_for( + A380HydraulicCircuitController::DELAY_TO_REOPEN_LEAK_VALVE_AFTER_CARGO_DOOR_USE, + ); + assert!(test_bed.is_yellow_leak_meas_valve_commanded_open()); + + // Check elevators did stay drooped down after valve reopening + assert!(test_bed.get_left_elevator_position().get::() < 0.1); + assert!(test_bed.get_right_elevator_position().get::() < 0.1); + } + + #[test] + fn brakes_on_ground_work_after_emergency_extension() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + + test_bed = test_bed + .turn_emergency_gear_extension_n_turns(3) + .run_waiting_for(Duration::from_secs_f64(30.)); + assert!(test_bed.is_all_gears_really_down()); + assert!(test_bed.is_all_doors_really_down()); + + test_bed = test_bed + .on_the_ground_after_touchdown() + .set_left_brake(Ratio::new::(1.)) + .set_right_brake(Ratio::new::(1.)) + .run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(500.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(500.)); + } + } +} diff --git a/src/systems/a380_systems/src/lib.rs b/src/systems/a380_systems/src/lib.rs new file mode 100644 index 000000000000..7a78cfe9df0b --- /dev/null +++ b/src/systems/a380_systems/src/lib.rs @@ -0,0 +1,249 @@ +extern crate systems; + +mod air_conditioning; +mod electrical; +mod fuel; +pub mod hydraulic; +mod navigation; +mod pneumatic; +mod power_consumption; + +use self::{ + air_conditioning::A380AirConditioning, + fuel::A380Fuel, + pneumatic::{A380Pneumatic, A380PneumaticOverheadPanel}, +}; +use electrical::{ + A380Electrical, A380ElectricalOverheadPanel, A380EmergencyElectricalOverheadPanel, + APU_START_MOTOR_BUS_TYPE, +}; +use hydraulic::{A380Hydraulic, A380HydraulicOverheadPanel}; +use navigation::A380RadioAltimeters; +use power_consumption::A380PowerConsumption; +use systems::simulation::InitContext; + +use systems::{ + apu::{ + Aps3200ApuGenerator, Aps3200StartMotor, AuxiliaryPowerUnit, AuxiliaryPowerUnitFactory, + AuxiliaryPowerUnitFireOverheadPanel, AuxiliaryPowerUnitOverheadPanel, + }, + electrical::{Electricity, ElectricitySource, ExternalPowerSource}, + engine::{leap_engine::LeapEngine, EngineFireOverheadPanel}, + hydraulic::brake_circuit::AutobrakePanel, + landing_gear::{LandingGear, LandingGearControlInterfaceUnitSet}, + navigation::adirs::{ + AirDataInertialReferenceSystem, AirDataInertialReferenceSystemOverheadPanel, + }, + pressurization::{Pressurization, PressurizationOverheadPanel}, + shared::ElectricalBusType, + simulation::{Aircraft, SimulationElement, SimulationElementVisitor, UpdateContext}, +}; + +pub struct A380 { + adirs: AirDataInertialReferenceSystem, + adirs_overhead: AirDataInertialReferenceSystemOverheadPanel, + air_conditioning: A380AirConditioning, + apu: AuxiliaryPowerUnit, + apu_fire_overhead: AuxiliaryPowerUnitFireOverheadPanel, + apu_overhead: AuxiliaryPowerUnitOverheadPanel, + pneumatic_overhead: A380PneumaticOverheadPanel, + electrical_overhead: A380ElectricalOverheadPanel, + emergency_electrical_overhead: A380EmergencyElectricalOverheadPanel, + fuel: A380Fuel, + engine_1: LeapEngine, + engine_2: LeapEngine, + engine_fire_overhead: EngineFireOverheadPanel<2>, + electrical: A380Electrical, + power_consumption: A380PowerConsumption, + ext_pwr: ExternalPowerSource, + lgcius: LandingGearControlInterfaceUnitSet, + hydraulic: A380Hydraulic, + hydraulic_overhead: A380HydraulicOverheadPanel, + autobrake_panel: AutobrakePanel, + landing_gear: LandingGear, + pressurization: Pressurization, + pressurization_overhead: PressurizationOverheadPanel, + pneumatic: A380Pneumatic, + radio_altimeters: A380RadioAltimeters, +} +impl A380 { + pub fn new(context: &mut InitContext) -> A380 { + A380 { + adirs: AirDataInertialReferenceSystem::new(context), + adirs_overhead: AirDataInertialReferenceSystemOverheadPanel::new(context), + air_conditioning: A380AirConditioning::new(context), + apu: AuxiliaryPowerUnitFactory::new_aps3200( + context, + 1, + APU_START_MOTOR_BUS_TYPE, + ElectricalBusType::DirectCurrentBattery, + ElectricalBusType::DirectCurrentBattery, + ), + apu_fire_overhead: AuxiliaryPowerUnitFireOverheadPanel::new(context), + apu_overhead: AuxiliaryPowerUnitOverheadPanel::new(context), + pneumatic_overhead: A380PneumaticOverheadPanel::new(context), + electrical_overhead: A380ElectricalOverheadPanel::new(context), + emergency_electrical_overhead: A380EmergencyElectricalOverheadPanel::new(context), + fuel: A380Fuel::new(context), + engine_1: LeapEngine::new(context, 1), + engine_2: LeapEngine::new(context, 2), + engine_fire_overhead: EngineFireOverheadPanel::new(context), + electrical: A380Electrical::new(context), + power_consumption: A380PowerConsumption::new(context), + ext_pwr: ExternalPowerSource::new(context), + lgcius: LandingGearControlInterfaceUnitSet::new( + context, + ElectricalBusType::DirectCurrentEssential, + ElectricalBusType::DirectCurrentGndFltService, + ), + hydraulic: A380Hydraulic::new(context), + hydraulic_overhead: A380HydraulicOverheadPanel::new(context), + autobrake_panel: AutobrakePanel::new(context), + landing_gear: LandingGear::new(context), + pressurization: Pressurization::new(context), + pressurization_overhead: PressurizationOverheadPanel::new(context), + pneumatic: A380Pneumatic::new(context), + radio_altimeters: A380RadioAltimeters::new(context), + } + } +} +impl Aircraft for A380 { + fn update_before_power_distribution( + &mut self, + context: &UpdateContext, + electricity: &mut Electricity, + ) { + self.apu.update_before_electrical( + context, + &self.apu_overhead, + &self.apu_fire_overhead, + self.pneumatic_overhead.apu_bleed_is_on(), + // This will be replaced when integrating the whole electrical system. + // For now we use the same logic as found in the JavaScript code; ignoring whether or not + // the engine generators are supplying electricity. + self.electrical_overhead.apu_generator_is_on() + && !(self.electrical_overhead.external_power_is_on() + && self.electrical_overhead.external_power_is_available()), + self.pneumatic.apu_bleed_air_valve(), + self.fuel.left_inner_tank_has_fuel_remaining(), + ); + + self.electrical.update( + context, + electricity, + &self.ext_pwr, + &self.electrical_overhead, + &self.emergency_electrical_overhead, + &mut self.apu, + &self.apu_overhead, + &self.engine_fire_overhead, + [&self.engine_1, &self.engine_2], + &self.hydraulic, + self.lgcius.lgciu1(), + ); + + self.electrical_overhead + .update_after_electrical(&self.electrical, electricity); + self.emergency_electrical_overhead + .update_after_electrical(context, &self.electrical); + } + + fn update_after_power_distribution(&mut self, context: &UpdateContext) { + self.apu.update_after_power_distribution(); + self.apu_overhead.update_after_apu(&self.apu); + + self.lgcius.update( + context, + &self.landing_gear, + self.hydraulic.gear_system(), + self.ext_pwr.output_potential().is_powered(), + ); + + self.radio_altimeters.update(context); + + self.pressurization.update( + context, + &self.pressurization_overhead, + [&self.engine_1, &self.engine_2], + [self.lgcius.lgciu1(), self.lgcius.lgciu2()], + ); + + self.hydraulic.update( + context, + &self.engine_1, + &self.engine_2, + &self.hydraulic_overhead, + &self.autobrake_panel, + &self.engine_fire_overhead, + &self.lgcius, + &self.emergency_electrical_overhead, + &self.electrical, + &self.pneumatic, + &self.adirs, + ); + + self.pneumatic.update_hydraulic_reservoir_spatial_volumes( + self.hydraulic.green_reservoir(), + self.hydraulic.blue_reservoir(), + self.hydraulic.yellow_reservoir(), + ); + + self.hydraulic_overhead.update(&self.hydraulic); + + self.adirs.update(context, &self.adirs_overhead); + self.adirs_overhead.update(context, &self.adirs); + + self.power_consumption.update(context); + + self.pneumatic.update( + context, + [&self.engine_1, &self.engine_2], + &self.pneumatic_overhead, + &self.engine_fire_overhead, + &self.apu, + &self.air_conditioning, + ); + self.air_conditioning.update( + context, + &self.adirs, + [&self.engine_1, &self.engine_2], + &self.engine_fire_overhead, + &self.pneumatic, + &self.pneumatic_overhead, + &self.pressurization, + &self.pressurization_overhead, + [self.lgcius.lgciu1(), self.lgcius.lgciu2()], + ); + } +} +impl SimulationElement for A380 { + fn accept(&mut self, visitor: &mut T) { + self.adirs.accept(visitor); + self.adirs_overhead.accept(visitor); + self.air_conditioning.accept(visitor); + self.apu.accept(visitor); + self.apu_fire_overhead.accept(visitor); + self.apu_overhead.accept(visitor); + self.electrical_overhead.accept(visitor); + self.emergency_electrical_overhead.accept(visitor); + self.fuel.accept(visitor); + self.pneumatic_overhead.accept(visitor); + self.engine_1.accept(visitor); + self.engine_2.accept(visitor); + self.engine_fire_overhead.accept(visitor); + self.electrical.accept(visitor); + self.power_consumption.accept(visitor); + self.ext_pwr.accept(visitor); + self.lgcius.accept(visitor); + self.radio_altimeters.accept(visitor); + self.autobrake_panel.accept(visitor); + self.hydraulic.accept(visitor); + self.hydraulic_overhead.accept(visitor); + self.landing_gear.accept(visitor); + self.pressurization.accept(visitor); + self.pressurization_overhead.accept(visitor); + self.pneumatic.accept(visitor); + + visitor.visit(self); + } +} diff --git a/src/systems/a380_systems/src/navigation.rs b/src/systems/a380_systems/src/navigation.rs new file mode 100644 index 000000000000..709d91966bb6 --- /dev/null +++ b/src/systems/a380_systems/src/navigation.rs @@ -0,0 +1,107 @@ +use systems::navigation::ala52b::{ + Ala52BAircraftInstallationDelay, Ala52BRadioAltimeter, Ala52BTransceiverPair, +}; +use systems::navigation::radio_altimeter::AntennaInstallation; +use systems::shared::ElectricalBusType; +use systems::simulation::{ + InitContext, SimulationElement, SimulationElementVisitor, UpdateContext, +}; +use uom::si::f64::Length; +use uom::si::length::{foot, meter}; + +pub struct A380RadioAltimeters { + radio_altimeter_1: A380RadioAltimeter, + radio_altimeter_2: A380RadioAltimeter, +} + +impl A380RadioAltimeters { + pub fn new(context: &mut InitContext) -> Self { + Self { + radio_altimeter_1: A380RadioAltimeter::new( + context, + 1, + ElectricalBusType::AlternatingCurrent(1), + AntennaInstallation::new( + // Sim CG minus RA height over ground + Length::new::(8.617) - Length::new::(1.8), + // Aft distance from Sim CG + Length::new::(9.89), + // Electric length of the antennas and cable modeling some material variation + Length::new::(22.4), + ), + AntennaInstallation::new( + Length::new::(8.617) - Length::new::(1.8), + Length::new::(9.19), + Length::new::(22.4), + ), + ), + radio_altimeter_2: A380RadioAltimeter::new( + context, + 2, + ElectricalBusType::AlternatingCurrent(2), + AntennaInstallation::new( + Length::new::(8.617) - Length::new::(1.8), + Length::new::(11.27), + Length::new::(21.4), + ), + AntennaInstallation::new( + Length::new::(8.617) - Length::new::(1.8), + Length::new::(11.96), + Length::new::(21.4), + ), + ), + } + } + + pub fn update(&mut self, context: &UpdateContext) { + self.radio_altimeter_1.update(context); + self.radio_altimeter_2.update(context); + } +} + +impl SimulationElement for A380RadioAltimeters { + fn accept(&mut self, visitor: &mut T) { + self.radio_altimeter_1.accept(visitor); + self.radio_altimeter_2.accept(visitor); + + visitor.visit(self); + } +} + +pub struct A380RadioAltimeter { + radio_altimeter: Ala52BRadioAltimeter, + transceivers: Ala52BTransceiverPair, +} + +impl A380RadioAltimeter { + fn new( + context: &mut InitContext, + number: usize, + powered_by: ElectricalBusType, + transmitter: AntennaInstallation, + receiver: AntennaInstallation, + ) -> Self { + Self { + radio_altimeter: Ala52BRadioAltimeter::new( + context, + number, + Ala52BAircraftInstallationDelay::FiftySevenFeet, + powered_by, + ), + transceivers: Ala52BTransceiverPair::new(context, transmitter, receiver), + } + } + + fn update(&mut self, context: &UpdateContext) { + self.radio_altimeter.update(context, &self.transceivers); + } +} + +impl SimulationElement for A380RadioAltimeter { + fn accept(&mut self, visitor: &mut T) { + self.transceivers.accept(visitor); + self.radio_altimeter.accept(visitor); + + visitor.visit(self); + } +} diff --git a/src/systems/a380_systems/src/pneumatic.rs b/src/systems/a380_systems/src/pneumatic.rs new file mode 100644 index 000000000000..5ffad2c0b8ec --- /dev/null +++ b/src/systems/a380_systems/src/pneumatic.rs @@ -0,0 +1,3238 @@ +use core::panic; +use std::{f64::consts::PI, time::Duration}; + +use uom::si::{ + f64::*, + pressure::psi, + ratio::ratio, + thermodynamic_temperature::degree_celsius, + volume::{cubic_meter, gallon}, +}; + +use systems::{ + accept_iterable, + air_conditioning::PackFlowControllers, + overhead::{AutoOffFaultPushButton, OnOffFaultPushButton}, + pneumatic::{ + valve::*, BleedMonitoringComputerChannelOperationMode, + BleedMonitoringComputerIsAliveSignal, CompressionChamber, ControllablePneumaticValve, + CrossBleedValveSelectorKnob, CrossBleedValveSelectorMode, + EngineCompressionChamberController, EngineModeSelector, EngineState, PneumaticContainer, + PneumaticPipe, PneumaticValveSignal, Precooler, PressurisedReservoirWithExhaustValve, + PressurizeableReservoir, TargetPressureTemperatureSignal, VariableVolumeContainer, + }, + shared::{ + pid::PidController, update_iterator::MaxStepLoop, ControllerSignal, ElectricalBusType, + ElectricalBuses, EngineBleedPushbutton, EngineCorrectedN1, EngineCorrectedN2, + EngineFirePushButtons, EngineStartState, HydraulicColor, PackFlowValveState, + PneumaticBleed, PneumaticValve, ReservoirAirPressure, + }, + simulation::{ + InitContext, Read, SimulationElement, SimulationElementVisitor, SimulatorReader, + SimulatorWriter, UpdateContext, VariableIdentifier, Write, + }, +}; + +macro_rules! valve_signal_implementation { + ($signal_type: ty) => { + impl PneumaticValveSignal for $signal_type { + fn new(target_open_amount: Ratio) -> Self { + Self { target_open_amount } + } + + fn target_open_amount(&self) -> Ratio { + self.target_open_amount + } + } + }; +} + +struct HighPressureValveSignal { + target_open_amount: Ratio, +} + +struct PressureRegulatingValveSignal { + target_open_amount: Ratio, +} + +struct EngineStarterValveSignal { + target_open_amount: Ratio, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +enum CrossBleedValveSignalType { + Manual, + Automatic, +} + +struct CrossBleedValveSignal { + target_open_amount: Ratio, + signal_type: CrossBleedValveSignalType, +} +impl CrossBleedValveSignal { + fn new(target_open_amount: Ratio, signal_type: CrossBleedValveSignalType) -> Self { + Self { + target_open_amount, + signal_type, + } + } + + fn target_open_amount(&self) -> Ratio { + self.target_open_amount + } + + fn new_open(signal_type: CrossBleedValveSignalType) -> Self { + Self::new(Ratio::new::(1.), signal_type) + } + + fn new_closed(signal_type: CrossBleedValveSignalType) -> Self { + Self::new(Ratio::new::(0.), signal_type) + } +} + +struct FanAirValveSignal { + target_open_amount: Ratio, +} + +struct PackFlowValveSignal { + target_open_amount: Ratio, +} + +valve_signal_implementation!(HighPressureValveSignal); +valve_signal_implementation!(PressureRegulatingValveSignal); +valve_signal_implementation!(EngineStarterValveSignal); +valve_signal_implementation!(FanAirValveSignal); +valve_signal_implementation!(PackFlowValveSignal); + +pub struct A380Pneumatic { + physics_updater: MaxStepLoop, + + cross_bleed_valve_open_id: VariableIdentifier, + apu_bleed_air_valve_open_id: VariableIdentifier, + + bleed_monitoring_computers: [BleedMonitoringComputer; 2], + engine_systems: [EngineBleedAirSystem; 2], + + cross_bleed_valve: CrossBleedValve, + + fadec: FullAuthorityDigitalEngineControl, + engine_starter_valve_controllers: [EngineStarterValveController; 2], + + apu_compression_chamber: CompressionChamber, + apu_bleed_air_valve: DefaultValve, + + hydraulic_reservoir_bleed_air_valves: [PurelyPneumaticValve; 2], + hydraulic_reservoir_bleed_air_pipe: PneumaticPipe, + + green_hydraulic_reservoir_with_valve: + PressurisedReservoirWithExhaustValve, + blue_hydraulic_reservoir_with_valve: + PressurisedReservoirWithExhaustValve, + yellow_hydraulic_reservoir_with_valve: + PressurisedReservoirWithExhaustValve, + + packs: [PackComplex; 2], +} +impl A380Pneumatic { + const PNEUMATIC_SIM_MAX_TIME_STEP: Duration = Duration::from_millis(100); + + pub fn new(context: &mut InitContext) -> Self { + Self { + physics_updater: MaxStepLoop::new(Self::PNEUMATIC_SIM_MAX_TIME_STEP), + cross_bleed_valve_open_id: context.get_identifier("PNEU_XBLEED_VALVE_OPEN".to_owned()), + apu_bleed_air_valve_open_id: context + .get_identifier("APU_BLEED_AIR_VALVE_OPEN".to_owned()), + bleed_monitoring_computers: [ + BleedMonitoringComputer::new(1, 2, ElectricalBusType::DirectCurrentEssentialShed), + BleedMonitoringComputer::new(2, 1, ElectricalBusType::DirectCurrent(2)), + ], + engine_systems: [ + EngineBleedAirSystem::new( + context, + 1, + ElectricalBusType::DirectCurrentEssentialShed, + ), + EngineBleedAirSystem::new(context, 2, ElectricalBusType::DirectCurrent(2)), + ], + cross_bleed_valve: CrossBleedValve::new(), + fadec: FullAuthorityDigitalEngineControl::new(context), + engine_starter_valve_controllers: [ + EngineStarterValveController::new(1), + EngineStarterValveController::new(2), + ], + apu_compression_chamber: CompressionChamber::new(Volume::new::(5.)), + apu_bleed_air_valve: DefaultValve::new_closed(), + hydraulic_reservoir_bleed_air_valves: [ + PurelyPneumaticValve::new(), + PurelyPneumaticValve::new(), + ], + hydraulic_reservoir_bleed_air_pipe: PneumaticPipe::new( + Volume::new::(0.2), + Pressure::new::(14.7), + ThermodynamicTemperature::new::(15.), + ), + green_hydraulic_reservoir_with_valve: PressurisedReservoirWithExhaustValve::new( + context, + HydraulicColor::Green, + VariableVolumeContainer::new( + Volume::new::(2.5), + Pressure::new::(52.), + ThermodynamicTemperature::new::(15.), + ), + Pressure::new::(75.), + 6e-2, + ), + blue_hydraulic_reservoir_with_valve: PressurisedReservoirWithExhaustValve::new( + context, + HydraulicColor::Blue, + VariableVolumeContainer::new( + Volume::new::(1.1), + Pressure::new::(55.), + ThermodynamicTemperature::new::(15.), + ), + Pressure::new::(75.), + 6e-2, + ), + yellow_hydraulic_reservoir_with_valve: PressurisedReservoirWithExhaustValve::new( + context, + HydraulicColor::Yellow, + VariableVolumeContainer::new( + Volume::new::(1.7), + Pressure::new::(65.), + ThermodynamicTemperature::new::(15.), + ), + Pressure::new::(75.), + 6e-2, + ), + packs: [PackComplex::new(context, 1), PackComplex::new(context, 2)], + } + } + + pub(crate) fn update( + &mut self, + context: &UpdateContext, + engines: [&(impl EngineCorrectedN1 + EngineCorrectedN2); 2], + overhead_panel: &A380PneumaticOverheadPanel, + engine_fire_push_buttons: &impl EngineFirePushButtons, + apu: &impl ControllerSignal, + pack_flow_valve_signals: &impl PackFlowControllers<3>, + ) { + self.physics_updater.update(context); + + for cur_time_step in self.physics_updater { + self.update_physics( + &context.with_delta(cur_time_step), + engines, + overhead_panel, + engine_fire_push_buttons, + apu, + pack_flow_valve_signals, + ); + } + } + + pub(crate) fn update_physics( + &mut self, + context: &UpdateContext, + engines: [&(impl EngineCorrectedN1 + EngineCorrectedN2); 2], + overhead_panel: &A380PneumaticOverheadPanel, + engine_fire_push_buttons: &impl EngineFirePushButtons, + apu: &impl ControllerSignal, + pack_flow_valve_signals: &impl PackFlowControllers<3>, + ) { + self.apu_compression_chamber.update(apu); + + for bleed_monitoring_computer in self.bleed_monitoring_computers.iter_mut() { + bleed_monitoring_computer.update( + context, + &self.engine_systems, + &self.apu_bleed_air_valve, + overhead_panel, + engine_fire_push_buttons, + &self.cross_bleed_valve, + &self.fadec, + ); + + // I am not exactly sure if both BMCs should actually control this valve all the time. + self.cross_bleed_valve + .update_open_amount(&bleed_monitoring_computer.main_channel); + } + + let [bmc_one, bmc_two] = &mut self.bleed_monitoring_computers; + + bmc_one.check_for_failure(bmc_two); + bmc_two.check_for_failure(bmc_one); + + for controller in self.engine_starter_valve_controllers.iter_mut() { + controller.update(&self.fadec); + } + + for (engine_system, hydraulic_valve) in self + .engine_systems + .iter_mut() + .zip(&mut self.hydraulic_reservoir_bleed_air_valves) + { + for bleed_monitoring_computer in self.bleed_monitoring_computers.iter() { + let index = engine_system.number - 1; + + // If we get an actual channel here, this means that the channel is not in slave mode + if let Some(channel) = + bleed_monitoring_computer.channel_for_engine(engine_system.number) + { + engine_system.update( + context, + channel, + channel, + &self.engine_starter_valve_controllers[index], + channel, + engines[index], + ); + } + } + + hydraulic_valve.update_move_fluid( + context, + engine_system, + &mut self.hydraulic_reservoir_bleed_air_pipe, + ); + } + + let [left_system, right_system] = &mut self.engine_systems; + self.apu_bleed_air_valve.update_move_fluid( + context, + &mut self.apu_compression_chamber, + left_system, + ); + + self.cross_bleed_valve + .update_move_fluid(context, left_system, right_system); + + self.green_hydraulic_reservoir_with_valve + .update_flow_through_valve(context, &mut self.hydraulic_reservoir_bleed_air_pipe); + self.blue_hydraulic_reservoir_with_valve + .update_flow_through_valve(context, &mut self.hydraulic_reservoir_bleed_air_pipe); + self.yellow_hydraulic_reservoir_with_valve + .update_flow_through_valve(context, &mut self.hydraulic_reservoir_bleed_air_pipe); + + self.packs + .iter_mut() + .zip(self.engine_systems.iter_mut()) + .for_each(|(pack, engine_system)| { + pack.update(context, engine_system, pack_flow_valve_signals) + }); + } + + // TODO: Returning a mutable reference here is not great. I was running into an issue with the update order: + // - The APU turbine must know about the bleed valve being open as soon as possible to update EGT properly + // - To open the bleed valve, we need a signal from the ecb + // - To get a signal from the ECB to open the bleed valve, we have to update the APU. + // For now, we just pass over control of the bleed valve to the APU, so it can be updated after the ECB update but before the turbine update. + pub fn apu_bleed_air_valve(&mut self) -> &mut impl ControllablePneumaticValve { + &mut self.apu_bleed_air_valve + } + + pub fn update_hydraulic_reservoir_spatial_volumes( + &mut self, + green_hydraulic_reservoir: &impl PressurizeableReservoir, + blue_hydraulic_reservoir: &impl PressurizeableReservoir, + yellow_hydraulic_reservoir: &impl PressurizeableReservoir, + ) { + self.green_hydraulic_reservoir_with_valve + .change_spatial_volume(green_hydraulic_reservoir.available_volume()); + self.blue_hydraulic_reservoir_with_valve + .change_spatial_volume(blue_hydraulic_reservoir.available_volume()); + self.yellow_hydraulic_reservoir_with_valve + .change_spatial_volume(yellow_hydraulic_reservoir.available_volume()); + } +} +impl PneumaticBleed for A380Pneumatic { + fn apu_bleed_is_on(&self) -> bool { + self.apu_bleed_air_valve.is_open() + } + fn engine_crossbleed_is_on(&self) -> bool { + self.cross_bleed_valve.is_open() + } +} +impl EngineStartState for A380Pneumatic { + fn left_engine_state(&self) -> EngineState { + self.fadec.engine_state(1) + } + fn right_engine_state(&self) -> EngineState { + self.fadec.engine_state(2) + } + fn engine_mode_selector(&self) -> EngineModeSelector { + self.fadec.engine_mode_selector() + } +} +impl PackFlowValveState for A380Pneumatic { + fn pack_flow_valve_open_amount(&self, pack_id: usize) -> Ratio { + self.packs[pack_id].pack_flow_valve_open_amount() + } + fn pack_flow_valve_air_flow(&self, pack_id: usize) -> MassRate { + self.packs[pack_id].pack_flow_valve_air_flow() + } +} +impl SimulationElement for A380Pneumatic { + fn accept(&mut self, visitor: &mut T) { + self.cross_bleed_valve.accept(visitor); + self.fadec.accept(visitor); + + accept_iterable!(self.bleed_monitoring_computers, visitor); + accept_iterable!(self.engine_systems, visitor); + accept_iterable!(self.packs, visitor); + + self.blue_hydraulic_reservoir_with_valve.accept(visitor); + self.yellow_hydraulic_reservoir_with_valve.accept(visitor); + self.green_hydraulic_reservoir_with_valve.accept(visitor); + + visitor.visit(self); + } + + fn write(&self, writer: &mut SimulatorWriter) { + writer.write( + &self.cross_bleed_valve_open_id, + self.cross_bleed_valve.is_open(), + ); + writer.write( + &self.apu_bleed_air_valve_open_id, + self.apu_bleed_air_valve.is_open(), + ); + } +} +impl ReservoirAirPressure for A380Pneumatic { + fn green_reservoir_pressure(&self) -> Pressure { + self.green_hydraulic_reservoir_with_valve.pressure() + } + + fn blue_reservoir_pressure(&self) -> Pressure { + self.blue_hydraulic_reservoir_with_valve.pressure() + } + + fn yellow_reservoir_pressure(&self) -> Pressure { + self.yellow_hydraulic_reservoir_with_valve.pressure() + } +} + +struct EngineStarterValveController { + number: usize, + engine_state: EngineState, +} +impl ControllerSignal for EngineStarterValveController { + fn signal(&self) -> Option { + match self.engine_state { + EngineState::Starting => Some(EngineStarterValveSignal::new_open()), + _ => Some(EngineStarterValveSignal::new_closed()), + } + } +} +impl EngineStarterValveController { + fn new(number: usize) -> Self { + Self { + number, + engine_state: EngineState::Off, + } + } + + fn update(&mut self, fadec: &FullAuthorityDigitalEngineControl) { + self.engine_state = fadec.engine_state(self.number); + } +} + +struct BleedMonitoringComputer { + main_channel_engine_number: usize, + backup_channel_engine_number: usize, + main_channel: BleedMonitoringComputerChannel, + backup_channel: BleedMonitoringComputerChannel, + powered_by: ElectricalBusType, + is_powered: bool, +} +impl BleedMonitoringComputer { + fn new( + main_channel_engine_number: usize, + backup_channel_engine_number: usize, + powered_by: ElectricalBusType, + ) -> Self { + Self { + main_channel_engine_number, + backup_channel_engine_number, + main_channel: BleedMonitoringComputerChannel::new( + main_channel_engine_number, + BleedMonitoringComputerChannelOperationMode::Master, + ), + backup_channel: BleedMonitoringComputerChannel::new( + backup_channel_engine_number, + BleedMonitoringComputerChannelOperationMode::Slave, + ), + powered_by, + is_powered: true, + } + } + + fn update( + &mut self, + context: &UpdateContext, + sensors: &[EngineBleedAirSystem; 2], + apu_bleed_valve: &impl PneumaticValve, + overhead_panel: &A380PneumaticOverheadPanel, + engine_fire_push_buttons: &impl EngineFirePushButtons, + cross_bleed_valve: &impl PneumaticValve, + fadec: &FullAuthorityDigitalEngineControl, + ) { + self.main_channel.update( + context, + &sensors[self.main_channel_engine_number - 1], + engine_fire_push_buttons.is_released(self.main_channel_engine_number), + apu_bleed_valve, + cross_bleed_valve, + overhead_panel, + fadec, + ); + + self.backup_channel.update( + context, + &sensors[self.backup_channel_engine_number - 1], + engine_fire_push_buttons.is_released(self.backup_channel_engine_number), + apu_bleed_valve, + cross_bleed_valve, + overhead_panel, + fadec, + ); + } + + fn check_for_failure(&mut self, other: &mut BleedMonitoringComputer) { + match other.signal() { + None => { + self.change_backup_channel_operation_mode( + BleedMonitoringComputerChannelOperationMode::Master, + ); + other.change_main_channel_operation_mode( + BleedMonitoringComputerChannelOperationMode::Slave, + ); + } + Some(_) => {} + } + } + + pub fn change_main_channel_operation_mode( + &mut self, + mode: BleedMonitoringComputerChannelOperationMode, + ) { + self.main_channel.set_operation_mode(mode); + } + + pub fn change_backup_channel_operation_mode( + &mut self, + mode: BleedMonitoringComputerChannelOperationMode, + ) { + self.backup_channel.set_operation_mode(mode); + } + + pub fn channel_for_engine( + &self, + engine_number: usize, + ) -> Option<&BleedMonitoringComputerChannel> { + if engine_number == self.main_channel_engine_number { + self.main_channel.or_none_if_slave() + } else if engine_number == self.backup_channel_engine_number { + self.backup_channel.or_none_if_slave() + } else { + None + } + } + + fn is_powered(&self) -> bool { + self.is_powered + } +} +impl SimulationElement for BleedMonitoringComputer { + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.is_powered = buses.is_powered(self.powered_by) + } +} +impl ControllerSignal for BleedMonitoringComputer { + fn signal(&self) -> Option { + if self.is_powered() { + Some(BleedMonitoringComputerIsAliveSignal) + } else { + None + } + } +} + +struct BleedMonitoringComputerChannel { + engine_number: usize, + operation_mode: BleedMonitoringComputerChannelOperationMode, + pressure_regulating_valve_is_closed: bool, + high_pressure_compressor_pressure: Pressure, + transfer_pressure: Pressure, + engine_starter_valve_is_open: bool, + is_engine_bleed_pushbutton_auto: bool, + is_engine_fire_pushbutton_released: bool, + is_apu_bleed_valve_open: bool, + is_apu_bleed_on: bool, + high_pressure_valve_pid: PidController, + pressure_regulating_valve_pid: PidController, + fan_air_valve_pid: PidController, + cross_bleed_valve_selector: CrossBleedValveSelectorMode, + cross_bleed_valve_is_open: bool, +} +impl BleedMonitoringComputerChannel { + const PRESSURE_REGULATING_VALVE_SINGLE_BLEED_CONFIG_TARGET_PSI: f64 = 52.; + const PRESSURE_REGULATING_VALVE_DUAL_BLEED_CONFIG_TARGET_PSI: f64 = 46.; + + fn new( + engine_number: usize, + operation_mode: BleedMonitoringComputerChannelOperationMode, + ) -> Self { + Self { + engine_number, + operation_mode, + pressure_regulating_valve_is_closed: false, + high_pressure_compressor_pressure: Pressure::new::(0.), + transfer_pressure: Pressure::new::(0.), + engine_starter_valve_is_open: false, + is_engine_bleed_pushbutton_auto: true, + is_engine_fire_pushbutton_released: false, + is_apu_bleed_valve_open: false, + is_apu_bleed_on: false, + high_pressure_valve_pid: PidController::new(0.05, 0.003, 0., 0., 1., 65., 1.), + pressure_regulating_valve_pid: PidController::new(0.05, 0.01, 0., 0., 1., 46., 1.), + fan_air_valve_pid: PidController::new(-0.005, -0.001, 0., 0., 1., 200., 1.), + cross_bleed_valve_selector: CrossBleedValveSelectorMode::Auto, + cross_bleed_valve_is_open: false, + } + } + + fn update( + &mut self, + context: &UpdateContext, + sensors: &EngineBleedAirSystem, + is_engine_fire_pushbutton_released: bool, + apu_bleed_valve: &impl PneumaticValve, + cross_bleed_valve: &impl PneumaticValve, + overhead_panel: &A380PneumaticOverheadPanel, + fadec: &FullAuthorityDigitalEngineControl, + ) { + self.high_pressure_compressor_pressure = sensors.high_pressure(); + self.transfer_pressure = sensors.transfer_pressure(); + + self.pressure_regulating_valve_pid.change_setpoint( + if fadec.is_single_vs_dual_bleed_config() { + Self::PRESSURE_REGULATING_VALVE_SINGLE_BLEED_CONFIG_TARGET_PSI + } else { + Self::PRESSURE_REGULATING_VALVE_DUAL_BLEED_CONFIG_TARGET_PSI + }, + ); + + self.pressure_regulating_valve_is_closed = !sensors.pressure_regulating_valve_is_open(); + + self.high_pressure_valve_pid + .next_control_output(self.transfer_pressure.get::(), Some(context.delta())); + self.pressure_regulating_valve_pid.next_control_output( + sensors.precooler_outlet_pressure().get::(), + Some(context.delta()), + ); + self.fan_air_valve_pid.next_control_output( + sensors + .precooler_outlet_temperature() + .get::(), + Some(context.delta()), + ); + + self.engine_starter_valve_is_open = sensors.engine_starter_valve_is_open(); + + self.is_engine_bleed_pushbutton_auto = + overhead_panel.engine_bleed_pb_is_auto(self.engine_number); + self.is_engine_fire_pushbutton_released = is_engine_fire_pushbutton_released; + + self.is_apu_bleed_valve_open = apu_bleed_valve.is_open(); + self.is_apu_bleed_on = overhead_panel.apu_bleed_is_on(); + + self.cross_bleed_valve_selector = overhead_panel.cross_bleed_mode(); + self.cross_bleed_valve_is_open = cross_bleed_valve.is_open(); + } + + fn operation_mode(&self) -> BleedMonitoringComputerChannelOperationMode { + self.operation_mode + } + + fn set_operation_mode(&mut self, mode: BleedMonitoringComputerChannelOperationMode) { + self.operation_mode = mode; + } + + fn or_none_if_slave(&self) -> Option<&BleedMonitoringComputerChannel> { + match self.operation_mode() { + BleedMonitoringComputerChannelOperationMode::Master => Some(self), + BleedMonitoringComputerChannelOperationMode::Slave => None, + } + } + + fn should_close_pressure_regulating_valve_because_apu_bleed_is_on(&self) -> bool { + self.is_apu_bleed_on + && self.is_apu_bleed_valve_open + && (self.engine_number == 1 || self.cross_bleed_valve_is_open) + } +} +impl ControllerSignal for BleedMonitoringComputerChannel { + fn signal(&self) -> Option { + if self.pressure_regulating_valve_is_closed + || self.high_pressure_compressor_pressure < Pressure::new::(18.) + { + Some(HighPressureValveSignal::new_closed()) + } else { + Some(HighPressureValveSignal::new(Ratio::new::( + self.high_pressure_valve_pid.output(), + ))) + } + } +} +impl ControllerSignal for BleedMonitoringComputerChannel { + fn signal(&self) -> Option { + if self.transfer_pressure < Pressure::new::(18.) + || (!self.is_engine_bleed_pushbutton_auto || self.is_engine_fire_pushbutton_released) + || self.should_close_pressure_regulating_valve_because_apu_bleed_is_on() + || self.engine_starter_valve_is_open + { + Some(PressureRegulatingValveSignal::new_closed()) + } else { + Some(PressureRegulatingValveSignal::new(Ratio::new::( + self.pressure_regulating_valve_pid.output(), + ))) + } + } +} +impl ControllerSignal for BleedMonitoringComputerChannel { + fn signal(&self) -> Option { + Some(FanAirValveSignal::new(Ratio::new::( + self.fan_air_valve_pid.output(), + ))) + } +} +impl ControllerSignal for BleedMonitoringComputerChannel { + fn signal(&self) -> Option { + match self.cross_bleed_valve_selector { + CrossBleedValveSelectorMode::Shut => Some(CrossBleedValveSignal::new_closed( + CrossBleedValveSignalType::Manual, + )), + CrossBleedValveSelectorMode::Open => Some(CrossBleedValveSignal::new_open( + CrossBleedValveSignalType::Manual, + )), + CrossBleedValveSelectorMode::Auto => { + if self.is_apu_bleed_valve_open { + Some(CrossBleedValveSignal::new_open( + CrossBleedValveSignalType::Automatic, + )) + } else { + Some(CrossBleedValveSignal::new_closed( + CrossBleedValveSignalType::Automatic, + )) + } + } + } + } +} + +struct EngineBleedAirSystem { + intermediate_pressure_id: VariableIdentifier, + high_pressure_id: VariableIdentifier, + transfer_pressure_id: VariableIdentifier, + precooler_inlet_pressure_id: VariableIdentifier, + precooler_outlet_pressure_id: VariableIdentifier, + starter_container_pressure_id: VariableIdentifier, + intermediate_temperature_id: VariableIdentifier, + high_temperature_id: VariableIdentifier, + transfer_temperature_id: VariableIdentifier, + precooler_inlet_temperature_id: VariableIdentifier, + precooler_outlet_temperature_id: VariableIdentifier, + starter_container_temperature_id: VariableIdentifier, + intermediate_pressure_valve_open_id: VariableIdentifier, + high_pressure_valve_open_id: VariableIdentifier, + pressure_regulating_valve_open_id: VariableIdentifier, + starter_valve_open_id: VariableIdentifier, + + number: usize, + fan_compression_chamber_controller: EngineCompressionChamberController, // Controls pressure just behind the main fan + intermediate_pressure_compression_chamber_controller: EngineCompressionChamberController, + high_pressure_compression_chamber_controller: EngineCompressionChamberController, + fan_compression_chamber: CompressionChamber, + intermediate_pressure_compression_chamber: CompressionChamber, + high_pressure_compression_chamber: CompressionChamber, + intermediate_pressure_valve: PurelyPneumaticValve, + high_pressure_valve: ElectroPneumaticValve, + pressure_regulating_valve: ElectroPneumaticValve, + transfer_pressure_pipe: PneumaticPipe, + precooler_inlet_pipe: PneumaticPipe, + precooler_outlet_pipe: PneumaticPipe, + precooler_supply_pipe: PneumaticPipe, + engine_starter_exhaust: PneumaticExhaust, + engine_starter_container: PneumaticPipe, + engine_starter_valve: DefaultValve, + fan_air_valve: ElectroPneumaticValve, + precooler: Precooler, +} +impl EngineBleedAirSystem { + fn new(context: &mut InitContext, number: usize, powered_by: ElectricalBusType) -> Self { + Self { + number, + intermediate_pressure_id: context + .get_identifier(format!("PNEU_ENG_{}_IP_PRESSURE", number)), + high_pressure_id: context.get_identifier(format!("PNEU_ENG_{}_HP_PRESSURE", number)), + transfer_pressure_id: context + .get_identifier(format!("PNEU_ENG_{}_TRANSFER_PRESSURE", number)), + precooler_inlet_pressure_id: context + .get_identifier(format!("PNEU_ENG_{}_PRECOOLER_INLET_PRESSURE", number)), + precooler_outlet_pressure_id: context + .get_identifier(format!("PNEU_ENG_{}_PRECOOLER_OUTLET_PRESSURE", number)), + starter_container_pressure_id: context + .get_identifier(format!("PNEU_ENG_{}_STARTER_CONTAINER_PRESSURE", number)), + intermediate_temperature_id: context + .get_identifier(format!("PNEU_ENG_{}_IP_TEMPERATURE", number)), + high_temperature_id: context + .get_identifier(format!("PNEU_ENG_{}_HP_TEMPERATURE", number)), + transfer_temperature_id: context + .get_identifier(format!("PNEU_ENG_{}_TRANSFER_TEMPERATURE", number)), + precooler_inlet_temperature_id: context + .get_identifier(format!("PNEU_ENG_{}_PRECOOLER_INLET_TEMPERATURE", number)), + precooler_outlet_temperature_id: context + .get_identifier(format!("PNEU_ENG_{}_PRECOOLER_OUTLET_TEMPERATURE", number)), + starter_container_temperature_id: context + .get_identifier(format!("PNEU_ENG_{}_STARTER_CONTAINER_TEMPERATURE", number)), + intermediate_pressure_valve_open_id: context + .get_identifier(format!("PNEU_ENG_{}_IP_VALVE_OPEN", number)), + high_pressure_valve_open_id: context + .get_identifier(format!("PNEU_ENG_{}_HP_VALVE_OPEN", number)), + pressure_regulating_valve_open_id: context + .get_identifier(format!("PNEU_ENG_{}_PR_VALVE_OPEN", number)), + starter_valve_open_id: context + .get_identifier(format!("PNEU_ENG_{}_STARTER_VALVE_OPEN", number)), + fan_compression_chamber_controller: EngineCompressionChamberController::new(1., 0., 2.), + intermediate_pressure_compression_chamber_controller: + EngineCompressionChamberController::new(3., 0., 4.), + high_pressure_compression_chamber_controller: EngineCompressionChamberController::new( + 3., 2., 4., + ), + fan_compression_chamber: CompressionChamber::new(Volume::new::(1.)), + intermediate_pressure_compression_chamber: CompressionChamber::new(Volume::new::< + cubic_meter, + >(1.)), + high_pressure_compression_chamber: CompressionChamber::new(Volume::new::( + 1., + )), + intermediate_pressure_valve: PurelyPneumaticValve::new(), + high_pressure_valve: ElectroPneumaticValve::new(powered_by), + pressure_regulating_valve: ElectroPneumaticValve::new(powered_by), + fan_air_valve: ElectroPneumaticValve::new(powered_by), + transfer_pressure_pipe: PneumaticPipe::new( + Volume::new::(1.), + Pressure::new::(14.7), + ThermodynamicTemperature::new::(15.), + ), + precooler_inlet_pipe: PneumaticPipe::new( + Volume::new::(0.5), + Pressure::new::(14.7), + ThermodynamicTemperature::new::(15.), + ), + precooler_outlet_pipe: PneumaticPipe::new( + Volume::new::(2.5), + Pressure::new::(14.7), + ThermodynamicTemperature::new::(15.), + ), + precooler_supply_pipe: PneumaticPipe::new( + Volume::new::(1.), + Pressure::new::(14.7), + ThermodynamicTemperature::new::(15.), + ), + engine_starter_container: PneumaticPipe::new( + Volume::new::(0.5), + Pressure::new::(14.7), + ThermodynamicTemperature::new::(15.), + ), + engine_starter_exhaust: PneumaticExhaust::new(3e-2, 3e-2, Pressure::new::(0.)), + engine_starter_valve: DefaultValve::new_closed(), + precooler: Precooler::new(180. * 2.), + } + } + + fn update( + &mut self, + context: &UpdateContext, + high_pressure_valve_controller: &impl ControllerSignal, + pressure_regulating_valve_controller: &impl ControllerSignal, + engine_starter_valve_controller: &impl ControllerSignal, + fan_air_valve_controller: &impl ControllerSignal, + engine: &(impl EngineCorrectedN1 + EngineCorrectedN2), + ) { + // Update engines + self.fan_compression_chamber_controller + .update(context, engine); + self.intermediate_pressure_compression_chamber_controller + .update(context, engine); + self.high_pressure_compression_chamber_controller + .update(context, engine); + + self.fan_compression_chamber + .update(&self.fan_compression_chamber_controller); + self.intermediate_pressure_compression_chamber + .update(&self.intermediate_pressure_compression_chamber_controller); + self.high_pressure_compression_chamber + .update(&self.high_pressure_compression_chamber_controller); + + self.high_pressure_valve + .update_open_amount(high_pressure_valve_controller); + self.pressure_regulating_valve + .update_open_amount(pressure_regulating_valve_controller); + self.engine_starter_valve + .update_open_amount(engine_starter_valve_controller); + self.fan_air_valve + .update_open_amount(fan_air_valve_controller); + + self.intermediate_pressure_valve.update_move_fluid( + context, + &mut self.intermediate_pressure_compression_chamber, + &mut self.transfer_pressure_pipe, + ); + self.high_pressure_valve.update_move_fluid( + context, + &mut self.high_pressure_compression_chamber, + &mut self.transfer_pressure_pipe, + ); + self.fan_air_valve.update_move_fluid( + context, + &mut self.fan_compression_chamber, + &mut self.precooler_supply_pipe, + ); + self.pressure_regulating_valve.update_move_fluid( + context, + &mut self.transfer_pressure_pipe, + &mut self.precooler_inlet_pipe, + ); + self.precooler.update( + context, + &mut self.precooler_inlet_pipe, + &mut self.precooler_supply_pipe, + &mut self.precooler_outlet_pipe, + ); + self.engine_starter_valve.update_move_fluid( + context, + &mut self.precooler_inlet_pipe, + &mut self.engine_starter_container, + ); + self.engine_starter_exhaust + .update_move_fluid(context, &mut self.engine_starter_container) + } + + fn intermediate_pressure(&self) -> Pressure { + self.intermediate_pressure_compression_chamber.pressure() + } + + fn high_pressure(&self) -> Pressure { + self.high_pressure_compression_chamber.pressure() + } + + fn transfer_pressure(&self) -> Pressure { + self.transfer_pressure_pipe.pressure() + } + + fn precooler_inlet_pressure(&self) -> Pressure { + self.precooler_inlet_pipe.pressure() + } + + fn precooler_outlet_pressure(&self) -> Pressure { + self.precooler_outlet_pipe.pressure() + } + + fn intermediate_temperature(&self) -> ThermodynamicTemperature { + self.intermediate_pressure_compression_chamber.temperature() + } + + fn high_temperature(&self) -> ThermodynamicTemperature { + self.high_pressure_compression_chamber.temperature() + } + + fn transfer_temperature(&self) -> ThermodynamicTemperature { + self.transfer_pressure_pipe.temperature() + } + + fn precooler_inlet_temperature(&self) -> ThermodynamicTemperature { + self.precooler_inlet_pipe.temperature() + } + + fn precooler_outlet_temperature(&self) -> ThermodynamicTemperature { + self.precooler_outlet_pipe.temperature() + } + + fn engine_starter_valve_is_open(&self) -> bool { + self.engine_starter_valve.is_open() + } + + fn pressure_regulating_valve_is_open(&self) -> bool { + self.pressure_regulating_valve.is_open() + } +} +impl SimulationElement for EngineBleedAirSystem { + fn accept(&mut self, visitor: &mut T) { + self.high_pressure_valve.accept(visitor); + self.pressure_regulating_valve.accept(visitor); + self.fan_air_valve.accept(visitor); + + visitor.visit(self); + } + + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.intermediate_pressure_id, self.intermediate_pressure()); + writer.write(&self.high_pressure_id, self.high_pressure()); + writer.write(&self.transfer_pressure_id, self.transfer_pressure()); + writer.write( + &self.precooler_inlet_pressure_id, + self.precooler_inlet_pressure(), + ); + writer.write( + &self.precooler_outlet_pressure_id, + self.precooler_outlet_pressure(), + ); + writer.write( + &self.starter_container_pressure_id, + self.engine_starter_container.pressure(), + ); + writer.write( + &self.intermediate_temperature_id, + self.intermediate_temperature(), + ); + writer.write(&self.high_temperature_id, self.high_temperature()); + writer.write(&self.transfer_temperature_id, self.transfer_temperature()); + writer.write( + &self.precooler_inlet_temperature_id, + self.precooler_inlet_temperature(), + ); + writer.write( + &self.precooler_outlet_temperature_id, + self.precooler_outlet_temperature(), + ); + writer.write( + &self.starter_container_temperature_id, + self.engine_starter_container.temperature(), + ); + writer.write( + &self.intermediate_pressure_valve_open_id, + self.intermediate_pressure_valve.is_open(), + ); + writer.write( + &self.high_pressure_valve_open_id, + self.high_pressure_valve.is_open(), + ); + writer.write( + &self.pressure_regulating_valve_open_id, + self.pressure_regulating_valve.is_open(), + ); + writer.write( + &self.starter_valve_open_id, + self.engine_starter_valve.is_open(), + ); + } +} +impl PneumaticContainer for EngineBleedAirSystem { + fn pressure(&self) -> Pressure { + self.precooler_outlet_pipe.pressure() + } + + fn volume(&self) -> Volume { + self.precooler_outlet_pipe.volume() + } + + fn temperature(&self) -> ThermodynamicTemperature { + self.precooler_outlet_pipe.temperature() + } + + fn mass(&self) -> Mass { + self.precooler_outlet_pipe.mass() + } + + fn change_fluid_amount( + &mut self, + fluid_amount: Mass, + fluid_temperature: ThermodynamicTemperature, + fluid_pressure: Pressure, + ) { + self.precooler_outlet_pipe.change_fluid_amount( + fluid_amount, + fluid_temperature, + fluid_pressure, + ) + } + + fn update_temperature(&mut self, temperature: TemperatureInterval) { + self.precooler_outlet_pipe.update_temperature(temperature); + } +} + +pub struct A380PneumaticOverheadPanel { + apu_bleed: OnOffFaultPushButton, + cross_bleed: CrossBleedValveSelectorKnob, + engine_1_bleed: AutoOffFaultPushButton, + engine_2_bleed: AutoOffFaultPushButton, +} +impl A380PneumaticOverheadPanel { + pub fn new(context: &mut InitContext) -> Self { + A380PneumaticOverheadPanel { + apu_bleed: OnOffFaultPushButton::new_on(context, "PNEU_APU_BLEED"), + cross_bleed: CrossBleedValveSelectorKnob::new_auto(context), + engine_1_bleed: AutoOffFaultPushButton::new_auto(context, "PNEU_ENG_1_BLEED"), + engine_2_bleed: AutoOffFaultPushButton::new_auto(context, "PNEU_ENG_2_BLEED"), + } + } + + pub fn apu_bleed_is_on(&self) -> bool { + self.apu_bleed.is_on() + } + + pub fn cross_bleed_mode(&self) -> CrossBleedValveSelectorMode { + self.cross_bleed.mode() + } + + pub fn engine_bleed_pb_is_auto(&self, engine_number: usize) -> bool { + match engine_number { + 1 => self.engine_1_bleed.is_auto(), + 2 => self.engine_2_bleed.is_auto(), + _ => panic!("Invalid engine number"), + } + } +} +impl EngineBleedPushbutton for A380PneumaticOverheadPanel { + fn engine_bleed_pushbuttons_are_auto(&self) -> [bool; 2] { + [self.engine_1_bleed.is_auto(), self.engine_2_bleed.is_auto()] + } +} +impl SimulationElement for A380PneumaticOverheadPanel { + fn accept(&mut self, visitor: &mut T) { + self.apu_bleed.accept(visitor); + self.cross_bleed.accept(visitor); + self.engine_1_bleed.accept(visitor); + self.engine_2_bleed.accept(visitor); + + visitor.visit(self); + } +} + +/// We use this simply as an interface to engine parameter simvars. It should probably not be part of the pneumatic system. +pub struct FullAuthorityDigitalEngineControl { + engine_1_state_id: VariableIdentifier, + engine_2_state_id: VariableIdentifier, + + engine_1_state: EngineState, + engine_2_state: EngineState, + + engine_mode_selector1_id: VariableIdentifier, + engine_mode_selector1_position: EngineModeSelector, +} +impl FullAuthorityDigitalEngineControl { + fn new(context: &mut InitContext) -> Self { + Self { + engine_1_state_id: context.get_identifier("ENGINE_STATE:1".to_owned()), + engine_2_state_id: context.get_identifier("ENGINE_STATE:2".to_owned()), + engine_1_state: EngineState::Off, + engine_2_state: EngineState::Off, + engine_mode_selector1_id: context + .get_identifier("TURB ENG IGNITION SWITCH EX1:1".to_owned()), + engine_mode_selector1_position: EngineModeSelector::Norm, + } + } + + fn engine_state(&self, number: usize) -> EngineState { + match number { + 1 => self.engine_1_state, + 2 => self.engine_2_state, + _ => panic!("Invalid engine number"), + } + } + + fn is_single_vs_dual_bleed_config(&self) -> bool { + (self.engine_1_state == EngineState::On) ^ (self.engine_2_state == EngineState::On) + } + + fn engine_mode_selector(&self) -> EngineModeSelector { + self.engine_mode_selector1_position + } +} +impl SimulationElement for FullAuthorityDigitalEngineControl { + fn read(&mut self, reader: &mut SimulatorReader) { + self.engine_1_state = reader.read(&self.engine_1_state_id); + self.engine_2_state = reader.read(&self.engine_2_state_id); + self.engine_mode_selector1_position = reader.read(&self.engine_mode_selector1_id); + } +} + +/// A struct to hold all the pack related components +struct PackComplex { + engine_number: usize, + pack_flow_valve_id: VariableIdentifier, + pack_flow_valve_flow_rate_id: VariableIdentifier, + pack_container: PneumaticPipe, + exhaust: PneumaticExhaust, + pack_flow_valve: DefaultValve, +} +impl PackComplex { + fn new(context: &mut InitContext, engine_number: usize) -> Self { + Self { + engine_number, + pack_flow_valve_id: context.get_identifier(Self::pack_flow_valve_id(engine_number)), + pack_flow_valve_flow_rate_id: context + .get_identifier(format!("PNEU_PACK_{}_FLOW_VALVE_FLOW_RATE", engine_number)), + pack_container: PneumaticPipe::new( + Volume::new::(2.), + Pressure::new::(14.7), + ThermodynamicTemperature::new::(15.), + ), + exhaust: PneumaticExhaust::new(0.3, 0.3, Pressure::new::(0.)), + pack_flow_valve: DefaultValve::new_closed(), + } + } + + fn pack_flow_valve_id(number: usize) -> String { + format!("COND_PACK_FLOW_VALVE_{}_IS_OPEN", number) + } + + fn update( + &mut self, + context: &UpdateContext, + from: &mut impl PneumaticContainer, + pack_flow_valve_signals: &impl PackFlowControllers<3>, + ) { + self.pack_flow_valve.update_open_amount( + &pack_flow_valve_signals.pack_flow_controller(self.engine_number.into()), + ); + + self.pack_flow_valve + .update_move_fluid(context, from, &mut self.pack_container); + + self.exhaust + .update_move_fluid(context, &mut self.pack_container); + } + + fn pack_flow_valve_open_amount(&self) -> Ratio { + self.pack_flow_valve.open_amount() + } + + fn pack_flow_valve_air_flow(&self) -> MassRate { + self.pack_flow_valve.fluid_flow() + } +} +impl PneumaticContainer for PackComplex { + fn pressure(&self) -> Pressure { + self.pack_container.pressure() + } + + fn volume(&self) -> Volume { + self.pack_container.volume() + } + + fn temperature(&self) -> ThermodynamicTemperature { + self.pack_container.temperature() + } + + fn mass(&self) -> Mass { + self.pack_container.mass() + } + + fn change_fluid_amount( + &mut self, + fluid_amount: Mass, + fluid_temperature: ThermodynamicTemperature, + fluid_pressure: Pressure, + ) { + self.pack_container + .change_fluid_amount(fluid_amount, fluid_temperature, fluid_pressure); + } + + fn update_temperature(&mut self, temperature_change: TemperatureInterval) { + self.pack_container.update_temperature(temperature_change); + } +} +impl SimulationElement for PackComplex { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write( + &self.pack_flow_valve_id, + self.pack_flow_valve_open_amount() > Ratio::new::(0.), + ); + writer.write( + &self.pack_flow_valve_flow_rate_id, + self.pack_flow_valve.fluid_flow(), + ); + } +} + +/// This is a unique valve (and specific to the A320 probably) because it is controlled by two motors. One for manual control and one for automatic control +pub struct CrossBleedValve { + open_amount: Ratio, + connector: PneumaticContainerConnector, + is_powered_for_manual_control: bool, + is_powered_for_automatic_control: bool, +} +impl CrossBleedValve { + const SPRING_CHARACTERISTIC: f64 = 1.; + + pub fn new() -> Self { + Self { + open_amount: Ratio::new::(0.), + connector: PneumaticContainerConnector::new(), + is_powered_for_manual_control: false, + is_powered_for_automatic_control: false, + } + } + + pub fn update_move_fluid( + &mut self, + context: &UpdateContext, + container_one: &mut impl PneumaticContainer, + container_two: &mut impl PneumaticContainer, + ) { + if !self.is_powered_for_manual_control && !self.is_powered_for_automatic_control { + self.set_open_amount_from_pressure_difference( + container_one.pressure() - container_two.pressure(), + ) + } + + self.connector + .with_transfer_speed_factor(self.open_amount) + .update_move_fluid(context, container_one, container_two); + } + + fn set_open_amount_from_pressure_difference(&mut self, pressure_difference: Pressure) { + self.open_amount = Ratio::new::( + 2. / PI + * (pressure_difference.get::() * Self::SPRING_CHARACTERISTIC) + .atan() + .max(0.), + ); + } + + fn update_open_amount(&mut self, controller: &impl ControllerSignal) { + if let Some(signal) = controller.signal() { + if signal.signal_type == CrossBleedValveSignalType::Manual + && self.is_powered_for_manual_control + || signal.signal_type == CrossBleedValveSignalType::Automatic + && self.is_powered_for_automatic_control + { + self.open_amount = signal.target_open_amount() + } + } + } +} +impl PneumaticValve for CrossBleedValve { + fn is_open(&self) -> bool { + self.open_amount.get::() > 0. + } +} +impl SimulationElement for CrossBleedValve { + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.is_powered_for_manual_control = + buses.is_powered(ElectricalBusType::DirectCurrentEssentialShed); + self.is_powered_for_automatic_control = + buses.is_powered(ElectricalBusType::DirectCurrent(2)); + } +} + +#[cfg(test)] +mod tests { + use systems::{ + air_conditioning::{ + acs_controller::{Pack, PackFlowController}, + AirConditioningSystem, PackFlowControllers, ZoneType, + }, + electrical::{test::TestElectricitySource, ElectricalBus, Electricity}, + engine::leap_engine::LeapEngine, + failures::FailureType, + pneumatic::{ + BleedMonitoringComputerChannelOperationMode, ControllablePneumaticValve, + CrossBleedValveSelectorMode, EngineState, PneumaticContainer, PneumaticValveSignal, + TargetPressureTemperatureSignal, + }, + pressurization::PressurizationOverheadPanel, + shared::{ + ApuBleedAirValveSignal, Cabin, ControllerSignal, ElectricalBusType, ElectricalBuses, + EmergencyElectricalState, EngineBleedPushbutton, EngineCorrectedN1, + EngineFirePushButtons, EngineStartState, GroundSpeed, HydraulicColor, + InternationalStandardAtmosphere, LgciuWeightOnWheels, MachNumber, PackFlowValveState, + PneumaticBleed, PneumaticValve, PotentialOrigin, + }, + simulation::{ + test::{SimulationTestBed, TestBed, WriteByName}, + Aircraft, InitContext, SimulationElement, SimulationElementVisitor, UpdateContext, + }, + }; + + use std::{fs, fs::File, time::Duration}; + + use uom::si::{ + f64::*, + length::foot, + mass_rate::kilogram_per_second, + pressure::{pascal, psi}, + ratio::ratio, + thermodynamic_temperature::degree_celsius, + velocity::knot, + }; + + use super::{A380Pneumatic, A380PneumaticOverheadPanel}; + + struct TestAirConditioning { + a380_air_conditioning_system: AirConditioningSystem<3>, + + adirs: TestAdirs, + lgciu: TestLgciu, + pressurization: TestPressurization, + pressurization_overhead: PressurizationOverheadPanel, + } + impl TestAirConditioning { + fn new(context: &mut InitContext) -> Self { + let cabin_zones: [ZoneType; 3] = + [ZoneType::Cockpit, ZoneType::Cabin(1), ZoneType::Cabin(2)]; + + Self { + a380_air_conditioning_system: AirConditioningSystem::new( + context, + cabin_zones, + vec![ElectricalBusType::DirectCurrent(1)], + vec![ElectricalBusType::DirectCurrent(2)], + ), + + adirs: TestAdirs::new(), + lgciu: TestLgciu::new(true), + pressurization: TestPressurization::new(), + pressurization_overhead: PressurizationOverheadPanel::new(context), + } + } + fn update( + &mut self, + context: &UpdateContext, + engines: [&impl EngineCorrectedN1; 2], + engine_fire_push_buttons: &impl EngineFirePushButtons, + pneumatic: &(impl EngineStartState + PackFlowValveState + PneumaticBleed), + pneumatic_overhead: &impl EngineBleedPushbutton, + ) { + self.a380_air_conditioning_system.update( + context, + &self.adirs, + engines, + engine_fire_push_buttons, + pneumatic, + pneumatic_overhead, + &self.pressurization, + &self.pressurization_overhead, + [&self.lgciu; 2], + ); + } + } + impl PackFlowControllers<3> for TestAirConditioning { + fn pack_flow_controller(&self, pack_id: Pack) -> PackFlowController<3> { + self.a380_air_conditioning_system + .pack_flow_controller(pack_id) + } + } + impl SimulationElement for TestAirConditioning { + fn accept(&mut self, visitor: &mut V) { + self.a380_air_conditioning_system.accept(visitor); + + visitor.visit(self); + } + } + + struct TestAdirs { + ground_speed: Velocity, + } + impl TestAdirs { + fn new() -> Self { + Self { + ground_speed: Velocity::new::(0.), + } + } + } + impl GroundSpeed for TestAdirs { + fn ground_speed(&self) -> Velocity { + self.ground_speed + } + } + + struct TestPressurization { + cabin_pressure: Pressure, + } + impl TestPressurization { + fn new() -> Self { + Self { + cabin_pressure: Pressure::new::(101315.), + } + } + } + impl Cabin for TestPressurization { + fn altitude(&self) -> Length { + Length::new::(0.) + } + + fn pressure(&self) -> Pressure { + self.cabin_pressure + } + } + + struct TestLgciu { + compressed: bool, + } + impl TestLgciu { + fn new(compressed: bool) -> Self { + Self { compressed } + } + } + impl LgciuWeightOnWheels for TestLgciu { + fn left_and_right_gear_compressed(&self, _treat_ext_pwr_as_ground: bool) -> bool { + self.compressed + } + fn right_gear_compressed(&self, _treat_ext_pwr_as_ground: bool) -> bool { + true + } + fn right_gear_extended(&self, _treat_ext_pwr_as_ground: bool) -> bool { + false + } + fn left_gear_compressed(&self, _treat_ext_pwr_as_ground: bool) -> bool { + true + } + fn left_gear_extended(&self, _treat_ext_pwr_as_ground: bool) -> bool { + false + } + fn left_and_right_gear_extended(&self, _treat_ext_pwr_as_ground: bool) -> bool { + false + } + fn nose_gear_compressed(&self, _treat_ext_pwr_as_ground: bool) -> bool { + true + } + fn nose_gear_extended(&self, _treat_ext_pwr_as_ground: bool) -> bool { + false + } + } + + struct TestApu { + bleed_air_valve_signal: ApuBleedAirValveSignal, + bleed_air_pressure: Pressure, + bleed_air_temperature: ThermodynamicTemperature, + } + impl TestApu { + fn new() -> Self { + Self { + bleed_air_valve_signal: ApuBleedAirValveSignal::new_closed(), + bleed_air_pressure: Pressure::new::(14.7), + bleed_air_temperature: ThermodynamicTemperature::new::(15.), + } + } + + fn update(&self, bleed_valve: &mut impl ControllablePneumaticValve) { + bleed_valve.update_open_amount::(self); + } + + fn set_bleed_air_pressure(&mut self, pressure: Pressure) { + self.bleed_air_pressure = pressure; + } + + fn set_bleed_air_temperature(&mut self, temperature: ThermodynamicTemperature) { + self.bleed_air_temperature = temperature; + } + + fn set_bleed_air_valve_signal(&mut self, signal: ApuBleedAirValveSignal) { + self.bleed_air_valve_signal = signal; + } + } + impl ControllerSignal for TestApu { + fn signal(&self) -> Option { + Some(self.bleed_air_valve_signal) + } + } + impl ControllerSignal for TestApu { + fn signal(&self) -> Option { + Some(TargetPressureTemperatureSignal::new( + self.bleed_air_pressure, + self.bleed_air_temperature, + )) + } + } + + struct TestEngineFirePushButtons { + is_released: [bool; 2], + } + impl TestEngineFirePushButtons { + fn new() -> Self { + Self { + is_released: [false, false], + } + } + + fn release(&mut self, engine_number: usize) { + self.is_released[engine_number - 1] = true; + } + } + impl EngineFirePushButtons for TestEngineFirePushButtons { + fn is_released(&self, engine_number: usize) -> bool { + self.is_released[engine_number - 1] + } + } + + struct A380TestElectrical { + airspeed: Velocity, + all_ac_lost: bool, + } + impl A380TestElectrical { + pub fn new() -> Self { + A380TestElectrical { + airspeed: Velocity::new::(100.), + all_ac_lost: false, + } + } + + fn update(&mut self, context: &UpdateContext) { + self.airspeed = context.indicated_airspeed(); + } + } + impl EmergencyElectricalState for A380TestElectrical { + fn is_in_emergency_elec(&self) -> bool { + self.all_ac_lost && self.airspeed >= Velocity::new::(100.) + } + } + impl SimulationElement for A380TestElectrical { + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.all_ac_lost = !buses.is_powered(ElectricalBusType::AlternatingCurrent(1)) + && !buses.is_powered(ElectricalBusType::AlternatingCurrent(2)); + } + } + + struct PneumaticTestAircraft { + pneumatic: A380Pneumatic, + air_conditioning: TestAirConditioning, + apu: TestApu, + engine_1: LeapEngine, + engine_2: LeapEngine, + pneumatic_overhead_panel: A380PneumaticOverheadPanel, + fire_pushbuttons: TestEngineFirePushButtons, + electrical: A380TestElectrical, + powered_source: TestElectricitySource, + dc_1_bus: ElectricalBus, + dc_2_bus: ElectricalBus, + dc_ess_bus: ElectricalBus, + dc_ess_shed_bus: ElectricalBus, + // Electric buses states to be able to kill them dynamically + is_dc_1_powered: bool, + is_dc_2_powered: bool, + is_dc_ess_powered: bool, + is_dc_ess_shed_powered: bool, + } + impl PneumaticTestAircraft { + fn new(context: &mut InitContext) -> Self { + Self { + pneumatic: A380Pneumatic::new(context), + air_conditioning: TestAirConditioning::new(context), + apu: TestApu::new(), + engine_1: LeapEngine::new(context, 1), + engine_2: LeapEngine::new(context, 2), + pneumatic_overhead_panel: A380PneumaticOverheadPanel::new(context), + fire_pushbuttons: TestEngineFirePushButtons::new(), + electrical: A380TestElectrical::new(), + powered_source: TestElectricitySource::powered( + context, + PotentialOrigin::EngineGenerator(1), + ), + dc_1_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(1)), + dc_2_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(2)), + dc_ess_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrentEssential), + dc_ess_shed_bus: ElectricalBus::new( + context, + ElectricalBusType::DirectCurrentEssentialShed, + ), + is_dc_1_powered: true, + is_dc_2_powered: true, + is_dc_ess_powered: true, + is_dc_ess_shed_powered: true, + } + } + + fn set_dc_2_bus_power(&mut self, is_powered: bool) { + self.is_dc_2_powered = is_powered; + } + + fn set_dc_ess_shed_bus_power(&mut self, is_powered: bool) { + self.is_dc_ess_shed_powered = is_powered; + } + } + impl Aircraft for PneumaticTestAircraft { + fn update_before_power_distribution( + &mut self, + _context: &UpdateContext, + electricity: &mut Electricity, + ) { + electricity.supplied_by(&self.powered_source); + + if self.is_dc_1_powered { + electricity.flow(&self.powered_source, &self.dc_1_bus); + } + + if self.is_dc_2_powered { + electricity.flow(&self.powered_source, &self.dc_2_bus); + } + + if self.is_dc_ess_powered { + electricity.flow(&self.powered_source, &self.dc_ess_bus); + } + + if self.is_dc_ess_shed_powered { + electricity.flow(&self.powered_source, &self.dc_ess_shed_bus); + } + } + + fn update_after_power_distribution(&mut self, context: &UpdateContext) { + self.electrical.update(context); + + self.apu.update(self.pneumatic.apu_bleed_air_valve()); + self.pneumatic.update( + context, + [&self.engine_1, &self.engine_2], + &self.pneumatic_overhead_panel, + &self.fire_pushbuttons, + &self.apu, + &self.air_conditioning, + ); + self.air_conditioning.update( + context, + [&self.engine_1, &self.engine_2], + &self.fire_pushbuttons, + &self.pneumatic, + &self.pneumatic_overhead_panel, + ) + } + } + impl SimulationElement for PneumaticTestAircraft { + fn accept(&mut self, visitor: &mut T) { + self.electrical.accept(visitor); + self.pneumatic.accept(visitor); + self.engine_1.accept(visitor); + self.engine_2.accept(visitor); + self.pneumatic_overhead_panel.accept(visitor); + self.air_conditioning.accept(visitor); + + visitor.visit(self); + } + } + struct PneumaticTestBed { + test_bed: SimulationTestBed, + } + impl TestBed for PneumaticTestBed { + type Aircraft = PneumaticTestAircraft; + + fn test_bed(&self) -> &SimulationTestBed { + &self.test_bed + } + + fn test_bed_mut(&mut self) -> &mut SimulationTestBed { + &mut self.test_bed + } + } + impl PneumaticTestBed { + fn new() -> Self { + let mut test_bed = Self { + test_bed: SimulationTestBed::::new(|context| { + PneumaticTestAircraft::new(context) + }), + }; + test_bed.command_pack_flow_selector_position(1); + + test_bed + } + + fn and_run(mut self) -> Self { + self.run(); + + self + } + + fn and_stabilize(mut self) -> Self { + self.test_bed.run_multiple_frames(Duration::from_secs(16)); + + self + } + + fn mach_number(mut self, mach: MachNumber) -> Self { + self.write_by_name("AIRSPEED MACH", mach); + + self + } + + fn in_isa_atmosphere(mut self, altitude: Length) -> Self { + self.set_ambient_pressure(InternationalStandardAtmosphere::pressure_at_altitude( + altitude, + )); + self.set_ambient_temperature(InternationalStandardAtmosphere::temperature_at_altitude( + altitude, + )); + + self + } + + fn idle_eng1(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:1", true); + self.write_by_name("TURB ENG CORRECTED N2:1", Ratio::new::(0.55)); + self.write_by_name("TURB ENG CORRECTED N1:1", Ratio::new::(0.2)); + self.write_by_name("ENGINE_STATE:1", EngineState::On); + + self + } + + fn idle_eng2(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:2", true); + self.write_by_name("TURB ENG CORRECTED N2:2", Ratio::new::(0.55)); + self.write_by_name("TURB ENG CORRECTED N1:2", Ratio::new::(0.2)); + self.write_by_name("ENGINE_STATE:2", EngineState::On); + + self + } + + fn toga_eng1(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:1", true); + self.write_by_name("TURB ENG CORRECTED N2:1", Ratio::new::(0.65)); + self.write_by_name("TURB ENG CORRECTED N1:1", Ratio::new::(0.99)); + self.write_by_name("ENGINE_STATE:1", EngineState::On); + + self + } + + fn toga_eng2(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:2", true); + self.write_by_name("TURB ENG CORRECTED N2:2", Ratio::new::(0.65)); + self.write_by_name("TURB ENG CORRECTED N1:2", Ratio::new::(0.99)); + self.write_by_name("ENGINE_STATE:2", EngineState::On); + + self + } + + fn stop_eng1(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:1", false); + self.write_by_name("TURB ENG CORRECTED N2:1", Ratio::new::(0.)); + self.write_by_name("TURB ENG CORRECTED N1:1", Ratio::new::(0.)); + self.write_by_name("ENGINE_STATE:1", EngineState::Off); + + self + } + + fn stop_eng2(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:2", false); + self.write_by_name("TURB ENG CORRECTED N2:2", Ratio::new::(0.)); + self.write_by_name("TURB ENG CORRECTED N1:2", Ratio::new::(0.)); + self.write_by_name("ENGINE_STATE:2", EngineState::Off); + + self + } + + fn start_eng1(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:1", true); + self.write_by_name("ENGINE_STATE:1", EngineState::Starting); + + self + } + + fn start_eng2(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:2", true); + self.write_by_name("ENGINE_STATE:2", EngineState::Starting); + + self + } + + fn cross_bleed_valve_selector_knob(mut self, mode: CrossBleedValveSelectorMode) -> Self { + self.write_by_name("KNOB_OVHD_AIRCOND_XBLEED_Position", mode); + + self + } + + fn ip_pressure(&self, number: usize) -> Pressure { + self.query(|a| a.pneumatic.engine_systems[number - 1].intermediate_pressure()) + } + + fn hp_pressure(&self, number: usize) -> Pressure { + self.query(|a| a.pneumatic.engine_systems[number - 1].high_pressure()) + } + + fn transfer_pressure(&self, number: usize) -> Pressure { + self.query(|a| a.pneumatic.engine_systems[number - 1].transfer_pressure()) + } + + fn precooler_inlet_pressure(&self, number: usize) -> Pressure { + self.query(|a| a.pneumatic.engine_systems[number - 1].precooler_inlet_pressure()) + } + + fn precooler_outlet_pressure(&self, number: usize) -> Pressure { + self.query(|a| a.pneumatic.engine_systems[number - 1].precooler_outlet_pressure()) + } + + fn precooler_supply_pressure(&self, number: usize) -> Pressure { + self.query(|a| { + a.pneumatic.engine_systems[number - 1] + .precooler_supply_pipe + .pressure() + }) + } + + fn engine_starter_container_pressure(&self, number: usize) -> Pressure { + self.query(|a| { + a.pneumatic.engine_systems[number - 1] + .engine_starter_container + .pressure() + }) + } + + fn ip_temperature(&self, number: usize) -> ThermodynamicTemperature { + self.query(|a| a.pneumatic.engine_systems[number - 1].intermediate_temperature()) + } + + fn hp_temperature(&self, number: usize) -> ThermodynamicTemperature { + self.query(|a| a.pneumatic.engine_systems[number - 1].high_temperature()) + } + + fn transfer_temperature(&self, number: usize) -> ThermodynamicTemperature { + self.query(|a| a.pneumatic.engine_systems[number - 1].transfer_temperature()) + } + + fn precooler_inlet_temperature(&self, number: usize) -> ThermodynamicTemperature { + self.query(|a| a.pneumatic.engine_systems[number - 1].precooler_inlet_temperature()) + } + + fn precooler_outlet_temperature(&self, number: usize) -> ThermodynamicTemperature { + self.query(|a| a.pneumatic.engine_systems[number - 1].precooler_outlet_temperature()) + } + + fn precooler_supply_temperature(&self, number: usize) -> ThermodynamicTemperature { + self.query(|a| { + a.pneumatic.engine_systems[number - 1] + .precooler_supply_pipe + .temperature() + }) + } + + fn engine_starter_container_temperature(&self, number: usize) -> ThermodynamicTemperature { + self.query(|a| { + a.pneumatic.engine_systems[number - 1] + .engine_starter_container + .temperature() + }) + } + + fn hp_valve_is_open(&self, number: usize) -> bool { + self.query(|a| { + a.pneumatic.engine_systems[number - 1] + .high_pressure_valve + .is_open() + }) + } + + fn pr_valve_is_open(&self, number: usize) -> bool { + self.query(|a| { + a.pneumatic.engine_systems[number - 1] + .pressure_regulating_valve + .is_open() + }) + } + + fn es_valve_is_open(&self, number: usize) -> bool { + self.query(|a| a.pneumatic.engine_systems[number - 1].engine_starter_valve_is_open()) + } + + fn apu_bleed_valve_is_open(&self) -> bool { + self.query(|a| a.pneumatic.apu_bleed_air_valve.is_open()) + } + + fn hp_valve_is_powered(&self, number: usize) -> bool { + self.query(|a| { + a.pneumatic.engine_systems[number - 1] + .high_pressure_valve + .is_powered() + }) + } + + fn pr_valve_is_powered(&self, number: usize) -> bool { + self.query(|a| { + a.pneumatic.engine_systems[number - 1] + .pressure_regulating_valve + .is_powered() + }) + } + + fn fan_air_valve_is_powered(&self, number: usize) -> bool { + self.query(|a| { + a.pneumatic.engine_systems[number - 1] + .fan_air_valve + .is_powered() + }) + } + + fn set_engine_bleed_push_button_off(mut self, number: usize) -> Self { + self.write_by_name(&format!("OVHD_PNEU_ENG_{}_BLEED_PB_IS_AUTO", number), false); + + self + } + + fn set_apu_bleed_valve_signal(mut self, signal: ApuBleedAirValveSignal) -> Self { + self.command(|a| a.apu.set_bleed_air_valve_signal(signal)); + + self + } + + fn set_apu_bleed_air_pb(mut self, is_on: bool) -> Self { + self.write_by_name("OVHD_APU_BLEED_PB_IS_ON", is_on); + + self + } + + fn set_bleed_air_running(mut self) -> Self { + self.command(|a| a.apu.set_bleed_air_pressure(Pressure::new::(42.))); + self.command(|a| { + a.apu + .set_bleed_air_temperature(ThermodynamicTemperature::new::( + 250., + )) + }); + + self.set_apu_bleed_valve_signal(ApuBleedAirValveSignal::new_open()) + .set_apu_bleed_air_pb(true) + } + + fn release_fire_pushbutton(mut self, number: usize) -> Self { + self.command(|a| a.fire_pushbuttons.release(number)); + + self + } + + fn set_engine_state(mut self, number: usize, engine_state: EngineState) -> Self { + self.write_by_name(&format!("ENGINE_STATE:{}", number), engine_state); + + self + } + + fn engine_state(&self, number: usize) -> EngineState { + self.query(|a| a.pneumatic.fadec.engine_state(number)) + } + + fn cross_bleed_valve_is_open(&self) -> bool { + self.query(|a| a.pneumatic.cross_bleed_valve.is_open()) + } + + fn cross_bleed_valve_selector(&self) -> CrossBleedValveSelectorMode { + self.query(|a| a.pneumatic_overhead_panel.cross_bleed_mode()) + } + + fn engine_bleed_push_button_is_auto(&self, number: usize) -> bool { + self.query(|a| a.pneumatic_overhead_panel.engine_bleed_pb_is_auto(number)) + } + + fn green_hydraulic_reservoir_pressure(&self) -> Pressure { + self.query(|a| a.pneumatic.green_hydraulic_reservoir_with_valve.pressure()) + } + + fn blue_hydraulic_reservoir_pressure(&self) -> Pressure { + self.query(|a| a.pneumatic.blue_hydraulic_reservoir_with_valve.pressure()) + } + + fn yellow_hydraulic_reservoir_pressure(&self) -> Pressure { + self.query(|a| a.pneumatic.yellow_hydraulic_reservoir_with_valve.pressure()) + } + + fn set_pack_flow_pb_is_auto(mut self, number: usize, is_auto: bool) -> Self { + self.write_by_name(&format!("OVHD_COND_PACK_{}_PB_IS_ON", number), is_auto); + + self + } + + fn pack_flow_valve_is_open(&self, number: usize) -> bool { + self.query(|a| a.pneumatic.packs[number - 1].pack_flow_valve.is_open()) + } + + fn both_packs_auto(self) -> Self { + self.set_pack_flow_pb_is_auto(1, true) + .set_pack_flow_pb_is_auto(2, true) + } + + fn bmc_operation_mode_for_engine( + &self, + bmc_number: usize, + engine_number: usize, + ) -> BleedMonitoringComputerChannelOperationMode { + self.query(|a| { + let bmc = &a.pneumatic.bleed_monitoring_computers[bmc_number - 1]; + + let channel = if bmc_number == engine_number { + &bmc.main_channel + } else { + &bmc.backup_channel + }; + + channel.operation_mode() + }) + } + + fn set_dc_2_bus_power(mut self, is_powered: bool) -> Self { + self.command(|a| a.set_dc_2_bus_power(is_powered)); + + self + } + + fn set_dc_ess_shed_bus_power(mut self, is_powered: bool) -> Self { + self.command(|a| a.set_dc_ess_shed_bus_power(is_powered)); + + self + } + + fn bmc_is_powered(&self, bmc_number: usize) -> bool { + self.query(|a| a.pneumatic.bleed_monitoring_computers[bmc_number - 1].is_powered()) + } + + fn fadec_single_vs_dual_bleed_config(&self) -> bool { + self.query(|a| a.pneumatic.fadec.is_single_vs_dual_bleed_config()) + } + + fn pack_flow_valve_flow(&self, engine_number: usize) -> MassRate { + self.query(|a| { + a.pneumatic.packs[engine_number - 1] + .pack_flow_valve + .fluid_flow() + }) + } + + fn pack_pressure(&self, engine_number: usize) -> Pressure { + self.query(|a| { + a.pneumatic.packs[engine_number - 1] + .pack_container + .pressure() + }) + } + + fn cross_bleed_valve_is_powered_for_automatic_control(&self) -> bool { + self.query(|a| { + a.pneumatic + .cross_bleed_valve + .is_powered_for_automatic_control + }) + } + + fn cross_bleed_valve_is_powered_for_manual_control(&self) -> bool { + self.query(|a| a.pneumatic.cross_bleed_valve.is_powered_for_manual_control) + } + + fn command_pack_flow_selector_position(&mut self, value: u8) { + self.write_by_name("KNOB_OVHD_AIRCOND_PACKFLOW_Position", value); + } + } + + fn test_bed() -> PneumaticTestBed { + PneumaticTestBed::new() + } + + fn test_bed_with() -> PneumaticTestBed { + test_bed() + } + + fn pressure_tolerance() -> Pressure { + Pressure::new::(0.5) + } + + fn flow_rate_tolerance() -> MassRate { + MassRate::new::(0.1) + } + + // Just a way for me to plot some graphs + #[test] + #[ignore] + fn full_graphing_test() { + let alt = Length::new::(0.); + + let mut test_bed = test_bed_with() + .idle_eng1() + .idle_eng2() + .in_isa_atmosphere(alt) + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Auto) + .mach_number(MachNumber(0.)) + .both_packs_auto(); + + let mut time_points = Vec::new(); + let mut high_pressures = Vec::new(); + let mut intermediate_pressures = Vec::new(); + let mut transfer_pressures = Vec::new(); // Transfer pressure (before PRV) + let mut precooler_inlet_pressures = Vec::new(); // Precooler inlet pressure + let mut precooler_outlet_pressures = Vec::new(); // Precooler outlet pressure + let mut precooler_supply_pressures = Vec::new(); // Precooler cooling air pressure + let mut engine_starter_container_pressures = Vec::new(); // Precooler cooling air pressure + let mut intermediate_pressure_compressor_temperatures = Vec::new(); + let mut high_pressure_compressor_temperatures = Vec::new(); // Transfer temperature (before PRV) + let mut transfer_temperatures = Vec::new(); // Precooler inlet temperature + let mut precooler_inlet_temperatures = Vec::new(); // Precooler outlet temperature + let mut precooler_outlet_temperatures = Vec::new(); // Precooler cooling air temperature + let mut precooler_supply_temperatures = Vec::new(); + let mut engine_starter_container_temperatures = Vec::new(); + let mut high_pressure_valve_open_amounts = Vec::new(); + let mut pressure_regulating_valve_open_amounts = Vec::new(); + let mut intermediate_pressure_valve_open_amounts = Vec::new(); + let mut engine_starter_valve_open_amounts = Vec::new(); + let mut apu_bleed_valve_open_amounts = Vec::new(); + let mut fan_air_valve_open_amounts = Vec::new(); + + for i in 1..6000 { + time_points.push(i as f64 * 16.); + + if i == 2000 { + test_bed = test_bed.stop_eng2().stop_eng2(); + } else if i == 4000 { + test_bed = test_bed.start_eng2().start_eng2(); + } + + high_pressures.push(test_bed.hp_pressure(1).get::()); + intermediate_pressures.push(test_bed.ip_pressure(1).get::()); + transfer_pressures.push(test_bed.transfer_pressure(1).get::()); + precooler_inlet_pressures.push(test_bed.precooler_inlet_pressure(1).get::()); + precooler_outlet_pressures.push(test_bed.precooler_outlet_pressure(1).get::()); + precooler_supply_pressures.push(test_bed.precooler_supply_pressure(1).get::()); + engine_starter_container_pressures + .push(test_bed.engine_starter_container_pressure(1).get::()); + + intermediate_pressure_compressor_temperatures + .push(test_bed.ip_temperature(1).get::()); + high_pressure_compressor_temperatures + .push(test_bed.hp_temperature(1).get::()); + transfer_temperatures.push(test_bed.transfer_temperature(1).get::()); + precooler_inlet_temperatures.push( + test_bed + .precooler_inlet_temperature(1) + .get::(), + ); + precooler_outlet_temperatures.push( + test_bed + .precooler_outlet_temperature(1) + .get::(), + ); + precooler_supply_temperatures.push( + test_bed + .precooler_supply_temperature(1) + .get::(), + ); + engine_starter_container_temperatures.push( + test_bed + .engine_starter_container_temperature(1) + .get::(), + ); + + high_pressure_valve_open_amounts.push(test_bed.query(|aircraft| { + aircraft.pneumatic.engine_systems[0] + .high_pressure_valve + .open_amount() + .get::() + })); + + pressure_regulating_valve_open_amounts.push(test_bed.query(|aircraft| { + aircraft.pneumatic.engine_systems[0] + .pressure_regulating_valve + .open_amount() + .get::() + })); + + intermediate_pressure_valve_open_amounts.push(test_bed.query(|aircraft| { + aircraft.pneumatic.engine_systems[0] + .intermediate_pressure_valve + .open_amount() + .get::() + })); + + engine_starter_valve_open_amounts.push(test_bed.query(|aircraft| { + aircraft.pneumatic.engine_systems[0] + .engine_starter_valve + .open_amount() + .get::() + })); + + apu_bleed_valve_open_amounts.push(if test_bed.apu_bleed_valve_is_open() { + 1. + } else { + 0. + }); + + fan_air_valve_open_amounts.push(test_bed.query(|aircraft| { + aircraft.pneumatic.engine_systems[0] + .fan_air_valve + .open_amount() + .get::() + })); + + test_bed.run_with_delta(Duration::from_millis(32)); + } + + assert!(test_bed.hp_valve_is_powered(1)); + assert!(test_bed.pr_valve_is_powered(1)); + assert!(test_bed.fan_air_valve_is_powered(1)); + + // If anyone is wondering, I am using python to plot pressure curves. This will be removed once the model is complete. + let data = vec![ + time_points, + high_pressures, + intermediate_pressures, + transfer_pressures, + precooler_inlet_pressures, + precooler_outlet_pressures, + precooler_supply_pressures, + engine_starter_container_pressures, + high_pressure_compressor_temperatures, + intermediate_pressure_compressor_temperatures, + transfer_temperatures, + precooler_inlet_temperatures, + precooler_outlet_temperatures, + precooler_supply_temperatures, + engine_starter_container_temperatures, + high_pressure_valve_open_amounts, + pressure_regulating_valve_open_amounts, + intermediate_pressure_valve_open_amounts, + engine_starter_valve_open_amounts, + apu_bleed_valve_open_amounts, + fan_air_valve_open_amounts, + ]; + + if fs::create_dir_all("../a320_pneumatic_simulation_graph_data").is_ok() { + let mut file = File::create("../a320_pneumatic_simulation_graph_data/generic_data.txt") + .expect("Could not create file"); + + use std::io::Write; + + writeln!(file, "{:?}", data).expect("Could not write file"); + }; + } + + #[test] + #[ignore] + fn hydraulic_reservoir_pressurization_graphs() { + let alt = Length::new::(0.); + + let mut test_bed = test_bed_with() + .in_isa_atmosphere(alt) + .stop_eng1() + .stop_eng2() + .set_bleed_air_running() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Auto); + + let mut ts = Vec::new(); + let mut green_pressures = Vec::new(); + let mut blue_pressures = Vec::new(); + let mut yellow_pressures = Vec::new(); + + for i in 1..1000 { + ts.push(i as f64 * 16.); + + green_pressures.push(test_bed.green_hydraulic_reservoir_pressure().get::()); + blue_pressures.push(test_bed.blue_hydraulic_reservoir_pressure().get::()); + yellow_pressures.push(test_bed.yellow_hydraulic_reservoir_pressure().get::()); + + test_bed.run_with_delta(Duration::from_millis(16)); + } + + assert!(test_bed.green_hydraulic_reservoir_pressure() > Pressure::new::(35.)); + assert!(test_bed.blue_hydraulic_reservoir_pressure() > Pressure::new::(35.)); + assert!(test_bed.yellow_hydraulic_reservoir_pressure() > Pressure::new::(35.)); + + // If anyone is wondering, I am using python to plot pressure curves. This will be removed once the model is complete. + let data = vec![ts, green_pressures, blue_pressures, yellow_pressures]; + + if fs::create_dir_all("../a320_pneumatic_simulation_graph_data").is_ok() { + let mut file = File::create( + "../a320_pneumatic_simulation_graph_data/hydraulic_reservoir_pressures_data.txt", + ) + .expect("Could not create file"); + + use std::io::Write; + + writeln!(file, "{:?}", data).expect("Could not write file"); + }; + } + + #[test] + #[ignore] + fn pack_pressurization_graphs() { + let alt = Length::new::(0.); + + let mut test_bed = test_bed_with() + .in_isa_atmosphere(alt) + .stop_eng1() + .stop_eng2() + .set_bleed_air_running() + .both_packs_auto() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Open); + + let mut ts = Vec::new(); + let mut left_pressures = Vec::new(); + let mut right_pressures = Vec::new(); + + for i in 1..10000 { + ts.push(i as f64 * 16.); + + left_pressures.push(test_bed.pack_pressure(1).get::()); + right_pressures.push(test_bed.pack_pressure(2).get::()); + + test_bed.run_with_delta(Duration::from_millis(16)); + } + + // If anyone is wondering, I am using python to plot pressure curves. This will be removed once the model is complete. + let data = vec![ts, left_pressures, right_pressures]; + + if fs::create_dir_all("../a320_pneumatic_simulation_graph_data").is_ok() { + let mut file = + File::create("../a320_pneumatic_simulation_graph_data/pack_pressures_data.txt") + .expect("Could not create file"); + + use std::io::Write; + + writeln!(file, "{:?}", data).expect("Could not write file"); + }; + } + + #[test] + fn cold_and_dark_full_state() { + let altitude = Length::new::(0.); + let ambient_pressure = InternationalStandardAtmosphere::pressure_at_altitude(altitude); + + let test_bed = test_bed_with() + .stop_eng1() + .stop_eng2() + .in_isa_atmosphere(altitude) + .mach_number(MachNumber(0.)) + .set_pack_flow_pb_is_auto(1, false) + .set_pack_flow_pb_is_auto(2, false) + .and_stabilize(); + + assert!((test_bed.ip_pressure(1) - ambient_pressure).abs() < pressure_tolerance()); + assert!((test_bed.ip_pressure(2) - ambient_pressure).abs() < pressure_tolerance()); + + assert!((test_bed.hp_pressure(1) - ambient_pressure).abs() < pressure_tolerance()); + assert!((test_bed.hp_pressure(2) - ambient_pressure).abs() < pressure_tolerance()); + + assert!((test_bed.transfer_pressure(1) - ambient_pressure).abs() < pressure_tolerance()); + assert!((test_bed.transfer_pressure(2) - ambient_pressure).abs() < pressure_tolerance()); + + assert!( + (test_bed.precooler_inlet_pressure(1) - ambient_pressure).abs() < pressure_tolerance() + ); + assert!( + (test_bed.precooler_inlet_pressure(2) - ambient_pressure).abs() < pressure_tolerance() + ); + + assert!( + (test_bed.precooler_outlet_pressure(1) - ambient_pressure).abs() < pressure_tolerance() + ); + assert!( + (test_bed.precooler_outlet_pressure(2) - ambient_pressure).abs() < pressure_tolerance() + ); + + assert!(!test_bed.hp_valve_is_open(1)); + assert!(!test_bed.hp_valve_is_open(2)); + + assert!(!test_bed.pr_valve_is_open(1)); + assert!(!test_bed.pr_valve_is_open(2)); + + assert!(!test_bed.cross_bleed_valve_is_open()) + } + + #[test] + fn single_engine_idle_full_state() { + let altitude = Length::new::(0.); + let test_bed = test_bed_with() + .idle_eng1() + .stop_eng2() + .in_isa_atmosphere(altitude) + .mach_number(MachNumber(0.)) + .and_stabilize(); + + let ambient_pressure = InternationalStandardAtmosphere::pressure_at_altitude(altitude); + + assert!(test_bed.ip_pressure(1) - ambient_pressure > pressure_tolerance()); + assert!((test_bed.ip_pressure(2) - ambient_pressure).abs() < pressure_tolerance()); + + assert!(test_bed.hp_pressure(1) - ambient_pressure > pressure_tolerance()); + assert!((test_bed.hp_pressure(2) - ambient_pressure).abs() < pressure_tolerance()); + + assert!(test_bed.transfer_pressure(1) - ambient_pressure > pressure_tolerance()); + assert!((test_bed.transfer_pressure(2) - ambient_pressure).abs() < pressure_tolerance()); + + assert!((test_bed.precooler_inlet_pressure(1) - ambient_pressure) > pressure_tolerance()); + assert!(!test_bed.precooler_inlet_pressure(2).is_nan()); + + assert!((test_bed.precooler_outlet_pressure(1) - ambient_pressure) > pressure_tolerance()); + assert!(!test_bed.precooler_outlet_pressure(2).is_nan()); + + assert!(test_bed.hp_valve_is_open(1)); + assert!(!test_bed.hp_valve_is_open(2)); + + assert!(!test_bed.es_valve_is_open(1)); + assert!(!test_bed.es_valve_is_open(2)); + + assert!(!test_bed.cross_bleed_valve_is_open()); + } + + #[test] + fn two_engine_idle_full_state() { + let altitude = Length::new::(0.); + let test_bed = test_bed_with() + .idle_eng1() + .idle_eng2() + .in_isa_atmosphere(altitude) + .mach_number(MachNumber(0.)) + .and_stabilize(); + + let ambient_pressure = InternationalStandardAtmosphere::pressure_at_altitude(altitude); + + assert!(test_bed.ip_pressure(1) - ambient_pressure > pressure_tolerance()); + assert!(test_bed.ip_pressure(2) - ambient_pressure > pressure_tolerance()); + + assert!(test_bed.hp_pressure(1) - ambient_pressure > pressure_tolerance()); + assert!(test_bed.hp_pressure(2) - ambient_pressure > pressure_tolerance()); + + assert!(test_bed.transfer_pressure(1) - ambient_pressure > pressure_tolerance()); + assert!(test_bed.transfer_pressure(2) - ambient_pressure > pressure_tolerance()); + + assert!((test_bed.precooler_inlet_pressure(1) - ambient_pressure) > pressure_tolerance()); + assert!((test_bed.precooler_inlet_pressure(2) - ambient_pressure) > pressure_tolerance()); + + assert!((test_bed.precooler_outlet_pressure(1) - ambient_pressure) > pressure_tolerance()); + assert!((test_bed.precooler_outlet_pressure(2) - ambient_pressure) > pressure_tolerance()); + + assert!(test_bed.hp_valve_is_open(1)); + assert!(test_bed.hp_valve_is_open(2)); + + assert!(!test_bed.es_valve_is_open(1)); + assert!(!test_bed.es_valve_is_open(2)); + + assert!(!test_bed.cross_bleed_valve_is_open()); + } + + #[test] + fn engine_shutdown() { + let altitude = Length::new::(0.); + let ambient_pressure = InternationalStandardAtmosphere::pressure_at_altitude(altitude); + + let mut test_bed = test_bed_with() + .idle_eng1() + .idle_eng2() + .in_isa_atmosphere(altitude) + .mach_number(MachNumber(0.)) + .and_stabilize(); + + assert!(test_bed.precooler_outlet_pressure(1) - ambient_pressure > pressure_tolerance()); + assert!(test_bed.precooler_outlet_pressure(2) - ambient_pressure > pressure_tolerance()); + + test_bed = test_bed.set_bleed_air_running(); + + assert!(!test_bed.precooler_outlet_pressure(1).get::().is_nan()); + assert!(!test_bed.precooler_outlet_pressure(2).get::().is_nan()); + } + + #[test] + fn starter_valve_opens_on_engine_start() { + let mut test_bed = test_bed_with().stop_eng1().stop_eng2().and_run(); + + assert!(!test_bed.es_valve_is_open(1)); + assert!(!test_bed.es_valve_is_open(2)); + + test_bed = test_bed.start_eng1().start_eng2().and_run(); + + assert!(test_bed.es_valve_is_open(1)); + assert!(test_bed.es_valve_is_open(2)); + } + + #[test] + fn cross_bleed_valve_opens_when_apu_bleed_valve_opens() { + let mut test_bed = test_bed_with() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Auto) + .and_run(); + + assert!(!test_bed.cross_bleed_valve_is_open()); + + test_bed = test_bed + .set_apu_bleed_valve_signal(ApuBleedAirValveSignal::new_open()) + .and_run(); + + assert!(test_bed.cross_bleed_valve_is_open()); + } + + #[test] + fn cross_bleed_valve_closes_when_apu_bleed_valve_closes() { + let mut test_bed = test_bed_with() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Auto) + .set_apu_bleed_valve_signal(ApuBleedAirValveSignal::new_open()) + .and_run(); + + assert!(test_bed.cross_bleed_valve_is_open()); + + test_bed = test_bed + .set_apu_bleed_valve_signal(ApuBleedAirValveSignal::new_closed()) + .and_run(); + + assert!(!test_bed.cross_bleed_valve_is_open()); + } + + #[test] + fn cross_bleed_valve_manual_overrides_everything() { + let mut test_bed = test_bed_with() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Shut) + .set_apu_bleed_valve_signal(ApuBleedAirValveSignal::new_open()) + .and_run(); + + assert!(!test_bed.cross_bleed_valve_is_open()); + + test_bed = test_bed + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Open) + .set_apu_bleed_valve_signal(ApuBleedAirValveSignal::new_closed()) + .and_run(); + + assert!(test_bed.cross_bleed_valve_is_open()); + } + + #[test] + fn vars_initialized_properly() { + let test_bed = test_bed() + .in_isa_atmosphere(Length::new::(0.)) + .and_run(); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_IP_PRESSURE")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_IP_PRESSURE")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_HP_PRESSURE")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_HP_PRESSURE")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_TRANSFER_PRESSURE")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_TRANSFER_PRESSURE")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_PRECOOLER_INLET_PRESSURE")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_PRECOOLER_INLET_PRESSURE")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_PRECOOLER_OUTLET_PRESSURE")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_PRECOOLER_OUTLET_PRESSURE")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_STARTER_CONTAINER_PRESSURE")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_STARTER_CONTAINER_PRESSURE")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_IP_TEMPERATURE")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_IP_TEMPERATURE")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_HP_TEMPERATURE")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_HP_TEMPERATURE")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_TRANSFER_TEMPERATURE")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_TRANSFER_TEMPERATURE")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_PRECOOLER_INLET_TEMPERATURE")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_PRECOOLER_INLET_TEMPERATURE")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_PRECOOLER_OUTLET_TEMPERATURE")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_PRECOOLER_OUTLET_TEMPERATURE")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_STARTER_CONTAINER_TEMPERATURE")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_STARTER_CONTAINER_TEMPERATURE")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_IP_PRESSURE")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_IP_PRESSURE")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_IP_VALVE_OPEN")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_IP_VALVE_OPEN")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_HP_VALVE_OPEN")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_HP_VALVE_OPEN")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_PR_VALVE_OPEN")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_PR_VALVE_OPEN")); + + assert!(test_bed.contains_variable_with_name("PNEU_ENG_1_STARTER_VALVE_OPEN")); + assert!(test_bed.contains_variable_with_name("PNEU_ENG_2_STARTER_VALVE_OPEN")); + + assert!(test_bed.contains_variable_with_name("PNEU_PACK_1_FLOW_VALVE_FLOW_RATE")); + assert!(test_bed.contains_variable_with_name("PNEU_PACK_2_FLOW_VALVE_FLOW_RATE")); + + assert!(test_bed.contains_variable_with_name("OVHD_PNEU_ENG_1_BLEED_PB_HAS_FAULT")); + assert!(test_bed.contains_variable_with_name("OVHD_PNEU_ENG_2_BLEED_PB_HAS_FAULT")); + + assert!(test_bed.contains_variable_with_name("OVHD_PNEU_ENG_1_BLEED_PB_IS_AUTO")); + assert!(test_bed.contains_variable_with_name("OVHD_PNEU_ENG_2_BLEED_PB_IS_AUTO")); + + assert!(test_bed.contains_variable_with_name("PNEU_XBLEED_VALVE_OPEN")); + } + + #[test] + fn pressure_regulating_valve_closes_with_ovhd_engine_bleed_off() { + let mut test_bed = test_bed().idle_eng1().idle_eng2().and_run(); + + // We have to run two update ticks for the pressure to propagate through the system. + test_bed.run_with_delta(Duration::from_secs(5)); + + assert!(test_bed.pr_valve_is_open(1)); + assert!(test_bed.pr_valve_is_open(2)); + + test_bed = test_bed.set_engine_bleed_push_button_off(1).and_run(); + + assert!(!test_bed.pr_valve_is_open(1)); + assert!(test_bed.pr_valve_is_open(2)); + + test_bed = test_bed.set_engine_bleed_push_button_off(2).and_run(); + + assert!(!test_bed.pr_valve_is_open(1)); + assert!(!test_bed.pr_valve_is_open(2)); + } + + #[test] + fn pressure_regulating_valve_closes_with_ovhd_engine_fire_pushbutton_released() { + let mut test_bed = test_bed().idle_eng1().idle_eng2().and_run(); + + test_bed.run_with_delta(Duration::from_secs(5)); + + assert!(test_bed.pr_valve_is_open(1)); + assert!(test_bed.pr_valve_is_open(2)); + + test_bed = test_bed.release_fire_pushbutton(1).and_run(); + + assert!(!test_bed.pr_valve_is_open(1)); + assert!(test_bed.pr_valve_is_open(2)); + + test_bed = test_bed.release_fire_pushbutton(2).and_run(); + + assert!(!test_bed.pr_valve_is_open(1)); + assert!(!test_bed.pr_valve_is_open(2)); + } + + #[test] + fn pressure_regulating_valve_1_closes_with_apu_bleed_on() { + let mut test_bed = test_bed_with().idle_eng1().idle_eng2().and_run(); + + test_bed.run_with_delta(Duration::from_secs(5)); + + assert!(test_bed.pr_valve_is_open(1)); + + test_bed = test_bed + .set_apu_bleed_air_pb(true) + .set_apu_bleed_valve_signal(ApuBleedAirValveSignal::new_open()) + .and_run(); + + assert!(!test_bed.pr_valve_is_open(1)); + } + + #[test] + fn pressure_regulating_valve_2_closes_with_apu_bleed_on_and_cross_bleed_open() { + let mut test_bed = test_bed_with().idle_eng1().idle_eng2().and_run(); + + test_bed.run_with_delta(Duration::from_secs(5)); + + assert!(test_bed.pr_valve_is_open(2)); + + test_bed = test_bed + .set_apu_bleed_air_pb(true) + .set_apu_bleed_valve_signal(ApuBleedAirValveSignal::new_open()) + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Open) + .and_run(); + + assert!(!test_bed.pr_valve_is_open(2)); + } + + #[test] + fn fadec_represents_engine_state() { + let mut test_bed = test_bed_with() + .set_engine_state(1, EngineState::Off) + .set_engine_state(2, EngineState::Off); + + assert_eq!(test_bed.engine_state(1), EngineState::Off); + assert_eq!(test_bed.engine_state(2), EngineState::Off); + + test_bed = test_bed + .set_engine_state(1, EngineState::Starting) + .and_run(); + + assert_eq!(test_bed.engine_state(1), EngineState::Starting); + assert_eq!(test_bed.engine_state(2), EngineState::Off); + + test_bed = test_bed + .set_engine_state(2, EngineState::Starting) + .and_run(); + + assert_eq!(test_bed.engine_state(1), EngineState::Starting); + assert_eq!(test_bed.engine_state(2), EngineState::Starting); + + test_bed = test_bed.set_engine_state(1, EngineState::On).and_run(); + + assert_eq!(test_bed.engine_state(1), EngineState::On); + assert_eq!(test_bed.engine_state(2), EngineState::Starting); + + test_bed = test_bed.set_engine_state(2, EngineState::On).and_run(); + + assert_eq!(test_bed.engine_state(1), EngineState::On); + assert_eq!(test_bed.engine_state(2), EngineState::On); + + test_bed = test_bed + .set_engine_state(1, EngineState::Shutting) + .and_run(); + + assert_eq!(test_bed.engine_state(1), EngineState::Shutting); + assert_eq!(test_bed.engine_state(2), EngineState::On); + + test_bed = test_bed + .set_engine_state(2, EngineState::Shutting) + .and_run(); + + assert_eq!(test_bed.engine_state(1), EngineState::Shutting); + assert_eq!(test_bed.engine_state(2), EngineState::Shutting); + } + + #[test] + fn apu_bleed_provides_at_least_35_psi_with_open_cross_bleed_valve() { + let test_bed = test_bed_with() + .stop_eng1() + .stop_eng2() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Auto) + .set_bleed_air_running() + .set_pack_flow_pb_is_auto(1, false) + .set_pack_flow_pb_is_auto(2, false) + .and_stabilize(); + + assert!(test_bed.cross_bleed_valve_is_open()); + + assert!(!test_bed.pr_valve_is_open(1)); + assert!(!test_bed.pr_valve_is_open(2)); + + assert!(test_bed.precooler_outlet_pressure(1) > Pressure::new::(35.)); + assert!(test_bed.precooler_outlet_pressure(2) > Pressure::new::(35.)); + } + + #[test] + fn hydraulic_reservoirs_get_pressurized() { + let test_bed = test_bed_with() + .stop_eng1() + .stop_eng2() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Auto) + .set_bleed_air_running() + .and_stabilize(); + + assert!(test_bed.green_hydraulic_reservoir_pressure() > Pressure::new::(35.)); + assert!(test_bed.blue_hydraulic_reservoir_pressure() > Pressure::new::(35.)); + assert!(test_bed.yellow_hydraulic_reservoir_pressure() > Pressure::new::(35.)); + } + + #[test] + fn hydraulic_reservoirs_is_pressurized_by_left_system() { + let mut test_bed = test_bed_with() + .idle_eng1() + .stop_eng2() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Shut); + test_bed.fail(FailureType::ReservoirAirLeak(HydraulicColor::Green)); + test_bed.fail(FailureType::ReservoirAirLeak(HydraulicColor::Blue)); + test_bed.fail(FailureType::ReservoirAirLeak(HydraulicColor::Yellow)); + test_bed + .test_bed + .run_multiple_frames(Duration::from_secs(16)); + + test_bed.unfail(FailureType::ReservoirAirLeak(HydraulicColor::Green)); + test_bed.unfail(FailureType::ReservoirAirLeak(HydraulicColor::Blue)); + test_bed.unfail(FailureType::ReservoirAirLeak(HydraulicColor::Yellow)); + test_bed + .test_bed + .run_multiple_frames(Duration::from_secs(16)); + + assert!(test_bed.green_hydraulic_reservoir_pressure() > Pressure::new::(40.)); + assert!(test_bed.blue_hydraulic_reservoir_pressure() > Pressure::new::(40.)); + assert!(test_bed.yellow_hydraulic_reservoir_pressure() > Pressure::new::(40.)); + } + + #[test] + fn hydraulic_reservoirs_is_pressurized_by_right_system() { + let mut test_bed = test_bed_with() + .stop_eng1() + .idle_eng2() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Shut); + test_bed.fail(FailureType::ReservoirAirLeak(HydraulicColor::Green)); + test_bed.fail(FailureType::ReservoirAirLeak(HydraulicColor::Blue)); + test_bed.fail(FailureType::ReservoirAirLeak(HydraulicColor::Yellow)); + test_bed + .test_bed + .run_multiple_frames(Duration::from_secs(16)); + + test_bed.unfail(FailureType::ReservoirAirLeak(HydraulicColor::Green)); + test_bed.unfail(FailureType::ReservoirAirLeak(HydraulicColor::Blue)); + test_bed.unfail(FailureType::ReservoirAirLeak(HydraulicColor::Yellow)); + test_bed + .test_bed + .run_multiple_frames(Duration::from_secs(16)); + + assert!(test_bed.green_hydraulic_reservoir_pressure() > Pressure::new::(40.)); + assert!(test_bed.blue_hydraulic_reservoir_pressure() > Pressure::new::(40.)); + assert!(test_bed.yellow_hydraulic_reservoir_pressure() > Pressure::new::(40.)); + } + + #[test] + fn apu_bleed_provides_at_least_35_psi_to_left_system_with_closed_cross_bleed_valve() { + let test_bed = test_bed_with() + .stop_eng1() + .stop_eng2() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Shut) + .set_bleed_air_running() + .set_pack_flow_pb_is_auto(1, false) + .set_pack_flow_pb_is_auto(2, false) + .and_stabilize(); + + assert!(!test_bed.pr_valve_is_open(1)); + assert!(!test_bed.cross_bleed_valve_is_open()); + + assert!(test_bed.precooler_outlet_pressure(1) > Pressure::new::(35.)); + assert!(!test_bed.precooler_outlet_pressure(2).is_nan()); + } + + #[test] + fn bleed_monitoring_computers_powered_by_correct_buses() { + let mut test_bed = test_bed() + .set_dc_ess_shed_bus_power(false) + .set_dc_2_bus_power(false) + .and_run(); + + test_bed.run(); + + assert!(!test_bed.bmc_is_powered(1)); + assert!(!test_bed.bmc_is_powered(2)); + + test_bed = test_bed.set_dc_ess_shed_bus_power(true).and_run(); + + assert!(test_bed.bmc_is_powered(1)); + assert!(!test_bed.bmc_is_powered(2)); + + test_bed = test_bed.set_dc_2_bus_power(true).and_run(); + + assert!(test_bed.bmc_is_powered(1)); + assert!(test_bed.bmc_is_powered(2)); + } + + #[test] + fn bleed_monitoring_computers_initialize_in_correct_configuration() { + let test_bed = test_bed() + .set_dc_2_bus_power(true) + .set_dc_ess_shed_bus_power(true) + .and_run(); + + assert_eq!( + test_bed.bmc_operation_mode_for_engine(1, 1), + BleedMonitoringComputerChannelOperationMode::Master + ); + assert_eq!( + test_bed.bmc_operation_mode_for_engine(2, 2), + BleedMonitoringComputerChannelOperationMode::Master + ); + + assert_eq!( + test_bed.bmc_operation_mode_for_engine(1, 2), + BleedMonitoringComputerChannelOperationMode::Slave + ); + assert_eq!( + test_bed.bmc_operation_mode_for_engine(2, 1), + BleedMonitoringComputerChannelOperationMode::Slave + ); + } + + #[test] + fn bleed_monitoring_computer_one_takes_over_for_dc_2_failure() { + let test_bed = test_bed() + .set_dc_2_bus_power(false) + .set_dc_ess_shed_bus_power(true) + .and_run(); + + assert!(test_bed.bmc_is_powered(1)); + assert!(!test_bed.bmc_is_powered(2)); + + assert_eq!( + test_bed.bmc_operation_mode_for_engine(1, 1), + BleedMonitoringComputerChannelOperationMode::Master + ); + assert_eq!( + test_bed.bmc_operation_mode_for_engine(1, 2), + BleedMonitoringComputerChannelOperationMode::Master + ); + + assert_eq!( + test_bed.bmc_operation_mode_for_engine(2, 1), + BleedMonitoringComputerChannelOperationMode::Slave + ); + assert_eq!( + test_bed.bmc_operation_mode_for_engine(2, 2), + BleedMonitoringComputerChannelOperationMode::Slave + ); + } + + #[test] + fn bleed_monitoring_computer_two_takes_over_for_dc_ess_shed_failure() { + let test_bed = test_bed() + .set_dc_2_bus_power(true) + .set_dc_ess_shed_bus_power(false) + .and_run(); + + assert!(!test_bed.bmc_is_powered(1)); + assert!(test_bed.bmc_is_powered(2)); + + assert_eq!( + test_bed.bmc_operation_mode_for_engine(2, 1), + BleedMonitoringComputerChannelOperationMode::Master + ); + assert_eq!( + test_bed.bmc_operation_mode_for_engine(2, 2), + BleedMonitoringComputerChannelOperationMode::Master + ); + + assert_eq!( + test_bed.bmc_operation_mode_for_engine(1, 1), + BleedMonitoringComputerChannelOperationMode::Slave + ); + assert_eq!( + test_bed.bmc_operation_mode_for_engine(1, 2), + BleedMonitoringComputerChannelOperationMode::Slave + ); + } + + #[test] + fn valves_powered_by_correct_busses() { + let mut test_bed = test_bed() + .set_dc_ess_shed_bus_power(true) + .set_dc_2_bus_power(true) + .and_run(); + + assert!(test_bed.hp_valve_is_powered(1)); + assert!(test_bed.hp_valve_is_powered(2)); + + assert!(test_bed.pr_valve_is_powered(1)); + assert!(test_bed.pr_valve_is_powered(2)); + + test_bed = test_bed.set_dc_ess_shed_bus_power(false).and_run(); + + assert!(!test_bed.hp_valve_is_powered(1)); + assert!(test_bed.hp_valve_is_powered(2)); + + assert!(!test_bed.pr_valve_is_powered(1)); + assert!(test_bed.pr_valve_is_powered(2)); + + test_bed = test_bed.set_dc_2_bus_power(false).and_run(); + + assert!(!test_bed.hp_valve_is_powered(1)); + assert!(!test_bed.hp_valve_is_powered(2)); + + assert!(!test_bed.pr_valve_is_powered(1)); + assert!(!test_bed.pr_valve_is_powered(2)); + } + + #[test] + fn fadec_detects_single_vs_dual_bleed_config() { + let mut test_bed = test_bed_with().stop_eng1().stop_eng2().and_run(); + + assert!(!test_bed.fadec_single_vs_dual_bleed_config()); + + test_bed = test_bed.idle_eng1().stop_eng2().and_run(); + assert!(test_bed.fadec_single_vs_dual_bleed_config()); + + test_bed = test_bed.stop_eng1().idle_eng2().and_run(); + assert!(test_bed.fadec_single_vs_dual_bleed_config()); + + test_bed = test_bed.idle_eng1().idle_eng2().and_run(); + assert!(!test_bed.fadec_single_vs_dual_bleed_config()); + } + + #[test] + fn hydraulic_reservoirs_maintain_pressure_after_bleed_pressure_loss() { + let mut test_bed = test_bed_with() + .toga_eng1() + .toga_eng2() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Shut) + .mach_number(MachNumber(0.)) + .both_packs_auto() + .and_stabilize(); + + let pressure_before_green = test_bed.green_hydraulic_reservoir_pressure(); + let pressure_before_blue = test_bed.blue_hydraulic_reservoir_pressure(); + let pressure_before_yellow = test_bed.yellow_hydraulic_reservoir_pressure(); + + test_bed = test_bed.idle_eng1().and_stabilize(); + + assert!(test_bed.green_hydraulic_reservoir_pressure() >= pressure_before_green); + assert!(test_bed.blue_hydraulic_reservoir_pressure() >= pressure_before_blue); + assert!(test_bed.yellow_hydraulic_reservoir_pressure() >= pressure_before_yellow); + } + + #[test] + fn cross_bleed_valve_is_powered_by_two_electrical_busses() { + let mut test_bed = test_bed_with() + .stop_eng1() + .stop_eng2() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Auto) + .mach_number(MachNumber(0.)) + .both_packs_auto() + .set_dc_2_bus_power(true) + .set_dc_ess_shed_bus_power(true) + .and_run(); + + assert!(test_bed.cross_bleed_valve_is_powered_for_automatic_control()); + assert!(test_bed.cross_bleed_valve_is_powered_for_manual_control()); + assert!(!test_bed.cross_bleed_valve_is_open()); + + test_bed = test_bed.set_dc_2_bus_power(false).and_run(); + assert!(!test_bed.cross_bleed_valve_is_powered_for_automatic_control()); + + test_bed = test_bed + .set_dc_2_bus_power(true) + .set_dc_ess_shed_bus_power(false) + .and_run(); + assert!(test_bed.cross_bleed_valve_is_powered_for_automatic_control()); + assert!(!test_bed.cross_bleed_valve_is_powered_for_manual_control()); + } + + #[test] + fn cross_bleed_valve_does_not_accept_manual_signal_when_bus_unpowered() { + let mut test_bed = test_bed_with() + .stop_eng1() + .stop_eng2() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Shut) + .mach_number(MachNumber(0.)) + .both_packs_auto() + .set_dc_ess_shed_bus_power(false) + .and_run(); + + assert!(test_bed.cross_bleed_valve_is_powered_for_automatic_control()); + assert!(!test_bed.cross_bleed_valve_is_powered_for_manual_control()); + assert!(!test_bed.cross_bleed_valve_is_open()); + + test_bed = test_bed + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Open) + .and_run(); + + assert!(!test_bed.cross_bleed_valve_is_open()); + } + + #[test] + fn large_time_step_stability() { + let mut test_bed = test_bed_with() + .idle_eng1() + .idle_eng2() + .mach_number(MachNumber(0.)) + .both_packs_auto() + .and_stabilize(); + + // Introduce perturbation + test_bed = test_bed.toga_eng1().toga_eng2(); + + test_bed.run_with_delta(Duration::from_millis(1000)); + + assert!(!test_bed.precooler_inlet_pressure(1).is_nan()); + assert!(!test_bed.precooler_inlet_pressure(2).is_nan()); + } + + mod overhead { + use super::*; + + #[test] + fn ovhd_engine_bleed_push_buttons() { + let mut test_bed = test_bed().and_run(); + + assert!(test_bed.engine_bleed_push_button_is_auto(1)); + assert!(test_bed.engine_bleed_push_button_is_auto(2)); + + test_bed = test_bed + .set_engine_bleed_push_button_off(1) + .set_engine_bleed_push_button_off(2) + .and_run(); + + assert!(!test_bed.engine_bleed_push_button_is_auto(1)); + assert!(!test_bed.engine_bleed_push_button_is_auto(2)); + } + + #[test] + fn ovhd_cross_bleed_valve_mode_selector() { + let mut test_bed = test_bed().and_run(); + + assert_eq!( + test_bed.cross_bleed_valve_selector(), + CrossBleedValveSelectorMode::Auto + ); + + test_bed = test_bed + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Open) + .and_run(); + + assert_eq!( + test_bed.cross_bleed_valve_selector(), + CrossBleedValveSelectorMode::Open + ); + + test_bed = test_bed + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Shut) + .and_run(); + + assert_eq!( + test_bed.cross_bleed_valve_selector(), + CrossBleedValveSelectorMode::Shut + ); + } + } + + mod pack_flow_valve_tests { + use super::*; + + #[test] + fn pack_flow_valve_starts_closed() { + let test_bed = test_bed(); + + assert!(!test_bed.pack_flow_valve_is_open(1)); + assert!(!test_bed.pack_flow_valve_is_open(2)); + } + + #[test] + fn pack_flow_valve_opens_when_conditions_met() { + let test_bed = test_bed_with() + .idle_eng1() + .idle_eng2() + .set_pack_flow_pb_is_auto(1, true) + .set_pack_flow_pb_is_auto(2, true) + .and_stabilize(); + + assert!(test_bed.pack_flow_valve_is_open(1)); + assert!(test_bed.pack_flow_valve_is_open(2)); + } + + #[test] + fn pack_flow_valve_closes_with_pack_pb_off() { + let mut test_bed = test_bed_with() + .idle_eng1() + .idle_eng2() + .set_pack_flow_pb_is_auto(1, true) + .set_pack_flow_pb_is_auto(2, false) + .and_stabilize(); + + assert!(test_bed.pack_flow_valve_is_open(1)); + assert!(!test_bed.pack_flow_valve_is_open(2)); + + test_bed = test_bed + .set_pack_flow_pb_is_auto(1, false) + .and_run() + .and_run(); + + assert!(!test_bed.pack_flow_valve_is_open(1)); + assert!(!test_bed.pack_flow_valve_is_open(2)); + } + + #[test] + fn pack_flow_drops_when_valve_is_closed() { + let mut test_bed = test_bed_with() + .idle_eng1() + .idle_eng2() + .cross_bleed_valve_selector_knob(CrossBleedValveSelectorMode::Shut) + .mach_number(MachNumber(0.)) + .both_packs_auto() + .and_stabilize(); + + assert!(test_bed.pack_flow_valve_flow(1) > flow_rate_tolerance()); + assert!(test_bed.pack_flow_valve_flow(2) > flow_rate_tolerance()); + + test_bed = test_bed + .set_pack_flow_pb_is_auto(1, false) + .set_pack_flow_pb_is_auto(2, false) + .and_run() + .and_run(); + + assert!(test_bed.pack_flow_valve_flow(1) < flow_rate_tolerance()); + assert!(test_bed.pack_flow_valve_flow(2) < flow_rate_tolerance()); + } + } +} diff --git a/src/systems/a380_systems/src/power_consumption.rs b/src/systems/a380_systems/src/power_consumption.rs new file mode 100644 index 000000000000..a36d28ff5f3d --- /dev/null +++ b/src/systems/a380_systems/src/power_consumption.rs @@ -0,0 +1,347 @@ +use systems::simulation::InitContext; +use systems::{ + electrical::consumption::{FlightPhasePowerConsumer, PowerConsumerFlightPhase}, + shared::ElectricalBusType, + simulation::{SimulationElement, SimulationElementVisitor, UpdateContext}, +}; +use uom::si::{f64::*, power::watt}; + +/// This type provides an aggregated form of power consumption. +/// We haven't yet implemented all power consumers and thus need something to +/// consume power, as otherwise electrical load is nearly 0. +pub(super) struct A380PowerConsumption { + ac_bus_1_consumer: FlightPhasePowerConsumer, + ac_bus_2_consumer: FlightPhasePowerConsumer, + ac_ess_bus_consumer: FlightPhasePowerConsumer, + ac_ess_shed_bus_consumer: FlightPhasePowerConsumer, + ac_stat_inv_bus_consumer: FlightPhasePowerConsumer, + ac_gnd_flt_service_consumer: FlightPhasePowerConsumer, + dc_bus_1_consumer: FlightPhasePowerConsumer, + dc_bus_2_consumer: FlightPhasePowerConsumer, + dc_ess_bus_consumer: FlightPhasePowerConsumer, + dc_ess_shed_bus_consumer: FlightPhasePowerConsumer, + dc_bat_bus_consumer: FlightPhasePowerConsumer, + dc_hot_bus_1_consumer: FlightPhasePowerConsumer, + dc_hot_bus_2_consumer: FlightPhasePowerConsumer, + dc_gnd_flt_service_consumer: FlightPhasePowerConsumer, +} +impl A380PowerConsumption { + pub fn new(context: &mut InitContext) -> Self { + // The watts in this function are all provided by komp. + Self { + ac_bus_1_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::AlternatingCurrent(1), + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(26816.3), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(30350.1), + ), + ( + PowerConsumerFlightPhase::Takeoff, + Power::new::(33797.3), + ), + ( + PowerConsumerFlightPhase::Flight, + Power::new::(39032.5), + ), + ( + PowerConsumerFlightPhase::Landing, + Power::new::(30733.3), + ), + ( + PowerConsumerFlightPhase::TaxiIn, + Power::new::(30243.1), + ), + ]), + ac_bus_2_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::AlternatingCurrent(2), + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(26960.2), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(21735.8), + ), + ( + PowerConsumerFlightPhase::Takeoff, + Power::new::(25183.), + ), + ( + PowerConsumerFlightPhase::Flight, + Power::new::(29777.4), + ), + ( + PowerConsumerFlightPhase::Landing, + Power::new::(22119.), + ), + ( + PowerConsumerFlightPhase::TaxiIn, + Power::new::(24475.8), + ), + ]), + ac_ess_bus_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::AlternatingCurrentEssential, + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(455.7), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(715.7), + ), + (PowerConsumerFlightPhase::Takeoff, Power::new::(875.7)), + (PowerConsumerFlightPhase::Flight, Power::new::(875.7)), + (PowerConsumerFlightPhase::Landing, Power::new::(715.7)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(715.7)), + ]), + ac_ess_shed_bus_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::AlternatingCurrentEssentialShed, + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(560.5), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(823.5), + ), + (PowerConsumerFlightPhase::Takeoff, Power::new::(823.5)), + (PowerConsumerFlightPhase::Flight, Power::new::(823.5)), + (PowerConsumerFlightPhase::Landing, Power::new::(823.5)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(823.5)), + ]), + ac_stat_inv_bus_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::AlternatingCurrentStaticInverter, + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(135.), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(135.), + ), + (PowerConsumerFlightPhase::Takeoff, Power::new::(135.)), + (PowerConsumerFlightPhase::Flight, Power::new::(135.)), + (PowerConsumerFlightPhase::Landing, Power::new::(135.)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(135.)), + ]), + ac_gnd_flt_service_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::AlternatingCurrentGndFltService, + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(4718.), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(3663.), + ), + (PowerConsumerFlightPhase::Takeoff, Power::new::(2628.)), + (PowerConsumerFlightPhase::Flight, Power::new::(2628.)), + (PowerConsumerFlightPhase::Landing, Power::new::(2628.)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(3663.)), + ]), + dc_bus_1_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::DirectCurrent(1), + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(252.), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(308.), + ), + (PowerConsumerFlightPhase::Takeoff, Power::new::(364.)), + (PowerConsumerFlightPhase::Flight, Power::new::(280.)), + (PowerConsumerFlightPhase::Landing, Power::new::(364.)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(336.)), + ]), + dc_bus_2_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::DirectCurrent(2), + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(532.), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(448.), + ), + (PowerConsumerFlightPhase::Takeoff, Power::new::(392.)), + (PowerConsumerFlightPhase::Flight, Power::new::(392.)), + (PowerConsumerFlightPhase::Landing, Power::new::(392.)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(448.)), + ]), + dc_ess_bus_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::DirectCurrentEssential, + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(168.), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(140.), + ), + (PowerConsumerFlightPhase::Takeoff, Power::new::(168.)), + (PowerConsumerFlightPhase::Flight, Power::new::(140.)), + (PowerConsumerFlightPhase::Landing, Power::new::(168.)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(140.)), + ]), + dc_ess_shed_bus_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::DirectCurrentEssentialShed, + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(224.), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(168.), + ), + (PowerConsumerFlightPhase::Takeoff, Power::new::(196.)), + (PowerConsumerFlightPhase::Flight, Power::new::(196.)), + (PowerConsumerFlightPhase::Landing, Power::new::(196.)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(168.)), + ]), + dc_bat_bus_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::DirectCurrentBattery, + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(0.), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(28.), + ), + (PowerConsumerFlightPhase::Takeoff, Power::new::(28.)), + (PowerConsumerFlightPhase::Flight, Power::new::(28.)), + (PowerConsumerFlightPhase::Landing, Power::new::(28.)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(28.)), + ]), + dc_hot_bus_1_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::DirectCurrentHot(1), + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(108.), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(11.), + ), + (PowerConsumerFlightPhase::Takeoff, Power::new::(15.3)), + (PowerConsumerFlightPhase::Flight, Power::new::(15.3)), + (PowerConsumerFlightPhase::Landing, Power::new::(15.3)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(11.)), + ]), + dc_hot_bus_2_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::DirectCurrentHot(2), + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(24.3), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(24.3), + ), + (PowerConsumerFlightPhase::Takeoff, Power::new::(24.3)), + (PowerConsumerFlightPhase::Flight, Power::new::(24.3)), + (PowerConsumerFlightPhase::Landing, Power::new::(24.3)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(24.3)), + ]), + dc_gnd_flt_service_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::DirectCurrentGndFltService, + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(168.), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(84.), + ), + (PowerConsumerFlightPhase::Takeoff, Power::new::(84.)), + (PowerConsumerFlightPhase::Flight, Power::new::(84.)), + (PowerConsumerFlightPhase::Landing, Power::new::(112.)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(84.)), + ]), + } + } + + pub fn update(&mut self, context: &UpdateContext) { + self.ac_bus_1_consumer.update(context); + self.ac_bus_2_consumer.update(context); + self.ac_ess_bus_consumer.update(context); + self.ac_ess_shed_bus_consumer.update(context); + self.ac_stat_inv_bus_consumer.update(context); + self.ac_gnd_flt_service_consumer.update(context); + self.dc_bus_1_consumer.update(context); + self.dc_bus_2_consumer.update(context); + self.dc_ess_bus_consumer.update(context); + self.dc_ess_shed_bus_consumer.update(context); + self.dc_bat_bus_consumer.update(context); + self.dc_hot_bus_1_consumer.update(context); + self.dc_hot_bus_2_consumer.update(context); + self.dc_gnd_flt_service_consumer.update(context); + } +} +impl SimulationElement for A380PowerConsumption { + fn accept(&mut self, visitor: &mut T) { + self.ac_bus_1_consumer.accept(visitor); + self.ac_bus_2_consumer.accept(visitor); + self.ac_ess_bus_consumer.accept(visitor); + self.ac_ess_shed_bus_consumer.accept(visitor); + self.ac_stat_inv_bus_consumer.accept(visitor); + self.ac_gnd_flt_service_consumer.accept(visitor); + self.dc_bus_1_consumer.accept(visitor); + self.dc_bus_2_consumer.accept(visitor); + self.dc_ess_bus_consumer.accept(visitor); + self.dc_ess_shed_bus_consumer.accept(visitor); + self.dc_bat_bus_consumer.accept(visitor); + self.dc_hot_bus_1_consumer.accept(visitor); + self.dc_hot_bus_2_consumer.accept(visitor); + self.dc_gnd_flt_service_consumer.accept(visitor); + + visitor.visit(self); + } +} diff --git a/src/systems/a380_systems_wasm/Cargo.toml b/src/systems/a380_systems_wasm/Cargo.toml new file mode 100644 index 000000000000..671c44a7402e --- /dev/null +++ b/src/systems/a380_systems_wasm/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "a380_systems_wasm" +version = "0.1.0" +authors = ["FlyByWire Simulations"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] +test = false + +[dependencies] +uom = "0.33.0" +a380_systems = { path = "../a380_systems" } +systems = { path = "../systems" } +systems_wasm = { path = "../systems_wasm" } +msfs = { git = "https://github.com/flybywiresim/msfs-rs", branch = "main" } diff --git a/src/systems/a380_systems_wasm/src/ailerons.rs b/src/systems/a380_systems_wasm/src/ailerons.rs new file mode 100644 index 000000000000..96cb3c8ca9fb --- /dev/null +++ b/src/systems/a380_systems_wasm/src/ailerons.rs @@ -0,0 +1,96 @@ +use std::error::Error; + +use systems::shared::to_bool; +use systems_wasm::aspects::{ExecuteOn, MsfsAspectBuilder, ObjectWrite, VariablesToObject}; +use systems_wasm::{set_data_on_sim_object, Variable}; + +use msfs::sim_connect; +use msfs::{sim_connect::SimConnect, sim_connect::SIMCONNECT_OBJECT_ID_USER}; + +pub(super) fn ailerons(builder: &mut MsfsAspectBuilder) -> Result<(), Box> { + // Inputs from FBW becomes the aileron position demand for hydraulic system + // MSFS uses [-1;1] ranges, and Left aileron UP is -1 while right aileron UP is 1 + // Systems use [0;1], 1 is UP + builder.map( + ExecuteOn::PreTick, + Variable::named("AILERON_LEFT_DEFLECTION_DEMAND"), + |value| (-value + 1.) / 2., + Variable::aspect("HYD_AILERON_LEFT_DEMAND"), + ); + builder.map( + ExecuteOn::PreTick, + Variable::named("AILERON_RIGHT_DEFLECTION_DEMAND"), + |value| (value + 1.) / 2., + Variable::aspect("HYD_AILERON_RIGHT_DEMAND"), + ); + + // Aileron positions returned by hydraulic system are converted to MSFS format + // It means we just invert left side direction and do [0;1] -> [-1;1] + builder.map( + ExecuteOn::PostTick, + Variable::aspect("HYD_AIL_LEFT_DEFLECTION"), + |value| -1. * (value * 2. - 1.), + Variable::named("HYD_AILERON_LEFT_DEFLECTION"), + ); + builder.map( + ExecuteOn::PostTick, + Variable::aspect("HYD_AIL_RIGHT_DEFLECTION"), + |value| (value * 2. - 1.), + Variable::named("HYD_AILERON_RIGHT_DEFLECTION"), + ); + + // AILERON POSITION FEEDBACK TO SIM + // Here we separate ailerons from spoiler to build a unique roll torque + // Asymmetry of elevator adds a part in roll torque + builder.map_many( + ExecuteOn::PostTick, + vec![ + Variable::aspect("HYD_AIL_LEFT_DEFLECTION"), + Variable::aspect("HYD_AIL_RIGHT_DEFLECTION"), + Variable::aspect("HYD_ELEV_LEFT_DEFLECTION"), + Variable::aspect("HYD_ELEV_RIGHT_DEFLECTION"), + Variable::named("HYD_SPOILERS_LEFT_DEFLECTION"), + Variable::named("HYD_SPOILERS_RIGHT_DEFLECTION"), + ], + |values| { + const SPOILER_ROLL_COEFF: f64 = 0.5; + const AILERON_ROLL_COEFF: f64 = 0.7; + const ELEVATOR_ROLL_COEFF: f64 = 0.2; + + let elevator_roll_component = ELEVATOR_ROLL_COEFF * (values[3] - values[2]); + let aileron_roll_asymetry = AILERON_ROLL_COEFF * (values[1] - values[0]); + let spoiler_roll_asymetry = SPOILER_ROLL_COEFF * (values[5] - values[4]); + + aileron_roll_asymetry + spoiler_roll_asymetry + elevator_roll_component + }, + Variable::aspect("HYD_FINAL_AILERON_FEEDBACK"), + ); + + builder.variables_to_object(Box::new(RollSimOutput { ailerons: 0. })); + + Ok(()) +} + +#[sim_connect::data_definition] +struct RollSimOutput { + #[name = "AILERON POSITION"] + #[unit = "Position"] + ailerons: f64, +} +impl VariablesToObject for RollSimOutput { + fn variables(&self) -> Vec { + vec![ + Variable::aspect("HYD_FINAL_AILERON_FEEDBACK"), + Variable::named("FLIGHT_CONTROLS_TRACKING_MODE"), + ] + } + + fn write(&mut self, values: Vec) -> ObjectWrite { + self.ailerons = values[0]; + + // Not writing control feedback when in tracking mode + ObjectWrite::on(!to_bool(values[1])) + } + + set_data_on_sim_object!(); +} diff --git a/src/systems/a380_systems_wasm/src/autobrakes.rs b/src/systems/a380_systems_wasm/src/autobrakes.rs new file mode 100644 index 000000000000..effcdcd7cfc4 --- /dev/null +++ b/src/systems/a380_systems_wasm/src/autobrakes.rs @@ -0,0 +1,101 @@ +use std::error::Error; +use std::time::Duration; +use systems_wasm::aspects::{EventToVariableMapping, EventToVariableOptions, MsfsAspectBuilder}; +use systems_wasm::Variable; + +pub(super) fn autobrakes(builder: &mut MsfsAspectBuilder) -> Result<(), Box> { + let options = |options: EventToVariableOptions| { + options + .leading_debounce(Duration::from_millis(1500)) + .afterwards_reset_to(0.) + }; + + builder.event_to_variable( + "AUTOBRAKE_LO_SET", + EventToVariableMapping::Value(1.), + Variable::named("OVHD_AUTOBRK_LOW_ON_IS_PRESSED"), + options, + )?; + builder.event_to_variable( + "AUTOBRAKE_MED_SET", + EventToVariableMapping::Value(1.), + Variable::named("OVHD_AUTOBRK_MED_ON_IS_PRESSED"), + options, + )?; + builder.event_to_variable( + "AUTOBRAKE_HI_SET", + EventToVariableMapping::Value(1.), + Variable::named("OVHD_AUTOBRK_MAX_ON_IS_PRESSED"), + options, + )?; + builder.event_to_variable( + "AUTOBRAKE_DISARM", + EventToVariableMapping::Value(1.), + Variable::named("AUTOBRAKE_DISARM"), + |options| options.afterwards_reset_to(0.), + )?; + + let options_set = |options: EventToVariableOptions| { + options + .leading_debounce(Duration::from_millis(125)) + .afterwards_reset_to(-1.) + }; + + builder.event_to_variable( + "A32NX.AUTOBRAKE_SET", + EventToVariableMapping::EventDataToValue(|event_data| event_data as f64), + Variable::named("AUTOBRAKES_ARMED_MODE_SET"), + options_set, + )?; + builder.event_to_variable( + "A32NX.AUTOBRAKE_SET_DISARM", + EventToVariableMapping::Value(0.), + Variable::named("AUTOBRAKES_ARMED_MODE_SET"), + options_set, + )?; + builder.event_to_variable( + "A32NX.AUTOBRAKE_SET_LO", + EventToVariableMapping::Value(1.), + Variable::named("AUTOBRAKES_ARMED_MODE_SET"), + options_set, + )?; + builder.event_to_variable( + "A32NX.AUTOBRAKE_SET_MED", + EventToVariableMapping::Value(2.), + Variable::named("AUTOBRAKES_ARMED_MODE_SET"), + options_set, + )?; + builder.event_to_variable( + "A32NX.AUTOBRAKE_SET_MAX", + EventToVariableMapping::Value(3.), + Variable::named("AUTOBRAKES_ARMED_MODE_SET"), + options_set, + )?; + + let options_buttons = |options: EventToVariableOptions| { + options + .leading_debounce(Duration::from_millis(125)) + .afterwards_reset_to(0.) + }; + + builder.event_to_variable( + "A32NX.AUTOBRAKE_BUTTON_LO", + EventToVariableMapping::Value(1.), + Variable::named("OVHD_AUTOBRK_LOW_ON_IS_PRESSED"), + options_buttons, + )?; + builder.event_to_variable( + "A32NX.AUTOBRAKE_BUTTON_MED", + EventToVariableMapping::Value(1.), + Variable::named("OVHD_AUTOBRK_MED_ON_IS_PRESSED"), + options_buttons, + )?; + builder.event_to_variable( + "A32NX.AUTOBRAKE_BUTTON_MAX", + EventToVariableMapping::Value(1.), + Variable::named("OVHD_AUTOBRK_MAX_ON_IS_PRESSED"), + options_buttons, + )?; + + Ok(()) +} diff --git a/src/systems/a380_systems_wasm/src/brakes.rs b/src/systems/a380_systems_wasm/src/brakes.rs new file mode 100644 index 000000000000..3b220e8665fe --- /dev/null +++ b/src/systems/a380_systems_wasm/src/brakes.rs @@ -0,0 +1,107 @@ +use std::error::Error; +use systems::shared::{from_bool, to_bool}; +use systems_wasm::aspects::{ + max, EventToVariableMapping, ExecuteOn, MsfsAspectBuilder, VariableToEventMapping, + VariableToEventWriteOn, +}; +use systems_wasm::Variable; + +pub(super) fn brakes(builder: &mut MsfsAspectBuilder) -> Result<(), Box> { + builder.event_to_variable( + "PARKING_BRAKES", + EventToVariableMapping::CurrentValueToValue(|current_value| { + from_bool(!to_bool(current_value)) + }), + Variable::named("PARK_BRAKE_LEVER_POS"), + |options| options.mask(), + )?; + builder.event_to_variable( + "PARKING_BRAKE_SET", + EventToVariableMapping::EventDataToValue(|event_data| from_bool(event_data == 1)), + Variable::named("PARK_BRAKE_LEVER_POS"), + |options| options.mask(), + )?; + + // Controller inputs for the left and right brakes are captured and translated + // to a variable so that it can be used by the simulation. + // After running the simulation, the variable value is written back to the simulator + // through the event. + let axis_left_brake_set_event_id = builder.event_to_variable( + "AXIS_LEFT_BRAKE_SET", + EventToVariableMapping::EventData32kPosition, + Variable::aspect("BRAKES_LEFT_EVENT"), + |options| options.mask(), + )?; + builder.variable_to_event_id( + Variable::aspect("BRAKE LEFT FORCE FACTOR"), + VariableToEventMapping::EventData32kPosition, + VariableToEventWriteOn::EveryTick, + axis_left_brake_set_event_id, + ); + let axis_right_brake_set_event_id = builder.event_to_variable( + "AXIS_RIGHT_BRAKE_SET", + EventToVariableMapping::EventData32kPosition, + Variable::aspect("BRAKES_RIGHT_EVENT"), + |options| options.mask(), + )?; + builder.variable_to_event_id( + Variable::aspect("BRAKE RIGHT FORCE FACTOR"), + VariableToEventMapping::EventData32kPosition, + VariableToEventWriteOn::EveryTick, + axis_right_brake_set_event_id, + ); + + // Inputs for both brakes, left brake, and right brake are captured and + // translated via a smooth press function into a ratio which is written to variables. + const KEYBOARD_PRESS_SPEED: f64 = 0.6; + const KEYBOARD_RELEASE_SPEED: f64 = 0.3; + builder.event_to_variable( + "BRAKES", + EventToVariableMapping::SmoothPress(KEYBOARD_PRESS_SPEED, KEYBOARD_RELEASE_SPEED), + Variable::aspect("BRAKES"), + |options| options.mask(), + )?; + builder.event_to_variable( + "BRAKES_LEFT", + EventToVariableMapping::SmoothPress(KEYBOARD_PRESS_SPEED, KEYBOARD_RELEASE_SPEED), + Variable::aspect("BRAKES_LEFT"), + |options| options.mask(), + )?; + builder.event_to_variable( + "BRAKES_RIGHT", + EventToVariableMapping::SmoothPress(KEYBOARD_PRESS_SPEED, KEYBOARD_RELEASE_SPEED), + Variable::aspect("BRAKES_RIGHT"), + |options| options.mask(), + )?; + + // The maximum braking demand of all controller inputs + // is calculated and made available as a percentage. + builder.reduce( + ExecuteOn::PreTick, + vec![ + Variable::aspect("BRAKES"), + Variable::aspect("BRAKES_LEFT"), + Variable::aspect("BRAKES_LEFT_EVENT"), + ], + 0., + to_percent_max, + Variable::named("LEFT_BRAKE_PEDAL_INPUT"), + ); + builder.reduce( + ExecuteOn::PreTick, + vec![ + Variable::aspect("BRAKES"), + Variable::aspect("BRAKES_RIGHT"), + Variable::aspect("BRAKES_RIGHT_EVENT"), + ], + 0., + to_percent_max, + Variable::named("RIGHT_BRAKE_PEDAL_INPUT"), + ); + + Ok(()) +} + +fn to_percent_max(accumulator: f64, item: f64) -> f64 { + max(accumulator, item * 100.) +} diff --git a/src/systems/a380_systems_wasm/src/elevators.rs b/src/systems/a380_systems_wasm/src/elevators.rs new file mode 100644 index 000000000000..d04b2fc4d995 --- /dev/null +++ b/src/systems/a380_systems_wasm/src/elevators.rs @@ -0,0 +1,125 @@ +use std::error::Error; + +use systems::shared::to_bool; +use systems_wasm::aspects::{ExecuteOn, MsfsAspectBuilder, ObjectWrite, VariablesToObject}; +use systems_wasm::{set_data_on_sim_object, Variable}; + +use msfs::sim_connect; +use msfs::{sim_connect::SimConnect, sim_connect::SIMCONNECT_OBJECT_ID_USER}; + +pub(super) fn elevators(builder: &mut MsfsAspectBuilder) -> Result<(), Box> { + const MIN_MSFS_DEFLECTION_ANGLE: f64 = 11.5; + const MAX_MSFS_DEFLECTION_ANGLE: f64 = 16.; + + builder.map( + ExecuteOn::PreTick, + Variable::named("ELEVATOR_DEFLECTION_DEMAND"), + |value| { + msfs_deflection_to_hyd_demand( + value, + MIN_MSFS_DEFLECTION_ANGLE, + MAX_MSFS_DEFLECTION_ANGLE, + ) + }, + Variable::aspect("HYD_ELEVATOR_DEMAND"), + ); + + builder.map( + ExecuteOn::PostTick, + Variable::aspect("HYD_ELEV_LEFT_DEFLECTION"), + |value| { + hyd_deflection_to_msfs_deflection( + value, + MIN_MSFS_DEFLECTION_ANGLE, + MAX_MSFS_DEFLECTION_ANGLE, + ) + }, + Variable::named("HYD_ELEVATOR_LEFT_DEFLECTION"), + ); + builder.map( + ExecuteOn::PostTick, + Variable::aspect("HYD_ELEV_RIGHT_DEFLECTION"), + |value| { + hyd_deflection_to_msfs_deflection( + value, + MIN_MSFS_DEFLECTION_ANGLE, + MAX_MSFS_DEFLECTION_ANGLE, + ) + }, + Variable::named("HYD_ELEVATOR_RIGHT_DEFLECTION"), + ); + + // ELEVATOR POSITION FEEDBACK TO SIM + builder.map_many( + ExecuteOn::PostTick, + vec![ + Variable::named("HYD_ELEVATOR_LEFT_DEFLECTION"), + Variable::named("HYD_ELEVATOR_RIGHT_DEFLECTION"), + ], + |values| (values[1] + values[0]) / 2., + Variable::aspect("HYD_FINAL_ELEVATOR_FEEDBACK"), + ); + + builder.variables_to_object(Box::new(PitchSimOutput { elevator: 0. })); + + Ok(()) +} + +#[sim_connect::data_definition] +struct PitchSimOutput { + #[name = "ELEVATOR POSITION"] + #[unit = "Position"] + elevator: f64, +} +impl VariablesToObject for PitchSimOutput { + fn variables(&self) -> Vec { + vec![ + Variable::aspect("HYD_FINAL_ELEVATOR_FEEDBACK"), + Variable::named("FLIGHT_CONTROLS_TRACKING_MODE"), + ] + } + + fn write(&mut self, values: Vec) -> ObjectWrite { + self.elevator = values[0]; + + // Not writing control feedback when in tracking mode + ObjectWrite::on(!to_bool(values[1])) + } + + set_data_on_sim_object!(); +} + +fn hyd_deflection_to_msfs_deflection( + hyd_deflection: f64, + min_msfs_angle: f64, + max_msfs_angle: f64, +) -> f64 { + let elevator_range: f64 = max_msfs_angle + min_msfs_angle; + + let msfs_angle_zero_offset = hyd_deflection * elevator_range; + let msfs_angle = msfs_angle_zero_offset - min_msfs_angle; + + if msfs_angle <= 0. { + msfs_angle / min_msfs_angle + } else { + msfs_angle / max_msfs_angle + } +} + +fn msfs_deflection_to_hyd_demand( + msfs_deflection: f64, + min_msfs_angle: f64, + max_msfs_angle: f64, +) -> f64 { + let elevator_range: f64 = max_msfs_angle + min_msfs_angle; + + let msfs_angle_demand = if msfs_deflection <= 0. { + msfs_deflection * min_msfs_angle + } else { + msfs_deflection * max_msfs_angle + }; + + let msfs_angle_zero_offset = msfs_angle_demand + min_msfs_angle; + + msfs_angle_zero_offset / elevator_range +} diff --git a/src/systems/a380_systems_wasm/src/flaps.rs b/src/systems/a380_systems_wasm/src/flaps.rs new file mode 100644 index 000000000000..d1f6ae2ebcaa --- /dev/null +++ b/src/systems/a380_systems_wasm/src/flaps.rs @@ -0,0 +1,211 @@ +use msfs::sim_connect; +use msfs::{sim_connect::SimConnect, sim_connect::SIMCONNECT_OBJECT_ID_USER}; +use std::error::Error; +use systems_wasm::aspects::{ + EventToVariableMapping, ExecuteOn, MsfsAspectBuilder, ObjectWrite, VariablesToObject, +}; +use systems_wasm::{set_data_on_sim_object, Variable}; + +pub(super) fn flaps(builder: &mut MsfsAspectBuilder) -> Result<(), Box> { + builder.event_to_variable( + "FLAPS_INCR", + EventToVariableMapping::CurrentValueToValue(|current_value| (current_value + 1.).min(4.)), + Variable::named("FLAPS_HANDLE_INDEX"), + |options| options.mask(), + )?; + builder.event_to_variable( + "FLAPS_DECR", + EventToVariableMapping::CurrentValueToValue(|current_value| (current_value - 1.).max(0.)), + Variable::named("FLAPS_HANDLE_INDEX"), + |options| options.mask(), + )?; + flaps_event_to_value(builder, "FLAPS_UP", 0.)?; + flaps_event_to_value(builder, "FLAPS_1", 1.)?; + flaps_event_to_value(builder, "FLAPS_2", 2.)?; + flaps_event_to_value(builder, "FLAPS_3", 3.)?; + flaps_event_to_value(builder, "FLAPS_DOWN", 4.)?; + builder.event_to_variable( + "FLAPS_SET", + EventToVariableMapping::EventDataAndCurrentValueToValue(|event_data, current_value| { + let normalized_input: f64 = (event_data as i32 as f64) / 8192. - 1.; + get_handle_pos_from_0_1(normalized_input, current_value) + }), + Variable::named("FLAPS_HANDLE_INDEX"), + |options| options.mask(), + )?; + builder.event_to_variable( + "AXIS_FLAPS_SET", + EventToVariableMapping::EventDataAndCurrentValueToValue(|event_data, current_value| { + let normalized_input: f64 = (event_data as i32 as f64) / 16384.; + get_handle_pos_from_0_1(normalized_input, current_value) + }), + Variable::named("FLAPS_HANDLE_INDEX"), + |options| options.mask(), + )?; + + builder.map( + ExecuteOn::PreTick, + Variable::named("FLAPS_HANDLE_INDEX"), + |value| value / 4., + Variable::named("FLAPS_HANDLE_PERCENT"), + ); + + builder.variables_to_object(Box::new(FlapsSurface { + left_flap: 0., + right_flap: 0., + })); + builder.variables_to_object(Box::new(SlatsSurface { + left_slat: 0., + right_slat: 0., + })); + builder.variables_to_object(Box::new(FlapsHandleIndex { index: 0. })); + + Ok(()) +} + +fn flaps_event_to_value( + builder: &mut MsfsAspectBuilder, + event_name: &str, + value: f64, +) -> Result<(), Box> { + builder.event_to_variable( + event_name, + EventToVariableMapping::Value(value), + Variable::named("FLAPS_HANDLE_INDEX"), + |options| options.mask(), + )?; + + Ok(()) +} + +fn get_handle_pos_from_0_1(input: f64, current_value: f64) -> f64 { + if input < -0.8 { + 0. + } else if input > -0.7 && input < -0.3 { + 1. + } else if input > -0.2 && input < 0.2 { + 2. + } else if input > 0.3 && input < 0.7 { + 3. + } else if input > 0.8 { + 4. + } else { + current_value + } +} + +#[sim_connect::data_definition] +struct FlapsSurface { + #[name = "TRAILING EDGE FLAPS LEFT PERCENT"] + #[unit = "Percent"] + left_flap: f64, + + #[name = "TRAILING EDGE FLAPS RIGHT PERCENT"] + #[unit = "Percent"] + right_flap: f64, +} + +impl VariablesToObject for FlapsSurface { + fn variables(&self) -> Vec { + vec![ + Variable::named("LEFT_FLAPS_POSITION_PERCENT"), + Variable::named("RIGHT_FLAPS_POSITION_PERCENT"), + ] + } + + fn write(&mut self, values: Vec) -> ObjectWrite { + self.left_flap = values[0]; + self.right_flap = values[1]; + + ObjectWrite::default() + } + + set_data_on_sim_object!(); +} + +#[sim_connect::data_definition] +struct SlatsSurface { + #[name = "LEADING EDGE FLAPS LEFT PERCENT"] + #[unit = "Percent"] + left_slat: f64, + + #[name = "LEADING EDGE FLAPS RIGHT PERCENT"] + #[unit = "Percent"] + right_slat: f64, +} + +impl VariablesToObject for SlatsSurface { + fn variables(&self) -> Vec { + vec![ + Variable::named("LEFT_SLATS_POSITION_PERCENT"), + Variable::named("RIGHT_SLATS_POSITION_PERCENT"), + ] + } + + fn write(&mut self, values: Vec) -> ObjectWrite { + self.left_slat = values[0]; + self.right_slat = values[1]; + + ObjectWrite::default() + } + + set_data_on_sim_object!(); +} + +#[sim_connect::data_definition] +struct FlapsHandleIndex { + #[name = "FLAPS HANDLE INDEX"] + #[unit = "Number"] + index: f64, +} + +impl VariablesToObject for FlapsHandleIndex { + fn variables(&self) -> Vec { + vec![ + Variable::named("LEFT_FLAPS_POSITION_PERCENT"), + Variable::named("RIGHT_FLAPS_POSITION_PERCENT"), + Variable::named("LEFT_SLATS_POSITION_PERCENT"), + Variable::named("RIGHT_SLATS_POSITION_PERCENT"), + ] + } + + fn write(&mut self, values: Vec) -> ObjectWrite { + self.index = Self::msfs_flap_index_from_surfaces_positions_percent(values); + ObjectWrite::default() + } + + set_data_on_sim_object!(); +} + +impl FlapsHandleIndex { + /// Tries to take actual surfaces position PERCENTS and convert it into flight model FLAP HANDLE INDEX + /// This index is used by MSFS to select correct aerodynamic properties + /// There is no index available for flaps but no slats configurations (possible plane failure case) + /// The percent thresholds can be tuned to change the timing of aerodynamic impact versus surface actual position + fn msfs_flap_index_from_surfaces_positions_percent(values: Vec) -> f64 { + let left_flaps_position = values[0]; + let right_flaps_position = values[1]; + let left_slats_position = values[2]; + let right_slats_position = values[3]; + let flap_mean_position = (left_flaps_position + right_flaps_position) / 2.; + let slat_mean_position = (left_slats_position + right_slats_position) / 2.; + + if flap_mean_position < 2. && slat_mean_position < 2. { + // Clean configuration no flaps no slats + 0. + } else if flap_mean_position < 12. && slat_mean_position > 15. { + // Almost no flaps but some slats -> CONF 1 + 1. + } else if flap_mean_position > 80. { + 5. + } else if flap_mean_position > 49. { + 4. + } else if flap_mean_position > 30. { + 3. + } else if flap_mean_position > 12. { + 2. + } else { + 0. + } + } +} diff --git a/src/systems/a380_systems_wasm/src/gear.rs b/src/systems/a380_systems_wasm/src/gear.rs new file mode 100644 index 000000000000..cebf2865ccc2 --- /dev/null +++ b/src/systems/a380_systems_wasm/src/gear.rs @@ -0,0 +1,101 @@ +use std::error::Error; + +use msfs::sim_connect; +use msfs::{sim_connect::SimConnect, sim_connect::SIMCONNECT_OBJECT_ID_USER}; + +use systems_wasm::aspects::{ + EventToVariableMapping, MsfsAspectBuilder, ObjectWrite, VariableToEventMapping, + VariableToEventWriteOn, VariablesToObject, +}; +use systems_wasm::{set_data_on_sim_object, Variable}; + +pub(super) fn gear(builder: &mut MsfsAspectBuilder) -> Result<(), Box> { + // Read gear demand from all sim sim events and mask them + let gear_set_set_event_id = builder.event_to_variable( + "GEAR_SET", + EventToVariableMapping::EventDataRaw, + Variable::aspect("GEAR_LEVER_POSITION_REQUEST"), + |options| options.mask(), + )?; + + builder.event_to_variable( + "GEAR_TOGGLE", + EventToVariableMapping::CurrentValueToValue( + |current_value| { + if current_value > 0.5 { + 0. + } else { + 1. + } + }, + ), + Variable::aspect("GEAR_LEVER_POSITION_REQUEST"), + |options| options.mask(), + )?; + + builder.event_to_variable( + "GEAR_UP", + EventToVariableMapping::Value(0.), + Variable::aspect("GEAR_LEVER_POSITION_REQUEST"), + |options| options.mask(), + )?; + + builder.event_to_variable( + "GEAR_DOWN", + EventToVariableMapping::Value(1.), + Variable::aspect("GEAR_LEVER_POSITION_REQUEST"), + |options| options.mask(), + )?; + + // Feedback the gear event to the sim + builder.variable_to_event_id( + Variable::aspect("GEAR_HANDLE_POSITION"), + VariableToEventMapping::EventDataRaw, + VariableToEventWriteOn::Change, + gear_set_set_event_id, + ); + + // GEAR POSITION FEEDBACK TO SIM + builder.variables_to_object(Box::new(GearPosition { + nose_position: 1., + left_position: 1., + right_position: 1., + })); + + Ok(()) +} + +#[sim_connect::data_definition] +struct GearPosition { + #[name = "GEAR CENTER POSITION"] + #[unit = "Percent over 100"] + nose_position: f64, + + #[name = "GEAR LEFT POSITION"] + #[unit = "Percent over 100"] + left_position: f64, + + #[name = "GEAR RIGHT POSITION"] + #[unit = "Percent over 100"] + right_position: f64, +} + +impl VariablesToObject for GearPosition { + fn variables(&self) -> Vec { + vec![ + Variable::named("GEAR_CENTER_POSITION"), + Variable::named("GEAR_LEFT_POSITION"), + Variable::named("GEAR_RIGHT_POSITION"), + ] + } + + fn write(&mut self, values: Vec) -> ObjectWrite { + self.nose_position = values[0] / 100.; + self.left_position = values[1] / 100.; + self.right_position = values[2] / 100.; + + ObjectWrite::default() + } + + set_data_on_sim_object!(); +} diff --git a/src/systems/a380_systems_wasm/src/lib.rs b/src/systems/a380_systems_wasm/src/lib.rs new file mode 100644 index 000000000000..503eee5b24f7 --- /dev/null +++ b/src/systems/a380_systems_wasm/src/lib.rs @@ -0,0 +1,281 @@ +mod ailerons; +mod autobrakes; +mod brakes; +mod elevators; +mod flaps; +mod gear; +mod nose_wheel_steering; +mod rudder; +mod spoilers; +mod trimmable_horizontal_stabilizer; + +use a380_systems::A380; +use ailerons::ailerons; +use autobrakes::autobrakes; +use brakes::brakes; +use elevators::elevators; +use flaps::flaps; +use gear::gear; +use nose_wheel_steering::nose_wheel_steering; +use rudder::rudder; +use spoilers::spoilers; +use std::error::Error; +use systems::failures::FailureType; +use systems::shared::{ + ElectricalBusType, GearActuatorId, HydraulicColor, LgciuId, ProximityDetectorId, +}; +use systems_wasm::aspects::ExecuteOn; +use systems_wasm::{MsfsSimulationBuilder, Variable}; +use trimmable_horizontal_stabilizer::trimmable_horizontal_stabilizer; + +#[msfs::gauge(name=systems)] +async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { + let mut sim_connect = gauge.open_simconnect("systems")?; + + let key_prefix = "A32NX_"; + let (mut simulation, mut handler) = MsfsSimulationBuilder::new( + key_prefix, + Variable::named(&format!("{}START_STATE", key_prefix)), + sim_connect.as_mut().get_mut(), + ) + .with_electrical_buses([ + (ElectricalBusType::AlternatingCurrent(1), 2), + (ElectricalBusType::AlternatingCurrent(2), 3), + (ElectricalBusType::AlternatingCurrentEssential, 4), + (ElectricalBusType::AlternatingCurrentEssentialShed, 5), + (ElectricalBusType::AlternatingCurrentStaticInverter, 6), + (ElectricalBusType::AlternatingCurrentGndFltService, 14), + (ElectricalBusType::DirectCurrent(1), 7), + (ElectricalBusType::DirectCurrent(2), 8), + (ElectricalBusType::DirectCurrentEssential, 9), + (ElectricalBusType::DirectCurrentEssentialShed, 10), + (ElectricalBusType::DirectCurrentBattery, 11), + (ElectricalBusType::DirectCurrentHot(1), 12), + (ElectricalBusType::DirectCurrentHot(2), 13), + (ElectricalBusType::DirectCurrentGndFltService, 15), + ])? + .with_auxiliary_power_unit(Variable::named("OVHD_APU_START_PB_IS_AVAILABLE"), 8)? + .with_failures(vec![ + (24_000, FailureType::TransformerRectifier(1)), + (24_001, FailureType::TransformerRectifier(2)), + (24_002, FailureType::TransformerRectifier(3)), + (29_000, FailureType::ReservoirLeak(HydraulicColor::Green)), + (29_001, FailureType::ReservoirLeak(HydraulicColor::Blue)), + (29_002, FailureType::ReservoirLeak(HydraulicColor::Yellow)), + (29_003, FailureType::ReservoirAirLeak(HydraulicColor::Green)), + (29_004, FailureType::ReservoirAirLeak(HydraulicColor::Blue)), + ( + 29_005, + FailureType::ReservoirAirLeak(HydraulicColor::Yellow), + ), + ( + 29_006, + FailureType::ReservoirReturnLeak(HydraulicColor::Green), + ), + ( + 29_007, + FailureType::ReservoirReturnLeak(HydraulicColor::Blue), + ), + ( + 29_008, + FailureType::ReservoirReturnLeak(HydraulicColor::Yellow), + ), + (32_000, FailureType::LgciuPowerSupply(LgciuId::Lgciu1)), + (32_001, FailureType::LgciuPowerSupply(LgciuId::Lgciu2)), + (32_002, FailureType::LgciuInternalError(LgciuId::Lgciu1)), + (32_003, FailureType::LgciuInternalError(LgciuId::Lgciu2)), + ( + 32_004, + FailureType::GearProxSensorDamage(ProximityDetectorId::UplockGearNose1), + ), + ( + 32_005, + FailureType::GearProxSensorDamage(ProximityDetectorId::DownlockGearNose2), + ), + ( + 32_006, + FailureType::GearProxSensorDamage(ProximityDetectorId::UplockGearRight1), + ), + ( + 32_007, + FailureType::GearProxSensorDamage(ProximityDetectorId::DownlockGearRight2), + ), + ( + 32_008, + FailureType::GearProxSensorDamage(ProximityDetectorId::UplockGearLeft2), + ), + ( + 32_009, + FailureType::GearProxSensorDamage(ProximityDetectorId::DownlockGearLeft1), + ), + ( + 32_010, + FailureType::GearProxSensorDamage(ProximityDetectorId::UplockDoorNose1), + ), + ( + 32_011, + FailureType::GearProxSensorDamage(ProximityDetectorId::DownlockDoorNose2), + ), + ( + 32_012, + FailureType::GearProxSensorDamage(ProximityDetectorId::UplockDoorRight2), + ), + ( + 32_013, + FailureType::GearProxSensorDamage(ProximityDetectorId::DownlockDoorRight1), + ), + ( + 32_014, + FailureType::GearProxSensorDamage(ProximityDetectorId::UplockDoorLeft2), + ), + ( + 32_015, + FailureType::GearProxSensorDamage(ProximityDetectorId::DownlockDoorLeft1), + ), + ( + 32_020, + FailureType::GearActuatorJammed(GearActuatorId::GearNose), + ), + ( + 32_021, + FailureType::GearActuatorJammed(GearActuatorId::GearLeft), + ), + ( + 32_022, + FailureType::GearActuatorJammed(GearActuatorId::GearRight), + ), + ( + 32_023, + FailureType::GearActuatorJammed(GearActuatorId::GearDoorNose), + ), + ( + 32_024, + FailureType::GearActuatorJammed(GearActuatorId::GearDoorLeft), + ), + ( + 32_025, + FailureType::GearActuatorJammed(GearActuatorId::GearDoorRight), + ), + (34_000, FailureType::RadioAltimeter(1)), + (34_001, FailureType::RadioAltimeter(2)), + ]) + .provides_aircraft_variable("ACCELERATION BODY X", "feet per second squared", 0)? + .provides_aircraft_variable("ACCELERATION BODY Y", "feet per second squared", 0)? + .provides_aircraft_variable("ACCELERATION BODY Z", "feet per second squared", 0)? + .provides_aircraft_variable("AIRSPEED INDICATED", "Knots", 0)? + .provides_aircraft_variable("AIRSPEED MACH", "Mach", 0)? + .provides_aircraft_variable("AIRSPEED TRUE", "Knots", 0)? + .provides_aircraft_variable("AMBIENT DENSITY", "Slugs per cubic feet", 0)? + .provides_aircraft_variable("AMBIENT PRESSURE", "inHg", 0)? + .provides_aircraft_variable("AMBIENT TEMPERATURE", "celsius", 0)? + .provides_aircraft_variable("AMBIENT WIND DIRECTION", "Degrees", 0)? + .provides_aircraft_variable("AMBIENT WIND VELOCITY", "Knots", 0)? + .provides_aircraft_variable("AMBIENT WIND X", "meter per second", 0)? + .provides_aircraft_variable("AMBIENT WIND Y", "meter per second", 0)? + .provides_aircraft_variable("AMBIENT WIND Z", "meter per second", 0)? + .provides_aircraft_variable("ANTISKID BRAKES ACTIVE", "Bool", 0)? + .provides_aircraft_variable("EXTERNAL POWER AVAILABLE", "Bool", 1)? + .provides_aircraft_variable("FUEL TANK LEFT MAIN QUANTITY", "Pounds", 0)? + .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 0)? + .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 1)? + .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 2)? + .provides_aircraft_variable("GEAR CENTER POSITION", "Percent", 0)? + .provides_aircraft_variable("GEAR LEFT POSITION", "Percent", 0)? + .provides_aircraft_variable("GEAR RIGHT POSITION", "Percent", 0)? + .provides_aircraft_variable("GENERAL ENG STARTER ACTIVE", "Bool", 1)? + .provides_aircraft_variable("GENERAL ENG STARTER ACTIVE", "Bool", 2)? + .provides_aircraft_variable("GPS GROUND SPEED", "Knots", 0)? + .provides_aircraft_variable("GPS GROUND MAGNETIC TRACK", "Degrees", 0)? + .provides_aircraft_variable("GPS GROUND TRUE TRACK", "Degrees", 0)? + .provides_aircraft_variable("INDICATED ALTITUDE", "Feet", 0)? + .provides_aircraft_variable("INTERACTIVE POINT OPEN:0", "Percent", 0)? + .provides_aircraft_variable("INTERACTIVE POINT OPEN:3", "Percent", 0)? + .provides_aircraft_variable("LIGHT BEACON ON", "Bool", 0)? + .provides_aircraft_variable("PLANE ALT ABOVE GROUND", "Feet", 0)? + .provides_aircraft_variable("PLANE PITCH DEGREES", "Degrees", 0)? + .provides_aircraft_variable("PLANE BANK DEGREES", "Degrees", 0)? + .provides_aircraft_variable("PLANE HEADING DEGREES MAGNETIC", "Degrees", 0)? + .provides_aircraft_variable("PLANE HEADING DEGREES TRUE", "Degrees", 0)? + .provides_aircraft_variable("PLANE LATITUDE", "degree latitude", 0)? + .provides_aircraft_variable("PLANE LONGITUDE", "degree longitude", 0)? + .provides_aircraft_variable("PUSHBACK STATE", "Enum", 0)? + .provides_aircraft_variable("PUSHBACK ANGLE", "Radians", 0)? + .provides_aircraft_variable("SEA LEVEL PRESSURE", "Millibars", 0)? + .provides_aircraft_variable("SIM ON GROUND", "Bool", 0)? + .provides_aircraft_variable("TOTAL AIR TEMPERATURE", "celsius", 0)? + .provides_aircraft_variable("TRAILING EDGE FLAPS LEFT PERCENT", "Percent", 0)? + .provides_aircraft_variable("TRAILING EDGE FLAPS RIGHT PERCENT", "Percent", 0)? + .provides_aircraft_variable("TURB ENG CORRECTED N1", "Percent", 1)? + .provides_aircraft_variable("TURB ENG CORRECTED N1", "Percent", 2)? + .provides_aircraft_variable("TURB ENG CORRECTED N2", "Percent", 1)? + .provides_aircraft_variable("TURB ENG CORRECTED N2", "Percent", 2)? + .provides_aircraft_variable("TURB ENG IGNITION SWITCH EX1", "Enum", 1)? + .provides_aircraft_variable("UNLIMITED FUEL", "Bool", 0)? + .provides_aircraft_variable("VELOCITY BODY X", "feet per second", 0)? + .provides_aircraft_variable("VELOCITY BODY Y", "feet per second", 0)? + .provides_aircraft_variable("VELOCITY BODY Z", "feet per second", 0)? + .provides_aircraft_variable("VELOCITY WORLD Y", "feet per minute", 0)? + .provides_aircraft_variable("INCIDENCE ALPHA", "degree", 0)? + .provides_aircraft_variable("ROTATION VELOCITY BODY X", "degree per second", 0)? + .provides_aircraft_variable("ROTATION VELOCITY BODY Y", "degree per second", 0)? + .provides_aircraft_variable("ROTATION VELOCITY BODY Z", "degree per second", 0)? + .with_aspect(|builder| { + builder.copy( + Variable::aircraft("APU GENERATOR SWITCH", "Bool", 0), + Variable::aspect("OVHD_ELEC_APU_GEN_PB_IS_ON"), + ); + + builder.copy( + Variable::aircraft("BLEED AIR ENGINE", "Bool", 1), + Variable::aspect("OVHD_PNEU_ENG_1_BLEED_PB_IS_AUTO"), + ); + builder.copy( + Variable::aircraft("BLEED AIR ENGINE", "Bool", 2), + Variable::aspect("OVHD_PNEU_ENG_2_BLEED_PB_IS_AUTO"), + ); + + builder.copy( + Variable::aircraft("EXTERNAL POWER AVAILABLE", "Bool", 1), + Variable::aspect("OVHD_ELEC_EXT_PWR_PB_IS_AVAILABLE"), + ); + builder.copy( + Variable::aircraft("EXTERNAL POWER ON", "Bool", 1), + Variable::aspect("OVHD_ELEC_EXT_PWR_PB_IS_ON"), + ); + + builder.copy( + Variable::aircraft("GENERAL ENG MASTER ALTERNATOR", "Bool", 1), + Variable::aspect("OVHD_ELEC_ENG_GEN_1_PB_IS_ON"), + ); + builder.copy( + Variable::aircraft("GENERAL ENG MASTER ALTERNATOR", "Bool", 2), + Variable::aspect("OVHD_ELEC_ENG_GEN_2_PB_IS_ON"), + ); + + builder.map( + ExecuteOn::PreTick, + Variable::aircraft("INTERACTIVE POINT OPEN", "Position", 5), + |value| if value > 0. { 1. } else { 0. }, + Variable::aspect("FWD_DOOR_CARGO_OPEN_REQ"), + ); + + Ok(()) + })? + .with_aspect(brakes)? + .with_aspect(autobrakes)? + .with_aspect(nose_wheel_steering)? + .with_aspect(flaps)? + .with_aspect(spoilers)? + .with_aspect(ailerons)? + .with_aspect(elevators)? + .with_aspect(rudder)? + .with_aspect(gear)? + .with_aspect(trimmable_horizontal_stabilizer)? + .build(A380::new)?; + + while let Some(event) = gauge.next_event().await { + handler.handle(event, &mut simulation, sim_connect.as_mut().get_mut())?; + } + + Ok(()) +} diff --git a/src/systems/a380_systems_wasm/src/nose_wheel_steering.rs b/src/systems/a380_systems_wasm/src/nose_wheel_steering.rs new file mode 100644 index 000000000000..e01d7e435118 --- /dev/null +++ b/src/systems/a380_systems_wasm/src/nose_wheel_steering.rs @@ -0,0 +1,183 @@ +use std::error::Error; +use systems::shared::to_bool; +use systems_wasm::aspects::{ + EventToVariableMapping, ExecuteOn, MsfsAspectBuilder, VariableToEventMapping, + VariableToEventWriteOn, +}; +use systems_wasm::Variable; + +pub(super) fn nose_wheel_steering(builder: &mut MsfsAspectBuilder) -> Result<(), Box> { + // The rudder pedals should start in a centered position. + builder.init_variable(Variable::aspect("RAW_RUDDER_PEDAL_POSITION"), 0.5); + + builder.map( + ExecuteOn::PreTick, + Variable::named("RUDDER_PEDAL_POSITION"), + // Convert rudder pedal position to [-1;1], -1 is left + |value| ((value + 100.) / 200.) * 2. - 1., + Variable::aspect("RAW_RUDDER_PEDAL_POSITION"), + ); + + builder.map_many( + ExecuteOn::PostTick, + vec![ + Variable::named("REALISTIC_TILLER_ENABLED"), + Variable::aspect("RAW_RUDDER_PEDAL_POSITION"), + ], + |values| { + let realistic_tiller_enabled = to_bool(values[0]); + let rudder_pedal_position = values[1]; + if realistic_tiller_enabled { + rudder_pedal_position + } else { + 0. + } + }, + Variable::aspect("RUDDER_PEDAL_POSITION_RATIO"), + ); + + // The tiller handle should start in a centered position. + builder.init_variable(Variable::aspect("RAW_TILLER_HANDLE_POSITION"), 0.5); + + // This axis is kept for legacy reasons and was used before the steering axis was available + builder.event_to_variable( + "AXIS_MIXTURE4_SET", + EventToVariableMapping::EventData32kPosition, + Variable::aspect("RAW_TILLER_HANDLE_POSITION"), + |options| options.mask(), + )?; + + builder.event_to_variable( + "AXIS_STEERING_SET", + EventToVariableMapping::EventData32kPositionInverted, + Variable::aspect("RAW_TILLER_HANDLE_POSITION"), + |options| options.mask(), + )?; + + const TILLER_KEYBOARD_INCREMENTS: f64 = 0.05; + builder.event_to_variable( + "STEERING_INC", + EventToVariableMapping::CurrentValueToValue(|current_value| { + recenter_when_close_to_center( + (current_value + TILLER_KEYBOARD_INCREMENTS).min(1.), + TILLER_KEYBOARD_INCREMENTS, + ) + }), + Variable::aspect("RAW_TILLER_HANDLE_POSITION"), + |options| options.mask(), + )?; + builder.event_to_variable( + "STEERING_DEC", + EventToVariableMapping::CurrentValueToValue(|current_value| { + recenter_when_close_to_center( + (current_value - TILLER_KEYBOARD_INCREMENTS).max(0.), + TILLER_KEYBOARD_INCREMENTS, + ) + }), + Variable::aspect("RAW_TILLER_HANDLE_POSITION"), + |options| options.mask(), + )?; + + // Lacking a better event to bind to, we've picked the toggle water rudder event for + // disconnecting the rudder pedals via the PEDALS DISC button on the tiller. + builder.event_to_variable( + "TOGGLE_WATER_RUDDER", + EventToVariableMapping::Value(1.), + Variable::aspect("TILLER_PEDAL_DISCONNECT"), + |options| options.mask().afterwards_reset_to(0.), + )?; + + builder.map_many( + ExecuteOn::PostTick, + vec![ + Variable::named("REALISTIC_TILLER_ENABLED"), + Variable::aspect("RAW_RUDDER_PEDAL_POSITION"), + Variable::aspect("RAW_TILLER_HANDLE_POSITION"), + Variable::aspect("TILLER_PEDAL_DISCONNECT"), + ], + |values| { + let realistic_tiller_enabled = to_bool(values[0]); + let rudder_pedal_position = values[1]; + let tiller_handle_position = values[2]; + let tiller_pedal_disconnect = to_bool(values[3]); + + if realistic_tiller_enabled { + // Convert tiller handle position to [-1;1], -1 is left + tiller_handle_position * 2. - 1. + } else if !tiller_pedal_disconnect { + rudder_pedal_position + } else { + 0. + } + }, + Variable::named("TILLER_HANDLE_POSITION"), + ); + + builder.map( + ExecuteOn::PostTick, + Variable::aspect("NOSE_WHEEL_POSITION_RATIO"), + steering_animation_to_msfs_from_steering_angle, + Variable::named("NOSE_WHEEL_POSITION"), + ); + + builder.map_many( + ExecuteOn::PostTick, + vec![ + Variable::aspect("NOSE_WHEEL_POSITION_RATIO"), + Variable::aircraft("RUDDER POSITION", "Position", 0), + ], + |values| { + let nose_wheel_position = values[0]; + let rudder_position = (values[1] + 1.) / 2.; + + steering_demand_to_msfs_from_steering_angle(nose_wheel_position, rudder_position) + }, + Variable::aspect("STEERING_ANGLE"), + ); + + builder.variable_to_event( + Variable::aspect("STEERING_ANGLE"), + VariableToEventMapping::EventData32kPosition, + VariableToEventWriteOn::EveryTick, + "STEERING_SET", + )?; + + Ok(()) +} + +fn recenter_when_close_to_center(value: f64, increment: f64) -> f64 { + if value < 0.5 + increment && value > 0.5 - increment { + 0.5 + } else { + value + } +} + +const MAX_CONTROLLABLE_STEERING_ANGLE_DEGREES: f64 = 75.; + +fn steering_animation_to_msfs_from_steering_angle(nose_wheel_position: f64) -> f64 { + const STEERING_ANIMATION_TOTAL_RANGE_DEGREES: f64 = 360.; + + ((nose_wheel_position * MAX_CONTROLLABLE_STEERING_ANGLE_DEGREES + / (STEERING_ANIMATION_TOTAL_RANGE_DEGREES / 2.)) + / 2.) + + 0.5 +} + +fn steering_demand_to_msfs_from_steering_angle( + nose_wheel_position: f64, + rudder_position: f64, +) -> f64 { + const MAX_MSFS_STEERING_ANGLE_DEGREES: f64 = 90.; + + // Steering in msfs is the max we want rescaled to the max in msfs + let steering_ratio_converted = nose_wheel_position * MAX_CONTROLLABLE_STEERING_ANGLE_DEGREES + / MAX_MSFS_STEERING_ANGLE_DEGREES + / 2. + + 0.5; + + // Steering demand is reverted in msfs so we do 1 - angle. + // Then we hack msfs by adding the rudder value that it will always substract internally + // This way we end up with actual angle we required + (1. - steering_ratio_converted) + (rudder_position - 0.5) +} diff --git a/src/systems/a380_systems_wasm/src/rudder.rs b/src/systems/a380_systems_wasm/src/rudder.rs new file mode 100644 index 000000000000..60e1fc26c227 --- /dev/null +++ b/src/systems/a380_systems_wasm/src/rudder.rs @@ -0,0 +1,60 @@ +use std::error::Error; + +use systems::shared::to_bool; +use systems_wasm::aspects::{ExecuteOn, MsfsAspectBuilder, ObjectWrite, VariablesToObject}; + +use msfs::sim_connect; +use msfs::{sim_connect::SimConnect, sim_connect::SIMCONNECT_OBJECT_ID_USER}; +use systems_wasm::{set_data_on_sim_object, Variable}; + +pub(super) fn rudder(builder: &mut MsfsAspectBuilder) -> Result<(), Box> { + builder.map( + ExecuteOn::PreTick, + Variable::named("RUDDER_DEFLECTION_DEMAND"), + |value| (value + 1.) / 2., + Variable::aspect("HYD_RUDDER_DEMAND"), + ); + + builder.map( + ExecuteOn::PostTick, + Variable::aspect("HYD_RUD_DEFLECTION"), + |value| 100. * (value * 2. - 1.), + Variable::named("HYD_RUDDER_DEFLECTION"), + ); + + // AILERON POSITION FEEDBACK TO SIM + builder.map( + ExecuteOn::PostTick, + Variable::aspect("HYD_RUD_DEFLECTION"), + |value| value * 2. - 1., + Variable::aspect("HYD_FINAL_RUDDER_FEEDBACK"), + ); + + builder.variables_to_object(Box::new(YawSimOutput { rudder: 0. })); + + Ok(()) +} + +#[sim_connect::data_definition] +struct YawSimOutput { + #[name = "RUDDER POSITION"] + #[unit = "Position"] + rudder: f64, +} +impl VariablesToObject for YawSimOutput { + fn variables(&self) -> Vec { + vec![ + Variable::aspect("HYD_FINAL_RUDDER_FEEDBACK"), + Variable::named("FLIGHT_CONTROLS_TRACKING_MODE"), + ] + } + + fn write(&mut self, values: Vec) -> ObjectWrite { + self.rudder = values[0]; + + // Not writing control feedback when in tracking mode + ObjectWrite::on(!to_bool(values[1])) + } + + set_data_on_sim_object!(); +} diff --git a/src/systems/a380_systems_wasm/src/spoilers.rs b/src/systems/a380_systems_wasm/src/spoilers.rs new file mode 100644 index 000000000000..f51998907154 --- /dev/null +++ b/src/systems/a380_systems_wasm/src/spoilers.rs @@ -0,0 +1,95 @@ +use std::error::Error; +use systems_wasm::aspects::{ExecuteOn, MsfsAspectBuilder}; +use systems_wasm::Variable; + +pub(super) fn spoilers(builder: &mut MsfsAspectBuilder) -> Result<(), Box> { + builder.map( + ExecuteOn::PreTick, + Variable::named("SPOILERS_LEFT_DEFLECTION_DEMAND"), + |value| value, + Variable::aspect("HYD_SPOILER_1_LEFT_DEMAND"), + ); + builder.map( + ExecuteOn::PreTick, + Variable::named("SPOILERS_LEFT_DEFLECTION_DEMAND"), + |value| value, + Variable::aspect("HYD_SPOILER_2_LEFT_DEMAND"), + ); + builder.map( + ExecuteOn::PreTick, + Variable::named("SPOILERS_LEFT_DEFLECTION_DEMAND"), + |value| value, + Variable::aspect("HYD_SPOILER_3_LEFT_DEMAND"), + ); + builder.map( + ExecuteOn::PreTick, + Variable::named("SPOILERS_LEFT_DEFLECTION_DEMAND"), + |value| value, + Variable::aspect("HYD_SPOILER_4_LEFT_DEMAND"), + ); + builder.map( + ExecuteOn::PreTick, + Variable::named("SPOILERS_LEFT_DEFLECTION_DEMAND"), + |value| value, + Variable::aspect("HYD_SPOILER_5_LEFT_DEMAND"), + ); + + builder.map( + ExecuteOn::PreTick, + Variable::named("SPOILERS_RIGHT_DEFLECTION_DEMAND"), + |value| value, + Variable::aspect("HYD_SPOILER_1_RIGHT_DEMAND"), + ); + builder.map( + ExecuteOn::PreTick, + Variable::named("SPOILERS_RIGHT_DEFLECTION_DEMAND"), + |value| value, + Variable::aspect("HYD_SPOILER_2_RIGHT_DEMAND"), + ); + builder.map( + ExecuteOn::PreTick, + Variable::named("SPOILERS_RIGHT_DEFLECTION_DEMAND"), + |value| value, + Variable::aspect("HYD_SPOILER_3_RIGHT_DEMAND"), + ); + builder.map( + ExecuteOn::PreTick, + Variable::named("SPOILERS_RIGHT_DEFLECTION_DEMAND"), + |value| value, + Variable::aspect("HYD_SPOILER_4_RIGHT_DEMAND"), + ); + builder.map( + ExecuteOn::PreTick, + Variable::named("SPOILERS_RIGHT_DEFLECTION_DEMAND"), + |value| value, + Variable::aspect("HYD_SPOILER_5_RIGHT_DEMAND"), + ); + + builder.map_many( + ExecuteOn::PostTick, + vec![ + Variable::aspect("HYD_SPOIL_1_LEFT_DEFLECTION"), + Variable::aspect("HYD_SPOIL_2_LEFT_DEFLECTION"), + Variable::aspect("HYD_SPOIL_3_LEFT_DEFLECTION"), + Variable::aspect("HYD_SPOIL_4_LEFT_DEFLECTION"), + Variable::aspect("HYD_SPOIL_5_LEFT_DEFLECTION"), + ], + |values| values.iter().sum::() / (values.len() as f64), + Variable::named("HYD_SPOILERS_LEFT_DEFLECTION"), + ); + + builder.map_many( + ExecuteOn::PostTick, + vec![ + Variable::aspect("HYD_SPOIL_1_RIGHT_DEFLECTION"), + Variable::aspect("HYD_SPOIL_2_RIGHT_DEFLECTION"), + Variable::aspect("HYD_SPOIL_3_RIGHT_DEFLECTION"), + Variable::aspect("HYD_SPOIL_4_RIGHT_DEFLECTION"), + Variable::aspect("HYD_SPOIL_5_RIGHT_DEFLECTION"), + ], + |values| values.iter().sum::() / (values.len() as f64), + Variable::named("HYD_SPOILERS_RIGHT_DEFLECTION"), + ); + + Ok(()) +} diff --git a/src/systems/a380_systems_wasm/src/trimmable_horizontal_stabilizer.rs b/src/systems/a380_systems_wasm/src/trimmable_horizontal_stabilizer.rs new file mode 100644 index 000000000000..1090ae1ec0b6 --- /dev/null +++ b/src/systems/a380_systems_wasm/src/trimmable_horizontal_stabilizer.rs @@ -0,0 +1,130 @@ +use std::error::Error; + +use systems_wasm::aspects::{ + EventToVariableMapping, ExecuteOn, MsfsAspectBuilder, ObjectWrite, VariablesToObject, +}; +use systems_wasm::{set_data_on_sim_object, Variable}; + +use systems::shared::to_bool; + +use msfs::sim_connect; +use msfs::{sim_connect::SimConnect, sim_connect::SIMCONNECT_OBJECT_ID_USER}; + +pub(super) fn trimmable_horizontal_stabilizer( + builder: &mut MsfsAspectBuilder, +) -> Result<(), Box> { + builder.event_to_variable( + "ELEV_TRIM_UP", + EventToVariableMapping::Value(35.), + Variable::aspect("THS_MANUAL_CONTROL_SPEED_KEY"), + |options| options.mask().afterwards_reset_to(0.), + )?; + + builder.event_to_variable( + "ELEV_TRIM_DN", + EventToVariableMapping::Value(-35.), + Variable::aspect("THS_MANUAL_CONTROL_SPEED_KEY"), + |options| options.mask().afterwards_reset_to(0.), + )?; + + builder.event_to_variable( + "ELEVATOR_TRIM_SET", + EventToVariableMapping::EventDataToValue(|event_data| (event_data as f64) / 16383.), + Variable::aspect("THS_MAN_POS_SET_16K"), + |options| options.mask().afterwards_reset_to(-1.), + )?; + + builder.event_to_variable( + "AXIS_ELEV_TRIM_SET", + EventToVariableMapping::EventData32kPosition, + Variable::aspect("THS_MAN_POS_SET_32K"), + |options| options.mask().afterwards_reset_to(-1.), + )?; + + // Sends a trim speed from position error + builder.map_many( + ExecuteOn::PreTick, + vec![ + Variable::aspect("THS_MAN_POS_SET_16K"), + Variable::aspect("THS_MAN_POS_SET_32K"), + Variable::named("HYD_TRIM_WHEEL_PERCENT"), + ], + |values| { + if values[0] >= 0. { + pos_error_to_speed(values[0] - values[2] / 100.) + } else if values[1] >= 0. { + pos_error_to_speed(values[1] - values[2] / 100.) + } else { + 0. + } + }, + Variable::aspect("THS_MANUAL_CONTROL_SPEED_AXIS"), + ); + + // Selects final speed to use from keys or axis events + builder.map_many( + ExecuteOn::PreTick, + vec![ + Variable::aspect("THS_MANUAL_CONTROL_SPEED_KEY"), + Variable::aspect("THS_MANUAL_CONTROL_SPEED_AXIS"), + ], + |values| { + if values[0].abs() > 0. { + values[0] + } else { + values[1] + } + }, + Variable::aspect("THS_MANUAL_CONTROL_SPEED"), + ); + + // Sends manual control state when receiveing an event even if position is not moving or when keys event are used + builder.map_many( + ExecuteOn::PreTick, + vec![ + Variable::aspect("THS_MAN_POS_SET_16K"), + Variable::aspect("THS_MAN_POS_SET_32K"), + Variable::aspect("THS_MANUAL_CONTROL_SPEED_KEY"), + ], + |values| { + if values[0] >= 0. || values[1] >= 0. || values[2].abs() > 0. { + 1. + } else { + 0. + } + }, + Variable::aspect("THS_MANUAL_CONTROL_ACTIVE"), + ); + + builder.variables_to_object(Box::new(PitchTrimSimOutput { elevator_trim: 0. })); + + Ok(()) +} + +#[sim_connect::data_definition] +struct PitchTrimSimOutput { + #[name = "ELEVATOR TRIM POSITION"] + #[unit = "DEGREE"] + elevator_trim: f64, +} +impl VariablesToObject for PitchTrimSimOutput { + fn variables(&self) -> Vec { + vec![ + Variable::aspect("HYD_FINAL_THS_DEFLECTION"), + Variable::named("FLIGHT_CONTROLS_TRACKING_MODE"), + ] + } + + fn write(&mut self, values: Vec) -> ObjectWrite { + self.elevator_trim = values[0]; + + //Not writing control feedback when in tracking mode + ObjectWrite::on(!to_bool(values[1])) + } + + set_data_on_sim_object!(); +} + +fn pos_error_to_speed(error: f64) -> f64 { + (1000. * error).min(45.).max(-45.) +} diff --git a/src/systems/systems/Cargo.toml b/src/systems/systems/Cargo.toml index 38681c472fb0..9d7b31c767fb 100644 --- a/src/systems/systems/Cargo.toml +++ b/src/systems/systems/Cargo.toml @@ -5,7 +5,7 @@ authors = ["FlyByWire Simulations"] edition = "2018" [dependencies] -uom = "0.32.0" +uom = "0.33.0" rand = { version = "0.8.0", features = ["small_rng"] } rand_distr = "0.4.3" ntest = "0.7.2" diff --git a/src/systems/systems/src/air_conditioning/acs_controller.rs b/src/systems/systems/src/air_conditioning/acs_controller.rs index 6eb9521b2afa..a068999d99f1 100644 --- a/src/systems/systems/src/air_conditioning/acs_controller.rs +++ b/src/systems/systems/src/air_conditioning/acs_controller.rs @@ -1,9 +1,10 @@ use crate::{ - pneumatic::{EngineModeSelector, EngineState}, + pneumatic::{EngineModeSelector, EngineState, PneumaticValveSignal}, pressurization::PressurizationOverheadPanel, shared::{ - pid::PidController, Cabin, ControllerSignal, EngineCorrectedN1, EngineFirePushButtons, - EngineStartState, GroundSpeed, LgciuWeightOnWheels, PneumaticBleed, + pid::PidController, Cabin, ControllerSignal, ElectricalBusType, ElectricalBuses, + EngineBleedPushbutton, EngineCorrectedN1, EngineFirePushButtons, EngineStartState, + GroundSpeed, LgciuWeightOnWheels, PackFlowValveState, PneumaticBleed, }, simulation::{ InitContext, Read, SimulationElement, SimulationElementVisitor, SimulatorReader, @@ -12,8 +13,8 @@ use crate::{ }; use super::{ - AirConditioningSystemOverhead, DuctTemperature, OverheadFlowSelector, PackFlow, PackFlowValve, - ZoneType, + AirConditioningSystemOverhead, DuctTemperature, OverheadFlowSelector, PackFlow, + PackFlowControllers, ZoneType, }; use std::time::Duration; @@ -27,14 +28,31 @@ use uom::si::{ velocity::knot, }; +#[derive(PartialEq, Clone, Copy)] +enum ACSCActiveComputer { + Primary, + Secondary, + None, +} + pub(super) struct AirConditioningSystemController { aircraft_state: AirConditioningStateManager, zone_controller: Vec>, - pack_flow_controller: PackFlowController, + pack_flow_controller: [PackFlowController; 2], + + primary_powered_by: Vec, + primary_is_powered: bool, + secondary_powered_by: Vec, + secondary_is_powered: bool, } impl AirConditioningSystemController { - pub fn new(context: &mut InitContext, cabin_zone_ids: &[ZoneType; ZONES]) -> Self { + pub fn new( + context: &mut InitContext, + cabin_zone_ids: &[ZoneType; ZONES], + primary_powered_by: Vec, + secondary_powered_by: Vec, + ) -> Self { let zone_controller = cabin_zone_ids .iter() .map(|id| ZoneController::new(context, id)) @@ -42,7 +60,14 @@ impl AirConditioningSystemController { Self { aircraft_state: AirConditioningStateManager::new(), zone_controller, - pack_flow_controller: PackFlowController::new(context), + pack_flow_controller: [ + PackFlowController::new(context, Pack(1)), + PackFlowController::new(context, Pack(2)), + ], + primary_powered_by, + primary_is_powered: false, + secondary_powered_by, + secondary_is_powered: false, } } @@ -51,40 +76,64 @@ impl AirConditioningSystemController { context: &UpdateContext, adirs: &impl GroundSpeed, acs_overhead: &AirConditioningSystemOverhead, - pack_flow_valve: &[PackFlowValve; 2], engines: [&impl EngineCorrectedN1; 2], engine_fire_push_buttons: &impl EngineFirePushButtons, - pneumatic: &(impl PneumaticBleed + EngineStartState), + pneumatic: &(impl EngineStartState + PackFlowValveState + PneumaticBleed), + pneumatic_overhead: &impl EngineBleedPushbutton, pressurization: &impl Cabin, pressurization_overhead: &PressurizationOverheadPanel, lgciu: [&impl LgciuWeightOnWheels; 2], ) { self.aircraft_state = self.aircraft_state.update(context, adirs, engines, lgciu); - self.pack_flow_controller.update( - &self.aircraft_state, - acs_overhead, - engine_fire_push_buttons, - pneumatic, - pressurization, - pressurization_overhead, - pack_flow_valve, - ); + + let operation_mode = self.operation_mode_determination(); + + for pack_flow_controller in self.pack_flow_controller.iter_mut() { + pack_flow_controller.update( + context, + &self.aircraft_state, + acs_overhead, + engines, + engine_fire_push_buttons, + pneumatic, + pneumatic_overhead, + pressurization, + pressurization_overhead, + operation_mode, + ); + } + + let pack_flow = self.pack_flow(); for zone in self.zone_controller.iter_mut() { zone.update( context, acs_overhead, - &self.pack_flow_controller, + pack_flow, pressurization, + &operation_mode, ) } } + fn operation_mode_determination(&self) -> ACSCActiveComputer { + // TODO: Add failures + if self.primary_is_powered { + ACSCActiveComputer::Primary + } else if self.secondary_is_powered { + ACSCActiveComputer::Secondary + } else { + ACSCActiveComputer::None + } + } + pub(super) fn pack_fault_determination( &self, - pack_flow_valve: &[PackFlowValve; 2], + pneumatic: &impl PackFlowValveState, ) -> [bool; 2] { - self.pack_flow_controller - .fcv_status_determination(pack_flow_valve) + [ + self.pack_flow_controller[Pack(1).to_index()].fcv_status_determination(pneumatic), + self.pack_flow_controller[Pack(2).to_index()].fcv_status_determination(pneumatic), + ] } } @@ -100,25 +149,31 @@ impl DuctTemperature for AirConditioningSystemController PackFlow for AirConditioningSystemController { fn pack_flow(&self) -> MassRate { - self.pack_flow_controller.pack_flow() + self.pack_flow_controller[0].pack_flow() + self.pack_flow_controller[1].pack_flow() } } -impl ControllerSignal - for AirConditioningSystemController -{ - fn signal(&self) -> Option { - self.pack_flow_controller.signal() +impl PackFlowControllers for AirConditioningSystemController { + fn pack_flow_controller(&self, pack_id: Pack) -> PackFlowController { + self.pack_flow_controller[pack_id.to_index()] } } impl SimulationElement for AirConditioningSystemController { fn accept(&mut self, visitor: &mut T) { accept_iterable!(self.zone_controller, visitor); - self.pack_flow_controller.accept(visitor); + accept_iterable!(self.pack_flow_controller, visitor); visitor.visit(self); } + + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.primary_is_powered = self.primary_powered_by.iter().all(|&p| buses.is_powered(p)); + self.secondary_is_powered = self + .secondary_powered_by + .iter() + .all(|&p| buses.is_powered(p)); + } } #[derive(Copy, Clone)] @@ -419,20 +474,37 @@ impl ZoneController { &mut self, context: &UpdateContext, acs_overhead: &AirConditioningSystemOverhead, - pack_flow: &impl PackFlow, + pack_flow: MassRate, pressurization: &impl Cabin, + operation_mode: &ACSCActiveComputer, ) { - self.zone_selected_temperature = acs_overhead.selected_cabin_temperature(self.zone_id); - self.duct_demand_temperature = - if pack_flow.pack_flow() < MassRate::new::(0.01) { - // When there's no pack flow, duct temperature is mostly determined by cabin recirculated air and ambient temperature - ThermodynamicTemperature::new::( - 0.8 * self.zone_measured_temperature.get::() - + 0.2 * context.ambient_temperature().get::(), - ) + self.zone_selected_temperature = if matches!(operation_mode, ACSCActiveComputer::Secondary) + { + // If the Zone controller is working on secondary power, the zones are controlled to + // 24 degrees by the secondary computer + ThermodynamicTemperature::new::(24.) + } else { + acs_overhead.selected_cabin_temperature(self.zone_id) + }; + self.duct_demand_temperature = if pack_flow < MassRate::new::(0.01) { + // When there's no pack flow, duct temperature is mostly determined by cabin recirculated + // air and ambient temperature + ThermodynamicTemperature::new::( + 0.8 * self.zone_measured_temperature.get::() + + 0.2 * context.ambient_temperature().get::(), + ) + } else if matches!(operation_mode, ACSCActiveComputer::None) { + // If unpowered or failed, the pack controller would take over and deliver a fixed 20deg + // for the cockpit and 10 for the cabin + // Simulated here until packs are modelled + if self.zone_id == 0 { + ThermodynamicTemperature::new::(20.) } else { - self.calculate_duct_temp_demand(context, pressurization) - }; + ThermodynamicTemperature::new::(10.) + } + } else { + self.calculate_duct_temp_demand(context, pressurization) + }; } fn calculate_duct_temp_demand( @@ -533,27 +605,53 @@ impl SimulationElement for ZoneController { } pub struct PackFlowValveSignal { - target_open_amount: [Ratio; 2], + target_open_amount: Ratio, } -impl PackFlowValveSignal { - pub fn new(target_open_amount: [Ratio; 2]) -> Self { +impl PneumaticValveSignal for PackFlowValveSignal { + fn new(target_open_amount: Ratio) -> Self { Self { target_open_amount } } - pub fn target_open_amount(&self, pack_id: usize) -> Ratio { - self.target_open_amount[pack_id - 1] + fn target_open_amount(&self) -> Ratio { + self.target_open_amount + } +} + +#[derive(Clone, Copy)] +/// Pack ID can be 1 or 2 +pub struct Pack(usize); + +impl Pack { + fn to_index(self) -> usize { + self.0 - 1 + } +} + +impl From for Pack { + fn from(value: usize) -> Self { + if value != 1 && value != 2 { + panic!("Pack ID number out of bounds.") + } else { + Pack(value) + } } } -struct PackFlowController { +#[derive(Copy, Clone)] +pub struct PackFlowController { pack_flow_id: VariableIdentifier, + id: usize, flow_demand: Ratio, - fcv_1_open_allowed: bool, - fcv_2_open_allowed: bool, - should_open_fcv: [bool; 2], + fcv_open_allowed: bool, + should_open_fcv: bool, pack_flow: MassRate, + pack_flow_demand: MassRate, + pid: PidController, + operation_mode: ACSCActiveComputer, + + fcv_timer_open: Duration, } impl PackFlowController { @@ -567,63 +665,88 @@ impl PackFlowController { const FLOW_CONSTANT_C: f64 = 0.5675; // kg/s const FLOW_CONSTANT_XCAB: f64 = 0.00001828; // kg(feet*s) - fn new(context: &mut InitContext) -> Self { + fn new(context: &mut InitContext, pack_id: Pack) -> Self { Self { - pack_flow_id: context.get_identifier("COND_PACK_FLOW".to_owned()), + pack_flow_id: context.get_identifier(Self::pack_flow_id(pack_id.to_index())), + id: pack_id.to_index(), flow_demand: Ratio::new::(0.), - fcv_1_open_allowed: false, - fcv_2_open_allowed: false, - should_open_fcv: [false, false], + fcv_open_allowed: false, + should_open_fcv: false, pack_flow: MassRate::new::(0.), + pack_flow_demand: MassRate::new::(0.), + pid: PidController::new(0.01, 1.5, 0., 0., 1., 0., 1.), + operation_mode: ACSCActiveComputer::None, + + fcv_timer_open: Duration::from_secs(0), } } + fn pack_flow_id(number: usize) -> String { + format!("COND_PACK_FLOW_{}", number + 1) + } + fn update( &mut self, + context: &UpdateContext, aircraft_state: &AirConditioningStateManager, acs_overhead: &AirConditioningSystemOverhead, + engines: [&impl EngineCorrectedN1; 2], engine_fire_push_buttons: &impl EngineFirePushButtons, - pneumatic: &(impl PneumaticBleed + EngineStartState), + pneumatic: &(impl EngineStartState + PackFlowValveState + PneumaticBleed), + pneumatic_overhead: &impl EngineBleedPushbutton, pressurization: &impl Cabin, pressurization_overhead: &PressurizationOverheadPanel, - pack_flow_valve: &[PackFlowValve; 2], + operation_mode: ACSCActiveComputer, ) { // TODO: Add overheat protection - self.flow_demand = self.flow_demand_determination( - aircraft_state, - pack_flow_valve, - acs_overhead, - pneumatic, - ); - self.fcv_open_allowed_determination( + self.operation_mode = operation_mode; + self.flow_demand = self.flow_demand_determination(aircraft_state, acs_overhead, pneumatic); + self.fcv_open_allowed = self.fcv_open_allowed_determination( acs_overhead, engine_fire_push_buttons, pressurization_overhead, pneumatic, ); - self.should_open_fcv = [self.fcv_1_open_allowed, self.fcv_2_open_allowed]; - self.pack_flow = self.pack_flow_calculation(pack_flow_valve, pressurization); + self.should_open_fcv = + self.fcv_open_allowed && self.can_move_fcv(engines, pneumatic, pneumatic_overhead); + self.update_timer(context); + self.pack_flow_demand = self.absolute_flow_calculation(pressurization); + + self.pid + .change_setpoint(self.pack_flow_demand.get::()); + + self.pid.next_control_output( + pneumatic + .pack_flow_valve_air_flow(self.id) + .get::(), + Some(context.delta()), + ); + + self.pack_flow = pneumatic.pack_flow_valve_air_flow(self.id); } - fn pack_start_condition_determination(&self, pack_flow_valve: &[PackFlowValve; 2]) -> bool { + fn pack_start_condition_determination(&self) -> bool { // Returns true when one of the packs is in start condition - pack_flow_valve - .iter() - .any(|fcv| fcv.fcv_timer() <= Duration::from_secs_f64(Self::PACK_START_TIME_SECOND)) + self.fcv_timer_open <= Duration::from_secs_f64(Self::PACK_START_TIME_SECOND) } fn flow_demand_determination( &self, aircraft_state: &AirConditioningStateManager, - pack_flow_valve: &[PackFlowValve; 2], acs_overhead: &AirConditioningSystemOverhead, - pneumatic: &(impl PneumaticBleed + EngineStartState), + pneumatic: &(impl EngineStartState + PackFlowValveState + PneumaticBleed), ) -> Ratio { + if matches!(self.operation_mode, ACSCActiveComputer::None) { + // If the computer is unpowered, return previous flow demand + return self.flow_demand; + } else if matches!(self.operation_mode, ACSCActiveComputer::Secondary) { + // If Secondary computer is active flow setting optimization is not available + return Ratio::new::(100.); + } let mut intermediate_flow: Ratio = acs_overhead.flow_selector_position().into(); - let pack_in_start_condition = self.pack_start_condition_determination(pack_flow_valve); // TODO: Add "insufficient performance" based on Pack Mixer Temperature Demand - if pack_in_start_condition { + if self.pack_start_condition_determination() { intermediate_flow = intermediate_flow.max(Ratio::new::(Self::PACK_START_FLOW_LIMIT)); } @@ -632,7 +755,9 @@ impl PackFlowController { intermediate_flow.max(Ratio::new::(Self::APU_SUPPLY_FLOW_LIMIT)); } // Single pack operation determination - if pack_flow_valve[0].fcv_is_open() != pack_flow_valve[1].fcv_is_open() { + if (pneumatic.pack_flow_valve_open_amount(self.id) > Ratio::new::(0.)) + != (pneumatic.pack_flow_valve_open_amount(1 - self.id) > Ratio::new::(0.)) + { intermediate_flow = intermediate_flow.max(Ratio::new::(Self::ONE_PACK_FLOW_LIMIT)); } @@ -646,21 +771,20 @@ impl PackFlowController { intermediate_flow = intermediate_flow.min(Ratio::new::(Self::FLOW_REDUCTION_LIMIT)); } - // If both flow control valves are closed, the flow indication is in the Lo position - if pack_flow_valve.iter().all(|fcv| !fcv.fcv_is_open()) { + // If the flow control valve is closed the indication is in the Lo position + if !(pneumatic.pack_flow_valve_open_amount(self.id) > Ratio::new::(0.)) { OverheadFlowSelector::Lo.into() } else { intermediate_flow.max(Ratio::new::(Self::BACKFLOW_LIMIT)) } } - // This calculates the flow based on the demand, when the packs are modelled this needs to be changed - // so the demand actuates the valve, and then the flow is calculated based on that fn absolute_flow_calculation(&self, pressurization: &impl Cabin) -> MassRate { - let absolute_flow = self.flow_demand.get::() - * (Self::FLOW_CONSTANT_XCAB * pressurization.altitude().get::() - + Self::FLOW_CONSTANT_C); - MassRate::new::(absolute_flow) + MassRate::new::( + self.flow_demand.get::() + * (Self::FLOW_CONSTANT_XCAB * pressurization.altitude().get::() + + Self::FLOW_CONSTANT_C), + ) } fn fcv_open_allowed_determination( @@ -669,53 +793,71 @@ impl PackFlowController { engine_fire_push_buttons: &impl EngineFirePushButtons, pressurization_overhead: &PressurizationOverheadPanel, pneumatic: &(impl PneumaticBleed + EngineStartState), - ) { - // Flow Control Valve 1 - self.fcv_1_open_allowed = acs_overhead.pack_pushbuttons_state()[0] - && !(pneumatic.left_engine_state() == EngineState::Starting) - && (!(pneumatic.right_engine_state() == EngineState::Starting) - || !pneumatic.engine_crossbleed_is_on()) - && (pneumatic.engine_mode_selector() != EngineModeSelector::Ignition - || (pneumatic.left_engine_state() != EngineState::Off - && pneumatic.left_engine_state() != EngineState::Shutting)) - && !engine_fire_push_buttons.is_released(1) - && !pressurization_overhead.ditching_is_on(); - // && ! pack 1 overheat - // Flow Control Valve 2 - self.fcv_2_open_allowed = acs_overhead.pack_pushbuttons_state()[1] - && !(pneumatic.right_engine_state() == EngineState::Starting) - && (!(pneumatic.left_engine_state() == EngineState::Starting) - || !pneumatic.engine_crossbleed_is_on()) - && (pneumatic.engine_mode_selector() != EngineModeSelector::Ignition - || (pneumatic.right_engine_state() != EngineState::Off - && pneumatic.right_engine_state() != EngineState::Shutting)) - && !engine_fire_push_buttons.is_released(2) - && !pressurization_overhead.ditching_is_on(); - // && ! pack 2 overheat - } - - fn fcv_status_determination(&self, pack_flow_valve: &[PackFlowValve; 2]) -> [bool; 2] { - [ - pack_flow_valve[0].fcv_is_open() != self.fcv_1_open_allowed, - pack_flow_valve[1].fcv_is_open() != self.fcv_2_open_allowed, - ] + ) -> bool { + match Pack::from(self.id + 1) { + Pack(1) => { + acs_overhead.pack_pushbuttons_state()[0] + && !(pneumatic.left_engine_state() == EngineState::Starting) + && (!(pneumatic.right_engine_state() == EngineState::Starting) + || !pneumatic.engine_crossbleed_is_on()) + && (pneumatic.engine_mode_selector() != EngineModeSelector::Ignition + || (pneumatic.left_engine_state() != EngineState::Off + && pneumatic.left_engine_state() != EngineState::Shutting)) + && !engine_fire_push_buttons.is_released(1) + && !pressurization_overhead.ditching_is_on() + // && ! pack 1 overheat + } + Pack(2) => { + acs_overhead.pack_pushbuttons_state()[1] + && !(pneumatic.right_engine_state() == EngineState::Starting) + && (!(pneumatic.left_engine_state() == EngineState::Starting) + || !pneumatic.engine_crossbleed_is_on()) + && (pneumatic.engine_mode_selector() != EngineModeSelector::Ignition + || (pneumatic.right_engine_state() != EngineState::Off + && pneumatic.right_engine_state() != EngineState::Shutting)) + && !engine_fire_push_buttons.is_released(2) + && !pressurization_overhead.ditching_is_on() + // && ! pack 2 overheat + } + _ => panic!("Pack ID number out of bounds."), + } } - fn pack_flow_calculation( + fn can_move_fcv( &self, - pack_flow_valve: &[PackFlowValve; 2], - pressurization: &impl Cabin, - ) -> MassRate { - let absolute_flow: MassRate = self.absolute_flow_calculation(pressurization); - if pack_flow_valve.iter().any(|fcv| fcv.fcv_is_open()) { - // Single pack operation determination - if pack_flow_valve[0].fcv_is_open() != pack_flow_valve[1].fcv_is_open() { - absolute_flow - } else { - absolute_flow * 2. - } + engines: [&impl EngineCorrectedN1; 2], + pneumatic: &(impl PneumaticBleed + EngineStartState), + pneumatic_overhead: &impl EngineBleedPushbutton, + ) -> bool { + // Pneumatic overhead represents engine bleed pushbutton for left [0] and right [1] engine(s) + ((engines[self.id].corrected_n1() >= Ratio::new::(15.) + && pneumatic_overhead.engine_bleed_pushbuttons_are_auto()[(self.id == 1) as usize]) + || (engines[(self.id == 0) as usize].corrected_n1() >= Ratio::new::(15.) + && pneumatic_overhead.engine_bleed_pushbuttons_are_auto()[(self.id == 0) as usize] + && pneumatic.engine_crossbleed_is_on())) + || pneumatic.apu_bleed_is_on() + } + + fn update_timer(&mut self, context: &UpdateContext) { + if self.should_open_fcv { + self.fcv_timer_open += context.delta(); } else { + self.fcv_timer_open = Duration::from_secs(0); + } + } + + fn fcv_status_determination(&self, pneumatic: &impl PackFlowValveState) -> bool { + (pneumatic.pack_flow_valve_open_amount(self.id) > Ratio::new::(0.)) + != self.fcv_open_allowed + } + + #[cfg(test)] + fn pack_flow_demand(&self) -> MassRate { + // Only send signal to move the valve if the computer is powered + if !matches!(self.operation_mode, ACSCActiveComputer::None) && !self.should_open_fcv { MassRate::new::(0.) + } else { + self.pack_flow_demand } } } @@ -728,18 +870,17 @@ impl PackFlow for PackFlowController { impl ControllerSignal for PackFlowController { fn signal(&self) -> Option { - let target_open: Vec = self - .should_open_fcv - .iter() - .map(|&fcv| { - if fcv { - Ratio::new::(100.) - } else { - Ratio::new::(0.) - } - }) - .collect(); - Some(PackFlowValveSignal::new([target_open[0], target_open[1]])) + // Only send signal to move the valve if the computer is powered + if !matches!(self.operation_mode, ACSCActiveComputer::None) { + let target_open: Ratio = if self.should_open_fcv { + Ratio::new::(self.pid.output()) + } else { + Ratio::new::(0.) + }; + Some(PackFlowValveSignal::new(target_open)) + } else { + None + } } } @@ -754,17 +895,21 @@ mod acs_controller_tests { use super::*; use crate::{ air_conditioning::cabin_air::CabinZone, + electrical::{test::TestElectricitySource, ElectricalBus, Electricity}, overhead::AutoOffFaultPushButton, - pneumatic::{valve::DefaultValve, EngineModeSelector}, - shared::{EngineBleedPushbutton, PneumaticValve}, + pneumatic::{ + valve::{DefaultValve, PneumaticExhaust}, + ControllablePneumaticValve, EngineModeSelector, PneumaticContainer, PneumaticPipe, + }, + shared::{EngineBleedPushbutton, PneumaticValve, PotentialOrigin}, simulation::{ test::{ReadByName, SimulationTestBed, TestBed, WriteByName}, Aircraft, SimulationElement, SimulationElementVisitor, UpdateContext, }, }; use uom::si::{ - length::foot, pressure::hectopascal, thermodynamic_temperature::degree_celsius, - velocity::knot, volume::cubic_meter, + length::foot, pressure::hectopascal, pressure::psi, + thermodynamic_temperature::degree_celsius, velocity::knot, volume::cubic_meter, }; struct TestAdirs { @@ -963,19 +1108,39 @@ mod acs_controller_tests { struct TestPneumatic { apu_bleed_air_valve: DefaultValve, + engine_bleed: [TestEngineBleed; 2], cross_bleed_valve: DefaultValve, fadec: TestFadec, + packs: [TestPneumaticPackComplex; 2], } impl TestPneumatic { fn new(context: &mut InitContext) -> Self { Self { apu_bleed_air_valve: DefaultValve::new_closed(), + engine_bleed: [TestEngineBleed::new(), TestEngineBleed::new()], cross_bleed_valve: DefaultValve::new_closed(), fadec: TestFadec::new(context), + packs: [ + TestPneumaticPackComplex::new(1), + TestPneumaticPackComplex::new(2), + ], } } + fn update( + &mut self, + context: &UpdateContext, + pack_flow_valve_signals: &impl PackFlowControllers<2>, + ) { + self.packs + .iter_mut() + .zip(self.engine_bleed.iter_mut()) + .for_each(|(pack, engine_bleed)| { + pack.update(context, engine_bleed, pack_flow_valve_signals) + }); + } + fn set_apu_bleed_air_valve_open(&mut self) { self.apu_bleed_air_valve = DefaultValve::new_open(); } @@ -1008,6 +1173,14 @@ mod acs_controller_tests { self.fadec.engine_mode_selector() } } + impl PackFlowValveState for TestPneumatic { + fn pack_flow_valve_open_amount(&self, pack_id: usize) -> Ratio { + self.packs[pack_id].pfv_open_amount() + } + fn pack_flow_valve_air_flow(&self, pack_id: usize) -> MassRate { + self.packs[pack_id].pack_flow_valve_air_flow() + } + } impl SimulationElement for TestPneumatic { fn accept(&mut self, visitor: &mut V) { self.fadec.accept(visitor); @@ -1016,6 +1189,99 @@ mod acs_controller_tests { } } + struct TestEngineBleed { + precooler_outlet_pipe: PneumaticPipe, + } + impl TestEngineBleed { + fn new() -> Self { + Self { + precooler_outlet_pipe: PneumaticPipe::new( + Volume::new::(2.5), + Pressure::new::(14.7), + ThermodynamicTemperature::new::(15.), + ), + } + } + } + impl PneumaticContainer for TestEngineBleed { + fn pressure(&self) -> Pressure { + self.precooler_outlet_pipe.pressure() + } + + fn volume(&self) -> Volume { + self.precooler_outlet_pipe.volume() + } + + fn temperature(&self) -> ThermodynamicTemperature { + self.precooler_outlet_pipe.temperature() + } + + fn mass(&self) -> Mass { + self.precooler_outlet_pipe.mass() + } + + fn change_fluid_amount( + &mut self, + fluid_amount: Mass, + fluid_temperature: ThermodynamicTemperature, + fluid_pressure: Pressure, + ) { + self.precooler_outlet_pipe.change_fluid_amount( + fluid_amount, + fluid_temperature, + fluid_pressure, + ) + } + + fn update_temperature(&mut self, temperature: TemperatureInterval) { + self.precooler_outlet_pipe.update_temperature(temperature); + } + } + + struct TestPneumaticPackComplex { + engine_number: usize, + pack_container: PneumaticPipe, + exhaust: PneumaticExhaust, + pack_flow_valve: DefaultValve, + } + impl TestPneumaticPackComplex { + fn new(engine_number: usize) -> Self { + Self { + engine_number, + pack_container: PneumaticPipe::new( + Volume::new::(2.), + Pressure::new::(14.7), + ThermodynamicTemperature::new::(15.), + ), + exhaust: PneumaticExhaust::new(0.3, 0.3, Pressure::new::(0.)), + pack_flow_valve: DefaultValve::new_closed(), + } + } + fn update( + &mut self, + context: &UpdateContext, + from: &mut impl PneumaticContainer, + pack_flow_valve_signals: &impl PackFlowControllers<2>, + ) { + self.pack_flow_valve.update_open_amount( + &pack_flow_valve_signals.pack_flow_controller(self.engine_number.into()), + ); + self.pack_flow_valve + .update_move_fluid(context, from, &mut self.pack_container); + self.exhaust + .update_move_fluid(context, &mut self.pack_container); + } + fn pfv_open_amount(&self) -> Ratio { + self.pack_flow_valve.open_amount() + } + fn pack_flow_valve_air_flow(&self) -> MassRate { + // Note for the future: + // This is a little hack to make the tests pass without simulating the whole pneumatic system + // I'd recommend any new tests to be set up in the top level or directly in pneumatic + self.pack_flow_valve.fluid_flow() * 2e3 + } + } + struct TestCabin { cockpit: CabinZone<2>, passenger_cabin: CabinZone<2>, @@ -1083,7 +1349,6 @@ mod acs_controller_tests { struct TestAircraft { acsc: AirConditioningSystemController<2>, acs_overhead: AirConditioningSystemOverhead<2>, - pack_flow_valve: [PackFlowValve; 2], adirs: TestAdirs, engine_1: TestEngine, engine_2: TestEngine, @@ -1095,6 +1360,14 @@ mod acs_controller_tests { lgciu1: TestLgciu, lgciu2: TestLgciu, test_cabin: TestCabin, + powered_dc_source_1: TestElectricitySource, + powered_ac_source_1: TestElectricitySource, + powered_dc_source_2: TestElectricitySource, + powered_ac_source_2: TestElectricitySource, + dc_1_bus: ElectricalBus, + ac_1_bus: ElectricalBus, + dc_2_bus: ElectricalBus, + ac_2_bus: ElectricalBus, } impl TestAircraft { fn new(context: &mut InitContext) -> Self { @@ -1102,15 +1375,19 @@ mod acs_controller_tests { acsc: AirConditioningSystemController::new( context, &[ZoneType::Cockpit, ZoneType::Cabin(1)], + vec![ + ElectricalBusType::DirectCurrent(1), + ElectricalBusType::AlternatingCurrent(1), + ], + vec![ + ElectricalBusType::DirectCurrent(2), + ElectricalBusType::AlternatingCurrent(2), + ], ), acs_overhead: AirConditioningSystemOverhead::new( context, &[ZoneType::Cockpit, ZoneType::Cabin(1)], ), - pack_flow_valve: [ - PackFlowValve::new(context, 1), - PackFlowValve::new(context, 2), - ], adirs: TestAdirs::new(), engine_1: TestEngine::new(Ratio::new::(0.)), engine_2: TestEngine::new(Ratio::new::(0.)), @@ -1122,6 +1399,26 @@ mod acs_controller_tests { lgciu1: TestLgciu::new(false), lgciu2: TestLgciu::new(false), test_cabin: TestCabin::new(context), + powered_dc_source_1: TestElectricitySource::powered( + context, + PotentialOrigin::Battery(1), + ), + powered_ac_source_1: TestElectricitySource::powered( + context, + PotentialOrigin::EngineGenerator(1), + ), + powered_dc_source_2: TestElectricitySource::powered( + context, + PotentialOrigin::Battery(2), + ), + powered_ac_source_2: TestElectricitySource::powered( + context, + PotentialOrigin::EngineGenerator(2), + ), + dc_1_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(1)), + ac_1_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(1)), + dc_2_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(2)), + ac_2_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(2)), } } @@ -1154,35 +1451,74 @@ mod acs_controller_tests { fn set_cross_bleed_valve_open(&mut self) { self.pneumatic.set_cross_bleed_valve_open(); } + + fn unpower_dc_1_bus(&mut self) { + self.powered_dc_source_1.unpower(); + } + + fn power_dc_1_bus(&mut self) { + self.powered_dc_source_1.power(); + } + + fn unpower_ac_1_bus(&mut self) { + self.powered_ac_source_1.unpower(); + } + + fn power_ac_1_bus(&mut self) { + self.powered_ac_source_1.power(); + } + + fn unpower_dc_2_bus(&mut self) { + self.powered_dc_source_2.unpower(); + } + + fn power_dc_2_bus(&mut self) { + self.powered_dc_source_2.power(); + } + + fn unpower_ac_2_bus(&mut self) { + self.powered_ac_source_2.unpower(); + } + + fn power_ac_2_bus(&mut self) { + self.powered_ac_source_2.power(); + } } impl Aircraft for TestAircraft { + fn update_before_power_distribution( + &mut self, + _context: &UpdateContext, + electricity: &mut Electricity, + ) { + electricity.supplied_by(&self.powered_dc_source_1); + electricity.supplied_by(&self.powered_ac_source_1); + electricity.supplied_by(&self.powered_dc_source_2); + electricity.supplied_by(&self.powered_ac_source_2); + electricity.flow(&self.powered_dc_source_1, &self.dc_1_bus); + electricity.flow(&self.powered_ac_source_1, &self.ac_1_bus); + electricity.flow(&self.powered_dc_source_2, &self.dc_2_bus); + electricity.flow(&self.powered_ac_source_2, &self.ac_2_bus); + } + fn update_after_power_distribution(&mut self, context: &UpdateContext) { + self.pneumatic.update(context, &self.acsc); self.acsc.update( context, &self.adirs, &self.acs_overhead, - &self.pack_flow_valve, [&self.engine_1, &self.engine_2], &self.engine_fire_push_buttons, &self.pneumatic, + &self.pneumatic_overhead, &self.pressurization, &self.pressurization_overhead, [&self.lgciu1, &self.lgciu2], ); self.test_cabin .update(context, &self.acsc, &self.acsc, &self.pressurization); - for fcv in self.pack_flow_valve.iter_mut() { - fcv.update( - context, - &self.acsc, - [&self.engine_1, &self.engine_2], - &self.pneumatic, - &self.pneumatic_overhead, - ); - } - self.acs_overhead.set_pack_pushbutton_fault( - self.acsc.pack_fault_determination(&self.pack_flow_valve), - ); + + self.acs_overhead + .set_pack_pushbutton_fault(self.acsc.pack_fault_determination(&self.pneumatic)); } } impl SimulationElement for TestAircraft { @@ -1344,6 +1680,46 @@ mod acs_controller_tests { ) } + fn unpowered_dc_1_bus(mut self) -> Self { + self.command(|a| a.unpower_dc_1_bus()); + self + } + + fn powered_dc_1_bus(mut self) -> Self { + self.command(|a| a.power_dc_1_bus()); + self + } + + fn unpowered_ac_1_bus(mut self) -> Self { + self.command(|a| a.unpower_ac_1_bus()); + self + } + + fn powered_ac_1_bus(mut self) -> Self { + self.command(|a| a.power_ac_1_bus()); + self + } + + fn unpowered_dc_2_bus(mut self) -> Self { + self.command(|a| a.unpower_dc_2_bus()); + self + } + + fn powered_dc_2_bus(mut self) -> Self { + self.command(|a| a.power_dc_2_bus()); + self + } + + fn unpowered_ac_2_bus(mut self) -> Self { + self.command(|a| a.unpower_ac_2_bus()); + self + } + + fn powered_ac_2_bus(mut self) -> Self { + self.command(|a| a.power_ac_2_bus()); + self + } + fn command_selected_temperature( mut self, temp_array: [ThermodynamicTemperature; 2], @@ -1430,7 +1806,10 @@ mod acs_controller_tests { } fn pack_flow(&self) -> MassRate { - self.query(|a| a.acsc.pack_flow()) + self.query(|a| { + a.acsc.pack_flow_controller[0].pack_flow_demand() + + a.acsc.pack_flow_controller[1].pack_flow_demand() + }) } fn pack_1_has_fault(&mut self) -> bool { @@ -1729,7 +2108,7 @@ mod acs_controller_tests { .command_selected_temperature( [ThermodynamicTemperature::new::(24.); 2], ) - .iterate_with_delta(100, Duration::from_secs(10)); + .iterate_with_delta(200, Duration::from_secs(10)); assert!( (test_bed.duct_demand_temperature()[1].get::() - 24.).abs() < 1. @@ -1811,7 +2190,7 @@ mod acs_controller_tests { .command_selected_temperature( [ThermodynamicTemperature::new::(30.); 2], ) - .iterate_with_delta(3, Duration::from_secs(1)); + .iterate_with_delta(100, Duration::from_secs(1)); let mut previous_temp = test_bed.duct_demand_temperature()[1]; test_bed.run(); @@ -1823,9 +2202,6 @@ mod acs_controller_tests { let final_temp_diff = test_bed.duct_demand_temperature()[1].get::() - previous_temp.get::(); - assert!( - (test_bed.duct_demand_temperature()[1].get::() - 30.).abs() < 1. - ); assert!(initial_temp_diff > final_temp_diff); } @@ -1840,7 +2216,7 @@ mod acs_controller_tests { .command_selected_temperature( [ThermodynamicTemperature::new::(24.); 2], ) - .iterate_with_delta(100, Duration::from_secs(10)); + .iterate_with_delta(200, Duration::from_secs(10)); let initial_temperature = test_bed.duct_demand_temperature()[1]; @@ -1864,7 +2240,7 @@ mod acs_controller_tests { test_bed.command_measured_temperature( [ThermodynamicTemperature::new::(24.); 2], ); - test_bed = test_bed.iterate_with_delta(3, Duration::from_secs(1)); + test_bed = test_bed.iterate_with_delta(200, Duration::from_secs(1)); assert!( (test_bed.duct_demand_temperature()[1].get::() - 8.).abs() < 1. ); @@ -1883,6 +2259,77 @@ mod acs_controller_tests { (test_bed.duct_demand_temperature()[1].get::() - 2.).abs() < 1. ); } + + #[test] + fn knobs_dont_affect_duct_demand_when_primary_unpowered() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .and() + .unpowered_dc_1_bus() + .unpowered_ac_1_bus() + .command_selected_temperature( + [ThermodynamicTemperature::new::(30.); 2], + ); + + test_bed = test_bed.iterate_with_delta(100, Duration::from_secs(10)); + + assert!( + (test_bed.duct_demand_temperature()[1].get::() - 24.).abs() < 1. + ); + } + + #[test] + fn unpowering_the_system_gives_control_to_packs() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .iterate(2) + .and() + .unpowered_dc_1_bus() + .unpowered_ac_1_bus() + .unpowered_dc_2_bus() + .unpowered_ac_2_bus() + .command_selected_temperature( + [ThermodynamicTemperature::new::(30.); 2], + ); + + test_bed = test_bed.iterate_with_delta(100, Duration::from_secs(10)); + + assert!( + (test_bed.duct_demand_temperature()[0].get::() - 20.).abs() < 1. + ); + assert!( + (test_bed.duct_demand_temperature()[1].get::() - 10.).abs() < 1. + ); + } + + #[test] + fn unpowering_and_repowering_primary_behaves_as_expected() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .and() + .unpowered_dc_1_bus() + .unpowered_ac_1_bus() + .command_selected_temperature( + [ThermodynamicTemperature::new::(30.); 2], + ); + test_bed = test_bed.iterate_with_delta(100, Duration::from_secs(10)); + assert!( + (test_bed.duct_demand_temperature()[1].get::() - 24.).abs() < 1. + ); + + test_bed = test_bed.powered_dc_1_bus().powered_ac_1_bus(); + test_bed = test_bed.iterate_with_delta(100, Duration::from_secs(10)); + assert!(test_bed.duct_demand_temperature()[1].get::() > 24.); + } } mod pack_flow_controller_tests { @@ -1984,6 +2431,21 @@ mod acs_controller_tests { assert!(test_bed.pack_flow() > initial_flow); } + #[test] + fn pack_flow_increases_when_pack_in_start_condition() { + let mut test_bed = test_bed().with().both_packs_on().and().engine_idle(); + + test_bed.command_pack_flow_selector_position(0); + test_bed = test_bed.iterate(2); + + let initial_flow = test_bed.pack_flow(); + + test_bed.run_with_delta(Duration::from_secs(31)); + test_bed.run(); + + assert!(test_bed.pack_flow() < initial_flow); + } + #[test] fn pack_flow_reduces_when_single_pack_operation() { let mut test_bed = test_bed() @@ -2178,5 +2640,107 @@ mod acs_controller_tests { assert!(test_bed.pack_1_has_fault()); assert!(test_bed.pack_2_has_fault()); } + + #[test] + fn pack_flow_controller_is_unresponsive_when_unpowered() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .iterate(2); + assert!(test_bed.pack_flow() > MassRate::new::(0.)); + + test_bed = test_bed + .unpowered_dc_1_bus() + .unpowered_ac_1_bus() + .unpowered_dc_2_bus() + .unpowered_ac_2_bus(); + test_bed.command_ditching_on(); + test_bed = test_bed.iterate(2); + + assert!(test_bed.pack_flow() > MassRate::new::(0.)); + } + + #[test] + fn unpowering_ac_or_dc_unpowers_system() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .iterate(2); + assert!(test_bed.pack_flow() > MassRate::new::(0.)); + + test_bed = test_bed.unpowered_dc_1_bus().unpowered_ac_2_bus(); + test_bed.command_ditching_on(); + test_bed = test_bed.iterate(2); + assert!(test_bed.pack_flow() > MassRate::new::(0.)); + + test_bed = test_bed + .powered_dc_1_bus() + .unpowered_ac_1_bus() + .unpowered_dc_2_bus() + .powered_ac_2_bus(); + test_bed = test_bed.iterate(2); + assert!(test_bed.pack_flow() > MassRate::new::(0.)); + + test_bed = test_bed.powered_ac_1_bus().powered_dc_2_bus().iterate(2); + assert_eq!( + test_bed.pack_flow(), + MassRate::new::(0.), + ); + } + + #[test] + fn pack_flow_loses_optimization_when_secondary_computer_active() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .iterate(2); + + let initial_flow = test_bed.pack_flow(); + test_bed.command_apu_bleed_on(); + test_bed.run(); + assert!(test_bed.pack_flow() > initial_flow); + + test_bed = test_bed.unpowered_dc_1_bus().unpowered_ac_1_bus(); + test_bed.command_pack_flow_selector_position(0); + test_bed = test_bed.iterate(2); + assert_eq!(test_bed.pack_flow(), initial_flow); + } + + #[test] + fn pack_flow_controller_signals_resets_after_power_reset() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .iterate(2); + assert!(test_bed.pack_flow() > MassRate::new::(0.)); + + test_bed = test_bed + .unpowered_dc_1_bus() + .unpowered_ac_1_bus() + .unpowered_dc_2_bus() + .unpowered_ac_2_bus(); + test_bed.command_ditching_on(); + test_bed = test_bed.iterate(2); + assert!(test_bed.pack_flow() > MassRate::new::(0.)); + + test_bed = test_bed + .powered_dc_1_bus() + .powered_ac_1_bus() + .powered_dc_2_bus() + .powered_ac_2_bus() + .iterate(2); + assert_eq!( + test_bed.pack_flow(), + MassRate::new::(0.), + ); + } } } diff --git a/src/systems/systems/src/air_conditioning/mod.rs b/src/systems/systems/src/air_conditioning/mod.rs index d6fabe6e315b..f0f9a1591d09 100644 --- a/src/systems/systems/src/air_conditioning/mod.rs +++ b/src/systems/systems/src/air_conditioning/mod.rs @@ -1,19 +1,19 @@ -use self::acs_controller::{AirConditioningSystemController, PackFlowValveSignal}; +use self::acs_controller::{AirConditioningSystemController, Pack, PackFlowController}; use crate::{ overhead::{OnOffFaultPushButton, ValueKnob}, pressurization::PressurizationOverheadPanel, shared::{ - Cabin, ControllerSignal, EngineBleedPushbutton, EngineCorrectedN1, EngineFirePushButtons, - EngineStartState, GroundSpeed, LgciuWeightOnWheels, PneumaticBleed, + Cabin, ElectricalBusType, EngineBleedPushbutton, EngineCorrectedN1, EngineFirePushButtons, + EngineStartState, GroundSpeed, LgciuWeightOnWheels, PackFlowValveState, PneumaticBleed, }, simulation::{ InitContext, Read, Reader, SimulationElement, SimulationElementVisitor, SimulatorReader, - SimulatorWriter, UpdateContext, VariableIdentifier, Write, Writer, + UpdateContext, VariableIdentifier, Write, Writer, }, }; -use std::{fmt::Display, time::Duration}; +use std::fmt::Display; use uom::si::{ f64::*, mass_rate::kilogram_per_second, pressure::hectopascal, ratio::percent, @@ -31,6 +31,10 @@ pub trait PackFlow { fn pack_flow(&self) -> MassRate; } +pub trait PackFlowControllers { + fn pack_flow_controller(&self, pack_id: Pack) -> PackFlowController; +} + pub enum ZoneType { Cockpit, Cabin(u8), @@ -62,21 +66,26 @@ impl Display for ZoneType { pub struct AirConditioningSystem { acs_overhead: AirConditioningSystemOverhead, acsc: AirConditioningSystemController, - pack_flow_valves: [PackFlowValve; 2], // TODO: pack: [AirConditioningPack; 2], // TODO: mixer_unit: MixerUnit, // TODO: trim_air_system: TrimAirSystem, } impl AirConditioningSystem { - pub fn new(context: &mut InitContext, cabin_zones: [ZoneType; ZONES]) -> Self { + pub fn new( + context: &mut InitContext, + cabin_zones: [ZoneType; ZONES], + acsc_primary_powered_by: Vec, + acsc_secondary_powered_by: Vec, + ) -> Self { Self { acs_overhead: AirConditioningSystemOverhead::new(context, &cabin_zones), - acsc: AirConditioningSystemController::new(context, &cabin_zones), - pack_flow_valves: [ - PackFlowValve::new(context, 1), - PackFlowValve::new(context, 2), - ], + acsc: AirConditioningSystemController::new( + context, + &cabin_zones, + acsc_primary_powered_by, + acsc_secondary_powered_by, + ), } } @@ -86,7 +95,7 @@ impl AirConditioningSystem { adirs: &impl GroundSpeed, engines: [&impl EngineCorrectedN1; 2], engine_fire_push_buttons: &impl EngineFirePushButtons, - pneumatic: &(impl PneumaticBleed + EngineStartState), + pneumatic: &(impl EngineStartState + PackFlowValveState + PneumaticBleed), pneumatic_overhead: &impl EngineBleedPushbutton, pressurization: &impl Cabin, pressurization_overhead: &PressurizationOverheadPanel, @@ -96,21 +105,17 @@ impl AirConditioningSystem { context, adirs, &self.acs_overhead, - &self.pack_flow_valves, engines, engine_fire_push_buttons, pneumatic, + pneumatic_overhead, pressurization, pressurization_overhead, lgciu, ); - for pack_fv in self.pack_flow_valves.iter_mut() { - pack_fv.update(context, &self.acsc, engines, pneumatic, pneumatic_overhead); - } - self.acs_overhead - .set_pack_pushbutton_fault(self.acsc.pack_fault_determination(&self.pack_flow_valves)); + .set_pack_pushbutton_fault(self.acsc.pack_fault_determination(pneumatic)); } } @@ -126,11 +131,16 @@ impl PackFlow for AirConditioningSystem { } } +impl PackFlowControllers for AirConditioningSystem { + fn pack_flow_controller(&self, pack_id: Pack) -> PackFlowController { + self.acsc.pack_flow_controller(pack_id) + } +} + impl SimulationElement for AirConditioningSystem { fn accept(&mut self, visitor: &mut V) { self.acs_overhead.accept(visitor); self.acsc.accept(visitor); - accept_iterable!(self.pack_flow_valves, visitor); visitor.visit(self); } @@ -227,81 +237,6 @@ impl From for Ratio { } } -struct PackFlowValve { - pack_flow_valve_id: VariableIdentifier, - - number: usize, - is_open: bool, - timer_open: Duration, -} - -impl PackFlowValve { - fn new(context: &mut InitContext, number: usize) -> Self { - Self { - pack_flow_valve_id: context.get_identifier(Self::pack_flow_valve_id(number)), - number, - is_open: false, - timer_open: Duration::from_secs(0), - } - } - - fn pack_flow_valve_id(number: usize) -> String { - format!("COND_PACK_FLOW_VALVE_{}_IS_OPEN", number) - } - - fn update( - &mut self, - context: &UpdateContext, - open_fcv: &impl ControllerSignal, - engines: [&impl EngineCorrectedN1; 2], - pneumatic: &(impl PneumaticBleed + EngineStartState), - pneumatic_overhead: &impl EngineBleedPushbutton, - ) { - if self.can_move_fcv(engines, pneumatic, pneumatic_overhead) { - if let Some(signal) = open_fcv.signal() { - self.is_open = signal.target_open_amount(self.number) > Ratio::new::(0.) - } - if self.is_open { - self.timer_open += context.delta(); - } else { - self.timer_open = Duration::from_secs(0); - } - } else { - self.is_open = false; - } - } - - fn can_move_fcv( - &self, - engines: [&impl EngineCorrectedN1; 2], - pneumatic: &(impl PneumaticBleed + EngineStartState), - pneumatic_overhead: &impl EngineBleedPushbutton, - ) -> bool { - // Pneumatic overhead represents engine bleed pushbutton for left [0] and right [1] engine(s) - ((engines[self.number - 1].corrected_n1() >= Ratio::new::(15.) - && pneumatic_overhead.engine_bleed_pushbuttons_are_auto()[(self.number == 2) as usize]) - || (engines[(self.number == 1) as usize].corrected_n1() >= Ratio::new::(15.) - && pneumatic_overhead.engine_bleed_pushbuttons_are_auto() - [(self.number == 1) as usize] - && pneumatic.engine_crossbleed_is_on())) - || pneumatic.apu_bleed_is_on() - } - - fn fcv_timer(&self) -> Duration { - self.timer_open - } - - fn fcv_is_open(&self) -> bool { - self.is_open - } -} - -impl SimulationElement for PackFlowValve { - fn write(&self, writer: &mut SimulatorWriter) { - writer.write(&self.pack_flow_valve_id, self.is_open); - } -} - pub struct Air { temperature: ThermodynamicTemperature, pressure: Pressure, @@ -354,348 +289,3 @@ impl Default for Air { Self::new() } } - -#[cfg(test)] -mod air_conditioning_tests { - use super::*; - use crate::{ - overhead::AutoOffFaultPushButton, - pneumatic::{valve::DefaultValve, EngineModeSelector, EngineState}, - shared::PneumaticValve, - simulation::{ - test::{SimulationTestBed, TestBed}, - Aircraft, SimulationElement, - }, - }; - - struct TestEngine { - corrected_n1: Ratio, - } - impl TestEngine { - fn new(engine_corrected_n1: Ratio) -> Self { - Self { - corrected_n1: engine_corrected_n1, - } - } - fn set_engine_n1(&mut self, n: Ratio) { - self.corrected_n1 = n; - } - } - impl EngineCorrectedN1 for TestEngine { - fn corrected_n1(&self) -> Ratio { - self.corrected_n1 - } - } - - struct TestPneumaticOverhead { - engine_1_bleed: AutoOffFaultPushButton, - engine_2_bleed: AutoOffFaultPushButton, - } - - impl TestPneumaticOverhead { - fn new(context: &mut InitContext) -> Self { - Self { - engine_1_bleed: AutoOffFaultPushButton::new_auto(context, "PNEU_ENG_1_BLEED"), - engine_2_bleed: AutoOffFaultPushButton::new_auto(context, "PNEU_ENG_2_BLEED"), - } - } - - fn left_engine_bleed_pushbutton_set_auto(&mut self) { - self.engine_1_bleed.set_auto(true); - } - - fn right_engine_bleed_pushbutton_set_auto(&mut self) { - self.engine_2_bleed.set_auto(true); - } - } - - impl EngineBleedPushbutton for TestPneumaticOverhead { - fn engine_bleed_pushbuttons_are_auto(&self) -> [bool; 2] { - [self.engine_1_bleed.is_auto(), self.engine_2_bleed.is_auto()] - } - } - - impl SimulationElement for TestPneumaticOverhead { - fn accept(&mut self, visitor: &mut T) { - self.engine_1_bleed.accept(visitor); - self.engine_2_bleed.accept(visitor); - - visitor.visit(self); - } - } - - struct TestFadec { - engine_1_state_id: VariableIdentifier, - engine_2_state_id: VariableIdentifier, - - engine_1_state: EngineState, - engine_2_state: EngineState, - - engine_mode_selector_id: VariableIdentifier, - engine_mode_selector_position: EngineModeSelector, - } - impl TestFadec { - fn new(context: &mut InitContext) -> Self { - Self { - engine_1_state_id: context.get_identifier("ENGINE_STATE:1".to_owned()), - engine_2_state_id: context.get_identifier("ENGINE_STATE:2".to_owned()), - engine_1_state: EngineState::Off, - engine_2_state: EngineState::Off, - engine_mode_selector_id: context - .get_identifier("TURB ENG IGNITION SWITCH EX1:1".to_owned()), - engine_mode_selector_position: EngineModeSelector::Norm, - } - } - - fn engine_state(&self, number: usize) -> EngineState { - match number { - 1 => self.engine_1_state, - 2 => self.engine_2_state, - _ => panic!("Invalid engine number"), - } - } - - fn engine_mode_selector(&self) -> EngineModeSelector { - self.engine_mode_selector_position - } - } - impl SimulationElement for TestFadec { - fn read(&mut self, reader: &mut SimulatorReader) { - self.engine_1_state = reader.read(&self.engine_1_state_id); - self.engine_2_state = reader.read(&self.engine_2_state_id); - self.engine_mode_selector_position = reader.read(&self.engine_mode_selector_id); - } - } - - struct TestPneumatic { - apu_bleed_air_valve: DefaultValve, - cross_bleed_valve: DefaultValve, - fadec: TestFadec, - } - - impl TestPneumatic { - fn new(context: &mut InitContext) -> Self { - Self { - apu_bleed_air_valve: DefaultValve::new_closed(), - cross_bleed_valve: DefaultValve::new_closed(), - fadec: TestFadec::new(context), - } - } - } - - impl PneumaticBleed for TestPneumatic { - fn apu_bleed_is_on(&self) -> bool { - self.apu_bleed_air_valve.is_open() - } - fn engine_crossbleed_is_on(&self) -> bool { - self.cross_bleed_valve.is_open() - } - } - impl EngineStartState for TestPneumatic { - fn left_engine_state(&self) -> EngineState { - self.fadec.engine_state(1) - } - fn right_engine_state(&self) -> EngineState { - self.fadec.engine_state(2) - } - fn engine_mode_selector(&self) -> EngineModeSelector { - self.fadec.engine_mode_selector() - } - } - impl SimulationElement for TestPneumatic { - fn accept(&mut self, visitor: &mut V) { - self.fadec.accept(visitor); - - visitor.visit(self); - } - } - - struct TestAircraft { - flow_control_valve: PackFlowValve, - actuator_signal: TestActuatorSignal, - engine_1: TestEngine, - engine_2: TestEngine, - pneumatic: TestPneumatic, - pneumatic_overhead: TestPneumaticOverhead, - } - - impl TestAircraft { - fn new(context: &mut InitContext) -> Self { - Self { - flow_control_valve: PackFlowValve::new(context, 1), - actuator_signal: TestActuatorSignal::new(), - engine_1: TestEngine::new(Ratio::new::(0.)), - engine_2: TestEngine::new(Ratio::new::(0.)), - pneumatic: TestPneumatic::new(context), - pneumatic_overhead: TestPneumaticOverhead::new(context), - } - } - - fn command_valve_open(&mut self) { - self.actuator_signal.open(); - } - - fn command_valve_close(&mut self) { - self.actuator_signal.close(); - } - - fn valve_is_open(&self) -> bool { - self.flow_control_valve.fcv_is_open() - } - - fn valve_timer(&self) -> Duration { - self.flow_control_valve.fcv_timer() - } - - fn set_engine_n1(&mut self, n: Ratio) { - self.engine_1.set_engine_n1(n); - self.engine_2.set_engine_n1(n); - } - - fn set_bleed_pb_to_auto(&mut self) { - self.pneumatic_overhead - .left_engine_bleed_pushbutton_set_auto(); - self.pneumatic_overhead - .right_engine_bleed_pushbutton_set_auto(); - } - } - - impl Aircraft for TestAircraft { - fn update_after_power_distribution(&mut self, context: &UpdateContext) { - self.flow_control_valve.update( - context, - &self.actuator_signal, - [&self.engine_1, &self.engine_2], - &self.pneumatic, - &self.pneumatic_overhead, - ); - } - } - - impl SimulationElement for TestAircraft {} - - struct TestActuatorSignal { - should_open_fcv: bool, - } - - impl TestActuatorSignal { - fn new() -> Self { - Self { - should_open_fcv: false, - } - } - - fn open(&mut self) { - self.should_open_fcv = true; - } - - fn close(&mut self) { - self.should_open_fcv = false; - } - } - - impl ControllerSignal for TestActuatorSignal { - fn signal(&self) -> Option { - let target_open = if self.should_open_fcv { - Ratio::new::(100.) - } else { - Ratio::new::(0.) - }; - Some(PackFlowValveSignal::new([target_open, target_open])) - } - } - - struct AirCondTestBed { - test_bed: SimulationTestBed, - } - impl AirCondTestBed { - fn new() -> Self { - Self { - test_bed: SimulationTestBed::new(TestAircraft::new), - } - } - } - impl TestBed for AirCondTestBed { - type Aircraft = TestAircraft; - - fn test_bed(&self) -> &SimulationTestBed { - &self.test_bed - } - - fn test_bed_mut(&mut self) -> &mut SimulationTestBed { - &mut self.test_bed - } - } - - fn test_bed_with_bleed() -> AirCondTestBed { - let mut test_bed = AirCondTestBed::new(); - test_bed.command(|a| a.set_engine_n1(Ratio::new::(15.))); - test_bed.command(|a| a.set_bleed_pb_to_auto()); - test_bed - } - - #[test] - fn fcv_starts_closed() { - let test_bed = SimulationTestBed::new(TestAircraft::new); - - assert!(!test_bed.query(|a| a.valve_is_open())); - } - - #[test] - fn fcv_opens_when_signal_to_open() { - let mut test_bed = test_bed_with_bleed(); - - test_bed.command(|a| a.command_valve_open()); - test_bed.run(); - - assert!(test_bed.query(|a| a.valve_is_open())); - } - - #[test] - fn fcv_closes_when_signal_to_close() { - let mut test_bed = test_bed_with_bleed(); - - test_bed.command(|a| a.command_valve_open()); - test_bed.run(); - test_bed.command(|a| a.command_valve_close()); - test_bed.run(); - - assert!(!test_bed.query(|a| a.valve_is_open())); - } - - #[test] - fn timer_starts_at_zero() { - let test_bed = test_bed_with_bleed(); - - assert_eq!(test_bed.query(|a| a.valve_timer()), Duration::from_secs(0)); - } - - #[test] - fn timer_starts_when_valve_opens() { - let mut test_bed = test_bed_with_bleed(); - - assert_eq!(test_bed.query(|a| a.valve_timer()), Duration::from_secs(0)); - - test_bed.command(|a| a.command_valve_open()); - test_bed.run(); - - assert!(test_bed.query(|a| a.valve_timer()) > Duration::from_secs(0)); - } - - #[test] - fn timer_resets_when_valve_closes() { - let mut test_bed = test_bed_with_bleed(); - - assert_eq!(test_bed.query(|a| a.valve_timer()), Duration::from_secs(0)); - - test_bed.command(|a| a.command_valve_open()); - test_bed.run(); - - assert!(test_bed.query(|a| a.valve_timer()) > Duration::from_secs(0)); - - test_bed.command(|a| a.command_valve_close()); - test_bed.run(); - - assert_eq!(test_bed.query(|a| a.valve_timer()), Duration::from_secs(0)); - } -} diff --git a/src/systems/systems/src/hydraulic/aerodynamic_model.rs b/src/systems/systems/src/hydraulic/aerodynamic_model.rs index 25e8152d5c86..e4b0bd388868 100644 --- a/src/systems/systems/src/hydraulic/aerodynamic_model.rs +++ b/src/systems/systems/src/hydraulic/aerodynamic_model.rs @@ -36,9 +36,9 @@ impl AerodynamicModel { area_coefficient: Ratio, ) -> Self { let mut obj = Self { - max_drag_normal: max_drag_normal.unwrap_or_default(), - lift_axis: lift_axis.unwrap_or_default(), - lift_normal: lift_normal.unwrap_or_default(), + max_drag_normal: max_drag_normal.unwrap_or_default().normalize(), + lift_axis: lift_axis.unwrap_or_default().normalize(), + lift_normal: lift_normal.unwrap_or_default().normalize(), lift_area: Area::default(), drag_area: Area::default(), @@ -188,9 +188,9 @@ fn area_projected(size: Vector3, projection_vector: Vector3) -> Are let norm_projection_vector = projection_vector.normalize(); Area::new::( - size[0].get::() * size[2].get::() * norm_projection_vector[1] - + size[0].get::() * size[1].get::() * norm_projection_vector[2] - + size[1].get::() * size[2].get::() * norm_projection_vector[0], + size[0].get::() * size[2].get::() * norm_projection_vector[1].abs() + + size[0].get::() * size[1].get::() * norm_projection_vector[2].abs() + + size[1].get::() * size[2].get::() * norm_projection_vector[0].abs(), ) } @@ -206,7 +206,7 @@ mod tests { use uom::si::{ angle::{degree, radian}, ratio::ratio, - velocity::meter_per_second, + velocity::{knot, meter_per_second}, }; struct TestAerodynamicBody { @@ -641,6 +641,193 @@ mod tests { assert!(z_area == Area::new::(2. * 1.)); } + #[test] + fn area_projection_on_x_y_z_gives_correct_area_with_negative_component_axis() { + let size = Vector3::::new( + Length::new::(1.), + Length::new::(2.), + Length::new::(3.), + ); + + let x_projection = Vector3::::new(-1., 0., 0.); + let y_projection = Vector3::::new(0., 1., 0.); + let z_projection = Vector3::::new(0., 0., 1.); + + let x_area = area_projected(size, x_projection); + let y_area = area_projected(size, y_projection); + let z_area = area_projected(size, z_projection); + + assert!(x_area == Area::new::(2. * 3.)); + assert!(y_area == Area::new::(1. * 3.)); + assert!(z_area == Area::new::(2. * 1.)); + } + + #[test] + fn nose_gear_door_right_has_aero_force_pushing_open_with_headwind() { + let nose_door_body = nose_gear_door_body(); + let nose_door_aero_model = nose_gear_door_aero(); + + let mut test_bed_nose_door = + test_bed(TestAircraft::new(nose_door_body, nose_door_aero_model)) + .with_wind_speed_and_aoa(Velocity::new::(200.), Angle::new::(10.)) + .with_left_wind(Velocity::new::(0.)); + + test_bed_nose_door.rotate_body(Angle::new::(80.)); + + test_bed_nose_door.run_without_delta(); + + let lateral_force = test_bed_nose_door.query(|a| a.body_aero_force_right_value()); + let long_force = test_bed_nose_door.query(|a| a.body_aero_force_forward_value()); + + // There's some drag + assert!(long_force <= Force::new::(-50.)); + + // Lift from door angle pushes it right (right door pushed open) + assert!(lateral_force >= Force::new::(200.)); + } + + #[test] + fn nose_gear_has_force_pushing_it_open_with_headwind() { + let nose_gear_body = nose_gear_body(); + let nose_gear_aero_model = nose_gear_aero(); + + let mut test_bed = test_bed(TestAircraft::new(nose_gear_body, nose_gear_aero_model)) + .with_headwind(Velocity::new::(200.)) + .with_left_wind(Velocity::new::(0.)); + + test_bed.rotate_body(Angle::new::(0.)); + + test_bed.run_without_delta(); + + let lateral_force = test_bed.query(|a| a.body_aero_force_right_value()); + let up_force = test_bed.query(|a| a.body_aero_force_up_value()); + let long_force = test_bed.query(|a| a.body_aero_force_forward_value()); + + // There's lot of drag + assert!(long_force <= Force::new::(-1000.)); + + // Up and lateral forces are minimal + assert!(lateral_force >= Force::new::(-50.)); + assert!(lateral_force <= Force::new::(50.)); + + assert!(up_force >= Force::new::(-50.)); + assert!(up_force <= Force::new::(50.)); + + // Retracting + test_bed.rotate_body(Angle::new::(-92.)); + + test_bed.run_without_delta(); + + // Less drag + assert!(test_bed.query(|a| a.body_aero_force_forward_value()) > long_force); + assert!(test_bed.query(|a| a.body_aero_force_forward_value()) < Force::new::(250.)); + } + + #[test] + fn right_gear_has_force_pushing_it_right_with_headwind() { + let right_gear_body = right_gear_body(); + let right_gear_aero_model = right_gear_aero(); + + let mut test_bed = test_bed(TestAircraft::new(right_gear_body, right_gear_aero_model)) + .with_headwind(Velocity::new::(200.)) + .with_left_wind(Velocity::new::(0.)); + + test_bed.rotate_body(Angle::new::(0.)); + + test_bed.run_without_delta(); + + let lateral_force = test_bed.query(|a| a.body_aero_force_right_value()); + let up_force = test_bed.query(|a| a.body_aero_force_up_value()); + let long_force = test_bed.query(|a| a.body_aero_force_forward_value()); + + // There's drag + assert!(long_force <= Force::new::(-100.)); + + // Up forces are minimal + assert!(up_force >= Force::new::(-50.)); + assert!(up_force <= Force::new::(50.)); + + // Gear pushed right + assert!(lateral_force >= Force::new::(500.)); + + // Retracting + test_bed.rotate_body(Angle::new::(-80.)); + + test_bed.run_without_delta(); + + // Gear not pushed right + assert!(test_bed.query(|a| a.body_aero_force_right_value()) <= Force::new::(300.)); + assert!(test_bed.query(|a| a.body_aero_force_right_value()) >= Force::new::(-300.)); + } + + #[test] + fn right_gear_door_has_force_pushing_it_left_with_headwind() { + let right_gear_door_body = right_gear_door_body(); + let right_gear_door_aero_model = right_gear_door_aero(); + + let mut test_bed = test_bed(TestAircraft::new( + right_gear_door_body, + right_gear_door_aero_model, + )) + .with_headwind(Velocity::new::(200.)) + .with_left_wind(Velocity::new::(0.)); + + test_bed.rotate_body(Angle::new::(-85.)); + + test_bed.run_without_delta(); + + let lateral_force = test_bed.query(|a| a.body_aero_force_right_value()); + + // Door pushed left + assert!(lateral_force <= Force::new::(-500.)); + + // Closing + test_bed.rotate_body(Angle::new::(0.)); + + test_bed.run_without_delta(); + + // Gear door not pushed left + assert!(test_bed.query(|a| a.body_aero_force_right_value()) <= Force::new::(50.)); + assert!(test_bed.query(|a| a.body_aero_force_right_value()) >= Force::new::(-50.)); + } + + #[test] + fn left_gear_has_force_pushing_it_left_with_headwind() { + let left_gear_body = right_gear_body(); // Same as left + let left_gear_aero_model = left_gear_aero(); + + let mut test_bed = test_bed(TestAircraft::new(left_gear_body, left_gear_aero_model)) + .with_headwind(Velocity::new::(200.)) + .with_left_wind(Velocity::new::(0.)); + + test_bed.rotate_body(Angle::new::(0.)); + + test_bed.run_without_delta(); + + let lateral_force = test_bed.query(|a| a.body_aero_force_right_value()); + let up_force = test_bed.query(|a| a.body_aero_force_up_value()); + let long_force = test_bed.query(|a| a.body_aero_force_forward_value()); + + // There's drag + assert!(long_force <= Force::new::(-100.)); + + // Up forces are minimal + assert!(up_force >= Force::new::(-50.)); + assert!(up_force <= Force::new::(50.)); + + // Gear pushed left + assert!(lateral_force <= Force::new::(-500.)); + + // Retracting + test_bed.rotate_body(Angle::new::(80.)); + + test_bed.run_without_delta(); + + // Gear not pushed left + assert!(test_bed.query(|a| a.body_aero_force_right_value()) <= Force::new::(300.)); + assert!(test_bed.query(|a| a.body_aero_force_right_value()) >= Force::new::(-300.)); + } + #[test] #[ignore] fn wind_tunnel_measurements() { @@ -669,6 +856,104 @@ mod tests { ) } + fn nose_gear_door_body() -> TestAerodynamicBody { + TestAerodynamicBody::new( + Vector3::new( + Length::new::(0.4), + Length::new::(0.02), + Length::new::(1.5), + ), + Vector3::new(0., 0., 1.), + Angle::new::(0.), + ) + } + + fn nose_gear_door_aero() -> AerodynamicModel { + AerodynamicModel::new( + &nose_gear_door_body(), + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., -0.2, 1.)), + Some(Vector3::new(0., -1., -0.2)), + Ratio::new::(0.7), + ) + } + + fn nose_gear_body() -> TestAerodynamicBody { + TestAerodynamicBody::new( + Vector3::new( + Length::new::(0.3), + Length::new::(2.453), + Length::new::(0.3), + ), + Vector3::new(1., 0., 0.), + Angle::new::(0.), + ) + } + + fn nose_gear_aero() -> AerodynamicModel { + AerodynamicModel::new( + &nose_gear_body(), + Some(Vector3::new(0., 0., 1.)), + None, + None, + Ratio::new::(1.0), + ) + } + + fn right_gear_door_body() -> TestAerodynamicBody { + TestAerodynamicBody::new( + Vector3::new( + Length::new::(1.73), + Length::new::(0.02), + Length::new::(1.7), + ), + Vector3::new(0., 0., 1.), + Angle::new::(0.), + ) + } + + fn right_gear_door_aero() -> AerodynamicModel { + AerodynamicModel::new( + &right_gear_door_body(), + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., -0.1, 1.)), + Some(Vector3::new(0., 1., 0.1)), + Ratio::new::(0.5), + ) + } + + fn right_gear_body() -> TestAerodynamicBody { + TestAerodynamicBody::new( + Vector3::new( + Length::new::(0.3), + Length::new::(3.453), + Length::new::(0.3), + ), + Vector3::new(0., 0., 1.), + Angle::new::(0.), + ) + } + + fn right_gear_aero() -> AerodynamicModel { + AerodynamicModel::new( + &right_gear_body(), + Some(Vector3::new(0., 0., 1.)), + Some(Vector3::new(0.3, 0., 1.)), + Some(Vector3::new(1., 0., -0.3)), + Ratio::new::(1.0), + ) + } + + fn left_gear_aero() -> AerodynamicModel { + AerodynamicModel::new( + &right_gear_body(), // Same axis as left gear + Some(Vector3::new(0., 0., 1.)), + Some(Vector3::new(-0.3, 0., 1.)), + Some(Vector3::new(-1., 0., -0.3)), + Ratio::new::(1.0), + ) + } + fn horizontal_surface_aero(body: &impl AerodynamicBody) -> AerodynamicModel { AerodynamicModel::new( body, diff --git a/src/systems/systems/src/hydraulic/brake_circuit.rs b/src/systems/systems/src/hydraulic/brake_circuit.rs index 7a7790e2e51c..e2a586b5e9d9 100644 --- a/src/systems/systems/src/hydraulic/brake_circuit.rs +++ b/src/systems/systems/src/hydraulic/brake_circuit.rs @@ -159,6 +159,7 @@ impl BrakeCircuit { accumulator_volume: Volume, accumulator_fluid_volume_at_init: Volume, total_displacement: Volume, + circuit_target_pressure: Pressure, ) -> BrakeCircuit { let mut has_accu = true; if accumulator_volume <= Volume::new::(0.) { @@ -185,6 +186,7 @@ impl BrakeCircuit { accumulator_volume, accumulator_fluid_volume_at_init, true, + circuit_target_pressure, ), total_volume_to_actuator: Volume::new::(0.), total_volume_to_reservoir: Volume::new::(0.), @@ -726,6 +728,7 @@ mod tests { init_max_vol, Volume::new::(0.0), Volume::new::(0.1), + Pressure::new::(3000.), ) })); @@ -750,6 +753,7 @@ mod tests { init_max_vol, init_max_vol / 2.0, Volume::new::(0.1), + Pressure::new::(3000.), ) })); @@ -862,6 +866,7 @@ mod tests { init_max_vol, init_max_vol / 2.0, Volume::new::(0.1), + Pressure::new::(3000.), ) } diff --git a/src/systems/systems/src/hydraulic/electrical_generator.rs b/src/systems/systems/src/hydraulic/electrical_generator.rs index d39d0b4a8ba1..e5e85daa0f44 100644 --- a/src/systems/systems/src/hydraulic/electrical_generator.rs +++ b/src/systems/systems/src/hydraulic/electrical_generator.rs @@ -16,8 +16,9 @@ use crate::shared::{ HydraulicGeneratorControlUnit, LgciuWeightOnWheels, SectionPressure, }; -use crate::simulation::{InitContext, VariableIdentifier}; -use crate::simulation::{SimulationElement, SimulatorWriter, UpdateContext, Write}; +use crate::simulation::{ + InitContext, SimulationElement, SimulatorWriter, UpdateContext, VariableIdentifier, Write, +}; use std::time::Duration; diff --git a/src/systems/systems/src/hydraulic/flap_slat.rs b/src/systems/systems/src/hydraulic/flap_slat.rs index d037e39e9b69..4917804870f9 100644 --- a/src/systems/systems/src/hydraulic/flap_slat.rs +++ b/src/systems/systems/src/hydraulic/flap_slat.rs @@ -134,12 +134,14 @@ pub struct FlapSlatAssembly { synchro_gear_breakpoints: [f64; 12], final_surface_angle_carac: [f64; 12], + + circuit_target_pressure: Pressure, } impl FlapSlatAssembly { const LOW_PASS_FILTER_SURFACE_POSITION_TRANSIENT_TIME_CONSTANT: Duration = Duration::from_millis(300); const BRAKE_PRESSURE_MIN_TO_ALLOW_MOVEMENT_PSI: f64 = 500.; - const MAX_CIRCUIT_PRESSURE_PSI: f64 = 3000.; + const ANGLE_THRESHOLD_FOR_REDUCED_SPEED_DEGREES: f64 = 6.69; const ANGULAR_SPEED_LIMIT_FACTOR_WHEN_APROACHING_POSITION: f64 = 0.5; const MIN_ANGULAR_SPEED_TO_REPORT_MOVING: f64 = 0.01; @@ -155,6 +157,7 @@ impl FlapSlatAssembly { surface_gear_ratio: Ratio, synchro_gear_breakpoints: [f64; 12], final_surface_angle_carac: [f64; 12], + circuit_target_pressure: Pressure, ) -> Self { Self { position_left_percent_id: context @@ -182,6 +185,7 @@ impl FlapSlatAssembly { right_motor: FlapSlatHydraulicMotor::new(motor_displacement), synchro_gear_breakpoints, final_surface_angle_carac, + circuit_target_pressure, } } @@ -253,11 +257,9 @@ impl FlapSlatAssembly { sfcc2_angle_request: Option, ) { if let Some(sfcc1_angle) = sfcc1_angle_request { - self.final_requested_synchro_gear_position = - self.feedback_angle_from_surface_angle(sfcc1_angle); + self.final_requested_synchro_gear_position = sfcc1_angle; } else if let Some(sfcc2_angle) = sfcc2_angle_request { - self.final_requested_synchro_gear_position = - self.feedback_angle_from_surface_angle(sfcc2_angle); + self.final_requested_synchro_gear_position = sfcc2_angle; } } @@ -283,12 +285,18 @@ impl FlapSlatAssembly { let new_theoretical_max_speed_left_side = AngularVelocity::new::( 0.5 * self.full_pressure_max_speed.get::() - * Self::max_speed_factor_from_pressure(final_left_pressure), + * Self::max_speed_factor_from_pressure( + final_left_pressure, + self.circuit_target_pressure, + ), ); let new_theoretical_max_speed_right_side = AngularVelocity::new::( 0.5 * self.full_pressure_max_speed.get::() - * Self::max_speed_factor_from_pressure(final_right_pressure), + * Self::max_speed_factor_from_pressure( + final_right_pressure, + self.circuit_target_pressure, + ), ); let mut new_theoretical_max_speed = @@ -303,12 +311,16 @@ impl FlapSlatAssembly { .update(context.delta(), new_theoretical_max_speed); } - fn max_speed_factor_from_pressure(current_pressure: Pressure) -> f64 { + fn max_speed_factor_from_pressure( + current_pressure: Pressure, + circuit_target_pressure: Pressure, + ) -> f64 { let press_corrected = current_pressure.get::() - Self::BRAKE_PRESSURE_MIN_TO_ALLOW_MOVEMENT_PSI; if current_pressure > Pressure::new::(Self::BRAKE_PRESSURE_MIN_TO_ALLOW_MOVEMENT_PSI) { (0.0004 * press_corrected.powi(2) - / (Self::MAX_CIRCUIT_PRESSURE_PSI - Self::BRAKE_PRESSURE_MIN_TO_ALLOW_MOVEMENT_PSI)) + / (circuit_target_pressure.get::() + - Self::BRAKE_PRESSURE_MIN_TO_ALLOW_MOVEMENT_PSI)) .min(1.) .max(0.) } else { @@ -416,6 +428,7 @@ impl FlapSlatAssembly { )) } + #[cfg(test)] /// Gets Feedback Position Pickup Unit (FPPU) position from current flap surface angle fn feedback_angle_from_surface_angle(&self, flap_surface_angle: Angle) -> Angle { Angle::new::(interpolation( @@ -493,6 +506,8 @@ mod tests { use crate::simulation::test::{SimulationTestBed, TestBed}; + const MAX_CIRCUIT_PRESSURE_PSI: f64 = 3000.; + #[derive(Default)] struct TestHydraulicSection { pressure: Pressure, @@ -548,18 +563,20 @@ mod tests { self.right_motor_pressure.set_pressure(right_motor_pressure); } - fn set_angle_request(&mut self, angle_request: Option) { - self.left_motor_angle_request = angle_request; - self.right_motor_angle_request = angle_request; + fn set_angle_request(&mut self, surface_angle_request: Option) { + self.left_motor_angle_request = flap_fppu_from_surface_angle(surface_angle_request); + self.right_motor_angle_request = flap_fppu_from_surface_angle(surface_angle_request); } fn set_angle_per_sfcc( &mut self, - angle_request_sfcc1: Option, - angle_request_sfcc2: Option, + surface_angle_request_sfcc1: Option, + surface_angle_request_sfcc2: Option, ) { - self.left_motor_angle_request = angle_request_sfcc1; - self.right_motor_angle_request = angle_request_sfcc2; + self.left_motor_angle_request = + flap_fppu_from_surface_angle(surface_angle_request_sfcc1); + self.right_motor_angle_request = + flap_fppu_from_surface_angle(surface_angle_request_sfcc2); } } impl Aircraft for TestAircraft { @@ -636,8 +653,8 @@ mod tests { test_bed.command(|a| a.set_angle_request(Some(Angle::new::(20.)))); test_bed.command(|a| { a.set_current_pressure( - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), ) }); @@ -683,8 +700,8 @@ mod tests { test_bed.command(|a| a.set_angle_request(Some(Angle::new::(20.)))); test_bed.command(|a| { a.set_current_pressure( - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), ) }); @@ -735,7 +752,7 @@ mod tests { test_bed.command(|a| { a.set_current_pressure( Pressure::new::(0.), - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), ) }); @@ -778,7 +795,7 @@ mod tests { test_bed.command(|a| a.set_angle_request(Some(Angle::new::(20.)))); test_bed.command(|a| { a.set_current_pressure( - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), Pressure::new::(0.), ) }); @@ -823,8 +840,8 @@ mod tests { test_bed.command(|a| a.set_angle_request(Some(flap_position_request))); test_bed.command(|a| { a.set_current_pressure( - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), ) }); @@ -849,8 +866,8 @@ mod tests { test_bed.command(|a| a.set_angle_request(Some(flap_position_request))); test_bed.command(|a| { a.set_current_pressure( - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), ) }); @@ -915,8 +932,8 @@ mod tests { test_bed.command(|a| a.set_angle_per_sfcc(None, Some(flap_position_request))); test_bed.command(|a| { a.set_current_pressure( - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), ) }); @@ -945,8 +962,8 @@ mod tests { test_bed.command(|a| a.set_angle_request(Some(flap_position_request))); test_bed.command(|a| { a.set_current_pressure( - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), ) }); @@ -973,8 +990,8 @@ mod tests { test_bed.command(|a| a.set_angle_request(Some(flap_position_request))); test_bed.command(|a| { a.set_current_pressure( - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), ) }); @@ -1011,8 +1028,8 @@ mod tests { test_bed.command(|a| a.set_angle_request(Some(flap_position_request))); test_bed.command(|a| { a.set_current_pressure( - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), - Pressure::new::(FlapSlatAssembly::MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), ) }); @@ -1064,6 +1081,25 @@ mod tests { 0., 10.318, 18.2561, 19.134, 21.59, 23.098, 24.13, 26.196, 26.72, 28.42, 36.703, 40., ], + Pressure::new::(MAX_CIRCUIT_PRESSURE_PSI), ) } + + #[cfg(test)] + fn flap_fppu_from_surface_angle(surface_angle: Option) -> Option { + let synchro_gear_map = [ + 0., 65., 115., 120.53, 136., 145.5, 152., 165., 168.3, 179., 231.2, 251.97, + ]; + let surface_degrees_breakpoints = [ + 0., 10.318, 18.2561, 19.134, 21.59, 23.098, 24.13, 26.196, 26.72, 28.42, 36.703, 40., + ]; + + surface_angle.map(|angle| { + Angle::new::(interpolation( + &surface_degrees_breakpoints, + &synchro_gear_map, + angle.get::(), + )) + }) + } } diff --git a/src/systems/systems/src/hydraulic/landing_gear.rs b/src/systems/systems/src/hydraulic/landing_gear.rs index 601407997f19..1d064ffd756f 100644 --- a/src/systems/systems/src/hydraulic/landing_gear.rs +++ b/src/systems/systems/src/hydraulic/landing_gear.rs @@ -11,8 +11,12 @@ use crate::{ }, }; -use super::linear_actuator::{ - Actuator, HydraulicAssemblyController, HydraulicLinearActuatorAssembly, LinearActuatorMode, +use super::{ + aerodynamic_model::AerodynamicModel, + linear_actuator::{ + Actuator, HydraulicAssemblyController, HydraulicLinearActuatorAssembly, HydraulicLocking, + LinearActuatorMode, + }, }; use uom::si::{f64::*, pressure::psi, ratio::ratio}; @@ -25,6 +29,8 @@ pub trait GearGravityExtension { pub struct HydraulicGearSystem { door_center_position_id: VariableIdentifier, + door_center_gear_slaved_position_id: VariableIdentifier, + door_left_position_id: VariableIdentifier, door_right_position_id: VariableIdentifier, @@ -51,9 +57,18 @@ impl HydraulicGearSystem { nose_gear: HydraulicLinearActuatorAssembly<1>, left_gear: HydraulicLinearActuatorAssembly<1>, right_gear: HydraulicLinearActuatorAssembly<1>, + gear_door_left_aerodynamic: AerodynamicModel, + gear_door_right_aerodynamic: AerodynamicModel, + gear_door_nose_aerodynamic: AerodynamicModel, + gear_left_aerodynamic: AerodynamicModel, + gear_right_aerodynamic: AerodynamicModel, + gear_nose_aerodynamic: AerodynamicModel, ) -> Self { Self { door_center_position_id: context.get_identifier("GEAR_DOOR_CENTER_POSITION".to_owned()), + door_center_gear_slaved_position_id: context + .get_identifier("GEAR_CENTER_SMALL_POSITION".to_owned()), + door_left_position_id: context.get_identifier("GEAR_DOOR_LEFT_POSITION".to_owned()), door_right_position_id: context.get_identifier("GEAR_DOOR_RIGHT_POSITION".to_owned()), @@ -76,6 +91,7 @@ impl HydraulicGearSystem { ProximityDetectorId::DownlockDoorNose1, ProximityDetectorId::DownlockDoorNose2, ], + gear_door_nose_aerodynamic, ), left_door_assembly: GearSystemComponentAssembly::new( GearActuatorId::GearDoorLeft, @@ -90,6 +106,7 @@ impl HydraulicGearSystem { ProximityDetectorId::DownlockDoorLeft1, ProximityDetectorId::DownlockDoorLeft2, ], + gear_door_left_aerodynamic, ), right_door_assembly: GearSystemComponentAssembly::new( GearActuatorId::GearDoorRight, @@ -104,6 +121,7 @@ impl HydraulicGearSystem { ProximityDetectorId::DownlockDoorRight1, ProximityDetectorId::DownlockDoorRight2, ], + gear_door_right_aerodynamic, ), // Nose gear has pull to retract system while main gears have push to retract @@ -120,6 +138,7 @@ impl HydraulicGearSystem { ProximityDetectorId::DownlockGearNose1, ProximityDetectorId::DownlockGearNose2, ], + gear_nose_aerodynamic, ), left_gear_assembly: GearSystemComponentAssembly::new( GearActuatorId::GearLeft, @@ -134,6 +153,7 @@ impl HydraulicGearSystem { ProximityDetectorId::DownlockGearLeft1, ProximityDetectorId::DownlockGearLeft2, ], + gear_left_aerodynamic, ), right_gear_assembly: GearSystemComponentAssembly::new( GearActuatorId::GearRight, @@ -148,6 +168,7 @@ impl HydraulicGearSystem { ProximityDetectorId::DownlockGearRight1, ProximityDetectorId::DownlockGearRight2, ], + gear_right_aerodynamic, ), } } @@ -269,6 +290,11 @@ impl SimulationElement for HydraulicGearSystem { &self.door_center_position_id, self.nose_door_assembly.position_normalized(), ); + writer.write( + &self.door_center_gear_slaved_position_id, + self.nose_gear_assembly.position_normalized(), + ); + writer.write( &self.door_left_position_id, self.left_door_assembly.position_normalized(), @@ -375,6 +401,8 @@ struct GearSystemComponentAssembly { uplock_proximity_detectors: [ProximityDetector; 2], hydraulic_uplock: HydraulicLock, hydraulic_downlock: Option, + + aerodynamic_model: AerodynamicModel, } impl GearSystemComponentAssembly { const OPENED_PROXIMITY_DETECTOR_MOUNTING_POSITION_RATIO: f64 = 1.; @@ -390,6 +418,7 @@ impl GearSystemComponentAssembly { has_hydraulic_downlock: bool, uplock_id: [ProximityDetectorId; 2], downlock_id: [ProximityDetectorId; 2], + aerodynamic_model: AerodynamicModel, ) -> Self { let mut obj = Self { component_id: id.into(), @@ -430,6 +459,7 @@ impl GearSystemComponentAssembly { } else { None }, + aerodynamic_model, }; obj.update_proximity_detectors(); @@ -448,6 +478,9 @@ impl GearSystemComponentAssembly { self.update_hydraulic_control(gear_system_controller, valves_controller, current_pressure); + self.aerodynamic_model + .update_body(context, self.hydraulic_assembly.body()); + self.hydraulic_assembly.update( context, std::slice::from_ref(&self.hydraulic_controller), @@ -558,6 +591,8 @@ struct GearSystemComponentHydraulicController { jammed_actuator_failure: Failure, jamming_position: Ratio, jamming_is_effective: bool, + + soft_downlock_is_active: bool, } impl GearSystemComponentHydraulicController { fn new(id: GearActuatorId, is_inverted_control: bool, is_soft_downlock: bool) -> Self { @@ -571,6 +606,7 @@ impl GearSystemComponentHydraulicController { jammed_actuator_failure: Failure::new(FailureType::GearActuatorJammed(id)), jamming_position: Ratio::new::(random_from_range(0., 1.)), jamming_is_effective: false, + soft_downlock_is_active: false, } } @@ -585,9 +621,9 @@ impl GearSystemComponentHydraulicController { self.actual_position = actual_position; self.requested_position = if should_open { - Ratio::new::(1.5) + Ratio::new::(1.1) } else { - Ratio::new::(-0.5) + Ratio::new::(-0.1) }; self.should_lock = actual_position.get::() > 0.5 && should_downlock @@ -599,6 +635,8 @@ impl GearSystemComponentHydraulicController { Ratio::new::(0.) }; + self.update_soft_downlock(); + self.update_jamming(); } @@ -619,13 +657,8 @@ impl GearSystemComponentHydraulicController { self.jamming_position = Ratio::new::(random_from_range(0., 1.)); } } -} -impl HydraulicAssemblyController for GearSystemComponentHydraulicController { - fn requested_mode(&self) -> LinearActuatorMode { - if self.jamming_is_effective { - return LinearActuatorMode::ClosedValves; - } + fn update_soft_downlock(&mut self) { if self.is_soft_downlock { if (!self.is_inverted_control && self.requested_position.get::() >= 1. @@ -634,10 +667,29 @@ impl HydraulicAssemblyController for GearSystemComponentHydraulicController { && self.requested_position.get::() <= 0. && self.actual_position.get::() <= 0.02) { - LinearActuatorMode::ClosedValves - } else { - LinearActuatorMode::PositionControl + self.soft_downlock_is_active = true; + } + + // We disable soft locking only if position demand is far away from current position + if self.soft_downlock_is_active + && (self.requested_position - self.actual_position) + .abs() + .get::() + > 0.5 + { + self.soft_downlock_is_active = false; } + } + } +} +impl HydraulicAssemblyController for GearSystemComponentHydraulicController { + fn requested_mode(&self) -> LinearActuatorMode { + if self.jamming_is_effective { + return LinearActuatorMode::ClosedValves; + } + + if self.soft_downlock_is_active { + LinearActuatorMode::ClosedValves } else { LinearActuatorMode::PositionControl } @@ -663,6 +715,7 @@ impl HydraulicAssemblyController for GearSystemComponentHydraulicController { } } } +impl HydraulicLocking for GearSystemComponentHydraulicController {} impl SimulationElement for GearSystemComponentHydraulicController { fn accept(&mut self, visitor: &mut T) { self.jammed_actuator_failure.accept(visitor); @@ -976,6 +1029,7 @@ mod tests { ProximityDetectorId::DownlockDoorRight1, ProximityDetectorId::DownlockDoorRight2, ], + gear_door_aero(), ), gear_assembly: GearSystemComponentAssembly::new( GearActuatorId::GearNose, @@ -990,6 +1044,7 @@ mod tests { ProximityDetectorId::DownlockGearRight1, ProximityDetectorId::DownlockGearRight2, ], + gear_aero(), ), component_controller: TestGearSystemController::new(), @@ -1432,18 +1487,21 @@ mod tests { 1, Length::new::(0.055), Length::new::(0.03), - VolumeRate::new::(0.08), + VolumeRate::new::(0.09), 20000., 5000., 2000., - 28000., + 9000., Duration::from_millis(100), - [0.5, 1., 1., 1., 1., 0.5], - [0., 0.2, 0.21, 0.79, 0.8, 1.], + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], + [0., 0.15, 0.16, 0.84, 0.85, 1.], DEFAULT_P_GAIN, DEFAULT_I_GAIN, DEFAULT_FORCE_GAIN, true, + false, + None, ) } @@ -1458,6 +1516,7 @@ mod tests { Mass::new::(50.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(-85.), @@ -1476,6 +1535,26 @@ mod tests { HydraulicLinearActuatorAssembly::new([actuator], rigid_body) } + fn gear_door_aero() -> AerodynamicModel { + AerodynamicModel::new( + &main_gear_door_right_body(true), + Some(Vector3::new(-1., 0., 0.)), + None, + None, + Ratio::new::(1.0), + ) + } + + fn gear_aero() -> AerodynamicModel { + AerodynamicModel::new( + &main_gear_right_body(true), + Some(Vector3::new(-1., 0., 0.)), + None, + None, + Ratio::new::(1.0), + ) + } + fn main_gear_actuator(bounded_linear_length: &impl BoundedLinearLength) -> LinearActuator { const DEFAULT_I_GAIN: f64 = 5.; const DEFAULT_P_GAIN: f64 = 0.05; @@ -1486,18 +1565,21 @@ mod tests { 1, Length::new::(0.145), Length::new::(0.105), - VolumeRate::new::(0.15), + VolumeRate::new::(0.17), 800000., 15000., 50000., 1200000., Duration::from_millis(100), - [1., 1., 1., 1., 1., 1.], - [0., 0.2, 0.21, 0.79, 0.8, 1.], + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], + [0., 0.1, 0.11, 0.89, 0.9, 1.], DEFAULT_P_GAIN, DEFAULT_I_GAIN, DEFAULT_FORCE_GAIN, true, + false, + None, ) } @@ -1512,6 +1594,7 @@ mod tests { Mass::new::(700.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(-80.), diff --git a/src/systems/systems/src/hydraulic/linear_actuator.rs b/src/systems/systems/src/hydraulic/linear_actuator.rs index 000bc8b1c83b..2d7471a8300f 100644 --- a/src/systems/systems/src/hydraulic/linear_actuator.rs +++ b/src/systems/systems/src/hydraulic/linear_actuator.rs @@ -18,7 +18,9 @@ use uom::si::{ }; use crate::{ - shared::{interpolation, low_pass_filter::LowPassFilter, pid::PidController}, + shared::{ + interpolation, low_pass_filter::LowPassFilter, pid::PidController, random_from_range, + }, simulation::UpdateContext, }; @@ -79,7 +81,8 @@ struct CoreHydraulicForce { fluid_compression_spring_constant: f64, fluid_compression_damping_constant: f64, - flow_open_loop_modifier_map: [f64; 6], + flow_open_loop_modifier_extension_map: [f64; 6], + flow_open_loop_modifier_retraction_map: [f64; 6], flow_open_loop_position_breakpoints: [f64; 6], max_flow: VolumeRate, @@ -100,6 +103,10 @@ struct CoreHydraulicForce { max_control_force: LowPassFilter, has_flow_restriction: bool, + + locks_position_in_closed_mode: bool, + is_soft_locked: bool, + soft_lock_velocity: (AngularVelocity, AngularVelocity), } impl CoreHydraulicForce { const MIN_MAX_FORCE_CONTROLLER_FILTER_TIME_CONSTANT: Duration = Duration::from_millis(30); @@ -123,12 +130,16 @@ impl CoreHydraulicForce { min_flow: VolumeRate, bore_side_area: Area, rod_side_area: Area, - flow_open_loop_modifier_map: [f64; 6], + flow_open_loop_modifier_extension_map: [f64; 6], + flow_open_loop_modifier_retraction_map: [f64; 6], flow_open_loop_position_breakpoints: [f64; 6], flow_control_proportional_gain: f64, flow_control_integral_gain: f64, flow_control_force_gain: f64, has_flow_restriction: bool, + + locks_position_in_closed_mode: bool, + soft_lock_velocity: Option<(AngularVelocity, AngularVelocity)>, ) -> Self { let max_force = Pressure::new::(3000.) * bore_side_area; let min_force = -Pressure::new::(3000.) * rod_side_area; @@ -141,7 +152,8 @@ impl CoreHydraulicForce { fluid_compression_spring_constant, fluid_compression_damping_constant, - flow_open_loop_modifier_map, + flow_open_loop_modifier_extension_map, + flow_open_loop_modifier_retraction_map, flow_open_loop_position_breakpoints, max_flow, @@ -171,6 +183,10 @@ impl CoreHydraulicForce { max_force, ), has_flow_restriction, + + locks_position_in_closed_mode, + is_soft_locked: locks_position_in_closed_mode, + soft_lock_velocity: soft_lock_velocity.unwrap_or_default(), } } @@ -289,22 +305,29 @@ impl CoreHydraulicForce { self.force_filtered.reset(self.force_raw); self.closed_valves_reference_position = position_normalized; self.current_mode = LinearActuatorMode::ClosedValves; + self.is_soft_locked = false; } fn go_to_position_control(&mut self) { self.pid_controller .reset_with_output(self.force_raw.get::()); self.current_mode = LinearActuatorMode::PositionControl; + self.is_soft_locked = false; } fn go_to_active_damping(&mut self) { self.force_filtered.reset(self.force_raw); self.current_mode = LinearActuatorMode::ActiveDamping; + self.is_soft_locked = false; } fn go_to_closed_circuit_damping(&mut self) { self.force_filtered.reset(self.force_raw); self.current_mode = LinearActuatorMode::ClosedCircuitDamping; + + if self.locks_position_in_closed_mode { + self.is_soft_locked = true; + } } fn update_force_from_current_mode( @@ -401,11 +424,19 @@ impl CoreHydraulicForce { ) }; - let open_loop_modifier_from_position = interpolation( - &self.flow_open_loop_position_breakpoints, - &self.flow_open_loop_modifier_map, - position_normalized.get::(), - ); + let open_loop_modifier_from_position = if position_error.get::() > 0. { + interpolation( + &self.flow_open_loop_position_breakpoints, + &self.flow_open_loop_modifier_extension_map, + position_normalized.get::(), + ) + } else { + interpolation( + &self.flow_open_loop_position_breakpoints, + &self.flow_open_loop_modifier_retraction_map, + position_normalized.get::(), + ) + }; (open_loop_flow_target.min(self.max_flow).max(self.min_flow)) * open_loop_modifier_from_position @@ -476,7 +507,47 @@ impl CoreHydraulicForce { self.last_control_force } } +impl HydraulicLocking for CoreHydraulicForce { + fn should_soft_lock(&self) -> bool { + self.is_soft_locked + } + + fn soft_lock_velocity(&self) -> (AngularVelocity, AngularVelocity) { + self.soft_lock_velocity + } +} + +pub struct LinearActuatorCharacteristics { + max_flow: VolumeRate, + slow_damping: f64, +} +impl LinearActuatorCharacteristics { + pub fn new( + min_damping: f64, + max_damping: f64, + nominal_flow: VolumeRate, + flow_dispersion: Ratio, + ) -> Self { + let flow_max_absolute_dispersion = nominal_flow + nominal_flow * flow_dispersion; + let flow_min_absolute_dispersion = nominal_flow - nominal_flow * flow_dispersion; + + Self { + max_flow: VolumeRate::new::(random_from_range( + flow_min_absolute_dispersion.get::(), + flow_max_absolute_dispersion.get::(), + )), + slow_damping: random_from_range(min_damping, max_damping), + } + } + pub fn max_flow(&self) -> VolumeRate { + self.max_flow + } + + pub fn slow_damping(&self) -> f64 { + self.slow_damping + } +} /// Represents a classical linear actuator with a rod side area and a bore side area /// It is connected between an anchor point on the plane and a control arm of a rigid body /// When the actuator moves, it takes fluid on one side and gives back to reservoir the fluid on other side @@ -531,12 +602,16 @@ impl LinearActuator { active_hydraulic_damping_constant: f64, slow_hydraulic_damping_constant: f64, slow_hydraulic_damping_filtering_constant: Duration, - flow_open_loop_modifier_map: [f64; 6], + flow_open_loop_modifier_extension_map: [f64; 6], + flow_open_loop_modifier_retraction_map: [f64; 6], flow_open_loop_position_breakpoints: [f64; 6], flow_control_proportional_gain: f64, flow_control_integral_gain: f64, flow_control_force_gain: f64, has_flow_restriction: bool, + + locks_position_in_closed_mode: bool, + soft_lock_velocity: Option<(AngularVelocity, AngularVelocity)>, ) -> Self { let total_travel = (bounded_linear_length.max_absolute_length_to_anchor() - bounded_linear_length.min_absolute_length_to_anchor()) @@ -615,12 +690,15 @@ impl LinearActuator { actual_min_flow, total_bore_side_area, total_rod_side_area, - flow_open_loop_modifier_map, + flow_open_loop_modifier_extension_map, + flow_open_loop_modifier_retraction_map, flow_open_loop_position_breakpoints, flow_control_proportional_gain, flow_control_integral_gain, flow_control_force_gain, has_flow_restriction, + locks_position_in_closed_mode, + soft_lock_velocity, ), } } @@ -733,6 +811,15 @@ impl Actuator for LinearActuator { self.total_volume_to_actuator = Volume::new::(0.); } } +impl HydraulicLocking for LinearActuator { + fn should_soft_lock(&self) -> bool { + self.core_hydraulics.should_soft_lock() + } + + fn soft_lock_velocity(&self) -> (AngularVelocity, AngularVelocity) { + self.core_hydraulics.soft_lock_velocity() + } +} pub trait HydraulicAssemblyController { fn requested_mode(&self) -> LinearActuatorMode; @@ -741,6 +828,32 @@ pub trait HydraulicAssemblyController { fn requested_lock_position(&self) -> Ratio; } +pub trait HydraulicLocking { + fn should_soft_lock(&self) -> bool { + false + } + + fn soft_lock_velocity(&self) -> (AngularVelocity, AngularVelocity) { + (AngularVelocity::default(), AngularVelocity::default()) + } +} + +fn get_more_restrictive_soft_lock_velocities( + controller1: &impl HydraulicLocking, + controller2: &impl HydraulicLocking, +) -> (AngularVelocity, AngularVelocity) { + ( + controller1 + .soft_lock_velocity() + .0 + .max(controller2.soft_lock_velocity().0), + controller1 + .soft_lock_velocity() + .1 + .min(controller2.soft_lock_velocity().1), + ) +} + pub struct HydraulicLinearActuatorAssembly { linear_actuators: [LinearActuator; N], rigid_body: LinearActuatedRigidBodyOnHingeAxis, @@ -768,18 +881,23 @@ impl HydraulicLinearActuatorAssembly { pub fn update( &mut self, context: &UpdateContext, - assembly_controllers: &[impl HydraulicAssemblyController], + assembly_controllers: &[impl HydraulicAssemblyController + HydraulicLocking], current_pressure: [Pressure; N], ) { for (index, actuator) in self.linear_actuators.iter_mut().enumerate() { - actuator.set_position_target(assembly_controllers[index].requested_position()); + actuator.set_position_target( + self.rigid_body + .linear_actuator_pos_normalized_from_angular_position_normalized( + assembly_controllers[index].requested_position(), + ), + ); } - // Only one lock mechanism on the connected rigid body so we only look at first controller demand - // We could decide it's a OR or AND over all actuators if not satisfying this way - self.update_lock_mechanism(&assembly_controllers[0]); + self.update_hard_lock_mechanism(assembly_controllers); if !self.rigid_body.is_locked() { + self.update_soft_lock_mechanism(assembly_controllers); + for (index, actuator) in self.linear_actuators.iter_mut().enumerate() { actuator.update_before_rigid_body( context, @@ -799,15 +917,67 @@ impl HydraulicLinearActuatorAssembly { } } - fn update_lock_mechanism(&mut self, assembly_controller: &impl HydraulicAssemblyController) { - if assembly_controller.should_lock() { - self.rigid_body - .lock_at_position_normalized(assembly_controller.requested_lock_position()) - } else { + fn update_hard_lock_mechanism( + &mut self, + assembly_controllers: &[impl HydraulicAssemblyController], + ) { + // The first controller requesting a lock locks the body + let mut no_lock = true; + for controller in assembly_controllers { + if controller.should_lock() { + self.rigid_body + .lock_at_position_normalized(controller.requested_lock_position()); + + no_lock = false; + + break; + } + } + + if no_lock { self.rigid_body.unlock(); } } + fn update_soft_lock_mechanism(&mut self, assembly_controllers: &[impl HydraulicLocking]) { + let mut no_lock = true; + + let mut min_velocity = AngularVelocity::new::(-10000.); + let mut max_velocity = AngularVelocity::new::(10000.); + + // Min velocity will be the max one amongst all the limit velocities requested + // Max velocity will be the min one amongst all the limit velocities requested + // This way we use the more restrictive speed limitation over all the controller demands + for (idx, controller) in assembly_controllers.iter().enumerate() { + if controller.should_soft_lock() || self.linear_actuators[idx].should_soft_lock() { + // If actuator itself already is locking we take the more restrictive locking speeds, else we only use external locking speeds + let (new_min, new_max) = if controller.should_soft_lock() + && self.linear_actuators[idx].should_soft_lock() + { + get_more_restrictive_soft_lock_velocities( + controller, + &self.linear_actuators[idx], + ) + } else if controller.should_soft_lock() { + controller.soft_lock_velocity() + } else { + self.linear_actuators[idx].soft_lock_velocity() + }; + + max_velocity = max_velocity.min(new_max); + min_velocity = min_velocity.max(new_min); + + no_lock = false; + } + } + + if no_lock { + self.rigid_body.soft_unlock(); + } else { + self.rigid_body.soft_lock(min_velocity, max_velocity) + } + } + pub fn is_locked(&self) -> bool { self.rigid_body.is_locked() } @@ -848,6 +1018,9 @@ pub struct LinearActuatedRigidBodyOnHingeAxis { center_of_gravity_offset: Vector3, center_of_gravity_actual: Vector3, + center_of_pressure_offset: Vector3, + center_of_pressure_actual: Vector3, + control_arm: Vector3, control_arm_actual: Vector3, actuator_extension_gives_positive_angle: bool, @@ -874,17 +1047,22 @@ pub struct LinearActuatedRigidBodyOnHingeAxis { axis_direction: Vector3, rotation_transform: Rotation3, - plane_acceleration_filtered: LowPassFilter>, + is_soft_locked: bool, + min_soft_lock_velocity: AngularVelocity, + max_soft_lock_velocity: AngularVelocity, + + min_absolute_length_to_anchor: Length, + max_absolute_length_to_anchor: Length, } impl LinearActuatedRigidBodyOnHingeAxis { // Rebound energy when hiting min or max position. 0.3 means the body rebounds at 30% of the speed it hit the min/max position const DEFAULT_MAX_MIN_POSITION_REBOUND_FACTOR: f64 = 0.3; - const PLANE_ACCELERATION_FILTERING_TIME_CONSTANT: Duration = Duration::from_millis(100); pub fn new( mass: Mass, size: Vector3, center_of_gravity_offset: Vector3, + center_of_pressure_offset: Vector3, control_arm: Vector3, anchor_point: Vector3, min_angle: Angle, @@ -910,6 +1088,8 @@ impl LinearActuatedRigidBodyOnHingeAxis { size, center_of_gravity_offset, center_of_gravity_actual: center_of_gravity_offset, + center_of_pressure_offset, + center_of_pressure_actual: center_of_pressure_offset, control_arm, control_arm_actual: control_arm, actuator_extension_gives_positive_angle: false, @@ -931,14 +1111,20 @@ impl LinearActuatedRigidBodyOnHingeAxis { &Unit::new_normalize(axis_direction), 0., ), - plane_acceleration_filtered: LowPassFilter::>::new( - Self::PLANE_ACCELERATION_FILTERING_TIME_CONSTANT, - ), + + is_soft_locked: false, + min_soft_lock_velocity: AngularVelocity::default(), + max_soft_lock_velocity: AngularVelocity::default(), + + min_absolute_length_to_anchor: Length::default(), + max_absolute_length_to_anchor: Length::default(), }; // Make sure the new object has coherent structure by updating internal roations and positions once new_body.initialize_actuator_force_direction(); new_body.update_all_rotations(); + new_body.init_min_max_linear_length(); new_body.init_position_normalized(); + new_body } @@ -962,8 +1148,7 @@ impl LinearActuatedRigidBodyOnHingeAxis { } pub fn apply_aero_force(&mut self, aerodynamic_force: Vector3) { - // Aero force applied at CG for now, might change to a different point - let torque = self.center_of_gravity_actual.cross(&Vector3::::new( + let torque = self.center_of_pressure_actual.cross(&Vector3::::new( aerodynamic_force[0].get::(), aerodynamic_force[1].get::(), aerodynamic_force[2].get::(), @@ -1028,30 +1213,15 @@ impl LinearActuatedRigidBodyOnHingeAxis { ); self.control_arm_actual = self.rotation_transform * self.control_arm; self.center_of_gravity_actual = self.rotation_transform * self.center_of_gravity_offset; + self.center_of_pressure_actual = self.rotation_transform * self.center_of_pressure_offset; } - // Computes local acceleration including world gravity and plane acceleration - // Note that this does not compute acceleration due to angular velocity of the plane - fn local_acceleration_and_gravity(&self, context: &UpdateContext) -> Torque { - let plane_acceleration_plane_reference = self.plane_acceleration_filtered.output(); - - let pitch_rotation = context.attitude().pitch_rotation_transform(); - - let bank_rotation = context.attitude().bank_rotation_transform(); - - let gravity_acceleration_world_reference = Vector3::new(0., -9.8, 0.); - - // Total acceleration in plane reference is the gravity in world reference rotated to plane reference. To this we substract - // the local plane reference to get final local acceleration (if plane falling at 1G final local accel is 1G of gravity - 1G local accel = 0G) - let total_acceleration_plane_reference = (pitch_rotation - * (bank_rotation * gravity_acceleration_world_reference)) - - plane_acceleration_plane_reference; - - // We add a 0 component to make the 2D CG position a 3D vector so we can compute a cross product easily - + // Computes torque caused by local plane acceleration + fn torque_from_local_acceleration_and_gravity(&self, context: &UpdateContext) -> Torque { // Force = m * G - let resultant_force_plane_reference = - total_acceleration_plane_reference * self.mass.get::(); + let resultant_force_plane_reference = context + .acceleration_plane_reference_filtered_ms2_vector() + * self.mass.get::(); // The Moment generated by acceleration force is the CoG offset from hinge position cross product with the acceleration force let gravity_moment_vector = self @@ -1070,12 +1240,9 @@ impl LinearActuatedRigidBodyOnHingeAxis { } pub fn update(&mut self, context: &UpdateContext) { - self.plane_acceleration_filtered - .update(context.delta(), context.acceleration().to_ms2_vector()); - if !self.is_locked { self.sum_of_torques += - self.natural_damping() + self.local_acceleration_and_gravity(context); + self.natural_damping() + self.torque_from_local_acceleration_and_gravity(context); self.angular_acceleration = AngularAcceleration::new::( self.sum_of_torques.get::() / self.inertia_at_hinge, @@ -1086,6 +1253,8 @@ impl LinearActuatedRigidBodyOnHingeAxis { * context.delta_as_secs_f64(), ); + self.limit_angular_speed_from_soft_lock(); + self.angular_position += Angle::new::( self.angular_speed.get::() * context.delta_as_secs_f64(), ); @@ -1114,6 +1283,16 @@ impl LinearActuatedRigidBodyOnHingeAxis { } } + /// If a soft lock is active, will limit the current angular velocity + fn limit_angular_speed_from_soft_lock(&mut self) { + if self.is_soft_locked { + self.angular_speed = self + .angular_speed + .min(self.max_soft_lock_velocity) + .max(self.min_soft_lock_velocity); + } + } + fn limit_position_to_range(&mut self) { if self.angular_position >= self.max_angle { self.angular_position = self.max_angle; @@ -1140,6 +1319,16 @@ impl LinearActuatedRigidBodyOnHingeAxis { self.is_locked } + pub fn soft_unlock(&mut self) { + self.is_soft_locked = false; + } + + pub fn soft_lock(&mut self, min_velocity: AngularVelocity, max_velocity: AngularVelocity) { + self.is_soft_locked = true; + self.min_soft_lock_velocity = min_velocity; + self.max_soft_lock_velocity = max_velocity; + } + fn absolute_length_to_anchor_at_angle(&self, position: Angle) -> Length { let rotation = Rotation3::from_axis_angle( &Unit::new_normalize(self.axis_direction), @@ -1149,18 +1338,51 @@ impl LinearActuatedRigidBodyOnHingeAxis { Length::new::((self.anchor_point - control_arm_position).norm()) } + + fn init_min_max_linear_length(&mut self) { + let length_at_min_angle = self.absolute_length_to_anchor_at_angle(self.min_angle); + let length_at_max_angle = self.absolute_length_to_anchor_at_angle(self.max_angle); + self.min_absolute_length_to_anchor = length_at_min_angle.min(length_at_max_angle); + self.max_absolute_length_to_anchor = length_at_min_angle.max(length_at_max_angle); + } + + fn linear_length_normalized_from_absolute_angle(&self, angle_demand: Angle) -> Ratio { + let total_linear_travel = + self.max_absolute_length_to_anchor - self.min_absolute_length_to_anchor; + + let current_length_from_angle = self.absolute_length_to_anchor_at_angle(angle_demand); + + (current_length_from_angle - self.min_absolute_length_to_anchor) / total_linear_travel + } + + fn angular_ratio_to_absolute_angle(&self, angular_ratio_normalized: Ratio) -> Angle { + if self.actuator_extension_gives_positive_angle() { + angular_ratio_normalized.get::() * self.total_travel + self.min_angle + } else { + -angular_ratio_normalized.get::() * self.total_travel + self.max_angle + } + } + + pub fn linear_actuator_pos_normalized_from_angular_position_normalized( + &self, + angular_ratio_normalized: Ratio, + ) -> Ratio { + // We allow 0.1 over min and max range so actuators that are controlled to end position do not slow down just before max position + let limited_ratio = angular_ratio_normalized + .max(Ratio::new::(-0.1)) + .min(Ratio::new::(1.1)); + self.linear_length_normalized_from_absolute_angle( + self.angular_ratio_to_absolute_angle(limited_ratio), + ) + } } impl BoundedLinearLength for LinearActuatedRigidBodyOnHingeAxis { fn min_absolute_length_to_anchor(&self) -> Length { - let length_at_min_angle = self.absolute_length_to_anchor_at_angle(self.min_angle); - let length_at_max_angle = self.absolute_length_to_anchor_at_angle(self.max_angle); - length_at_min_angle.min(length_at_max_angle) + self.min_absolute_length_to_anchor } fn max_absolute_length_to_anchor(&self) -> Length { - let length_at_min_angle = self.absolute_length_to_anchor_at_angle(self.min_angle); - let length_at_max_angle = self.absolute_length_to_anchor_at_angle(self.max_angle); - length_at_min_angle.max(length_at_max_angle) + self.max_absolute_length_to_anchor } fn absolute_length_to_anchor(&self) -> Length { @@ -1191,11 +1413,12 @@ impl AerodynamicBody for LinearActuatedRigidBodyOnHingeAxis { #[cfg(test)] mod tests { use nalgebra::Vector3; + use ntest::assert_about_eq; use super::*; use crate::shared::update_iterator::MaxStepLoop; - use crate::simulation::test::{SimulationTestBed, TestBed, WriteByName}; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed, WriteByName}; use crate::simulation::{Aircraft, SimulationElement}; use std::time::Duration; use uom::si::{angle::degree, mass::kilogram, pressure::psi}; @@ -1206,6 +1429,9 @@ mod tests { requested_position: Ratio, lock_request: bool, lock_position: Ratio, + + soft_lock_request: bool, + soft_lock_velocity: (AngularVelocity, AngularVelocity), } impl TestHydraulicAssemblyController { fn new() -> Self { @@ -1215,6 +1441,8 @@ mod tests { requested_position: Ratio::new::(0.), lock_request: true, lock_position: Ratio::new::(0.), + soft_lock_request: false, + soft_lock_velocity: (AngularVelocity::default(), AngularVelocity::default()), } } @@ -1234,6 +1462,11 @@ mod tests { fn set_position_target(&mut self, requested_position: Ratio) { self.requested_position = requested_position; } + + fn set_soft_lock(&mut self, lock_velocity: (AngularVelocity, AngularVelocity)) { + self.soft_lock_request = true; + self.soft_lock_velocity = lock_velocity; + } } impl HydraulicAssemblyController for TestHydraulicAssemblyController { fn requested_mode(&self) -> LinearActuatorMode { @@ -1252,6 +1485,15 @@ mod tests { self.lock_position } } + impl HydraulicLocking for TestHydraulicAssemblyController { + fn should_soft_lock(&self) -> bool { + self.soft_lock_request + } + + fn soft_lock_velocity(&self) -> (AngularVelocity, AngularVelocity) { + self.soft_lock_velocity + } + } struct TestAerodynamicModel { force: Vector3, @@ -1332,6 +1574,12 @@ mod tests { } } + fn command_soft_lock(&mut self, lock_velocity: (AngularVelocity, AngularVelocity)) { + for controller in &mut self.controllers { + controller.set_soft_lock(lock_velocity); + } + } + fn command_unlock(&mut self) { for controller in &mut self.controllers { controller.set_unlock(); @@ -1346,12 +1594,6 @@ mod tests { self.aero_forces.apply_up_force(force_up); } - fn actuator_position(&self, actuator_id: usize) -> Ratio { - assert!(actuator_id < N); - self.hydraulic_assembly - .actuator_position_normalized(actuator_id) - } - fn is_locked(&self) -> bool { self.hydraulic_assembly.is_locked() } @@ -1403,6 +1645,235 @@ mod tests { } impl SimulationElement for TestAircraft<2> {} + impl SimulationElement for LinearActuatedRigidBodyOnHingeAxis {} + + #[test] + fn asymetrical_deflection_body_converts_angle_to_linear_ratio() { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|_| elevator_body())); + + assert!( + test_bed + .command_element(|e| { + e.linear_length_normalized_from_absolute_angle(Angle::new::(0.)) + }) + .get::() + >= 0.35 + ); + assert!( + test_bed + .command_element(|e| { + e.linear_length_normalized_from_absolute_angle(Angle::new::(0.)) + }) + .get::() + <= 0.45 + ); + + assert_about_eq!( + test_bed + .command_element(|e| { + e.linear_length_normalized_from_absolute_angle(Angle::new::(-17.)) + }) + .get::(), + 0. + ); + + assert_about_eq!( + test_bed + .command_element(|e| { + e.linear_length_normalized_from_absolute_angle(Angle::new::(30.)) + }) + .get::(), + 1. + ); + } + + #[test] + fn asymetrical_deflection_body_converts_angle_ratio_to_absolute_angle() { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|_| elevator_body())); + + assert!( + test_bed + .command_element(|e| { + e.angular_ratio_to_absolute_angle(Ratio::new::(0.36)) + }) + .get::() + >= -1. + ); + assert!( + test_bed + .command_element(|e| { + e.angular_ratio_to_absolute_angle(Ratio::new::(0.36)) + }) + .get::() + <= 1. + ); + + assert_about_eq!( + test_bed + .command_element(|e| { e.angular_ratio_to_absolute_angle(Ratio::new::(0.)) }) + .get::(), + -17. + ); + + assert_about_eq!( + test_bed + .command_element(|e| { e.angular_ratio_to_absolute_angle(Ratio::new::(1.)) }) + .get::(), + 30. + ); + } + + #[test] + fn symetrical_deflection_body_converts_angle_to_linear_ratio() { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|_| aileron_body(true))); + + assert_about_eq!( + test_bed + .command_element(|e| { + e.linear_length_normalized_from_absolute_angle(Angle::new::(0.)) + }) + .get::(), + 0.5, + 0.001 + ); + + assert_about_eq!( + test_bed + .command_element(|e| { + e.linear_length_normalized_from_absolute_angle(Angle::new::(-25.)) + }) + .get::(), + 0. + ); + + assert_about_eq!( + test_bed + .command_element(|e| { + e.linear_length_normalized_from_absolute_angle(Angle::new::(25.)) + }) + .get::(), + 1. + ); + } + + #[test] + fn left_gear_body_converts_angle_ratio_to_absolute_angle() { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|_| main_gear_left_body(true))); + + assert!( + test_bed + .command_element(|e| { + e.angular_ratio_to_absolute_angle(Ratio::new::(0.5)) + }) + .get::() + >= 35. + ); + assert!( + test_bed + .command_element(|e| { + e.angular_ratio_to_absolute_angle(Ratio::new::(0.5)) + }) + .get::() + <= 45. + ); + + assert_about_eq!( + test_bed + .command_element(|e| { e.angular_ratio_to_absolute_angle(Ratio::new::(0.)) }) + .get::(), + 0. + ); + + assert_about_eq!( + test_bed + .command_element(|e| { e.angular_ratio_to_absolute_angle(Ratio::new::(1.)) }) + .get::(), + 80. + ); + } + + #[test] + fn right_gear_body_converts_angle_ratio_to_absolute_angle() { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|_| main_gear_right_body(true))); + + assert!( + test_bed + .command_element(|e| { + e.angular_ratio_to_absolute_angle(Ratio::new::(0.5)) + }) + .get::() + <= -35. + ); + assert!( + test_bed + .command_element(|e| { + e.angular_ratio_to_absolute_angle(Ratio::new::(0.5)) + }) + .get::() + >= -45. + ); + + assert_about_eq!( + test_bed + .command_element(|e| { e.angular_ratio_to_absolute_angle(Ratio::new::(0.)) }) + .get::(), + 0. + ); + + assert_about_eq!( + test_bed + .command_element(|e| { e.angular_ratio_to_absolute_angle(Ratio::new::(1.)) }) + .get::(), + -80. + ); + } + + #[test] + fn left_gear_body_converts_angle_to_linear_ratio() { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|_| main_gear_left_body(true))); + + assert_about_eq!( + test_bed + .command_element(|e| { + e.linear_length_normalized_from_absolute_angle(Angle::new::(0.)) + }) + .get::(), + 0. + ); + + assert_about_eq!( + test_bed + .command_element(|e| { + e.linear_length_normalized_from_absolute_angle(Angle::new::(80.)) + }) + .get::(), + 1. + ); + } + + #[test] + fn right_gear_body_converts_angle_to_linear_ratio() { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|_| main_gear_right_body(true))); + + assert_about_eq!( + test_bed + .command_element(|e| { + e.linear_length_normalized_from_absolute_angle(Angle::new::(0.)) + }) + .get::(), + 0. + ); + + assert_about_eq!( + test_bed + .command_element(|e| { + e.linear_length_normalized_from_absolute_angle(Angle::new::(-80.)) + }) + .get::(), + 1. + ); + } + #[test] fn linear_actuator_not_moving_on_locked_rigid_body() { let mut test_bed = SimulationTestBed::new(|_| { @@ -1632,6 +2103,44 @@ mod tests { assert!(test_bed.query(|a| a.body_position()) == Ratio::new::(0.3)); } + #[test] + fn soft_lock_with_zero_velocity_stops_the_body() { + let mut test_bed = SimulationTestBed::new(|_| { + TestAircraft::new(Duration::from_millis(33), cargo_door_assembly(true)) + }); + + test_bed.write_by_name(UpdateContext::PLANE_BANK_KEY, -45.); + + let actuator_position_at_init = test_bed.query(|a| a.body_position()); + test_bed.run_with_delta(Duration::from_secs(1)); + + assert!(test_bed.query(|a| a.body_position()) == actuator_position_at_init); + + test_bed.command(|a| a.command_unlock()); + test_bed.command(|a| a.command_active_damping_mode(0)); + + test_bed.run_with_delta(Duration::from_secs_f64(0.5)); + + assert!(test_bed.query(|a| a.body_position()) > actuator_position_at_init); + + let actuator_position_before_soft_lock = test_bed.query(|a| a.body_position()); + + test_bed.command(|a| { + a.command_soft_lock(( + AngularVelocity::new::(0.), + AngularVelocity::new::(0.), + )) + }); + + test_bed.run_with_delta(Duration::from_secs(1)); + + assert!(!test_bed.query(|a| a.is_locked())); + assert!( + test_bed.query(|a| (a.body_position() - actuator_position_before_soft_lock).abs()) + <= Ratio::new::(0.01) + ); + } + #[test] fn linear_actuator_can_control_position() { let mut test_bed = SimulationTestBed::new(|_| { @@ -1645,14 +2154,14 @@ mod tests { test_bed.run_with_delta(Duration::from_secs(20)); - assert!(test_bed.query(|a| a.actuator_position(0)) > Ratio::new::(0.68)); - assert!(test_bed.query(|a| a.actuator_position(0)) < Ratio::new::(0.72)); + assert!(test_bed.query(|a| a.body_position()) > Ratio::new::(0.68)); + assert!(test_bed.query(|a| a.body_position()) < Ratio::new::(0.72)); test_bed.command(|a| a.command_position_control(Ratio::new::(0.2), 0)); test_bed.run_with_delta(Duration::from_secs(20)); - assert!(test_bed.query(|a| a.actuator_position(0)) > Ratio::new::(0.18)); - assert!(test_bed.query(|a| a.actuator_position(0)) < Ratio::new::(0.22)); + assert!(test_bed.query(|a| a.body_position()) > Ratio::new::(0.18)); + assert!(test_bed.query(|a| a.body_position()) < Ratio::new::(0.22)); } #[test] @@ -1980,7 +2489,7 @@ mod tests { }); test_bed.command(|a| a.set_pressures([Pressure::new::(3000.)])); - test_bed.command(|a| a.command_position_control(Ratio::new::(-0.5), 0)); + test_bed.command(|a| a.command_position_control(Ratio::new::(-0.1), 0)); test_bed.command(|a| a.command_unlock()); test_bed.run_with_delta(Duration::from_secs(1)); @@ -2165,9 +2674,9 @@ mod tests { test_bed.command(|a| a.apply_up_aero_forces(test_force)); test_bed.run_with_delta(Duration::from_secs_f64(0.5)); - test_force += Force::new::(300.); + test_force += Force::new::(400.); - if test_force < Force::new::(8000.) { + if test_force < Force::new::(10000.) { assert!(test_bed.query(|a| a.body_position()) < Ratio::new::(0.51)); assert!(test_bed.query(|a| a.body_position()) > Ratio::new::(0.49)); } @@ -2197,7 +2706,7 @@ mod tests { for _ in 0..10 { println!("Reducing pressure {:.0}", test_pressure.get::()); test_bed.command(|a| a.set_pressures([test_pressure, test_pressure])); - test_bed.command(|a| a.apply_up_aero_forces(Force::new::(5000.))); + test_bed.command(|a| a.apply_up_aero_forces(Force::new::(7000.))); test_bed.run_with_delta(Duration::from_secs_f64(0.3)); test_pressure -= Pressure::new::(300.); @@ -2338,6 +2847,59 @@ mod tests { assert!(test_bed.query(|a| a.body_position()) > Ratio::new::(0.95)); } + #[test] + fn spoiler_position_can_go_down_but_not_up_when_soft_locked_up() { + let mut test_bed = SimulationTestBed::new(|_| { + TestAircraft::new(Duration::from_millis(10), spoiler_assembly()) + }); + + test_bed.command(|a| a.command_unlock()); + test_bed.command(|a| a.command_active_damping_mode(0)); + + test_bed.command(|a| a.apply_up_aero_forces(Force::new::(300.))); + test_bed.run_with_delta(Duration::from_secs_f64(1.)); + + let position_before_lock = test_bed.query(|a| a.body_position()); + + println!("Soft lock up direction"); + test_bed.command(|a| { + a.command_soft_lock(( + AngularVelocity::new::(-10000.), + AngularVelocity::new::(0.), + )) + }); + + test_bed.run_with_delta(Duration::from_secs_f64(2.)); + assert!( + test_bed.query(|a| (a.body_position() - position_before_lock).abs()) + < Ratio::new::(0.01) + ); + + println!("Aero force down"); + test_bed.command(|a| a.apply_up_aero_forces(Force::new::(-300.))); + test_bed.run_with_delta(Duration::from_secs_f64(1.)); + + assert!( + test_bed.query(|a| a.body_position() - position_before_lock) < Ratio::new::(0.2) + ); + } + + #[test] + fn spoiler_position_cant_go_up_when_not_pressurised() { + let mut test_bed = SimulationTestBed::new(|_| { + TestAircraft::new(Duration::from_millis(10), spoiler_assembly()) + }); + + test_bed.command(|a| a.command_unlock()); + test_bed.command(|a| a.command_position_control(Ratio::new::(1.), 0)); + test_bed.command(|a| a.set_pressures([Pressure::new::(0.)])); + + test_bed.command(|a| a.apply_up_aero_forces(Force::new::(5000.))); + test_bed.run_with_delta(Duration::from_secs_f64(2.)); + + assert!(test_bed.query(|a| a.body_position()) < Ratio::new::(0.2)); + } + fn cargo_door_actuator(bounded_linear_length: &impl BoundedLinearLength) -> LinearActuator { const DEFAULT_I_GAIN: f64 = 5.; const DEFAULT_P_GAIN: f64 = 0.05; @@ -2355,11 +2917,14 @@ mod tests { 1200000., Duration::from_millis(100), [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], [0., 0.2, 0.21, 0.79, 0.8, 1.], DEFAULT_P_GAIN, DEFAULT_I_GAIN, DEFAULT_FORCE_GAIN, false, + false, + None, ) } @@ -2381,6 +2946,7 @@ mod tests { Mass::new::(130.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(-23.), @@ -2429,12 +2995,15 @@ mod tests { 2000., 9000., Duration::from_millis(100), - [0.5, 0.5, 1., 1., 0.5, 0.5], + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], [0., 0.15, 0.16, 0.84, 0.85, 1.], DEFAULT_P_GAIN, DEFAULT_I_GAIN, DEFAULT_FORCE_GAIN, true, + false, + None, ) } @@ -2451,11 +3020,14 @@ mod tests { 0., Duration::from_millis(100), [0.5, 1., 1., 1., 1., 0.5], + [0.5, 1., 1., 1., 1., 0.5], [0., 0.2, 0.21, 0.79, 0.8, 1.], 0., 0., 0., false, + false, + None, ) } @@ -2470,6 +3042,7 @@ mod tests { Mass::new::(50.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(-85.), @@ -2492,6 +3065,7 @@ mod tests { Mass::new::(50.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(0.), @@ -2533,12 +3107,15 @@ mod tests { 50000., 1200000., Duration::from_millis(100), - [0.5, 0.5, 1., 1., 0.5, 0.5], + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], [0., 0.1, 0.11, 0.89, 0.9, 1.], DEFAULT_P_GAIN, DEFAULT_I_GAIN, DEFAULT_FORCE_GAIN, true, + false, + None, ) } @@ -2553,6 +3130,7 @@ mod tests { Mass::new::(700.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(-80.), @@ -2575,6 +3153,7 @@ mod tests { Mass::new::(700.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(0.), @@ -2609,12 +3188,15 @@ mod tests { 50000., 2200000., Duration::from_millis(100), - [0.5, 0.5, 1., 1., 0.5, 0.5], + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], [0., 0.1, 0.11, 0.89, 0.9, 1.], DEFAULT_P_GAIN, DEFAULT_I_GAIN, DEFAULT_FORCE_GAIN, true, + false, + None, ) } @@ -2629,6 +3211,7 @@ mod tests { Mass::new::(300.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(-101.), @@ -2663,12 +3246,15 @@ mod tests { 2000., 28000., Duration::from_millis(100), - [0.5, 0.5, 1., 1., 0.5, 0.5], + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], [0., 0.15, 0.16, 0.84, 0.85, 1.], DEFAULT_P_GAIN, DEFAULT_I_GAIN, DEFAULT_FORCE_GAIN, true, + false, + None, ) } @@ -2683,6 +3269,7 @@ mod tests { Mass::new::(40.), size, cg_offset, + cg_offset, control_arm, anchor, Angle::new::(0.), @@ -2718,17 +3305,21 @@ mod tests { 800000., Duration::from_millis(300), [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], [0., 0.2, 0.21, 0.79, 0.8, 1.], DEFAULT_P_GAIN, DEFAULT_I_GAIN, DEFAULT_FORCE_GAIN, false, + false, + None, ) } fn aileron_body(is_init_down: bool) -> LinearActuatedRigidBodyOnHingeAxis { let size = Vector3::new(3.325, 0.16, 0.58); let cg_offset = Vector3::new(0., 0., -0.5 * size[2]); + let aero_center_offset = Vector3::new(0., 0., -0.4 * size[2]); let control_arm = Vector3::new(0., -0.0525, 0.); let anchor = Vector3::new(0., -0.0525, 0.33); @@ -2743,6 +3334,7 @@ mod tests { Mass::new::(24.65), size, cg_offset, + aero_center_offset, control_arm, anchor, Angle::new::(-25.), @@ -2778,17 +3370,21 @@ mod tests { 10000000., Duration::from_millis(300), [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], [0., 0.2, 0.21, 0.79, 0.8, 1.], DEFAULT_P_GAIN, DEFAULT_I_GAIN, DEFAULT_FORCE_GAIN, false, + false, + None, ) } fn elevator_body() -> LinearActuatedRigidBodyOnHingeAxis { let size = Vector3::new(6., 0.405, 1.125); let cg_offset = Vector3::new(0., 0., -0.5 * size[2]); + let aero_center_offset = Vector3::new(0., 0., -0.3 * size[2]); let control_arm = Vector3::new(0., -0.091, 0.); let anchor = Vector3::new(0., -0.091, 0.41); @@ -2797,6 +3393,7 @@ mod tests { Mass::new::(58.6), size, cg_offset, + aero_center_offset, control_arm, anchor, Angle::new::(-17.), @@ -2832,17 +3429,24 @@ mod tests { 800000., Duration::from_millis(300), [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], [0., 0.2, 0.21, 0.79, 0.8, 1.], DEFAULT_P_GAIN, DEFAULT_I_GAIN, DEFAULT_FORCE_GAIN, false, + true, + Some(( + AngularVelocity::new::(-10000.), + AngularVelocity::new::(0.), + )), ) } fn spoiler_body() -> LinearActuatedRigidBodyOnHingeAxis { let size = Vector3::new(1.785, 0.1, 0.685); let cg_offset = Vector3::new(0., 0., -0.5 * size[2]); + let aero_center_offset = Vector3::new(0., 0., -0.4 * size[2]); let control_arm = Vector3::new(0., -0.067 * size[2], -0.26 * size[2]); let anchor = Vector3::new(0., -0.26 * size[2], 0.26 * size[2]); @@ -2851,6 +3455,7 @@ mod tests { Mass::new::(16.), size, cg_offset, + aero_center_offset, control_arm, anchor, Angle::new::(-10.), diff --git a/src/systems/systems/src/hydraulic/mod.rs b/src/systems/systems/src/hydraulic/mod.rs index 7e81a9757f6b..58c2b5967232 100644 --- a/src/systems/systems/src/hydraulic/mod.rs +++ b/src/systems/systems/src/hydraulic/mod.rs @@ -1,6 +1,8 @@ use self::linear_actuator::Actuator; use crate::failures::{Failure, FailureType}; -use crate::hydraulic::electrical_pump_physics::ElectricalPumpPhysics; +use crate::hydraulic::{ + electrical_pump_physics::ElectricalPumpPhysics, pumps::PumpCharacteristics, +}; use crate::pneumatic::PressurizeableReservoir; use crate::shared::{ interpolation, low_pass_filter::LowPassFilter, random_from_range, DelayedTrueLogicGate, @@ -31,6 +33,8 @@ pub mod flap_slat; pub mod landing_gear; pub mod linear_actuator; pub mod nose_steering; +pub mod pumps; +pub mod trimmable_horizontal_stabilizer; /// Indicates the pressure sensors info of an hydraulic circuit at different locations /// Information can be wrong in case of sensor failure -> do not use for physical pressure @@ -590,6 +594,9 @@ impl SimulationElement for PowerTransferUnit { pub trait HydraulicCircuitController { fn should_open_fire_shutoff_valve(&self, pump_index: usize) -> bool; fn should_open_leak_measurement_valve(&self) -> bool; + fn should_route_pump_to_auxiliary(&self, _pump_index: usize) -> bool { + false + } } pub struct Accumulator { @@ -601,6 +608,8 @@ pub struct Accumulator { current_flow: VolumeRate, current_delta_vol: Volume, has_control_valve: bool, + + circuit_target_pressure: Pressure, } impl Accumulator { const FLOW_DYNAMIC_LOW_PASS: f64 = 0.7; @@ -614,6 +623,7 @@ impl Accumulator { total_volume: Volume, fluid_vol_at_init: Volume, has_control_valve: bool, + circuit_target_pressure: Pressure, ) -> Self { // Taking care of case where init volume is maxed at accumulator capacity: we can't exceed max_volume minus a margin for gas to compress let limited_volume = fluid_vol_at_init.min(total_volume * 0.9); @@ -630,6 +640,7 @@ impl Accumulator { current_flow: VolumeRate::new::(0.), current_delta_vol: Volume::new::(0.), has_control_valve, + circuit_target_pressure, } } @@ -662,10 +673,8 @@ impl Accumulator { *delta_vol += volume_from_acc; } else if accumulator_delta_press.get::() < 0.0 { let fluid_volume_to_reach_equilibrium = self.total_volume - - Volume::new::( - (self.gas_init_precharge.get::() * self.total_volume.get::()) - / 3000., - ); + - ((self.gas_init_precharge * self.total_volume) / self.circuit_target_pressure); + let max_delta_vol = fluid_volume_to_reach_equilibrium - self.fluid_volume; let volume_to_acc = delta_vol .max(Volume::new::(0.0)) @@ -715,19 +724,28 @@ impl Accumulator { pub struct HydraulicCircuit { pump_sections: Vec
, system_section: Section, - pump_to_system_check_valves: Vec, + auxiliary_section: Option
, + + pump_sections_check_valves: Vec, + + // True routed to auxiliary False routed to system section + pump_section_routed_to_auxiliary_section: Vec, fluid: Fluid, reservoir: Reservoir, + + circuit_target_pressure: Pressure, } impl HydraulicCircuit { const PUMP_SECTION_MAX_VOLUME_GAL: f64 = 0.8; const PUMP_SECTION_STATIC_LEAK_GAL_P_S: f64 = 0.005; + // Size of auxiliary section vs system section. 0.5 means auxiliary is half the size of system section + const AUXILIARY_TO_SYSTEM_SECTION_SIZE_RATIO: f64 = 0.5; + const SYSTEM_SECTION_STATIC_LEAK_GAL_P_S: f64 = 0.03; const FLUID_BULK_MODULUS_PASCAL: f64 = 1450000000.0; - const TARGET_PRESSURE_PSI: f64 = 3000.; // Nitrogen PSI precharge pressure const ACCUMULATOR_GAS_PRE_CHARGE_PSI: f64 = 1885.0; @@ -757,12 +775,17 @@ impl HydraulicCircuit { pump_pressure_switch_hi_hyst: Pressure, connected_to_ptu_left_side: bool, connected_to_ptu_right_side: bool, + has_auxiliary_section: bool, + + circuit_target_pressure: Pressure, ) -> Self { assert!(number_of_pump_sections > 0); let mut pump_sections: Vec
= Vec::new(); let mut pump_to_system_check_valves: Vec = Vec::new(); + let mut pump_section_to_auxiliary: Vec = Vec::new(); + for pump_id in 1..=number_of_pump_sections { let fire_valve = Some(FireValve::new( context, @@ -791,6 +814,8 @@ impl HydraulicCircuit { )); pump_to_system_check_valves.push(CheckValve::new()); + + pump_section_to_auxiliary.push(false); } let system_section_volume = high_pressure_max_volume @@ -812,6 +837,7 @@ impl HydraulicCircuit { Volume::new::(Self::ACCUMULATOR_MAX_VOLUME_GALLONS), Volume::new::(0.), false, + circuit_target_pressure, )), system_pressure_switch_lo_hyst, system_pressure_switch_hi_hyst, @@ -822,9 +848,34 @@ impl HydraulicCircuit { Self::DEFAULT_LEAK_MEASUREMENT_VALVE_POWERING_BUS, )), ), - pump_to_system_check_valves, + auxiliary_section: if has_auxiliary_section { + Some(Section::new( + context, + id, + "AUXILIARY", + 1, + VolumeRate::new::(Self::SYSTEM_SECTION_STATIC_LEAK_GAL_P_S), + system_section_volume + * Self::AUXILIARY_TO_SYSTEM_SECTION_SIZE_RATIO + * priming_volume, + system_section_volume * Self::AUXILIARY_TO_SYSTEM_SECTION_SIZE_RATIO, + None, + system_pressure_switch_lo_hyst, + system_pressure_switch_hi_hyst, + None, + false, + false, + None, + )) + } else { + None + }, + + pump_sections_check_valves: pump_to_system_check_valves, + pump_section_routed_to_auxiliary_section: pump_section_to_auxiliary, fluid: Fluid::new(Pressure::new::(Self::FLUID_BULK_MODULUS_PASCAL)), reservoir, + circuit_target_pressure, } } @@ -832,15 +883,24 @@ impl HydraulicCircuit { self.pump_sections[pump_id].fire_valve_is_open() } - pub fn update_actuator_volumes(&mut self, actuator: &mut impl Actuator) { + pub fn update_system_actuator_volumes(&mut self, actuator: &mut impl Actuator) { self.system_section.update_actuator_volumes(actuator); } + pub fn update_auxiliary_actuator_volumes(&mut self, actuator: &mut impl Actuator) { + if let Some(auxiliary_section) = self.auxiliary_section.as_mut() { + auxiliary_section.update_actuator_volumes(actuator); + } else { + panic!("No auxiliary section available but an actuator was provided") + } + } + pub fn update( &mut self, context: &UpdateContext, main_section_pumps: &mut Vec<&mut impl PressureSource>, system_section_pump: Option<&mut impl PressureSource>, + auxiliary_section_pump: Option<&mut impl PressureSource>, ptu: Option<&PowerTransferUnit>, controller: &impl HydraulicCircuitController, reservoir_pressure: Pressure, @@ -849,6 +909,7 @@ impl HydraulicCircuit { self.update_shutoff_valves(controller); self.update_leak_measurement_valves(context, controller); + self.update_auxiliary_selector_valve(controller); // Taking care of leaks / consumers / actuators volumes self.update_flows(context, ptu); @@ -858,30 +919,55 @@ impl HydraulicCircuit { // Updating for each section its total maximum theoretical pumping capacity // "what max volume it could pump considering current reservoir state and pump rpm" - self.update_maximum_pumping_capacities(main_section_pumps, &system_section_pump); + self.update_maximum_pumping_capacities( + main_section_pumps, + &system_section_pump, + &auxiliary_section_pump, + ); // What flow can come through each valve considering what is consumed downstream self.update_maximum_valve_flows(context); // Update final flow that will go through each valve (spliting flow between multiple valves) - self.update_final_valves_flows(); + self.update_system_final_valves_flows(); + self.update_auxiliary_final_valves_flows(); self.update_delta_vol_from_valves(); // We have all flow information, now we set pump parameters (displacement) to where it // should be so we reach target pressure - self.update_pumps(context, main_section_pumps, system_section_pump); + self.update_pumps( + context, + main_section_pumps, + system_section_pump, + auxiliary_section_pump, + ); self.update_final_delta_vol_and_pressure(context); } fn update_delta_vol_from_valves(&mut self) { for (pump_index, section) in self.pump_sections.iter_mut().enumerate() { - section.update_downstream_delta_vol(&self.pump_to_system_check_valves[pump_index]); + section.update_downstream_delta_vol(&self.pump_sections_check_valves[pump_index]); + } + + for (pump_index, _) in self.pump_sections.iter_mut().enumerate() { + if self.auxiliary_section.is_some() + && self.pump_section_routed_to_auxiliary_section[pump_index] + { + self.auxiliary_section + .as_mut() + .unwrap() + .update_upstream_delta_vol(std::slice::from_ref( + &self.pump_sections_check_valves[pump_index], + )); + } else { + self.system_section + .update_upstream_delta_vol(std::slice::from_ref( + &self.pump_sections_check_valves[pump_index], + )); + } } - - self.system_section - .update_upstream_delta_vol(&self.pump_to_system_check_valves); } fn update_pumps( @@ -889,6 +975,7 @@ impl HydraulicCircuit { context: &UpdateContext, main_section_pumps: &mut Vec<&mut impl PressureSource>, system_section_pump: Option<&mut impl PressureSource>, + auxiliary_section_pump: Option<&mut impl PressureSource>, ) { for (pump_index, section) in self.pump_sections.iter_mut().enumerate() { section.update_pump_state(context, main_section_pumps[pump_index], &mut self.reservoir); @@ -898,6 +985,12 @@ impl HydraulicCircuit { self.system_section .update_pump_state(context, pump, &mut self.reservoir); } + + if let Some(pump) = auxiliary_section_pump { + if let Some(auxiliary_section) = self.auxiliary_section.as_mut() { + auxiliary_section.update_pump_state(context, pump, &mut self.reservoir); + } + } } fn update_final_delta_vol_and_pressure(&mut self, context: &UpdateContext) { @@ -907,14 +1000,24 @@ impl HydraulicCircuit { self.system_section .update_final_delta_vol_and_pressure(context, &self.fluid); + + if let Some(auxiliary_section) = self.auxiliary_section.as_mut() { + auxiliary_section.update_final_delta_vol_and_pressure(context, &self.fluid); + } } fn update_maximum_valve_flows(&mut self, context: &UpdateContext) { - for (pump_section_idx, valve) in self.pump_to_system_check_valves.iter_mut().enumerate() { + for (pump_section_idx, valve) in self.pump_sections_check_valves.iter_mut().enumerate() { valve.update_flow_forecast( context, &self.pump_sections[pump_section_idx], - &self.system_section, + if self.auxiliary_section.is_some() + && self.pump_section_routed_to_auxiliary_section[pump_section_idx] + { + self.auxiliary_section.as_mut().unwrap() + } else { + &self.system_section + }, &self.fluid, ); } @@ -924,6 +1027,7 @@ impl HydraulicCircuit { &mut self, main_section_pumps: &mut Vec<&mut impl PressureSource>, system_section_pump: &Option<&mut impl PressureSource>, + auxiliary_section_pump: &Option<&mut impl PressureSource>, ) { for (pump_index, section) in self.pump_sections.iter_mut().enumerate() { section.update_maximum_pumping_capacity(main_section_pumps[pump_index]); @@ -932,27 +1036,52 @@ impl HydraulicCircuit { if let Some(pump) = system_section_pump { self.system_section.update_maximum_pumping_capacity(*pump); } + + if let Some(pump) = auxiliary_section_pump { + if let Some(auxiliary_section) = self.auxiliary_section.as_mut() { + auxiliary_section.update_maximum_pumping_capacity(*pump); + } + } } fn update_target_volumes_after_flow(&mut self) { for section in &mut self.pump_sections { - section.update_target_volume_after_flow_update( - Pressure::new::(Self::TARGET_PRESSURE_PSI), - &self.fluid, - ); + section + .update_target_volume_after_flow_update(self.circuit_target_pressure, &self.fluid); + } + self.system_section + .update_target_volume_after_flow_update(self.circuit_target_pressure, &self.fluid); + + if let Some(auxiliary_section) = self.auxiliary_section.as_mut() { + auxiliary_section + .update_target_volume_after_flow_update(self.circuit_target_pressure, &self.fluid); } - self.system_section.update_target_volume_after_flow_update( - Pressure::new::(Self::TARGET_PRESSURE_PSI), - &self.fluid, - ); } fn update_flows(&mut self, context: &UpdateContext, ptu: Option<&PowerTransferUnit>) { for section in &mut self.pump_sections { - section.update_flow(context, &mut self.reservoir, ptu); + section.update_flow( + context, + &mut self.reservoir, + ptu, + self.circuit_target_pressure, + ); + } + self.system_section.update_flow( + context, + &mut self.reservoir, + ptu, + self.circuit_target_pressure, + ); + + if let Some(auxiliary_section) = self.auxiliary_section.as_mut() { + auxiliary_section.update_flow( + context, + &mut self.reservoir, + ptu, + self.circuit_target_pressure, + ); } - self.system_section - .update_flow(context, &mut self.reservoir, ptu); } fn update_shutoff_valves(&mut self, controller: &impl HydraulicCircuitController) { @@ -970,32 +1099,60 @@ impl HydraulicCircuit { .update_leak_measurement_valve(context, controller); } - fn update_final_valves_flows(&mut self) { + fn update_auxiliary_selector_valve(&mut self, controller: &impl HydraulicCircuitController) { + for (pump_section_index, _) in self.pump_sections_check_valves.iter().enumerate() { + self.pump_section_routed_to_auxiliary_section[pump_section_index] = + controller.should_route_pump_to_auxiliary(pump_section_index); + } + } + + fn update_final_valves_flows(&mut self, to_auxiliary: bool) { let mut total_max_valves_volume = Volume::new::(0.); - for valve in &mut self.pump_to_system_check_valves { - total_max_valves_volume += valve.max_virtual_volume; + for (idx, valve) in &mut self.pump_sections_check_valves.iter_mut().enumerate() { + if self.pump_section_routed_to_auxiliary_section[idx] == to_auxiliary { + total_max_valves_volume += valve.max_virtual_volume; + } } - let used_system_volume = self - .system_section + let downstream_section = if to_auxiliary { + &self.auxiliary_section.as_ref().unwrap() + } else { + &self.system_section + }; + + let used_downstream_volume = downstream_section .volume_target .max(Volume::new::(0.)); - if used_system_volume >= total_max_valves_volume { - // If all the volume upstream is used by system section, each valve will provide its max volume available - for valve in &mut self.pump_to_system_check_valves { - valve.current_volume = valve.max_virtual_volume; + if used_downstream_volume >= total_max_valves_volume { + // If all the volume upstream is used by downstream section, each valve will provide its max volume available + for (idx, valve) in &mut self.pump_sections_check_valves.iter_mut().enumerate() { + if self.pump_section_routed_to_auxiliary_section[idx] == to_auxiliary { + valve.current_volume = valve.max_virtual_volume; + } } } else if total_max_valves_volume > Volume::new::(0.) { - let needed_ratio = used_system_volume / total_max_valves_volume; + let needed_ratio = used_downstream_volume / total_max_valves_volume; - for valve in &mut self.pump_to_system_check_valves { - valve.current_volume = valve.max_virtual_volume * needed_ratio; + for (idx, valve) in &mut self.pump_sections_check_valves.iter_mut().enumerate() { + if self.pump_section_routed_to_auxiliary_section[idx] == to_auxiliary { + valve.current_volume = valve.max_virtual_volume * needed_ratio; + } } } } + fn update_system_final_valves_flows(&mut self) { + self.update_final_valves_flows(false); + } + + fn update_auxiliary_final_valves_flows(&mut self) { + if self.auxiliary_section.is_some() { + self.update_final_valves_flows(true); + } + } + pub fn pump_pressure(&self, idx: usize) -> Pressure { self.pump_sections[idx].pressure() } @@ -1042,6 +1199,10 @@ impl SimulationElement for HydraulicCircuit { self.system_section.accept(visitor); + if let Some(auxiliary_section) = self.auxiliary_section.as_mut() { + auxiliary_section.accept(visitor); + } + visitor.visit(self); } } @@ -1197,11 +1358,11 @@ impl Section { self.volume_target -= self.delta_volume_flow_pass; } - fn static_leak(&self, context: &UpdateContext) -> Volume { + fn static_leak(&self, context: &UpdateContext, target_pressure: Pressure) -> Volume { self.static_leak_at_max_press * context.delta_as_time() * (self.current_pressure - Pressure::new::(14.7)) - / Pressure::new::(3000.) + / target_pressure } /// Updates hydraulic flow from consumers like accumulator / ptu / any actuator @@ -1210,8 +1371,9 @@ impl Section { context: &UpdateContext, reservoir: &mut Reservoir, ptu: Option<&PowerTransferUnit>, + target_pressure: Pressure, ) { - let static_leak = self.static_leak(context); + let static_leak = self.static_leak(context, target_pressure); let mut delta_volume_flow_pass = -static_leak; reservoir.add_return_volume(static_leak); @@ -1384,12 +1546,10 @@ impl SimulationElement for Section { fn write(&self, writer: &mut SimulatorWriter) { writer.write(&self.pressure_id, self.pressure()); - if self.leak_measurement_valve.is_some() { - writer.write( - &self.pressure_switch_id, - self.pressure_switch_state() == PressureSwitchState::Pressurised, - ); - } + writer.write( + &self.pressure_switch_id, + self.pressure_switch_state() == PressureSwitchState::Pressurised, + ); } } impl SectionPressure for Section { @@ -1778,8 +1938,8 @@ pub struct Pump { current_displacement: Volume, current_flow: VolumeRate, current_max_displacement: LowPassFilter, - press_breakpoints: [f64; 9], - displacement_carac: [f64; 9], + + pump_characteristics: PumpCharacteristics, speed: AngularVelocity, @@ -1788,12 +1948,10 @@ pub struct Pump { impl Pump { const SECONDS_PER_MINUTES: f64 = 60.; const FLOW_CONSTANT_RPM_CUBIC_INCH_TO_GPM: f64 = 231.; - const AIR_PRESSURE_BREAKPTS_PSI: [f64; 9] = [0., 5., 10., 15., 20., 30., 50., 70., 100.]; - const AIR_PRESSURE_CARAC_RATIO: [f64; 9] = [0.0, 0.1, 0.6, 0.8, 0.9, 1., 1., 1., 1.]; const MAX_DISPLACEMENT_FILTER_TIME_CONSTANT: Duration = Duration::from_millis(150); - fn new(press_breakpoints: [f64; 9], displacement_carac: [f64; 9]) -> Self { + fn new(pump_characteristics: PumpCharacteristics) -> Self { Self { delta_vol_max: Volume::new::(0.), current_displacement: Volume::new::(0.), @@ -1801,8 +1959,7 @@ impl Pump { current_max_displacement: LowPassFilter::::new( Self::MAX_DISPLACEMENT_FILTER_TIME_CONSTANT, ), - press_breakpoints, - displacement_carac, + pump_characteristics, speed: AngularVelocity::new::(0.), @@ -1839,15 +1996,12 @@ impl Pump { } fn update_cavitation(&mut self, reservoir: &Reservoir) { - self.cavitation_efficiency = Ratio::new::(interpolation( - &Self::AIR_PRESSURE_BREAKPTS_PSI, - &Self::AIR_PRESSURE_CARAC_RATIO, - reservoir.air_pressure().get::(), - )); - - if reservoir.is_empty() { - self.cavitation_efficiency = Ratio::new::(0.); - } + self.cavitation_efficiency = if !reservoir.is_empty() { + self.pump_characteristics + .cavitation_efficiency(reservoir.air_pressure()) + } else { + Ratio::new::(0.) + }; } fn calculate_displacement( @@ -1856,11 +2010,8 @@ impl Pump { controller: &T, ) -> Volume { if controller.should_pressurise() { - Volume::new::(interpolation( - &self.press_breakpoints, - &self.displacement_carac, - section.pressure().get::(), - )) + self.pump_characteristics + .current_displacement(section.pressure()) } else { Volume::new::(0.) } @@ -1949,28 +2100,23 @@ pub struct ElectricPump { pump_physics: ElectricalPumpPhysics, } impl ElectricPump { - const NOMINAL_SPEED: f64 = 7600.0; - - const DISPLACEMENT_BREAKPTS: [f64; 9] = [ - 0.0, 500.0, 1000.0, 1500.0, 2175.0, 2850.0, 3080.0, 3100.0, 3500.0, - ]; - const DISPLACEMENT_MAP: [f64; 9] = [0.263, 0.263, 0.263, 0.263, 0.263, 0.2, 0.0, 0.0, 0.0]; - pub fn new( context: &mut InitContext, id: &str, bus_type: ElectricalBusType, max_current: ElectricCurrent, + pump_characteristics: PumpCharacteristics, ) -> Self { + let regulated_speed = pump_characteristics.regulated_speed(); Self { cavitation_id: context.get_identifier(format!("HYD_{}_EPUMP_CAVITATION", id)), - pump: Pump::new(Self::DISPLACEMENT_BREAKPTS, Self::DISPLACEMENT_MAP), + pump: Pump::new(pump_characteristics), pump_physics: ElectricalPumpPhysics::new( context, id, bus_type, max_current, - AngularVelocity::new::(Self::NOMINAL_SPEED), + regulated_speed, ), } } @@ -2058,17 +2204,16 @@ pub struct EngineDrivenPump { pump: Pump, } impl EngineDrivenPump { - const DISPLACEMENT_BREAKPTS: [f64; 9] = [ - 0.0, 500.0, 1000.0, 1500.0, 2800.0, 2910.0, 3025.0, 3050.0, 3500.0, - ]; - const DISPLACEMENT_MAP: [f64; 9] = [2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 0.0, 0.0, 0.0]; - - pub fn new(context: &mut InitContext, id: &str) -> Self { + pub fn new( + context: &mut InitContext, + id: &str, + pump_characteristics: PumpCharacteristics, + ) -> Self { Self { active_id: context.get_identifier(format!("HYD_{}_EDPUMP_ACTIVE", id)), is_active: false, speed: AngularVelocity::new::(0.), - pump: Pump::new(Self::DISPLACEMENT_BREAKPTS, Self::DISPLACEMENT_MAP), + pump: Pump::new(pump_characteristics), } } @@ -2253,20 +2398,15 @@ pub struct RamAirTurbine { position: f64, } impl RamAirTurbine { - const DISPLACEMENT_BREAKPTS: [f64; 9] = [ - 0.0, 500.0, 1000.0, 1500.0, 2100.0, 2300.0, 2600.0, 2700.0, 3500.0, - ]; - const DISPLACEMENT_MAP: [f64; 9] = [0.5, 0.8, 1.15, 1.15, 1.15, 0.8, 0.3, 0.0, 0.0]; - // Speed to go from 0 to 1 stow position per sec. 1 means full deploying in 1s const STOWING_SPEED: f64 = 1.; - pub fn new(context: &mut InitContext) -> Self { + pub fn new(context: &mut InitContext, pump_characteristics: PumpCharacteristics) -> Self { Self { stow_position_id: context.get_identifier("HYD_RAT_STOW_POSITION".to_owned()), deployment_commanded: false, - pump: Pump::new(Self::DISPLACEMENT_BREAKPTS, Self::DISPLACEMENT_MAP), + pump: Pump::new(pump_characteristics), pump_controller: AlwaysPressurisePumpController::new(), wind_turbine: WindTurbine::new(context), position: 0., @@ -2635,11 +2775,13 @@ mod tests { Pressure::new::(1800.), true, false, + false, + Pressure::new::(3000.), ) } fn engine_driven_pump(context: &mut InitContext) -> EngineDrivenPump { - EngineDrivenPump::new(context, "DEFAULT") + EngineDrivenPump::new(context, "DEFAULT", PumpCharacteristics::a320_edp()) } #[cfg(test)] diff --git a/src/systems/systems/src/hydraulic/pumps.rs b/src/systems/systems/src/hydraulic/pumps.rs new file mode 100644 index 000000000000..2e4239dc85af --- /dev/null +++ b/src/systems/systems/src/hydraulic/pumps.rs @@ -0,0 +1,132 @@ +use crate::shared::interpolation; + +use uom::si::{ + angular_velocity::revolution_per_minute, f64::*, pressure::psi, ratio::ratio, + volume::cubic_inch, +}; + +/// Defines a pump by: +/// displacement map: giving max possible displacement vs current pressure +/// cavitation map: giving the pumping efficiency vs low pressure side air pressure +/// regulated speed: regulation speed value for constant speed pumps +pub struct PumpCharacteristics { + pressure_map_breakpoints_psi: [f64; 9], + displacement_map_cubic_inch: [f64; 9], + + air_pressure_map_breakpoints_psi: [f64; 9], + cavitation_map_ratio: [f64; 9], + + regulated_speed: Option, +} +impl PumpCharacteristics { + const AIR_PRESSURE_BREAKPTS_PSI: [f64; 9] = [0., 5., 10., 15., 20., 30., 50., 70., 100.]; + const AIR_PRESSURE_CARAC_RATIO: [f64; 9] = [0.0, 0.1, 0.6, 0.8, 0.9, 1., 1., 1., 1.]; + + const A320_EDP_DISPLACEMENT_BREAKPTS_PSI: [f64; 9] = [ + 0.0, 500.0, 1000.0, 1500.0, 2800.0, 2910.0, 3025.0, 3050.0, 3500.0, + ]; + const A320_EDP_DISPLACEMENT_MAP_CUBIC_INCH: [f64; 9] = + [2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 0.0, 0.0, 0.0]; + + const A320_EPUMP_REGULATED_SPEED_RPM: f64 = 7600.0; + + const A320_EPUMP_DISPLACEMENT_BREAKPTS_PSI: [f64; 9] = [ + 0.0, 500.0, 1000.0, 1500.0, 2175.0, 2850.0, 3080.0, 3100.0, 3500.0, + ]; + const A320_EPUMP_DISPLACEMENT_MAP_CUBIC_INCH: [f64; 9] = + [0.263, 0.263, 0.263, 0.263, 0.263, 0.2, 0.0, 0.0, 0.0]; + + const A320_RAT_DISPLACEMENT_BREAKPTS_PSI: [f64; 9] = [ + 0.0, 500.0, 1000.0, 1500.0, 2100.0, 2300.0, 2600.0, 2700.0, 3500.0, + ]; + const A320_RAT_DISPLACEMENT_MAP_CUBIC_INCH: [f64; 9] = + [0.5, 0.8, 1.15, 1.15, 1.15, 0.8, 0.3, 0.0, 0.0]; + + const A380_EDP_DISPLACEMENT_BREAKPTS_PSI: [f64; 9] = [ + 0.0, 500.0, 1000.0, 3000.0, 5800.0, 5910.0, 5025.0, 5050.0, 5500.0, + ]; + const A380_EDP_DISPLACEMENT_MAP_CUBIC_INCH: [f64; 9] = + [2.4, 2.4, 2.4, 1., 0.3, 0.1, 0.0, 0.0, 0.0]; + + fn new( + pressure_map_breakpoints_psi: [f64; 9], + displacement_map_cubic_inch: [f64; 9], + + air_pressure_map_breakpoints_psi: [f64; 9], + cavitation_map_ratio: [f64; 9], + + regulated_speed: Option, + ) -> Self { + Self { + pressure_map_breakpoints_psi, + displacement_map_cubic_inch, + + air_pressure_map_breakpoints_psi, + cavitation_map_ratio, + + regulated_speed, + } + } + + pub fn a320_edp() -> Self { + PumpCharacteristics::new( + Self::A320_EDP_DISPLACEMENT_BREAKPTS_PSI, + Self::A320_EDP_DISPLACEMENT_MAP_CUBIC_INCH, + Self::AIR_PRESSURE_BREAKPTS_PSI, + Self::AIR_PRESSURE_CARAC_RATIO, + None, + ) + } + + pub fn a320_rat() -> Self { + PumpCharacteristics::new( + Self::A320_RAT_DISPLACEMENT_BREAKPTS_PSI, + Self::A320_RAT_DISPLACEMENT_MAP_CUBIC_INCH, + Self::AIR_PRESSURE_BREAKPTS_PSI, + Self::AIR_PRESSURE_CARAC_RATIO, + None, + ) + } + + pub fn a320_electric_pump() -> Self { + PumpCharacteristics::new( + Self::A320_EPUMP_DISPLACEMENT_BREAKPTS_PSI, + Self::A320_EPUMP_DISPLACEMENT_MAP_CUBIC_INCH, + Self::AIR_PRESSURE_BREAKPTS_PSI, + Self::AIR_PRESSURE_CARAC_RATIO, + Some(AngularVelocity::new::( + Self::A320_EPUMP_REGULATED_SPEED_RPM, + )), + ) + } + + pub fn a380_edp() -> Self { + PumpCharacteristics::new( + Self::A380_EDP_DISPLACEMENT_BREAKPTS_PSI, + Self::A380_EDP_DISPLACEMENT_MAP_CUBIC_INCH, + Self::AIR_PRESSURE_BREAKPTS_PSI, + Self::AIR_PRESSURE_CARAC_RATIO, + None, + ) + } + + pub fn current_displacement(&self, pressure: Pressure) -> Volume { + Volume::new::(interpolation( + &self.pressure_map_breakpoints_psi, + &self.displacement_map_cubic_inch, + pressure.get::(), + )) + } + + pub fn cavitation_efficiency(&self, air_pressure: Pressure) -> Ratio { + Ratio::new::(interpolation( + &self.air_pressure_map_breakpoints_psi, + &self.cavitation_map_ratio, + air_pressure.get::(), + )) + } + + pub fn regulated_speed(&self) -> AngularVelocity { + self.regulated_speed.unwrap_or_default() + } +} diff --git a/src/systems/systems/src/hydraulic/trimmable_horizontal_stabilizer.rs b/src/systems/systems/src/hydraulic/trimmable_horizontal_stabilizer.rs new file mode 100644 index 000000000000..06175393774f --- /dev/null +++ b/src/systems/systems/src/hydraulic/trimmable_horizontal_stabilizer.rs @@ -0,0 +1,1066 @@ +use uom::si::{ + angle::{degree, radian}, + angular_velocity::{radian_per_second, revolution_per_minute}, + f64::*, + pressure::psi, + ratio::ratio, + volume::gallon, + volume_rate::gallon_per_minute, +}; + +use crate::simulation::{ + InitContext, SimulationElement, SimulationElementVisitor, SimulatorWriter, UpdateContext, + VariableIdentifier, Write, +}; + +use crate::shared::{ + interpolation, low_pass_filter::LowPassFilter, ElectricalBusType, ElectricalBuses, +}; + +use super::linear_actuator::Actuator; + +use std::time::Duration; + +struct TrimWheels { + position_percent_id: VariableIdentifier, + + position: Angle, + trim_actuator_over_trim_wheel_ratio: Ratio, + + min_angle: Angle, + max_angle: Angle, +} +impl TrimWheels { + fn new( + context: &mut InitContext, + trim_actuator_over_trim_wheel_ratio: Ratio, + min_angle: Angle, + total_range_angle: Angle, + ) -> Self { + Self { + position_percent_id: context.get_identifier("HYD_TRIM_WHEEL_PERCENT".to_owned()), + + position: Angle::default(), + trim_actuator_over_trim_wheel_ratio, + min_angle, + max_angle: min_angle + total_range_angle, + } + } + + fn update(&mut self, pta: &PitchTrimActuator) { + self.position = pta.position / self.trim_actuator_over_trim_wheel_ratio.get::(); + } + + fn position_normalized(&self) -> Ratio { + ((self.position - self.min_angle) / (self.max_angle - self.min_angle)) + .min(Ratio::new::(100.)) + .max(Ratio::new::(0.)) + } +} +impl SimulationElement for TrimWheels { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write( + &self.position_percent_id, + self.position_normalized().get::() * 100., + ); + } +} + +#[derive(Clone, Copy)] +struct DriveMotor { + speed: LowPassFilter, + + is_active: bool, + max_speed: AngularVelocity, + + speed_regulation_coef_map: [f64; 7], + speed_error_breakpoint: [f64; 7], +} +impl DriveMotor { + fn new( + max_speed: AngularVelocity, + + speed_error_breakpoint: [f64; 7], + speed_regulation_coef_map: [f64; 7], + ) -> Self { + Self { + speed: LowPassFilter::new(Duration::from_millis(50)), + + is_active: false, + max_speed, + + speed_regulation_coef_map, + speed_error_breakpoint, + } + } + + fn set_active_state(&mut self, is_active: bool) { + self.is_active = is_active; + } + + fn update( + &mut self, + context: &UpdateContext, + measured_position: Angle, + requested_position: Angle, + ) { + let new_speed = if self.is_active { + let position_error = requested_position - measured_position; + + let speed_coef = interpolation( + &self.speed_error_breakpoint, + &self.speed_regulation_coef_map, + position_error.get::(), + ); + + self.max_speed * speed_coef + } else { + AngularVelocity::default() + }; + + self.speed.update(context.delta(), new_speed); + } + + fn speed(&self) -> AngularVelocity { + self.speed.output() + } + + fn set_max_speed(&mut self, new_max_speed: AngularVelocity) { + self.max_speed = new_max_speed; + } +} + +#[derive(PartialEq, Clone, Copy)] +enum ElectricalTrimMotorElecBus { + _Norm = 0, + Standby = 1, +} + +struct ElectricDriveMotor { + motor: DriveMotor, + + is_powered: bool, + + powered_by_bus_array: Vec, + powered_by_bus: ElectricalTrimMotorElecBus, +} +impl ElectricDriveMotor { + /// Creates an electric motor driving the trim input system. + /// Power bus provided can contain only one main bus, or one main bus plus a secondary standby bus + fn new( + max_speed: AngularVelocity, + + speed_error_breakpoint: [f64; 7], + speed_regulation_coef_map: [f64; 7], + + powered_by_bus_array: Vec, + ) -> Self { + // Only supports one main bus or one main plus one standby + assert!(powered_by_bus_array.len() <= 2); + + Self { + motor: DriveMotor::new(max_speed, speed_error_breakpoint, speed_regulation_coef_map), + is_powered: true, + + powered_by_bus_array, + + // TODO: Init with Standby to select most restrictive case until the bus selection logic is implemented + powered_by_bus: ElectricalTrimMotorElecBus::Standby, + } + } + + fn set_active_state(&mut self, is_active: bool) { + self.motor.set_active_state(is_active && self.is_powered); + } + + fn update( + &mut self, + context: &UpdateContext, + measured_position: Angle, + requested_position: Angle, + ) { + self.motor + .update(context, measured_position, requested_position); + } + + fn speed(&self) -> AngularVelocity { + self.motor.speed() + } + + /// Selects which bus powers the motor (if more than one bus available for that motor) + // TODO: The logic to select this bus is not implemented yet in flight computers + fn _set_power_bus_in_use(&mut self, elec_bus_in_use: ElectricalTrimMotorElecBus) { + self.powered_by_bus = elec_bus_in_use; + } + + fn is_powered(&self) -> bool { + self.is_powered + } +} +impl SimulationElement for ElectricDriveMotor { + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + let bus_selected_index: usize = + (self.powered_by_bus as usize).min(self.powered_by_bus_array.len() - 1); + + self.is_powered = buses.is_powered(self.powered_by_bus_array[bus_selected_index]); + } +} + +#[derive(Clone, Copy)] +struct HydraulicDriveMotor { + max_speed: AngularVelocity, + motor: DriveMotor, + + total_volume_to_actuator: Volume, + total_volume_to_reservoir: Volume, +} +impl HydraulicDriveMotor { + const FLOW_CONSTANT_RPM_CUBIC_INCH_TO_GPM: f64 = 231.; + const DISPLACEMENT_CUBIC_INCH: f64 = 0.4; + + // Hyd pressure at which motor has its max speed + const MAX_SPEED_HYD_PRESSURE_PSI: f64 = 3000.; + + fn new( + max_speed: AngularVelocity, + + speed_error_breakpoint: [f64; 7], + speed_regulation_coef_map: [f64; 7], + ) -> Self { + Self { + max_speed, + motor: DriveMotor::new(max_speed, speed_error_breakpoint, speed_regulation_coef_map), + + total_volume_to_actuator: Volume::default(), + total_volume_to_reservoir: Volume::default(), + } + } + + fn update( + &mut self, + context: &UpdateContext, + measured_position: Angle, + position_requested: Angle, + pressure: Pressure, + ) { + self.motor + .set_active_state(pressure > Pressure::new::(1450.)); + + self.motor + .set_max_speed(self.current_max_speed_from_hydraulic_pressure(pressure)); + + self.motor + .update(context, measured_position, position_requested); + + self.update_flow(context); + } + + fn update_flow(&mut self, context: &UpdateContext) { + let total_volume = self.flow() * context.delta_as_time(); + self.total_volume_to_actuator += total_volume.abs(); + self.total_volume_to_reservoir = self.total_volume_to_actuator; + } + + fn current_max_speed_from_hydraulic_pressure(&self, pressure: Pressure) -> AngularVelocity { + let pressure_coefficient = (pressure.get::() / Self::MAX_SPEED_HYD_PRESSURE_PSI) + .min(1.) + .max(0.); + + self.max_speed * pressure_coefficient + } + + fn speed(&self) -> AngularVelocity { + self.motor.speed() + } + + fn flow(&self) -> VolumeRate { + VolumeRate::new::( + self.speed().get::() * Self::DISPLACEMENT_CUBIC_INCH + / Self::FLOW_CONSTANT_RPM_CUBIC_INCH_TO_GPM, + ) + } +} +impl Actuator for HydraulicDriveMotor { + fn used_volume(&self) -> Volume { + self.total_volume_to_actuator + } + + fn reservoir_return(&self) -> Volume { + self.total_volume_to_reservoir + } + + fn reset_volumes(&mut self) { + self.total_volume_to_reservoir = Volume::new::(0.); + self.total_volume_to_actuator = Volume::new::(0.); + } +} + +#[derive(Clone, Copy)] +struct ElectricMotorClutch { + is_powered: bool, + is_energized: bool, +} +impl ElectricMotorClutch { + fn default() -> Self { + Self { + is_powered: true, + is_energized: false, + } + } + + fn is_clutch_engaged(&self) -> bool { + self.is_powered && self.is_energized + } + + fn set_energized(&mut self, is_energized: bool) { + self.is_energized = is_energized; + } + + fn set_is_powered(&mut self, elec_motor_is_powered: bool) { + self.is_powered = elec_motor_is_powered; + } +} + +pub trait PitchTrimActuatorController { + fn commanded_position(&self) -> Angle; + fn energised_motor(&self) -> [bool; 3]; +} + +pub trait ManualPitchTrimController { + fn is_manually_moved(&self) -> bool; + fn moving_speed(&self) -> AngularVelocity; +} + +struct PitchTrimActuator { + manual_override_id: VariableIdentifier, + + electric_motors: [ElectricDriveMotor; 3], + electric_clutches: [ElectricMotorClutch; 3], + manual_override_active: bool, + + position: Angle, + speed: AngularVelocity, + + elec_motor_over_trim_actuator_ratio: Ratio, + + min_actuator_angle: Angle, + max_actuator_angle: Angle, +} +impl PitchTrimActuator { + const ELECTRIC_MOTOR_POSITION_ERROR_BREAKPOINT: [f64; 7] = + [-50., -5., -0.05, 0., 0.05, 5., 50.]; + const ELECTRIC_MOTOR_SPEED_REGULATION_COEF_MAP: [f64; 7] = [-1., -1., -0.01, 0., 0.01, 1., 1.]; + + const MIN_ELEC_MOTOR_SPEED_FOR_MANUAL_OVERRIDE_DETECTION_RPM: f64 = 10.; + + fn new( + context: &mut InitContext, + min_actuator_angle: Angle, + total_actuator_range_angle: Angle, + max_elec_motor_speed: AngularVelocity, + elec_motor_over_trim_actuator_ratio: Ratio, + ) -> Self { + Self { + manual_override_id: context.get_identifier("HYD_THS_TRIM_MANUAL_OVERRIDE".to_owned()), + + electric_motors: [ + ElectricDriveMotor::new( + max_elec_motor_speed, + Self::ELECTRIC_MOTOR_POSITION_ERROR_BREAKPOINT, + Self::ELECTRIC_MOTOR_SPEED_REGULATION_COEF_MAP, + vec![ + ElectricalBusType::DirectCurrent(2), + ElectricalBusType::DirectCurrentHot(2), + ], + ), + ElectricDriveMotor::new( + max_elec_motor_speed, + Self::ELECTRIC_MOTOR_POSITION_ERROR_BREAKPOINT, + Self::ELECTRIC_MOTOR_SPEED_REGULATION_COEF_MAP, + vec![ElectricalBusType::DirectCurrentEssential], + ), + ElectricDriveMotor::new( + max_elec_motor_speed, + Self::ELECTRIC_MOTOR_POSITION_ERROR_BREAKPOINT, + Self::ELECTRIC_MOTOR_SPEED_REGULATION_COEF_MAP, + vec![ElectricalBusType::DirectCurrent(2)], + ), + ], + electric_clutches: [ElectricMotorClutch::default(); 3], + manual_override_active: false, + + position: Angle::default(), + speed: AngularVelocity::default(), + + elec_motor_over_trim_actuator_ratio, + + min_actuator_angle, + max_actuator_angle: min_actuator_angle + total_actuator_range_angle, + } + } + + fn update( + &mut self, + context: &UpdateContext, + electric_controller: &impl PitchTrimActuatorController, + manual_controller: &impl ManualPitchTrimController, + ths_hydraulic_assembly: &TrimmableHorizontalStabilizerHydraulics, + ) { + self.update_clutches_state(electric_controller); + self.update_motors(context, electric_controller, ths_hydraulic_assembly); + + self.update_speed_and_override(manual_controller, ths_hydraulic_assembly); + + self.update_position(context); + } + + fn update_position(&mut self, context: &UpdateContext) { + self.position += Angle::new::( + self.speed.get::() * context.delta_as_secs_f64(), + ); + + self.position = self + .position + .min(self.max_actuator_angle) + .max(self.min_actuator_angle); + } + + fn update_speed_and_override( + &mut self, + manual_controller: &impl ManualPitchTrimController, + ths_hydraulic_assembly: &TrimmableHorizontalStabilizerHydraulics, + ) { + let elec_drive_speed = self.elec_motor_drive_total_speed(); + + if manual_controller.is_manually_moved() { + self.speed = manual_controller.moving_speed(); + self.manual_override_active = elec_drive_speed.get::().abs() + > Self::MIN_ELEC_MOTOR_SPEED_FOR_MANUAL_OVERRIDE_DETECTION_RPM; + } else { + self.speed = elec_drive_speed; + self.manual_override_active = false + } + + if ths_hydraulic_assembly.is_at_max_down_spool_valve + && self.speed.get::() < 0. + || ths_hydraulic_assembly.is_at_max_up_spool_valve + && self.speed.get::() > 0. + { + self.speed = AngularVelocity::default(); + } + } + + fn elec_motor_drive_total_speed(&self) -> AngularVelocity { + let mut sum_of_speeds = AngularVelocity::default(); + + for (motor_index, motor) in self.electric_motors.iter().enumerate() { + if self.electric_clutches[motor_index].is_clutch_engaged() { + sum_of_speeds += + motor.speed() / self.elec_motor_over_trim_actuator_ratio.get::(); + } + } + + sum_of_speeds + } + + fn update_clutches_state(&mut self, controller: &impl PitchTrimActuatorController) { + for (clutch_index, clutch) in self.electric_clutches.iter_mut().enumerate() { + clutch.set_is_powered(self.electric_motors[clutch_index].is_powered()); + clutch.set_energized(controller.energised_motor()[clutch_index]); + } + } + + fn update_motors( + &mut self, + context: &UpdateContext, + controller: &impl PitchTrimActuatorController, + ths_hydraulic_assembly: &TrimmableHorizontalStabilizerHydraulics, + ) { + for (motor_index, motor) in self.electric_motors.iter_mut().enumerate() { + motor.set_active_state(controller.energised_motor()[motor_index]); + + let trim_actuator_normalized_position_request = ths_hydraulic_assembly + .normalized_position_from_ths_deflection(controller.commanded_position()); + + let final_trim_actuator_position_request = trim_actuator_normalized_position_request + .get::() + * (self.max_actuator_angle - self.min_actuator_angle) + + self.min_actuator_angle; + + motor.update(context, self.position, final_trim_actuator_position_request); + } + } + + fn position_normalized(&self) -> Ratio { + let range = self.max_actuator_angle - self.min_actuator_angle; + + Ratio::new::( + (self.position.get::() - self.min_actuator_angle.get::()) + / range.get::(), + ) + .min(Ratio::new::(100.)) + .max(Ratio::new::(0.)) + } +} +impl SimulationElement for PitchTrimActuator { + fn accept(&mut self, visitor: &mut T) { + accept_iterable!(self.electric_motors, visitor); + visitor.visit(self); + } + + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.manual_override_id, self.manual_override_active); + } +} + +pub struct TrimmableHorizontalStabilizerAssembly { + pitch_trim_actuator: PitchTrimActuator, + trim_wheel: TrimWheels, + ths_hydraulics: TrimmableHorizontalStabilizerHydraulics, +} +impl TrimmableHorizontalStabilizerAssembly { + pub fn new( + context: &mut InitContext, + + min_actuator_angle: Angle, + total_actuator_range_angle: Angle, + + min_trim_wheel_angle: Angle, + total_trim_wheel_range_angle: Angle, + + max_elec_motor_speed: AngularVelocity, + elec_motor_over_trim_actuator_ratio: Ratio, + + min_ths_deflection: Angle, + ths_deflection_range: Angle, + ) -> Self { + Self { + pitch_trim_actuator: PitchTrimActuator::new( + context, + min_actuator_angle, + total_actuator_range_angle, + max_elec_motor_speed, + elec_motor_over_trim_actuator_ratio, + ), + trim_wheel: TrimWheels::new( + context, + total_actuator_range_angle / total_trim_wheel_range_angle, + min_trim_wheel_angle, + total_trim_wheel_range_angle, + ), + ths_hydraulics: TrimmableHorizontalStabilizerHydraulics::new( + context, + min_ths_deflection, + ths_deflection_range, + ), + } + } + + pub fn update( + &mut self, + context: &UpdateContext, + electric_controller: &impl PitchTrimActuatorController, + manual_controller: &impl ManualPitchTrimController, + pressures: [Pressure; 2], + ) { + self.pitch_trim_actuator.update( + context, + electric_controller, + manual_controller, + &self.ths_hydraulics, + ); + + self.trim_wheel.update(&self.pitch_trim_actuator); + + self.ths_hydraulics.update( + context, + pressures, + self.pitch_trim_actuator.position_normalized(), + ) + } + + pub fn position_normalized(&self) -> Ratio { + self.pitch_trim_actuator.position_normalized() + } + + pub fn left_motor(&mut self) -> &mut impl Actuator { + self.ths_hydraulics.left_motor() + } + + pub fn right_motor(&mut self) -> &mut impl Actuator { + self.ths_hydraulics.right_motor() + } +} +impl SimulationElement for TrimmableHorizontalStabilizerAssembly { + fn accept(&mut self, visitor: &mut T) { + self.pitch_trim_actuator.accept(visitor); + self.trim_wheel.accept(visitor); + self.ths_hydraulics.accept(visitor); + } +} + +struct TrimmableHorizontalStabilizerHydraulics { + deflection_id: VariableIdentifier, + hydraulic_motors: [HydraulicDriveMotor; 2], + + speed: AngularVelocity, + actual_deflection: Angle, + + min_deflection: Angle, + max_deflection: Angle, + deflection_range: Angle, + + is_at_max_up_spool_valve: bool, + is_at_max_down_spool_valve: bool, +} +impl TrimmableHorizontalStabilizerHydraulics { + const HYDRAULIC_MOTOR_POSITION_ERROR_BREAKPOINT: [f64; 7] = + [-50., -0.5, -0.1, 0., 0.1, 0.5, 50.]; + const HYDRAULIC_MOTOR_SPEED_REGULATION_COEF_MAP: [f64; 7] = [-1., -1., -0.6, 0., 0.6, 1., 1.]; + + // Gain to convert hyd motor speed to ths deflection speed + const HYD_MOTOR_SPEED_TO_THS_DEFLECTION_SPEED_GAIN: f64 = 0.000085; + + const MAX_DEFLECTION_FOR_FULL_OPEN_SPOOL_VALVE_DEGREES: f64 = 0.4; + + pub fn new(context: &mut InitContext, min_deflection: Angle, deflection_range: Angle) -> Self { + Self { + deflection_id: context.get_identifier("HYD_FINAL_THS_DEFLECTION".to_owned()), + hydraulic_motors: [HydraulicDriveMotor::new( + AngularVelocity::new::(2500.), + Self::HYDRAULIC_MOTOR_POSITION_ERROR_BREAKPOINT, + Self::HYDRAULIC_MOTOR_SPEED_REGULATION_COEF_MAP, + ); 2], + + speed: AngularVelocity::default(), + actual_deflection: Angle::default(), + min_deflection, + max_deflection: min_deflection + deflection_range, + deflection_range, + + is_at_max_up_spool_valve: false, + is_at_max_down_spool_valve: false, + } + } + + pub fn update( + &mut self, + context: &UpdateContext, + pressures: [Pressure; 2], + trim_actuator_output_position_normalized: Ratio, + ) { + let deflection_demand = self.deflection_range + * trim_actuator_output_position_normalized.get::() + + self.min_deflection; + + self.update_spool_valve_lock_position(deflection_demand); + + for (motor_index, motor) in self.hydraulic_motors.iter_mut().enumerate() { + motor.update( + context, + self.actual_deflection, + deflection_demand, + pressures[motor_index], + ) + } + + self.update_speed(); + + self.update_position(context); + } + + fn update_speed(&mut self) { + let mut sum_of_speeds = AngularVelocity::default(); + + for motor in self.hydraulic_motors { + sum_of_speeds += motor.speed() * Self::HYD_MOTOR_SPEED_TO_THS_DEFLECTION_SPEED_GAIN; + } + + self.speed = sum_of_speeds; + } + + fn update_spool_valve_lock_position(&mut self, deflection_demand: Angle) { + let deflection_error = deflection_demand - self.actual_deflection; + self.is_at_max_up_spool_valve = deflection_error.get::() + > Self::MAX_DEFLECTION_FOR_FULL_OPEN_SPOOL_VALVE_DEGREES; + self.is_at_max_down_spool_valve = deflection_error.get::() + < -Self::MAX_DEFLECTION_FOR_FULL_OPEN_SPOOL_VALVE_DEGREES; + } + + fn update_position(&mut self, context: &UpdateContext) { + self.actual_deflection += Angle::new::( + self.speed.get::() * context.delta_as_secs_f64(), + ); + + self.actual_deflection = self + .actual_deflection + .min(self.max_deflection) + .max(self.min_deflection); + } + + fn normalized_position_from_ths_deflection(&self, deflection: Angle) -> Ratio { + (deflection - self.min_deflection) / self.deflection_range + } + + pub fn left_motor(&mut self) -> &mut impl Actuator { + &mut self.hydraulic_motors[0] + } + + pub fn right_motor(&mut self) -> &mut impl Actuator { + &mut self.hydraulic_motors[1] + } +} +impl SimulationElement for TrimmableHorizontalStabilizerHydraulics { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.deflection_id, self.actual_deflection.get::()); + } +} + +#[cfg(test)] +mod tests { + use uom::si::angle::degree; + use uom::si::{angular_velocity::degree_per_second, electric_potential::volt, ratio::percent}; + + use crate::electrical::test::TestElectricitySource; + use crate::electrical::ElectricalBus; + use crate::electrical::Electricity; + + use super::*; + use crate::shared::{update_iterator::FixedStepLoop, PotentialOrigin}; + use crate::simulation::test::{ReadByName, SimulationTestBed, TestBed}; + use crate::simulation::{Aircraft, SimulationElement}; + use std::time::Duration; + + use rstest::rstest; + + struct TestElecTrimControl { + control_active: bool, + position_request: Angle, + motor_idx_in_control: usize, + } + impl TestElecTrimControl { + fn with_motor_idx_and_pos_demand( + motor_idx_in_control: usize, + position_request: Angle, + ) -> Self { + Self { + control_active: true, + position_request, + motor_idx_in_control, + } + } + + fn inactive_control() -> Self { + Self { + control_active: false, + position_request: Angle::default(), + motor_idx_in_control: 0, + } + } + } + impl PitchTrimActuatorController for TestElecTrimControl { + fn commanded_position(&self) -> Angle { + self.position_request + } + + fn energised_motor(&self) -> [bool; 3] { + if !self.control_active { + [false, false, false] + } else { + let mut energized_array = [false, false, false]; + energized_array[self.motor_idx_in_control] = true; + energized_array + } + } + } + + struct TestManualTrimControl { + control_active: bool, + speed: AngularVelocity, + } + impl TestManualTrimControl { + fn with_manual_input(speed: AngularVelocity) -> Self { + Self { + control_active: true, + speed, + } + } + + fn without_manual_input() -> Self { + Self { + control_active: false, + speed: AngularVelocity::default(), + } + } + } + impl ManualPitchTrimController for TestManualTrimControl { + fn is_manually_moved(&self) -> bool { + self.control_active + } + + fn moving_speed(&self) -> AngularVelocity { + self.speed + } + } + + struct TestAircraft { + updater_fixed_step: FixedStepLoop, + + elec_trim_control: TestElecTrimControl, + manual_trim_control: TestManualTrimControl, + + trim_assembly: TrimmableHorizontalStabilizerAssembly, + + hydraulic_pressures: [Pressure; 2], + + powered_source_dc: TestElectricitySource, + dc_2_bus: ElectricalBus, + dc_hot_bus: ElectricalBus, + dc_ess_bus: ElectricalBus, + is_elec_powered: bool, + } + impl TestAircraft { + fn new(context: &mut InitContext) -> Self { + Self { + updater_fixed_step: FixedStepLoop::new(Duration::from_millis(33)), + elec_trim_control: TestElecTrimControl::inactive_control(), + manual_trim_control: TestManualTrimControl::without_manual_input(), + trim_assembly: TrimmableHorizontalStabilizerAssembly::new( + context, + Angle::new::(360. * -1.4), + Angle::new::(360. * 6.13), + Angle::new::(360. * -1.87), + Angle::new::(360. * 8.19), // 1.87 rotations down 6.32 up + AngularVelocity::new::(5000.), + Ratio::new::(2035. / 6.13), + Angle::new::(-4.), + Angle::new::(17.5), + ), + + hydraulic_pressures: [Pressure::new::(3000.); 2], + + powered_source_dc: TestElectricitySource::powered( + context, + PotentialOrigin::Battery(2), + ), + + dc_2_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(2)), + dc_hot_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrentHot(2)), + dc_ess_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrentEssential), + is_elec_powered: true, + } + } + + fn set_elec_trim_demand(&mut self, angle_request: Angle, motor_idx: usize) { + self.elec_trim_control = + TestElecTrimControl::with_motor_idx_and_pos_demand(motor_idx, angle_request); + } + + fn set_manual_trim_input(&mut self, trim_up: bool) { + self.manual_trim_control = if trim_up { + TestManualTrimControl::with_manual_input(AngularVelocity::new::( + 500., + )) + } else { + TestManualTrimControl::with_manual_input(AngularVelocity::new::( + -500., + )) + } + } + + fn set_no_manual_input(&mut self) { + self.manual_trim_control = TestManualTrimControl::without_manual_input(); + } + + fn set_no_elec_input(&mut self) { + self.elec_trim_control = TestElecTrimControl::inactive_control(); + } + + fn set_hyd_pressure(&mut self, pressures: [Pressure; 2]) { + self.hydraulic_pressures = pressures; + } + + fn set_no_elec_power(&mut self) { + self.is_elec_powered = false; + } + } + impl Aircraft for TestAircraft { + fn update_before_power_distribution( + &mut self, + _: &UpdateContext, + electricity: &mut Electricity, + ) { + self.powered_source_dc + .power_with_potential(ElectricPotential::new::(24.)); + electricity.supplied_by(&self.powered_source_dc); + + if self.is_elec_powered { + electricity.flow(&self.powered_source_dc, &self.dc_2_bus); + electricity.flow(&self.powered_source_dc, &self.dc_ess_bus); + electricity.flow(&self.powered_source_dc, &self.dc_hot_bus); + } + } + + fn update_after_power_distribution(&mut self, context: &UpdateContext) { + self.updater_fixed_step.update(context); + + for cur_time_step in &mut self.updater_fixed_step { + self.trim_assembly.update( + &context.with_delta(cur_time_step), + &self.elec_trim_control, + &self.manual_trim_control, + self.hydraulic_pressures, + ); + + println!( + "TW turns: {:.3} ,PTA turns {:.3}, PTA norm {:.2} EMotors rpm {:.0}/{:.0}/{:.0} HydMotors rpm {:.0} {:.0} THS Deg {:.1}", + self.trim_assembly.trim_wheel.position.get::() / 360., + self.trim_assembly + .pitch_trim_actuator + .position + .get::() + / 360., + self.trim_assembly.position_normalized().get::(), + self.trim_assembly.pitch_trim_actuator.electric_motors[0] + .speed() + .get::(), + self.trim_assembly.pitch_trim_actuator.electric_motors[1] + .speed() + .get::(), + self.trim_assembly.pitch_trim_actuator.electric_motors[2] + .speed() + .get::(), + self.trim_assembly.ths_hydraulics.hydraulic_motors[0].speed().get::(), + self.trim_assembly.ths_hydraulics.hydraulic_motors[1].speed().get::(), + self.trim_assembly.ths_hydraulics.actual_deflection.get::() + + ); + } + } + } + impl SimulationElement for TestAircraft { + fn accept(&mut self, visitor: &mut V) { + self.trim_assembly.accept(visitor); + + visitor.visit(self); + } + } + + #[test] + fn trim_assembly_init_state() { + let mut test_bed = SimulationTestBed::new(TestAircraft::new); + + test_bed.run_with_delta(Duration::from_millis(100)); + + let deflection: Angle = test_bed.read_by_name("HYD_FINAL_THS_DEFLECTION"); + assert!(deflection.get::().abs() < 0.01); + } + + #[rstest] + #[case(0)] + #[case(1)] + #[case(2)] + fn trim_assembly_trim_up_trim_down_motor_n(#[case] motor_idx: usize) { + let mut test_bed = SimulationTestBed::new(|context| TestAircraft::new(context)); + + test_bed.command(|a| a.set_elec_trim_demand(Angle::new::(13.), motor_idx)); + test_bed.command(|a| a.set_no_manual_input()); + + test_bed.run_with_delta(Duration::from_millis(20000)); + + let deflection: Angle = test_bed.read_by_name("HYD_FINAL_THS_DEFLECTION"); + assert!(deflection.get::() > 12.9); + assert!(deflection.get::() < 13.1); + + let man_override: f64 = test_bed.read_by_name("HYD_THS_TRIM_MANUAL_OVERRIDE"); + assert!(man_override <= 0.5); + + test_bed.command(|a| a.set_elec_trim_demand(Angle::new::(-2.), motor_idx)); + test_bed.run_with_delta(Duration::from_millis(25000)); + + let deflection: Angle = test_bed.read_by_name("HYD_FINAL_THS_DEFLECTION"); + assert!(deflection.get::() >= -2.1); + assert!(deflection.get::() < -1.9); + } + + #[test] + fn trim_assembly_moves_but_ths_stops_with_hyd_press_below_1450psi() { + let mut test_bed = SimulationTestBed::new(TestAircraft::new); + + test_bed.command(|a| a.set_elec_trim_demand(Angle::new::(13.), 0)); + test_bed.run_with_delta(Duration::from_millis(5000)); + + let deflection: Angle = test_bed.read_by_name("HYD_FINAL_THS_DEFLECTION"); + assert!(deflection.get::() > 2.); + + println!("PRESSURE DROP"); + test_bed.command(|a| { + a.set_hyd_pressure([Pressure::new::(1300.), Pressure::new::(1300.)]) + }); + test_bed.run_with_delta(Duration::from_millis(5000)); + + let deflection_after_hyd_fail: Angle = test_bed.read_by_name("HYD_FINAL_THS_DEFLECTION"); + assert!((deflection - deflection_after_hyd_fail).abs() < Angle::new::(1.)); + } + + #[test] + fn trim_assembly_max_motor_0() { + let mut test_bed = SimulationTestBed::new(|context| TestAircraft::new(context)); + + test_bed.command(|a| a.set_elec_trim_demand(Angle::new::(13.5), 0)); + test_bed.run_with_delta(Duration::from_millis(20000)); + + let deflection: Angle = test_bed.read_by_name("HYD_FINAL_THS_DEFLECTION"); + assert!(deflection.get::() > 13.45); + assert!(deflection.get::() < 13.55); + + let trim_wheel_position_percent: Ratio = test_bed.read_by_name("HYD_TRIM_WHEEL_PERCENT"); + assert!(trim_wheel_position_percent.get::() > 99.9); + assert!(trim_wheel_position_percent.get::() < 100.1); + } + + #[test] + fn trim_assembly_motor_0_without_elec_is_stuck() { + let mut test_bed = SimulationTestBed::new(|context| TestAircraft::new(context)); + + test_bed.command(|a| a.set_elec_trim_demand(Angle::new::(13.5), 0)); + test_bed.command(|a| a.set_no_elec_power()); + test_bed.run_with_delta(Duration::from_millis(20000)); + + let deflection: Angle = test_bed.read_by_name("HYD_FINAL_THS_DEFLECTION"); + assert!(deflection.get::() >= -0.1); + assert!(deflection.get::() <= 0.1); + } + + #[test] + fn trim_assembly_min_motor_0() { + let mut test_bed = SimulationTestBed::new(|context| TestAircraft::new(context)); + + test_bed.command(|a| a.set_elec_trim_demand(Angle::new::(-4.), 0)); + test_bed.run_with_delta(Duration::from_millis(20000)); + + let deflection: Angle = test_bed.read_by_name("HYD_FINAL_THS_DEFLECTION"); + assert!(deflection.get::() > -4.1); + assert!(deflection.get::() < -3.9); + + let trim_wheel_position_percent: Ratio = test_bed.read_by_name("HYD_TRIM_WHEEL_PERCENT"); + assert!(trim_wheel_position_percent.get::() > -0.1); + assert!(trim_wheel_position_percent.get::() < 0.1); + } + + #[test] + fn trim_wheel_moves_up_with_hyd_press_if_moved_manually() { + let mut test_bed = SimulationTestBed::new(TestAircraft::new); + + test_bed.command(|a| { + a.set_hyd_pressure([Pressure::new::(3000.), Pressure::new::(3000.)]) + }); + + test_bed.command(|a| a.set_no_elec_input()); + test_bed.command(|a| a.set_manual_trim_input(true)); + test_bed.run_with_delta(Duration::from_millis(5000)); + + let deflection: Angle = test_bed.read_by_name("HYD_FINAL_THS_DEFLECTION"); + assert!(deflection.get::() > 5.); + } +} diff --git a/src/systems/systems/src/landing_gear/mod.rs b/src/systems/systems/src/landing_gear/mod.rs index 7b42b38a4c53..b0efca0e120e 100644 --- a/src/systems/systems/src/landing_gear/mod.rs +++ b/src/systems/systems/src/landing_gear/mod.rs @@ -653,21 +653,23 @@ impl LandingGearControlInterfaceUnit { self.is_powered, ); - if is_master_computer && !self.is_active_computer_previous_state { - self.actions_when_becoming_master(); - } + if self.is_powered { + if is_master_computer && !self.is_active_computer_previous_state { + self.actions_when_becoming_master(); + } - if !is_master_computer { - self.actions_when_slave(); - } + if !is_master_computer { + self.actions_when_slave(); + } - if self.is_powered && !self.is_powered_previous_state { - self.actions_when_startup(); - } + if !self.is_powered_previous_state { + self.actions_when_startup(); + } - if is_master_computer { - self.gear_system_control - .update(&self.sensor_inputs, !self.is_gear_lever_down); + if is_master_computer { + self.gear_system_control + .update(&self.sensor_inputs, !self.is_gear_lever_down); + } } if context.is_sim_ready() { @@ -1026,7 +1028,13 @@ impl GearSystemStateMachine { if !gear_handle_position_is_up { GearSystemState::Extending } else { - self.gears_state + // Checking consistency as we should be all uplocked with lever up + if lgciu.all_up_and_locked() && lgciu.all_closed_and_locked() { + self.gears_state + } else { + // Else we are supposed to be all uplocked but we aren't so back to retraction + GearSystemState::Retracting + } } } GearSystemState::Retracting => { diff --git a/src/systems/systems/src/navigation/adirs.rs b/src/systems/systems/src/navigation/adirs.rs index 9aabb46d48c6..06362c24e910 100644 --- a/src/systems/systems/src/navigation/adirs.rs +++ b/src/systems/systems/src/navigation/adirs.rs @@ -17,6 +17,7 @@ use uom::si::{ angular_velocity::degree_per_second, f64::*, length::foot, + pressure::hectopascal, ratio::ratio, thermodynamic_temperature::degree_celsius, velocity::{foot_per_minute, knot}, @@ -708,6 +709,7 @@ struct AirDataReference { number: usize, is_on: bool, + corrected_average_static_pressure: AdirsData, altitude: AdirsData, computed_airspeed: AdirsData, mach: AdirsData, @@ -722,6 +724,7 @@ struct AirDataReference { } impl AirDataReference { const INITIALISATION_DURATION: Duration = Duration::from_secs(18); + const CORRECTED_AVERAGE_STATIC_PRESSURE: &'static str = "CORRECTED_AVERAGE_STATIC_PRESSURE"; const ALTITUDE: &'static str = "ALTITUDE"; const COMPUTED_AIRSPEED: &'static str = "COMPUTED_AIRSPEED"; const MACH: &'static str = "MACH"; @@ -742,6 +745,11 @@ impl AirDataReference { number, is_on: true, + corrected_average_static_pressure: AdirsData::new_adr( + context, + number, + Self::CORRECTED_AVERAGE_STATIC_PRESSURE, + ), altitude: AdirsData::new_adr(context, number, Self::ALTITUDE), computed_airspeed: AdirsData::new_adr(context, number, Self::COMPUTED_AIRSPEED), mach: AdirsData::new_adr(context, number, Self::MACH), @@ -801,6 +809,7 @@ impl AirDataReference { // If the ADR is off or not initialized, output all labels as FW with value 0. if !is_valid { + self.corrected_average_static_pressure.set_failure_warning(); self.altitude.set_failure_warning(); self.barometric_vertical_speed.set_failure_warning(); self.computed_airspeed.set_failure_warning(); @@ -813,6 +822,8 @@ impl AirDataReference { self.angle_of_attack.set_failure_warning(); } else { // If it is on and initialized, output normal values. + self.corrected_average_static_pressure + .set_normal_operation_value(context.ambient_pressure()); self.altitude .set_normal_operation_value(context.indicated_altitude()); self.barometric_vertical_speed @@ -884,6 +895,8 @@ impl TrueAirspeedSource for AirDataReference { } impl SimulationElement for AirDataReference { fn write(&self, writer: &mut SimulatorWriter) { + self.corrected_average_static_pressure + .write_to_converted(writer, |value| value.get::()); self.altitude.write_to(writer); self.computed_airspeed.write_to(writer); self.mach.write_to(writer); @@ -1610,6 +1623,14 @@ mod tests { )) } + fn corrected_average_static_pressure(&mut self, adiru_number: usize) -> Arinc429Word { + self.read_arinc429_by_name(&output_data_id( + OutputDataType::Adr, + adiru_number, + AirDataReference::CORRECTED_AVERAGE_STATIC_PRESSURE, + )) + } + fn altitude(&mut self, adiru_number: usize) -> Arinc429Word { self.read_arinc429_by_name(&output_data_id( OutputDataType::Adr, @@ -1888,6 +1909,12 @@ mod tests { } fn assert_adr_data_valid(&mut self, valid: bool, adiru_number: usize) { + assert_eq!( + !self + .corrected_average_static_pressure(adiru_number) + .is_failure_warning(), + valid + ); assert_eq!(!self.altitude(adiru_number).is_failure_warning(), valid); assert_eq!( !self.computed_airspeed(adiru_number).is_failure_warning(), @@ -2401,6 +2428,25 @@ mod tests { test_bed.assert_adr_data_valid(false, adiru_number); } + #[rstest] + #[case(1)] + #[case(2)] + #[case(3)] + fn corrected_average_static_pressure_is_supplied_by_adr(#[case] adiru_number: usize) { + let mut test_bed = all_adirus_aligned_test_bed(); + test_bed.set_ambient_pressure(Pressure::new::(1013.)); + + test_bed.run(); + + assert_about_eq!( + test_bed + .corrected_average_static_pressure(adiru_number) + .normal_value() + .unwrap(), + 1013. + ); + } + #[rstest] #[case(1)] #[case(2)] diff --git a/src/systems/systems/src/pressurization/mod.rs b/src/systems/systems/src/pressurization/mod.rs index 0d90c6395de5..8a54b72fd9b1 100644 --- a/src/systems/systems/src/pressurization/mod.rs +++ b/src/systems/systems/src/pressurization/mod.rs @@ -91,6 +91,8 @@ impl Pressurization { fwc_excess_residual_pressure_id: context .get_identifier("PRESS_EXCESS_RESIDUAL_PR".to_owned()), fwc_low_diff_pressure_id: context.get_identifier("PRESS_LOW_DIFF_PR".to_owned()), + // FIXME use ARINC429 vars to get FM landing elev + // FIXME CPC 1 and 2 should transmit their landing elev to the SDACs (FWC/DMCs get it from there) auto_landing_elevation_id: context .get_identifier("PRESS_AUTO_LANDING_ELEVATION".to_owned()), departure_elevation_id: context.get_identifier("DEPARTURE_ELEVATION".to_owned()), diff --git a/src/systems/systems/src/shared/low_pass_filter.rs b/src/systems/systems/src/shared/low_pass_filter.rs index 03fda5d3cfc0..9bb194916737 100644 --- a/src/systems/systems/src/shared/low_pass_filter.rs +++ b/src/systems/systems/src/shared/low_pass_filter.rs @@ -3,7 +3,7 @@ use std::ops::Mul; use std::ops::Sub; use std::time::Duration; -#[derive(PartialEq, Copy, Clone)] +#[derive(PartialEq, Copy, Clone, Debug)] /// First order low pass filter /// y(k) = y(k-1) + (1-a)*( x(k) - y(k-1) ) with a = exp (-T/tau) /// See diff --git a/src/systems/systems/src/shared/mod.rs b/src/systems/systems/src/shared/mod.rs index c6fec9288c46..5b4a45cdbb36 100644 --- a/src/systems/systems/src/shared/mod.rs +++ b/src/systems/systems/src/shared/mod.rs @@ -149,7 +149,7 @@ pub enum ProximityDetectorId { DownlockDoorRight2, } -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq, Debug)] pub enum GearActuatorId { GearNose, GearDoorNose, @@ -191,6 +191,12 @@ pub trait EngineBleedPushbutton { fn engine_bleed_pushbuttons_are_auto(&self) -> [bool; 2]; } +pub trait PackFlowValveState { + // Pack id is 1 or 2 + fn pack_flow_valve_open_amount(&self, pack_id: usize) -> Ratio; + fn pack_flow_valve_air_flow(&self, pack_id: usize) -> MassRate; +} + pub trait GroundSpeed { fn ground_speed(&self) -> Velocity; } diff --git a/src/systems/systems/src/simulation/update_context.rs b/src/systems/systems/src/simulation/update_context.rs index e809ae68441d..c31446dd5ffe 100644 --- a/src/systems/systems/src/simulation/update_context.rs +++ b/src/systems/systems/src/simulation/update_context.rs @@ -11,7 +11,7 @@ use uom::si::{ use super::{Read, SimulatorReader}; use crate::{ - shared::MachNumber, + shared::{low_pass_filter::LowPassFilter, MachNumber}, simulation::{InitContext, VariableIdentifier}, }; use nalgebra::{Rotation3, Vector3}; @@ -177,7 +177,10 @@ pub struct UpdateContext { ambient_pressure: Pressure, is_on_ground: bool, vertical_speed: Velocity, + local_acceleration: LocalAcceleration, + local_acceleration_plane_reference_filtered: LowPassFilter>, + world_ambient_wind: Velocity3D, local_relative_wind: Velocity3D, local_velocity: Velocity3D, @@ -210,6 +213,10 @@ impl UpdateContext { pub(crate) const LOCAL_LONGITUDINAL_SPEED_KEY: &'static str = "VELOCITY BODY Z"; pub(crate) const LOCAL_VERTICAL_SPEED_KEY: &'static str = "VELOCITY BODY Y"; + // Plane accelerations can become crazy with msfs collision handling. + // Having such filtering limits high frequencies transients in accelerations used for physics + const PLANE_ACCELERATION_FILTERING_TIME_CONSTANT: Duration = Duration::from_millis(400); + #[deprecated( note = "Do not create UpdateContext directly. Instead use the SimulationTestBed or your own custom test bed." )] @@ -272,6 +279,11 @@ impl UpdateContext { vertical_acceleration, longitudinal_acceleration, ), + local_acceleration_plane_reference_filtered: + LowPassFilter::>::new_with_init_value( + Self::PLANE_ACCELERATION_FILTERING_TIME_CONSTANT, + Vector3::new(0., -9.8, 0.), + ), world_ambient_wind: Velocity3D::new( Velocity::default(), Velocity::default(), @@ -330,6 +342,13 @@ impl UpdateContext { is_on_ground: Default::default(), vertical_speed: Default::default(), local_acceleration: Default::default(), + + local_acceleration_plane_reference_filtered: + LowPassFilter::>::new_with_init_value( + Self::PLANE_ACCELERATION_FILTERING_TIME_CONSTANT, + Vector3::new(0., -9.8, 0.), + ), + world_ambient_wind: Velocity3D::new( Velocity::default(), Velocity::default(), @@ -403,6 +422,29 @@ impl UpdateContext { self.true_heading = reader.read(&self.plane_true_heading_id); self.update_relative_wind(); + + self.update_local_acceleration_plane_reference(delta); + } + + // Computes local acceleration including world gravity and plane acceleration + // Note that this does not compute acceleration due to angular velocity of the plane + fn update_local_acceleration_plane_reference(&mut self, delta: Duration) { + let plane_acceleration_plane_reference = self.local_acceleration.to_ms2_vector(); + + let pitch_rotation = self.attitude().pitch_rotation_transform(); + + let bank_rotation = self.attitude().bank_rotation_transform(); + + let gravity_acceleration_world_reference = Vector3::new(0., -9.8, 0.); + + // Total acceleration in plane reference is the gravity in world reference rotated to plane reference. To this we substract + // the local plane reference to get final local acceleration (if plane falling at 1G final local accel is 1G of gravity - 1G local accel = 0G) + let total_acceleration_plane_reference = (pitch_rotation + * (bank_rotation * gravity_acceleration_world_reference)) + - plane_acceleration_plane_reference; + + self.local_acceleration_plane_reference_filtered + .update(delta, total_acceleration_plane_reference); } /// Relative wind could be directly read from simvar RELATIVE WIND VELOCITY XYZ. @@ -522,6 +564,10 @@ impl UpdateContext { self.local_acceleration } + pub fn acceleration_plane_reference_filtered_ms2_vector(&self) -> Vector3 { + self.local_acceleration_plane_reference_filtered.output() + } + pub fn pitch(&self) -> Angle { self.attitude.pitch() } diff --git a/src/systems/systems_wasm/Cargo.toml b/src/systems/systems_wasm/Cargo.toml index e6feaf0d107d..1b31f124dc58 100644 --- a/src/systems/systems_wasm/Cargo.toml +++ b/src/systems/systems_wasm/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" test = false [dependencies] -uom = "0.30.0" +uom = "0.33.0" systems = { path = "../systems" } msfs = { git = "https://github.com/flybywiresim/msfs-rs", branch = "main" } fxhash = "0.2.1" diff --git a/src/systems/systems_wasm/src/aspects.rs b/src/systems/systems_wasm/src/aspects.rs index e9e044323180..76c9385c4e59 100644 --- a/src/systems/systems_wasm/src/aspects.rs +++ b/src/systems/systems_wasm/src/aspects.rs @@ -496,9 +496,30 @@ pub fn min(accumulator: f64, item: f64) -> f64 { accumulator.min(item) } +#[derive(PartialEq)] +pub enum ObjectWrite { + Ignore, + ToSim, +} +impl ObjectWrite { + pub fn on(condition: bool) -> Self { + if condition { + Self::ToSim + } else { + Self::Ignore + } + } +} +impl Default for ObjectWrite { + fn default() -> Self { + Self::ToSim + } +} + +/// Write function provides the output to know if the object will be written to sim or not pub trait VariablesToObject { fn variables(&self) -> Vec; - fn write(&mut self, values: Vec); + fn write(&mut self, values: Vec) -> ObjectWrite; fn set_data_on_sim_object(&self, sim_connect: &mut SimConnect) -> Result<(), Box>; } @@ -528,8 +549,9 @@ impl ExecutableVariableAction for ToObject { .map(|variable_identifier| variables.read(variable_identifier)) .collect(); - self.target_object.write(values); - self.target_object.set_data_on_sim_object(sim_connect)?; + if self.target_object.write(values) == ObjectWrite::ToSim { + self.target_object.set_data_on_sim_object(sim_connect)?; + } Ok(()) } diff --git a/typings/asobo-vcockpits-instruments/html_ui/Pages/VCockpit/Instruments/Shared/FlightElements/WaypointLoader.d.ts b/typings/asobo-vcockpits-instruments/html_ui/Pages/VCockpit/Instruments/Shared/FlightElements/WaypointLoader.d.ts index ca3e7ac91f5c..9bc0df7d65a9 100644 --- a/typings/asobo-vcockpits-instruments/html_ui/Pages/VCockpit/Instruments/Shared/FlightElements/WaypointLoader.d.ts +++ b/typings/asobo-vcockpits-instruments/html_ui/Pages/VCockpit/Instruments/Shared/FlightElements/WaypointLoader.d.ts @@ -1,3 +1,3 @@ declare class FacilityLoader { - getFacilityRaw(icao: string, timeout?: number): Promise; + getFacilityRaw(icao: string, timeout?: number, skipIntersectionData?: boolean): Promise; } diff --git a/typings/fs-base-ui/html_ui/JS/Avionics.d.ts b/typings/fs-base-ui/html_ui/JS/Avionics.d.ts index 7f5eeab42868..2ed645ada88c 100644 --- a/typings/fs-base-ui/html_ui/JS/Avionics.d.ts +++ b/typings/fs-base-ui/html_ui/JS/Avionics.d.ts @@ -305,6 +305,8 @@ declare global { protected updateAlwaysList(): void; protected clearAlwaysList(): void; registerInstrument(_instrumentName: string, _instrumentClass: CustomElementConstructor): void; + guidanceController?: any; + navigation?: any; } declare function registerInstrument(_instrumentName: string, _instrumentClass: CustomElementConstructor): void; diff --git a/typings/fs-base-ui/html_ui/JS/SimPlane.d.ts b/typings/fs-base-ui/html_ui/JS/SimPlane.d.ts index 6dc01fe096c5..d19ca2ca1f7f 100644 --- a/typings/fs-base-ui/html_ui/JS/SimPlane.d.ts +++ b/typings/fs-base-ui/html_ui/JS/SimPlane.d.ts @@ -392,8 +392,8 @@ declare global { function getTrimNeutral(): PercentOver100 | null; function setTransponderToRegion(): void; function setTransponderToZero(): void; - function getTotalAirTemperature(): Celcius | null; - function getAmbientTemperature(): Celcius | null; + function getTotalAirTemperature(): Celsius | null; + function getAmbientTemperature(): Celsius | null; function getFlexTemperature(): number | null; function getFuelPercent(): Percent; function getFuelQuantity(): Gallons; diff --git a/typings/fs-base-ui/html_ui/JS/simvar.d.ts b/typings/fs-base-ui/html_ui/JS/simvar.d.ts index ae2a6c84a99f..85c5bf5b0a8e 100644 --- a/typings/fs-base-ui/html_ui/JS/simvar.d.ts +++ b/typings/fs-base-ui/html_ui/JS/simvar.d.ts @@ -4,7 +4,7 @@ declare namespace SimVar { var g_bUseWatcher: boolean; - type SimVarUnit = "" | "amp" | "ampere" | "amperes" | "amps" | "angl16" | "angl32" | "atm" | "atmosphere" | "atmospheres" | "bar" | "bars" | "Bco16" | "bel" | "bels" | "Bool" | "Boolean" | "boost cmHg" | "boost inHg" | "boost psi" | "celsius fs7 egt" | "celsius fs7 oil temp" | "celsius scaler 1/256" | "celsius scaler 16k" | "celsius scaler 256" | "celsius" | "centimeter of mercury" | "centimeter" | "centimeters of mercury" | "centimeters" | "cm" | "cm2" | "cm3" | "cmHg" | "cu cm" | "cu ft" | "cu in" | "cu km" | "cu m" | "cu mm" | "cu yd" | "cubic centimeter" | "cubic centimeters" | "cubic feet" | "cubic foot" | "cubic inch" | "cubic inches" | "cubic kilometer" | "cubic kilometers" | "cubic meter" | "cubic meters" | "cubic mile" | "cubic miles" | "cubic millimeter" | "cubic millimeters" | "cubic yard" | "cubic yards" | "day" | "days" | "decibel" | "decibels" | "decimile" | "decimiles" | "decinmile" | "decinmiles" | "degree angl16" | "degree angl32" | "degree latitude" | "degree longitude" | "degree per second ang16" | "degree per second" | "degree" | "degrees angl16" | "degrees angl32" | "degrees latitude" | "degrees longitude" | "degrees per second ang16" | "degrees per second" | "Degrees" | "degrees" | "Enum" | "fahrenheit" | "farenheit" | "feet per minute" | "feet per second squared" | "feet per second" | "feet" | "feet/minute" | "feet/second" | "flags" | "foot per second squared" | "foot pound" | "foot pounds" | "foot" | "foot-pound" | "foot-pounds" | "FractionalLatLonDigits" | "Frequency ADF BCD32" | "Frequency BCD16" | "Frequency BCD32" | "fs7 charging amps" | "fs7 oil quantity" | "ft lb per second" | "ft" | "ft-lbs" | "ft/min" | "ft2" | "ft3" | "G Force 624 scaled" | "G Force" | "gallon per hour" | "gallon" | "gallons per hour" | "gallons" | "geepound" | "geepounds" | "GForce" | "GLOBALP->delta_heading_rate" | "GLOBALP->eng1.manifold_pressure" | "GLOBALP->eng1.oil_prs" | "GLOBALP->eng1.oil_tmp" | "GLOBALP->vertical_speed" | "gph" | "grad" | "grads" | "half" | "halfs" | "hectopascal" | "hectopascals" | "Hertz" | "hour over 10" | "hour" | "hours over 10" | "hours" | "Hz" | "in" | "in2" | "in3" | "inch of mercury" | "inch" | "inches of mercury" | "inches" | "inHg 64 over 64k" | "inHg" | "kelvin" | "keyframe" | "keyframes" | "kg" | "kgf meter" | "kgf meters" | "KgFSqCm" | "KHz" | "kilogram force per square centimeter" | "kilogram meter squared" | "kilogram meter" | "kilogram meters" | "kilogram per cubic meter" | "kilogram per second" | "kilogram" | "kilograms meter squared" | "kilograms per cubic meter" | "kilograms per second" | "kilograms" | "Kilohertz" | "kilometer per hour" | "kilometer" | "kilometer/hour" | "kilometers per hour" | "kilometers" | "kilometers/hour" | "kilopascal" | "km" | "km2" | "km3" | "knot scaler 128" | "knot" | "knots scaler 128" | "knots" | "kPa" | "kph" | "LatLonFormat" | "lbf-feet" | "lbs" | "liter per hour" | "liter" | "liters per hour" | "liters" | "m/s" | "m2" | "m3" | "mach 3d2 over 64k" | "mach" | "machs" | "mask" | "mbar" | "mbars" | "Megahertz" | "meter cubed per second" | "meter cubed" | "meter latitude" | "meter per minute" | "meter per second scaler 256" | "meter per second squared" | "meter per second" | "meter scaler 256" | "meter" | "meter/second" | "meters cubed per second" | "meters cubed" | "meters latitude" | "meters per minute" | "meters per second scaler 256" | "meters per second squared" | "meters per second" | "meters scaler 256" | "meters" | "meters/second" | "MHz" | "mile per hour" | "mile" | "miles per hour" | "miles" | "millibar scaler 16" | "millibar" | "millibars scaler 16" | "millibars" | "millimeter of mercury" | "millimeter of water" | "millimeter" | "millimeters of mercury" | "millimeters of water" | "millimeters" | "minute per round" | "minute" | "minutes per round" | "minutes" | "mm2" | "mm3" | "mmHg" | "more_than_a_half" | "mph" | "nautical mile" | "nautical miles" | "newton meter" | "newton meters" | "newton per square meter" | "newtons per square meter" | "nice minute per round" | "nice minutes per round" | "Nm" | "nmile" | "nmiles" | "number" | "number_number" | "numbers" | "Pa" | "part" | "pascal" | "pascals" | "per degree" | "per hour" | "per minute" | "per radian" | "per second" | "percent over 100" | "percent scaler 16k" | "percent scaler 2pow23" | "percent scaler 32k" | "percent" | "percentage" | "position 128" | "position 16k" | "position 32k" | "position" | "pound per hour" | "pound scaler 256" | "pound" | "pound-force per square foot" | "pound-force per square inch" | "poundal feet" | "pounds per hour" | "pounds scaler 256" | "pounds" | "pph" | "psf scaler 16k" | "psf" | "psi 4 over 16k" | "psi fs7 oil pressure" | "psi scaler 16k" | "psi" | "quart" | "quarts" | "radian per second" | "radian" | "radians per second" | "radians" | "rankine" | "ratio" | "revolution per minute" | "revolutions per minute" | "round" | "rounds" | "rpm 1 over 16k" | "rpm" | "rpms" | "scaler" | "second" | "Seconds" | "seconds" | "slug feet squared" | "Slug per cubic feet" | "Slug per cubic foot" | "slug" | "Slug/ft3" | "slugs feet squared" | "Slugs per cubic feet" | "Slugs per cubic foot" | "slugs" | "sq cm" | "sq ft" | "sq in" | "sq km" | "sq m" | "sq mm" | "sq yd" | "square centimeter" | "square centimeters" | "square feet" | "square foot" | "square inch" | "square inches" | "square kilometer" | "square kilometers" | "square meter" | "square meters" | "square mile" | "square miles" | "square millimeter" | "square millimeters" | "square yard" | "square yards" | "third" | "thirds" | "times" | "volt" | "volts" | "Watt" | "Watts" | "yard" | "yards" | "yd2" | "yd3" | "year" | "years" | "string" | "latlonaltpbh" | "latlonalt" | "Millibars" | "PBH" | "XYZ" | "PID_STRUCT" | "POIList" | "GlassCockpitSettings" | "FuelLevels" | string; + type SimVarUnit = "" | "amp" | "ampere" | "amperes" | "amps" | "angl16" | "angl32" | "atm" | "atmosphere" | "atmospheres" | "bar" | "bars" | "Bco16" | "bel" | "bels" | "Bool" | "Boolean" | "boost cmHg" | "boost inHg" | "boost psi" | "celsius fs7 egt" | "celsius fs7 oil temp" | "celsius scaler 1/256" | "celsius scaler 16k" | "celsius scaler 256" | "celsius" | "centimeter of mercury" | "centimeter" | "centimeters of mercury" | "centimeters" | "cm" | "cm2" | "cm3" | "cmHg" | "cu cm" | "cu ft" | "cu in" | "cu km" | "cu m" | "cu mm" | "cu yd" | "cubic centimeter" | "cubic centimeters" | "cubic feet" | "cubic foot" | "cubic inch" | "cubic inches" | "cubic kilometer" | "cubic kilometers" | "cubic meter" | "cubic meters" | "cubic mile" | "cubic miles" | "cubic millimeter" | "cubic millimeters" | "cubic yard" | "cubic yards" | "day" | "days" | "decibel" | "decibels" | "decimile" | "decimiles" | "decinmile" | "decinmiles" | "degree angl16" | "degree angl32" | "degree latitude" | "degree longitude" | "degree per second ang16" | "degree per second" | "degree" | "degrees angl16" | "degrees angl32" | "degrees latitude" | "degrees longitude" | "degrees per second ang16" | "degrees per second" | "Degrees" | "degrees" | "Enum" | "fahrenheit" | "farenheit" | "feet per minute" | "feet per second squared" | "feet per second" | "feet" | "feet/minute" | "feet/second" | "flags" | "foot per second squared" | "foot pound" | "foot pounds" | "foot" | "foot-pound" | "foot-pounds" | "FractionalLatLonDigits" | "Frequency ADF BCD32" | "Frequency BCD16" | "Frequency BCD32" | "fs7 charging amps" | "fs7 oil quantity" | "ft lb per second" | "ft" | "ft-lbs" | "ft/min" | "ft2" | "ft3" | "G Force 624 scaled" | "G Force" | "gallon per hour" | "gallon" | "gallons per hour" | "gallons" | "geepound" | "geepounds" | "GForce" | "GLOBALP->delta_heading_rate" | "GLOBALP->eng1.manifold_pressure" | "GLOBALP->eng1.oil_prs" | "GLOBALP->eng1.oil_tmp" | "GLOBALP->vertical_speed" | "gph" | "grad" | "grads" | "half" | "halfs" | "hectopascal" | "hectopascals" | "Hertz" | "hour over 10" | "hour" | "hours over 10" | "hours" | "Hz" | "in" | "in2" | "in3" | "inch of mercury" | "inch" | "inches of mercury" | "inches" | "inHg 64 over 64k" | "inHg" | "kelvin" | "keyframe" | "keyframes" | "kg" | "kgf meter" | "kgf meters" | "KgFSqCm" | "KHz" | "kilogram force per square centimeter" | "kilogram meter squared" | "kilogram meter" | "kilogram meters" | "kilogram per cubic meter" | "kilogram per second" | "kilogram" | "kilograms meter squared" | "kilograms per cubic meter" | "kilograms per second" | "kilograms" | "Kilohertz" | "kilometer per hour" | "kilometer" | "kilometer/hour" | "kilometers per hour" | "kilometers" | "kilometers/hour" | "kilopascal" | "km" | "km2" | "km3" | "knot scaler 128" | "knot" | "knots scaler 128" | "knots" | "kPa" | "kph" | "LatLonFormat" | "lbf-feet" | "lbs" | "liter per hour" | "liter" | "liters per hour" | "liters" | "m/s" | "m2" | "m3" | "mach 3d2 over 64k" | "mach" | "machs" | "mask" | "mbar" | "mbars" | "Megahertz" | "meter cubed per second" | "meter cubed" | "meter latitude" | "meter per minute" | "meter per second scaler 256" | "meter per second squared" | "meter per second" | "meter scaler 256" | "meter" | "meter/second" | "meters cubed per second" | "meters cubed" | "meters latitude" | "meters per minute" | "meters per second scaler 256" | "meters per second squared" | "meters per second" | "meters scaler 256" | "meters" | "meters/second" | "MHz" | "mile per hour" | "mile" | "miles per hour" | "miles" | "millibar scaler 16" | "millibar" | "millibars scaler 16" | "millibars" | "millimeter of mercury" | "millimeter of water" | "millimeter" | "millimeters of mercury" | "millimeters of water" | "millimeters" | "minute per round" | "minute" | "minutes per round" | "minutes" | "mm2" | "mm3" | "mmHg" | "more_than_a_half" | "mph" | "nautical mile" | "nautical miles" | "newton meter" | "newton meters" | "newton per square meter" | "newtons per square meter" | "nice minute per round" | "nice minutes per round" | "Nm" | "nmile" | "nmiles" | "number" | "number_number" | "numbers" | "Pa" | "part" | "pascal" | "pascals" | "per degree" | "per hour" | "per minute" | "per radian" | "per second" | "percent over 100" | "percent scaler 16k" | "percent scaler 2pow23" | "percent scaler 32k" | "percent" | "percentage" | "position 128" | "position 16k" | "position 32k" | "position" | "pound per hour" | "pound scaler 256" | "pound" | "pound-force per square foot" | "pound-force per square inch" | "poundal feet" | "pounds per hour" | "pounds scaler 256" | "pounds" | "pph" | "psf scaler 16k" | "psf" | "psi 4 over 16k" | "psi fs7 oil pressure" | "psi scaler 16k" | "psi" | "quart" | "quarts" | "radian per second" | "radian" | "radians per second" | "radians" | "rankine" | "ratio" | "revolution per minute" | "revolutions per minute" | "round" | "rounds" | "rpm 1 over 16k" | "rpm" | "rpms" | "scaler" | "second" | "Seconds" | "seconds" | "slug feet squared" | "Slug per cubic feet" | "Slug per cubic foot" | "slug" | "Slug/ft3" | "slugs feet squared" | "Slugs per cubic feet" | "Slugs per cubic foot" | "slugs" | "sq cm" | "sq ft" | "sq in" | "sq km" | "sq m" | "sq mm" | "sq yd" | "square centimeter" | "square centimeters" | "square feet" | "square foot" | "square inch" | "square inches" | "square kilometer" | "square kilometers" | "square meter" | "square meters" | "square mile" | "square miles" | "square millimeter" | "square millimeters" | "square yard" | "square yards" | "third" | "thirds" | "times" | "volt" | "volts" | "Watt" | "Watts" | "yard" | "yards" | "yd2" | "yd3" | "year" | "years" | "string" | "latlonaltpbh" | "latlonalt" | "Millibars" | "PBH" | "XYZ" | "PID_STRUCT" | "POIList" | "GlassCockpitSettings" | "FuelLevels" | (string & {}); type SimVarType = number | LatLongAlt | LatLongAltPBH | string | PID_STRUCT | XYZ | PitchBankHeading | boolean; class SimVarValue { __type: string; diff --git a/typings/types.d.ts b/typings/types.d.ts index 996c208730d4..7c5dd1e4a533 100644 --- a/typings/types.d.ts +++ b/typings/types.d.ts @@ -24,7 +24,7 @@ declare global { type PercentOver100 = number; type Gallons = number; type Kilograms = number; - type Celcius = number; + type Celsius = number; type InchesOfMercury = number; type Millibar = number; type PressurePerSquareInch = number;