diff --git a/.gitattributes b/.gitattributes index e5fda4085c7..285465a4ca3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,9 @@ + * text=auto # Ensure LF for shell files, even on Windows, so that bash inside Docker does not panic *.sh text eol=lf + +# Ensure the language files update during build are not marked as changed due to different +# line endings. +/src/instruments/src/EFB/Localization/*.json text eol=lf diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index a2f16ca81f9..0c3bc5cbd84 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -248,6 +248,7 @@ 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. [EFB] Presets for Lighting and Aircraft states - @frankkopp (Frank Kopp) ## 0.7.0 diff --git a/.github/workflows/experimental.yml b/.github/workflows/experimental.yml index 7660fc40309..0d1e82af687 100644 --- a/.github/workflows/experimental.yml +++ b/.github/workflows/experimental.yml @@ -26,6 +26,7 @@ jobs: echo CLIENT_SECRET=${{ secrets.NAVIGRAPH_CLIENT_SECRET }} >> .env echo CHARTFOX_SECRET=${{ secrets.CHARTFOX_SECRET }} >> .env echo SENTRY_DSN=${{ secrets.SENTRY_DSN }} >> .env + echo LOCALAZY_READ_KEY=${{ secrets.LOCALAZY_READ_KEY }} >> .env - name: Build A32NX run: | ./scripts/dev-env/run.sh ./scripts/setup.sh diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 8418a376902..9183ec37a2f 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -28,6 +28,7 @@ jobs: echo CLIENT_SECRET=${{ secrets.NAVIGRAPH_CLIENT_SECRET }} >> .env echo CHARTFOX_SECRET=${{ secrets.CHARTFOX_SECRET }} >> .env echo SENTRY_DSN=${{ secrets.SENTRY_DSN }} >> .env + echo LOCALAZY_READ_KEY=${{ secrets.LOCALAZY_READ_KEY }} >> .env - name: Build A32NX run: | ./scripts/dev-env/run.sh ./scripts/setup.sh diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 809dddc00e2..d748ab0492b 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -58,6 +58,7 @@ jobs: echo CLIENT_SECRET=${{ secrets.NAVIGRAPH_CLIENT_SECRET }} >> .env echo CHARTFOX_SECRET=${{ secrets.CHARTFOX_SECRET }} >> .env echo SENTRY_DSN=${{ secrets.SENTRY_DSN }} >> .env + echo LOCALAZY_READ_KEY=${{ secrets.LOCALAZY_READ_KEY }} >> .env - name: Build A32NX run: | ./scripts/dev-env/run.sh ./scripts/setup.sh diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index aa8a5fc48a3..3abfef63808 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -22,6 +22,7 @@ jobs: echo CLIENT_SECRET=${{ secrets.NAVIGRAPH_CLIENT_SECRET }} >> .env echo CHARTFOX_SECRET=${{ secrets.CHARTFOX_SECRET }} >> .env echo SENTRY_DSN=${{ secrets.SENTRY_DSN }} >> .env + echo LOCALAZY_READ_KEY=${{ secrets.LOCALAZY_READ_KEY }} >> .env - name: Build A32NX run: | ./scripts/dev-env/run.sh ./scripts/setup.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 322e9e61074..d571a91e33e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,6 +23,7 @@ jobs: echo CLIENT_SECRET=${{ secrets.NAVIGRAPH_CLIENT_SECRET }} >> .env echo CHARTFOX_SECRET=${{ secrets.CHARTFOX_SECRET }} >> .env echo SENTRY_DSN=${{ secrets.SENTRY_DSN }} >> .env + echo LOCALAZY_READ_KEY=${{ secrets.LOCALAZY_READ_KEY }} >> .env - name: Build A32NX run: | ./scripts/dev-env/run.sh ./scripts/setup.sh diff --git a/.gitignore b/.gitignore index 6dd06d44eb2..35c1dd0082c 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ node_modules /src/fbw/obj/ /src/fdr2csv/build/ /src/fadec/obj/ +/src/presets/obj/ .env src/instruments/buildSrc/custom/* /flybywire-aircraft-a320-neo/MCDU SERVER/ @@ -70,3 +71,5 @@ src/instruments/buildSrc/custom/* /flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/TEXTURE/A320NEO_COCKPIT_DECALSTEXT_ALBD.TIF.dds msfs-avionics-mirror/src/sdk/build/** *.tgz +/src/instruments/src/EFB/Localization/downloaded/*.json +/.ace/ diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index e1a7b3e5f35..8a8e097cc13 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -31,4 +31,4 @@ } ], "version": 4 -} \ No newline at end of file +} diff --git a/docs/a320-simvars.md b/docs/a320-simvars.md index 5305ac8090b..5db4a518834 100644 --- a/docs/a320-simvars.md +++ b/docs/a320-simvars.md @@ -1157,6 +1157,36 @@ 0 or greater | Seconds elapsed -1 | Empty value +- A32NX_LOAD_LIGHTING_PRESET + - Number + - ID for preset + - When set to >0 the corresponding preset will be loaded if defined + - Will be reset to 0 after loading is done + +- A32NX_SAVE_LIGHTING_PRESET + - Number + - ID for preset + - When set to >0 the corresponding preset will be overwritten and saved to an ini file + - Will be reset to 0 after saving is done + +- A32NX_LOAD_AIRCRAFT_PRESET + - Number + - ID for preset (1..5) + - When set to >0 the corresponding preset will be loaded if defined + - Will be reset to 0 after loading is done + - When set to 0 during loading will stop and cancel the loading process + - Value | Meaning + --- | --- + 1 | Cold & Dark + 2 | Turnaround + 3 | Ready for Pushback + 4 | Ready for Taxi + 5 | Ready for Takeoff + +- A32NX_LOAD_AIRCRAFT_PRESET_PROGRESS + - Number (0.0..1.0) + - While loading a preset this will contain the percentage of the total progress of loading + ## EIS Display System - A32NX_EFIS_{side}_NAVAID_{1|2}_MODE diff --git a/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/panel.cfg b/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/panel.cfg index 2a5f3099543..874005afbcf 100644 --- a/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/panel.cfg +++ b/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/panel.cfg @@ -133,6 +133,7 @@ background_color=0,0,0 htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=systems.wasm&wasm_gauge=systems, 0,0,1,1 htmlgauge01=WasmInstrument/WasmInstrument.html?wasm_module=fbw.wasm&wasm_gauge=fbw, 0,0,1,1 htmlgauge02=WasmInstrument/WasmInstrument.html?wasm_module=fadec.wasm&wasm_gauge=FadecGauge, 0,0,1,1 +htmlgauge03=WasmInstrument/WasmInstrument.html?wasm_module=presets.wasm&wasm_gauge=Presets, 0,0,1,1 [VPainting01] size_mm = 2048,512 diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/Inter-Regular.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/Inter-Regular.ttf new file mode 100644 index 00000000000..96fd6a12d0e Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/Inter-Regular.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/Inter-SemiBold.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/Inter-SemiBold.ttf new file mode 100644 index 00000000000..ddb279290ba Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/Inter-SemiBold.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/JetBrainsMono-Regular.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/JetBrainsMono-Regular.ttf new file mode 100644 index 00000000000..5f2f00fe8a6 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/JetBrainsMono-Regular.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-Bold.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-Bold.ttf new file mode 100644 index 00000000000..8bbf0bd1fe9 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-Bold.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-Light.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-Light.ttf new file mode 100644 index 00000000000..f255257a815 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-Light.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-Medium.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-Medium.ttf new file mode 100644 index 00000000000..c73d7741b1b Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-Medium.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-Regular.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-Regular.ttf new file mode 100644 index 00000000000..c02b01bea3c Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-Regular.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-SemiBold.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-SemiBold.ttf new file mode 100644 index 00000000000..30ee031048b Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/Manrope-SemiBold.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-Bold.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-Bold.ttf new file mode 100644 index 00000000000..93780ab24e3 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-Bold.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-Light.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-Light.ttf new file mode 100644 index 00000000000..7923ddc4ac0 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-Light.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-Medium.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-Medium.ttf new file mode 100644 index 00000000000..332866dac8e Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-Medium.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-Regular.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-Regular.ttf new file mode 100644 index 00000000000..af79e477d6e Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-Regular.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-SemiBold.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-SemiBold.ttf new file mode 100644 index 00000000000..759796e8272 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansArabic-SemiBold.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-Bold.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-Bold.ttf new file mode 100644 index 00000000000..e20e88a1d18 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-Bold.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-Light.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-Light.ttf new file mode 100644 index 00000000000..3be15a7f392 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-Light.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-Medium.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-Medium.ttf new file mode 100644 index 00000000000..80bce15be44 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-Medium.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-Regular.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-Regular.ttf new file mode 100644 index 00000000000..47798bdf31c Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-Regular.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-SemiBold.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-SemiBold.ttf new file mode 100644 index 00000000000..a6e6bbedbd3 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansDevanagari-SemiBold.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-Bold.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-Bold.ttf new file mode 100644 index 00000000000..3fde550c923 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-Bold.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-Light.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-Light.ttf new file mode 100644 index 00000000000..62b40d88224 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-Light.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-Medium.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-Medium.ttf new file mode 100644 index 00000000000..938df31a886 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-Medium.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-Regular.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-Regular.ttf new file mode 100644 index 00000000000..e3845565215 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-Regular.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-SemiBold.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-SemiBold.ttf new file mode 100644 index 00000000000..f9779d92975 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansHebrew-SemiBold.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansJP-Bold.otf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansJP-Bold.otf new file mode 100644 index 00000000000..e5b538d1682 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansJP-Bold.otf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansJP-Light.otf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansJP-Light.otf new file mode 100644 index 00000000000..d60e0bc56ab Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansJP-Light.otf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansJP-Medium.otf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansJP-Medium.otf new file mode 100644 index 00000000000..ee727321030 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansJP-Medium.otf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansJP-Regular.otf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansJP-Regular.otf new file mode 100644 index 00000000000..776427dcb93 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansJP-Regular.otf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansKR-Bold.otf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansKR-Bold.otf new file mode 100644 index 00000000000..be388bf5f96 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansKR-Bold.otf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansKR-Light.otf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansKR-Light.otf new file mode 100644 index 00000000000..548e667e9b8 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansKR-Light.otf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansKR-Medium.otf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansKR-Medium.otf new file mode 100644 index 00000000000..5ddbbc03803 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansKR-Medium.otf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansKR-Regular.otf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansKR-Regular.otf new file mode 100644 index 00000000000..7c5c2fae3f7 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansKR-Regular.otf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansSC-Bold.otf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansSC-Bold.otf new file mode 100644 index 00000000000..172eb674127 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansSC-Bold.otf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansSC-Light.otf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansSC-Light.otf new file mode 100644 index 00000000000..85ccdf44a4c Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansSC-Light.otf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansSC-Medium.otf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansSC-Medium.otf new file mode 100644 index 00000000000..0a5bd9e5fe6 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansSC-Medium.otf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansSC-Regular.otf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansSC-Regular.otf new file mode 100644 index 00000000000..d350ffa79e3 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansSC-Regular.otf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-Bold.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-Bold.ttf new file mode 100644 index 00000000000..ae0881254ef Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-Bold.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-Light.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-Light.ttf new file mode 100644 index 00000000000..500ef4b00d0 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-Light.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-Medium.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-Medium.ttf new file mode 100644 index 00000000000..024dcda4357 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-Medium.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-Regular.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-Regular.ttf new file mode 100644 index 00000000000..d7351f8e332 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-Regular.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-SemiBold.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-SemiBold.ttf new file mode 100644 index 00000000000..36d65378a77 Binary files /dev/null and b/flybywire-aircraft-a320-neo/html_ui/Fonts/NotoSansThai-SemiBold.ttf differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/ibm-plex-mono-v6-latin-300.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/ibm-plex-mono-v6-latin-300.ttf deleted file mode 100644 index 4baf9a8573f..00000000000 Binary files a/flybywire-aircraft-a320-neo/html_ui/Fonts/ibm-plex-mono-v6-latin-300.ttf and /dev/null differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/nunito-sans-v6-latin-200.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/nunito-sans-v6-latin-200.ttf deleted file mode 100644 index 76ebfadb2a8..00000000000 Binary files a/flybywire-aircraft-a320-neo/html_ui/Fonts/nunito-sans-v6-latin-200.ttf and /dev/null differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/nunito-sans-v6-latin-700.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/nunito-sans-v6-latin-700.ttf deleted file mode 100644 index 2736009070c..00000000000 Binary files a/flybywire-aircraft-a320-neo/html_ui/Fonts/nunito-sans-v6-latin-700.ttf and /dev/null differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Fonts/nunito-sans-v6-latin-regular.ttf b/flybywire-aircraft-a320-neo/html_ui/Fonts/nunito-sans-v6-latin-regular.ttf deleted file mode 100644 index 242ae43c9a4..00000000000 Binary files a/flybywire-aircraft-a320-neo/html_ui/Fonts/nunito-sans-v6-latin-regular.ttf and /dev/null differ diff --git a/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_Core.js b/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_Core.js index 5e7a32365d8..40be636ea8d 100644 --- a/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_Core.js +++ b/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_Core.js @@ -62,7 +62,7 @@ class A32NX_Core { name: 'Speeds', module: new A32NX_Speeds(), updateInterval: 500, - }, + } ]; this.moduleThrottlers = {}; for (const moduleDefinition of this.modules) { diff --git a/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_Pushback.js b/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_Pushback.js new file mode 100644 index 00000000000..c6064978e98 --- /dev/null +++ b/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_Pushback.js @@ -0,0 +1,30 @@ +class A32NX_Pushback { + constructor() {} + + init() { + SimVar.SetSimVarValue('L:A32NX_PUSHBACK_TUG_DIRECTION_FACTOR', 'number', -1); + console.log("A32NX Pushback Initialized."); + } + + update(_deltaTime) { + const simOnGround = SimVar.GetSimVarValue('SIM ON GROUND', 'number'); + const pushBackAttached = SimVar.GetSimVarValue('Pushback Attached', 'bool'); + + if (!(pushBackAttached && simOnGround)) { + return; + } + console.log(SimVar.GetSimVarValue('A:RUDDER POSITION', 'number')); + const tugSpeed = SimVar.GetSimVarValue('L:A32NX_TUG_SPEED', 'number'); + const parkingBrakeEngaged = SimVar.GetSimVarValue('L:A32NX_PARK_BRAKE_LEVER_POS', 'Bool'); + const commandedTugDirectionFactor = SimVar.GetSimVarValue('L:A32NX_PUSHBACK_TUG_DIRECTION_FACTOR', 'number'); + const commandedTugHeadingFactor = SimVar.GetSimVarValue('L:A32NX_PUSHBACK_TUG_HEADING_FACTOR', 'number'); + + SimVar.SetSimVarValue('VELOCITY BODY X', 'Number', 0); + SimVar.SetSimVarValue('VELOCITY BODY Y', 'Number', 0); + SimVar.SetSimVarValue('VELOCITY BODY Z', 'Number', tugSpeed * (parkingBrakeEngaged ? 0.75 : 8) * commandedTugDirectionFactor); + + SimVar.SetSimVarValue('ROTATION VELOCITY BODY X', 'Number', 0); + SimVar.SetSimVarValue('ROTATION VELOCITY BODY Y', 'Number', tugSpeed * (parkingBrakeEngaged ? 0.015 : 0.16) * commandedTugHeadingFactor * commandedTugDirectionFactor); + SimVar.SetSimVarValue('ROTATION VELOCITY BODY Z', 'Number', 0); + } +} diff --git a/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/CDU/A320_Neo_CDU.html b/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/CDU/A320_Neo_CDU.html index d427f03ad11..7934b3ecebc 100644 --- a/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/CDU/A320_Neo_CDU.html +++ b/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/CDU/A320_Neo_CDU.html @@ -1,4 +1,4 @@ - + -
-
+
+
- - \ No newline at end of file diff --git a/src/instruments/src/EFB/index.tsx b/src/instruments/src/EFB/index.tsx index 580fc136cf4..e559b588be1 100644 --- a/src/instruments/src/EFB/index.tsx +++ b/src/instruments/src/EFB/index.tsx @@ -1,93 +1,100 @@ +/* eslint-disable max-len */ import React, { useState, useEffect } from 'react'; import { MemoryRouter as Router } from 'react-router-dom'; import { customAlphabet } from 'nanoid'; import { NXDataStore } from '@shared/persistence'; import { usePersistentProperty } from '@instruments/common/persistence'; +import { Provider } from 'react-redux'; +import { render } from '@instruments/common/index'; +import { ErrorBoundary } from 'react-error-boundary'; +import { SentryConsentState, SENTRY_CONSENT_KEY } from '../../../sentry-client/src/FbwAircraftSentryClient'; +import { ModalProvider } from './UtilComponents/Modals/Modals'; import { FailuresOrchestratorProvider } from './failures-orchestrator-provider'; import Efb from './Efb'; -import { render } from '../Common/index'; -import { readSettingsFromPersistentStorage } from './Settings/sync'; -import { useInteractionEvent } from '../util'; -import './Assets/Reset.scss'; import './Assets/Efb.scss'; +import './Assets/Theme.css'; +import './Assets/Slider.scss'; +import { readSettingsFromPersistentStorage } from './Settings/sync'; +import { store } from './Store/store'; +import { Error } from './Assets/Error'; -import logo from './Assets/fbw-logo.svg'; +const EFBLoad = () => { + const [, setSessionId] = usePersistentProperty('A32NX_SENTRY_SESSION_ID'); + useEffect(() => () => setSessionId(''), []); -const ScreenLoading = () => ( -
-
-
- logo - {' '} - flyPad -
-
-
-
-
-
-); + const [err, setErr] = useState(false); -export enum ContentState { - OFF, - LOADING, - LOADED -} + return ( + setErr(false)} resetKeys={[err]}> + + + + + + + + + ); +}; -interface PowerContextInterface { - content: ContentState, - setContent: (ContentState) => void +interface ErrorFallbackProps { + resetErrorBoundary: (...args: Array) => void; } -export const PowerContext = React.createContext(undefined as any); +export const ErrorFallback = ({ resetErrorBoundary }: ErrorFallbackProps) => { + const [sessionId] = usePersistentProperty('A32NX_SENTRY_SESSION_ID'); + const [sentryEnabled] = usePersistentProperty(SENTRY_CONSENT_KEY, SentryConsentState.Refused); -const EFBLoad = () => { - const [content, setContent] = useState(ContentState.OFF); - const [, setSessionId] = usePersistentProperty('A32NX_SENTRY_SESSION_ID'); + return ( +
+
+ +
+

A critical error has been encountered.

- function offToLoaded() { - setContent(ContentState.LOADING); - setTimeout(() => { - setContent(ContentState.LOADED); - }, 6000); - } +

You are able to reset this tablet to recover from this error.

- useEffect(() => () => setSessionId(''), []); + {sentryEnabled === SentryConsentState.Given && ( + <> +

+ You have opted into anonymous error reporting and this issue has been relayed to us. If you want immediate support, please share the following code to a member of staff in the #support channel on the FlyByWire Discord server: +

- useInteractionEvent('A32NX_EFB_POWER', () => { - if (content === ContentState.OFF) { - offToLoaded(); - } else { - setContent(ContentState.OFF); - } - }); - - switch (content) { - case ContentState.OFF: - return
offToLoaded()} />; - case ContentState.LOADING: - return ; - case ContentState.LOADED: - return ( - - - - - - ); - default: - throw new Error('Invalid content state provided'); - } +

{sessionId}

+ + )} + +
+

Reset Display

+
+
+
+
+ ); }; -readSettingsFromPersistentStorage(); +const setSessionId = () => { + const ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + const SESSION_ID_LENGTH = 14; + const nanoid = customAlphabet(ALPHABET, SESSION_ID_LENGTH); + const generatedSessionID = nanoid(); + + NXDataStore.set('A32NX_SENTRY_SESSION_ID', generatedSessionID); +}; -const ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; -const SESSION_ID_LENGTH = 14; -const nanoid = customAlphabet(ALPHABET, SESSION_ID_LENGTH); -const generatedSessionID = nanoid(); +const setup = () => { + readSettingsFromPersistentStorage(); + setSessionId(); +}; -NXDataStore.set('A32NX_SENTRY_SESSION_ID', generatedSessionID); +if (process.env.VITE_BUILD) { + window.addEventListener('AceInitialized', setup); +} else { + setup(); +} -render(, true, true); +render( + , + true, true, +); diff --git a/src/instruments/src/EFB/rollup.development.config.js b/src/instruments/src/EFB/rollup.development.config.js deleted file mode 100644 index e60dcd69d91..00000000000 --- a/src/instruments/src/EFB/rollup.development.config.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -import dotenv from 'dotenv'; - -const image = require('@rollup/plugin-image'); -const babel = require('@rollup/plugin-babel').default; -const commonjs = require('@rollup/plugin-commonjs'); -const nodeResolve = require('@rollup/plugin-node-resolve').default; -const replace = require('@rollup/plugin-replace'); -const postcss = require('rollup-plugin-postcss'); -const tailwindcss = require('tailwindcss'); -const copy = require('rollup-plugin-copy'); -const serve = require('rollup-plugin-serve'); -const livereload = require('rollup-plugin-livereload'); - -const extensions = ['.js', '.jsx', '.ts', '.tsx']; - -module.exports = { - input: `${__dirname}/index-web.tsx`, - plugins: [ - image(), - dotenv.config({ path: '../../../../.env' }), - nodeResolve({ extensions }), - commonjs({ include: /node_modules/ }), - babel({ - presets: [ - ['@babel/preset-env'], - ['@babel/preset-react', { runtime: 'automatic' }], - ['@babel/preset-typescript'], - ], - plugins: [ - '@babel/plugin-proposal-class-properties', - ['@babel/plugin-transform-runtime', { regenerator: true }], - ], - babelHelpers: 'runtime', - extensions, - }), - replace({ - 'preventAssignment': true, - 'process.env.NODE_ENV': JSON.stringify('development'), - 'process.env.CLIENT_ID': JSON.stringify(process.env.CLIENT_ID), - 'process.env.CLIENT_SECRET': JSON.stringify(process.env.CLIENT_SECRET), - 'process.env.CHARTFOX_SECRET': JSON.stringify(process.env.CHARTFOX_SECRET), - 'process.env.SIMVAR_DISABLE': 'true', - }), - postcss({ - use: { sass: {} }, - plugins: [tailwindcss(`${__dirname}/tailwind.config.js`)], - extract: `${__dirname}/web/bundle.css`, - }), - copy({ - targets: [ - { src: 'src/Assets/**', dest: 'web/Assets' }, - ], - }), - serve(), - livereload('web'), - ], - output: { - file: `${__dirname}/web/bundle.js`, - format: 'iife', - sourcemap: true, - }, -}; diff --git a/src/instruments/src/EFB/tailwind.config.js b/src/instruments/src/EFB/tailwind.config.js index 32a3cc258ba..96decdfdd7b 100644 --- a/src/instruments/src/EFB/tailwind.config.js +++ b/src/instruments/src/EFB/tailwind.config.js @@ -1,18 +1,22 @@ 'use strict'; -const reactComponentsClasses = require('../../../../node_modules/@flybywiresim/react-components/build/usedCSSClasses.json'); +const colors = require('tailwindcss/colors'); + +const fallbacks = [ + 'NotoSansJP', + 'NotoSansSC', + 'NotoSansKR', + 'NotoSansArabic', + 'NotoSansThai', + 'NotoSansHebrew', + 'NotoSansDevanagari', +]; module.exports = { mode: 'jit', - purge: { - content: [ - './**/*.{jsx,tsx}', - ], - safelist: [ - ...reactComponentsClasses, - ], - }, - darkMode: false, // or 'media' or 'class' + content: [ + './**/*.{jsx,tsx}', + ], theme: { extend: { width: () => ({ @@ -20,9 +24,8 @@ module.exports = { 'out-tk': '5.25rem', }), height: () => ({ - 'efb': '50rem', - 'efb-nav': '45.75rem', - '124': '34.75rem', + 'content-section-reduced': '54rem', + 'content-section-full': '57.25rem', }), inset: () => ({ 'ctr-tk-y': '18.75rem', @@ -45,11 +48,41 @@ module.exports = { '26.5': '26.5deg', '-26.5': '-26.5deg', }), + colors: { + colors, + 'theme-highlight': 'var(--color-highlight)', + 'theme-body': 'var(--color-body)', + 'theme-text': 'var(--color-text)', + 'theme-unselected': 'var(--color-unselected)', + 'theme-secondary': 'var(--color-secondary)', + 'theme-statusbar': 'var(--color-statusbar)', + 'theme-accent': 'var(--color-accent)', + 'cyan': { + DEFAULT: '#00E0FE', + medium: '#00C4F5', + }, + 'utility': { + 'red': 'var(--color-utility-red)', + 'green': 'var(--color-utility-green)', + 'orange': 'var(--color-utility-orange)', + 'amber': 'var(--color-utility-amber)', + 'blue': 'var(--color-utility-blue)', + 'purple': 'var(--color-utility-purple)', + 'pink': 'var(--color-utility-pink)', + 'salmon': 'var(--color-utility-salmon)', + 'grey': 'var(--color-utility-grey)', + 'dark-grey': 'var(--color-utility-dark-grey)', + }, + }, + maxWidth: { '1/2': '50%' }, + }, + fontFamily: { + mono: ['JetBrains Mono', ...fallbacks], + body: ['Inter', ...fallbacks], + title: ['Manrope', ...fallbacks], + rmp: ['AirbusRMP'], }, - fontFamily: { mono: ['IBMPlexMono'], efb: ['NunitoSans'] }, - letterSpacing: { nunito: '0.35px' }, }, - variants: { extend: {} }, // eslint-disable-next-line global-require plugins: [require('@flybywiresim/tailwind-config')], }; diff --git a/src/instruments/src/EFB/toast.css b/src/instruments/src/EFB/toast.css new file mode 100644 index 00000000000..fc45b041330 --- /dev/null +++ b/src/instruments/src/EFB/toast.css @@ -0,0 +1,116 @@ + +:root { + --toastify-toast-width: 600px; + + --toastify-color-info: var(--color-highlight); + --toastify-color-error: var(--color-highlight); + --toastify-color-success: var(--color-highlight); + --toastify-color-warning: var(--color-highlight); +} + +.Toastify__toast { + position: relative; + min-height: 90px; + box-sizing: border-box; + margin-bottom: 1rem; + padding: 8px; + border-radius: 9px; + box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 0.1), 0 2px 15px 0 rgba(0, 0, 0, 0.05); + border-width: 2px; + border-color: var(--color-accent); + display: -ms-flexbox; + display: flex; + -ms-flex-pack: justify; + justify-content: space-between; + max-height: var(--toastify-toast-max-height); + overflow: hidden; + cursor: pointer; + direction: ltr; + font-family: 'Manrope', 'NotoSansJP', 'NotoSansSC', 'NotoSansKR', 'NotoSansArabic', 'NotoSansThai', 'NotoSansHebrew', 'NotoSansDevanagari', sans-serif; + width: var(--toastify-toast-width); +} + +.Toastify__toast-body { + margin: auto 0; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding-left: 6px; + padding-right: 6px; + padding-top: 6px; + padding-bottom: 13px; + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; +} + +.Toastify__toast-body > div { + @apply text-2xl; +} + +.Toastify__toast-icon { + -webkit-margin-end: 10px; + margin-inline-end: 10px; + width: 40px; + margin-right: 21px; + -ms-flex-negative: 0; + flex-shrink: 0; + display: -ms-flexbox; + display: flex; +} + +.Toastify__spinner { + width: 40px; + height: 40px; + box-sizing: border-box; + border: 2px solid; + border-radius: 100%; + border-color: var(--toastify-spinner-color-empty-area); + border-right-color: var(--toastify-spinner-color); + animation: Toastify__spin 0.65s linear infinite; +} + +.Toastify__toast-container--top-center { + top: 2em; + left: 50%; + transform: translateX(-50%); +} + +.Toastify__toast-theme--light { + background: var(--color-body); + color: var(--color-text); +} + +.Toastify__close-button { + color: var(--color-text); + background: transparent; + outline: none; + border: none; + padding: 0; + cursor: pointer; + /* opacity: 1 !important; */ + transition: 0.3s ease; + -ms-flex-item-align: start; + align-self: flex-start; +} + +.Toastify__progress-bar { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 7px; + z-index: var(--toastify-z-index); + opacity: 0.7; + transform-origin: left; +} + +.Toastify__progress-bar-theme--light { + background: var(--color-highlight); +} + +.Toastify__close-button > svg { + fill: currentColor; + height: 32px; + width: 28px; +} diff --git a/src/instruments/src/EFB/translation.ts b/src/instruments/src/EFB/translation.ts new file mode 100644 index 00000000000..841547695b1 --- /dev/null +++ b/src/instruments/src/EFB/translation.ts @@ -0,0 +1,170 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +/* eslint-disable no-console */ + +import { NXDataStore } from '@shared/persistence'; + +// source language +import en from './Localization/en.json'; +// translations +import ar from './Localization/ar.json'; +import cs from './Localization/cs.json'; +import de from './Localization/de.json'; +import da from './Localization/da-DK.json'; +import el from './Localization/el.json'; +import es from './Localization/es.json'; +import eu from './Localization/eu.json'; +import fi from './Localization/fi.json'; +import fr from './Localization/fr.json'; +import he from './Localization/he.json'; +import hi from './Localization/hi.json'; +import hu from './Localization/hu.json'; +import id from './Localization/id.json'; +import it from './Localization/it.json'; +import ja from './Localization/ja.json'; +import ko from './Localization/ko.json'; +import lt from './Localization/lt.json'; +import nb from './Localization/nb.json'; +import nl from './Localization/nl.json'; +import pl from './Localization/pl.json'; +import ptBR from './Localization/pt-BR.json'; +import ptPT from './Localization/pt-PT.json'; +import ro from './Localization/ro.json'; +import ru from './Localization/ru.json'; +import sk from './Localization/sk.json'; +import sl from './Localization/sl.json'; +import sv from './Localization/sv.json'; +import th from './Localization/th.json'; +import tr from './Localization/tr.json'; +import vi from './Localization/vi.json'; +import zhHansCN from './Localization/zh-Hans-CN.json'; +import zhHantHK from './Localization/zh-Hant-HK.json'; +import zhHantTW from './Localization/zh-Hant-TW.json'; + +console.log('Initializing Translation'); + +// map of maps to hold key-value maps for each language +const allLanguagesMap = new Map>(); + +// Recursively iterates through a language data structure and creates a map with keys based on the +// property names of the children - essentially flatten the hierarchy: +// "Dashboard.ImportantInformation.GoToPage" ==> "Go to Page" +const initMap = (map, ln, path: Array) => { + const props = Object.getOwnPropertyNames(ln); + if (typeof ln !== 'object') { + map.set(path.join('.'), ln); + return; + } + props.forEach((p: string) => { + path.push(p); + initMap(map, ln[p], path); + path.pop(); + }); +}; + +// adds a key-value map to allLanguagesMap and initializes the map +const init = (lang:string, data) => { + const map = new Map(); + allLanguagesMap.set(lang, map); + initMap(allLanguagesMap.get(lang), data, []); + return map; +}; + +interface LanguageOption { + langCode: string; + langData: any; + langName: string; + alias: string; +} + +// used for the dropdown in the flyPad settings page +export const languageOptions: LanguageOption[] = [ + // Source language first + { langCode: 'en', langData: en, langName: 'English', alias: 'English' }, + // translations sorted by language code + { langCode: 'ar', langData: ar, langName: 'Arabic', alias: 'اَلْعَرَبِيَّةُ' }, + { langCode: 'cs', langData: cs, langName: 'Czech', alias: 'Čeština' }, + { langCode: 'da', langData: da, langName: 'Dansk', alias: 'Dansk' }, + { langCode: 'de', langData: de, langName: 'German', alias: 'Deutsch' }, + { langCode: 'el', langData: el, langName: 'Greek', alias: 'Ελληνικά' }, + { langCode: 'eu', langData: eu, langName: 'Basque', alias: 'Euskara' }, + { langCode: 'es', langData: es, langName: 'Spanish', alias: 'Español' }, + { langCode: 'fi', langData: fi, langName: 'Finnish', alias: 'Suomen kieli' }, + { langCode: 'fr', langData: fr, langName: 'French', alias: 'Français' }, + { langCode: 'he', langData: he, langName: 'Hebrew', alias: 'עִבְרִית' }, + { langCode: 'hi', langData: hi, langName: 'Hindi', alias: 'हिंदी' }, + { langCode: 'hu', langData: hu, langName: 'Hungarian', alias: 'Magyar' }, + { langCode: 'id', langData: id, langName: 'Indonesian', alias: 'Bahasa Indonesia' }, + { langCode: 'it', langData: it, langName: 'Italian', alias: 'Italiano' }, + { langCode: 'ja', langData: ja, langName: 'Japanese', alias: '日本語' }, + { langCode: 'ko', langData: ko, langName: 'Korean', alias: '한국어' }, + { langCode: 'lt', langData: lt, langName: 'Lithuanian', alias: 'Lietuvių kalba' }, + { langCode: 'nb', langData: nb, langName: 'Norwegian', alias: 'Norsk' }, + { langCode: 'nl', langData: nl, langName: 'Dutch', alias: 'Nederlands' }, + { langCode: 'pl', langData: pl, langName: 'Polish', alias: 'Polski' }, + { langCode: 'pt-BR', langData: ptBR, langName: 'Portuguese', alias: 'Português brasileiro' }, + { langCode: 'pt-PT', langData: ptPT, langName: 'Portuguese', alias: 'Português' }, + { langCode: 'ro', langData: ro, langName: 'Romanian', alias: 'Românește' }, + { langCode: 'ru', langData: ru, langName: 'Russian', alias: 'Русский' }, + { langCode: 'sk', langData: sk, langName: 'Slovak', alias: 'Slovenčina' }, + { langCode: 'sl', langData: sl, langName: 'Slovenian', alias: 'Slovenščina' }, + { langCode: 'sv', langData: sv, langName: 'Swedish', alias: 'Svenska' }, + { langCode: 'th', langData: th, langName: 'Thai', alias: 'ภาษาไทย' }, + { langCode: 'tr', langData: tr, langName: 'Turkish', alias: 'Türkçe' }, + { langCode: 'vi', langData: vi, langName: 'Vietnamese', alias: 'Tiếng Việt' }, + { langCode: 'zh-CN', langData: zhHansCN, langName: 'Chinese - CN', alias: '中国简体' }, + { langCode: 'zh-HK', langData: zhHantHK, langName: 'Chinese - HK', alias: '香港繁體' }, + { langCode: 'zh-TW', langData: zhHantTW, langName: 'Chinese - TW', alias: '台灣繁體' }, +]; + +// Init default language +const defaultLanguage = init('en', en); + +// Initialize all translated languages +languageOptions.forEach((ln) => { + if (ln.langCode !== 'en') { + init(ln.langCode, ln.langData); + } +}); + +// Current flyPad language +let currentEfbLanguage = 'en'; +let currentLanguageMap = defaultLanguage; + +// Listener to change the currently set language in the flyPad. +const watchLanguageChanges = () => { + NXDataStore.getAndSubscribe( + 'EFB_LANGUAGE', + (_, value) => { + // eslint-disable-next-line no-console + console.log(`language changed to ${value}`); + currentEfbLanguage = value; + currentLanguageMap = allLanguagesMap.get(currentEfbLanguage) || defaultLanguage; + }, + 'en', + ); +}; + +if (process.env.VITE_BUILD) { + window.addEventListener('AceInitialized', watchLanguageChanges); +} else { + watchLanguageChanges(); +} + +/** + * Returns localized string in the currently configured language when provided with + * correct identifier key. + * It will fall back to the default language and will try to + * find the key there. + * If the key is not available in the default language the key itself will be returned. + * + * Note: Currently all language files are imported and contain all keys so this is redundant + * but still implemented for future changes. + * @param key String identifier key + * @return translated string in the current language if available, or default + * language, or key string + */ +export function t(key: string): string { + return currentLanguageMap.get(key) || defaultLanguage.get(key) || key; +} diff --git a/src/instruments/src/EFB/tsconfig.json b/src/instruments/src/EFB/tsconfig.json new file mode 100644 index 00000000000..bffce160b74 --- /dev/null +++ b/src/instruments/src/EFB/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "resolveJsonModule" : true + } +} diff --git a/src/instruments/src/EFB/vite.config.ts b/src/instruments/src/EFB/vite.config.ts new file mode 100644 index 00000000000..bf49e19e95a --- /dev/null +++ b/src/instruments/src/EFB/vite.config.ts @@ -0,0 +1,46 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import tsconfigPaths from 'vite-tsconfig-paths'; +import { replaceCodePlugin } from 'vite-plugin-replace'; +import autoprefixer from 'autoprefixer'; +import tailwindcss from 'tailwindcss'; +import dotenv from 'dotenv'; + +dotenv.config({ path: '../../../../.env' }); + +const envVarsToReplace = ['CLIENT_ID', 'CLIENT_SECRET', 'SENTRY_DSN']; + +export default defineConfig({ + css: { + postcss: { + plugins: [ + tailwindcss({ config: 'tailwind.config.js' }), + autoprefixer(), + ], + }, + }, + plugins: [ + react(), + tsconfigPaths({ root: '../../../' }), + replaceCodePlugin({ + replacements: [ + { + from: 'process.env.VITE_BUILD', + to: 'true', + }, + ...envVarsToReplace.map((it) => { + const value = process.env[it]; + + if (!value) { + throw new Error(`Not env value for ${it}.`); + } + + return ({ + from: `process.env.${it}`, + to: `'${value}'`, + }); + }), + ], + }), + ], +}); diff --git a/src/instruments/src/ISIS/AltitudeIndicator.tsx b/src/instruments/src/ISIS/AltitudeIndicator.tsx index fe154a6d24a..cbc9af4aa84 100644 --- a/src/instruments/src/ISIS/AltitudeIndicator.tsx +++ b/src/instruments/src/ISIS/AltitudeIndicator.tsx @@ -1,4 +1,4 @@ -import { usePersistentProperty } from '@instruments/common/persistence'; +import { usePersistentNumberProperty } from '@instruments/common/persistence'; import React from 'react'; import { Bug } from './Bug'; import { DigitalAltitudeIndicator } from './DigitalAltitudeIndicator'; @@ -43,7 +43,7 @@ type AltitudeIndicatorProps = { } export const AltitudeIndicator: React.FC = ({ altitude, mda, bugs }) => { - const [metricAltitude] = usePersistentProperty('ISIS_METRIC_ALTITUDE', '0'); + const [metricAltitude] = usePersistentNumberProperty('ISIS_METRIC_ALTITUDE', 0); return ( @@ -61,7 +61,7 @@ export const AltitudeIndicator: React.FC = ({ altitude, /> - { metricAltitude === '1' && } + { !!metricAltitude && } ); }; diff --git a/src/instruments/src/ISIS/AutoBrightness.tsx b/src/instruments/src/ISIS/AutoBrightness.tsx index 3ecbe5dbbae..be274584e5c 100644 --- a/src/instruments/src/ISIS/AutoBrightness.tsx +++ b/src/instruments/src/ISIS/AutoBrightness.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { useSimVar } from '@instruments/common/simVars'; import { useInteractionEvent } from '@instruments/common/hooks'; -import { useInterval } from '@flybywiresim/react-components'; +import useInterval from '@instruments/common/useInterval'; type AutoBrightnessProps = { bugsActive: boolean diff --git a/src/model/efb/flypad.gltf b/src/model/efb/flypad.gltf index 6b116259cfc..ca409bd74ba 100644 --- a/src/model/efb/flypad.gltf +++ b/src/model/efb/flypad.gltf @@ -505,7 +505,10 @@ "alphaMode": "BLEND", "normalTexture": { "index": 0 }, "occlusionTexture": { "index": 2 }, - "pbrMetallicRoughness": { "baseColorTexture": { "index": 1 }, "metallicRoughnessTexture": { "index": 2 } } + "pbrMetallicRoughness": { + "baseColorTexture": { "index": 1 }, + "metallicRoughnessTexture": { "index": 2 } + } }, { "name": "$EFB" }, { diff --git a/src/presets/build.sh b/src/presets/build.sh new file mode 100755 index 00000000000..d59664a3a5d --- /dev/null +++ b/src/presets/build.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# get directory of this script relative to root +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +OUTPUT="${DIR}/../../flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/presets.wasm" + +if [ "$1" == "--debug" ]; then + CLANG_ARGS="-g -DDEBUG" +else + WASMLD_ARGS="--strip-debug" +fi + +set -ex + +# create temporary folder for o files +mkdir -p "${DIR}/obj" +pushd "${DIR}/obj" + +# compile c++ code +clang++ \ + -c \ + ${CLANG_ARGS} \ + -Wno-unused-command-line-argument \ + -Wno-ignored-attributes \ + -Wno-macro-redefined \ + --sysroot "${MSFS_SDK}/WASM/wasi-sysroot" \ + -target wasm32-unknown-wasi \ + -flto \ + -D_MSFS_WASM=1 \ + -D__wasi__ \ + -D_LIBCPP_HAS_NO_THREADS \ + -D_WINDLL \ + -D_MBCS \ + -mthread-model single \ + -fno-exceptions \ + -fms-extensions \ + -fvisibility=hidden \ + -I "${MSFS_SDK}/WASM/include" \ + -I "${MSFS_SDK}/SimConnect SDK/include" \ + -I "${DIR}/src" \ + -I "${DIR}/src/Lighting" \ + -I "${DIR}/src/Aircraft" \ + "${DIR}/src/Presets.cpp" \ + "${DIR}/src/Lighting/LightPreset.cpp" \ + "${DIR}/src/Aircraft/AircraftPreset.cpp" + +# restore directory +popd + +wasm-ld \ + --no-entry \ + --allow-undefined \ + -L "${MSFS_SDK}/WASM/wasi-sysroot/lib/wasm32-wasi" \ + -lc "${MSFS_SDK}/WASM/wasi-sysroot/lib/wasm32-wasi/libclang_rt.builtins-wasm32.a" \ + --export __wasm_call_ctors \ + ${WASMLD_ARGS} \ + --export-dynamic \ + --export malloc \ + --export free \ + --export __wasm_call_ctors \ + --export-table \ + --gc-sections \ + -O3 --lto-O3 \ + -lc++ -lc++abi \ + ${DIR}/obj/*.o \ + -o $OUTPUT diff --git a/src/presets/src/Aircraft/AircraftPreset.cpp b/src/presets/src/Aircraft/AircraftPreset.cpp new file mode 100644 index 00000000000..867a6aec581 --- /dev/null +++ b/src/presets/src/Aircraft/AircraftPreset.cpp @@ -0,0 +1,171 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#include +#include + +#include "AircraftPreset.h" + +AircraftPreset::AircraftPreset() { + m_Units = new Units(); +} + +AircraftPreset::~AircraftPreset() { + delete m_Units; +} + +void AircraftPreset::initialize() { + LoadAircraftPresetRequest = register_named_variable("A32NX_LOAD_AIRCRAFT_PRESET"); + this->setLoadAircraftPresetRequest(0); + ProgressAircraftPreset = register_named_variable("A32NX_LOAD_AIRCRAFT_PRESET_PROGRESS"); + ProgressAircraftPresetId = register_named_variable("A32NX_LOAD_AIRCRAFT_PRESET_CURRENT_ID"); + SimOnGround = get_aircraft_var_enum("SIM ON GROUND"); + isInitialized = true; + std::cout << "PRESETS: AircraftPresets initialized" << std::endl; +} + +void AircraftPreset::onUpdate(double deltaTime) { + if (!isInitialized) { + return; + } + + const auto loadAircraftPresetRequest = static_cast(getLoadAircraftPresetRequest()); + + // has request to load a preset been received? + if (loadAircraftPresetRequest) { + + // we do not allow loading of presets in the air to prevent users from + // accidentally changing the aircraft configuration + if (!getSimOnGround()) { + std::cout << "PRESETS: Aircraft must be on the ground to load a preset!" << std::endl; + setLoadAircraftPresetRequest(0); + loadingIsActive = false; + return; + } + + // check if we already have an active loading process or if this is a new request which + // needs to be initialized + if (!loadingIsActive) { + + // check if procedure ID exists + vector* requestedProcedure = procedures.getProcedure(loadAircraftPresetRequest); + if (requestedProcedure == nullptr) { + std::cout << "PRESETS: Preset " << loadAircraftPresetRequest << " not found!" << std::endl; + setLoadAircraftPresetRequest(0); + loadingIsActive = false; + return; + } + + // initialize new loading process + currentProcedureID = loadAircraftPresetRequest; + currentProcedure = requestedProcedure; + currentLoadingTime = 0; + currentDelay = 0; + currentStep = 0; + loadingIsActive = true; + setProgressAircraftPreset(0); + setProgressAircraftPresetId(0); + std::cout << "PRESETS: Aircraft Preset " << currentProcedureID << " starting procedure!" + << std::endl; + return; + } + + // reset the LVAR to the currently running procedure in case it has been changed + // during a running procedure. We only allow "0" as a signal to interrupt the + // current procedure + setLoadAircraftPresetRequest(static_cast(currentProcedureID)); + + // check if all procedure steps are done and the procedure is finished + if (currentStep >= currentProcedure->size()) { + std::cout << "PRESETS: Aircraft Preset " << currentProcedureID << " done!" + << std::endl; + setProgressAircraftPreset(0); + setProgressAircraftPresetId(0); + setLoadAircraftPresetRequest(0); + loadingIsActive = false; + return; + } + + // update run timer + currentLoadingTime += deltaTime * 1000; + + // check if we are in a delay and return if we have to wait + if (currentLoadingTime <= currentDelay) { + return; + } + + // update progress var + setProgressAircraftPreset((double) currentStep / currentProcedure->size()); + setProgressAircraftPresetId(currentProcedure->at(currentStep)->id); + + // convenience tmp + const auto currentStepPtr = currentProcedure->at(currentStep); + + // calculate next delay + currentDelay = currentLoadingTime + currentStepPtr->delayAfter; + + // prepare return values for execute_calculator_code + FLOAT64 fvalue = 0; + SINT32 ivalue = 0; + PCSTRINGZ svalue = ""; + + // check if the current step is a condition step and check the condition + if (currentStepPtr->isConditional) { + execute_calculator_code(currentStepPtr->actionCode.c_str(), &fvalue, &ivalue, &svalue); + if (static_cast(fvalue)) { + currentDelay = 0; + currentStep++; + } + else { + std::cout << "PRESETS: Aircraft Preset Step " << currentStep << " Condition: " + << currentStepPtr->description + << " (delay between tests: " << currentStepPtr->delayAfter << ")" << std::endl; + } + return; + } + + // test if the next step is required or if the state is already + // set then set in which case the action can be skipped and delay can be ignored. + fvalue = 0; + ivalue = 0; + svalue = ""; + if (!currentStepPtr->expectedStateCheckCode.empty()) { +#ifdef DEBUG + std::cout << "PRESETS: Aircraft Preset Step " << currentStep << " " + << currentStepPtr->description + << " TESTING: \"" << currentStepPtr->expectedStateCheckCode << "\"" << std::endl; +#endif + execute_calculator_code(currentStepPtr->expectedStateCheckCode.c_str(), &fvalue, &ivalue, &svalue); + if (static_cast(fvalue)) { +#ifdef DEBUG + std::cout << "PRESETS: Aircraft Preset Step " << currentStep << " " + << currentStepPtr->description + << " SKIPPING: \"" << currentStepPtr->expectedStateCheckCode << "\"" << std::endl; +#endif + + currentDelay = 0; + currentStep++; + return; + } + } + + // execute code to set expected state + std::cout << "PRESETS: Aircraft Preset Step " << currentStep << " Execute: " + << currentStepPtr->description + << " (delay after: " << currentStepPtr->delayAfter << ")" << std::endl; + execute_calculator_code(currentStepPtr->actionCode.c_str(), &fvalue, &ivalue, &svalue); + currentStep++; + + } + else if (loadingIsActive) { + // request lvar has been set to 0 while we were executing a procedure ==> cancel loading + std::cout << "PRESETS: Aircraft Preset " << currentProcedureID << " loading cancelled!" << + std::endl; + loadingIsActive = false; + } +} + +void AircraftPreset::shutdown() { + isInitialized = false; + std::cout << "PRESETS: AircraftPresets shutdown" << std::endl; +} diff --git a/src/presets/src/Aircraft/AircraftPreset.h b/src/presets/src/Aircraft/AircraftPreset.h new file mode 100644 index 00000000000..e489146458c --- /dev/null +++ b/src/presets/src/Aircraft/AircraftPreset.h @@ -0,0 +1,126 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "Units.h" +#include "../inih/ini.h" +#include "AircraftProcedures.h" + +using namespace std; + +/** + * Class for handling aircraft presets. + */ +class AircraftPreset { +private: + Units* m_Units{}; + + // Sim LVAR IDs + ID LoadAircraftPresetRequest{}; + ID ProgressAircraftPreset{}; + ID ProgressAircraftPresetId{}; + + // Simvar light variables + ENUM SimOnGround{}; + + bool isInitialized = false; + + // Procedures + AircraftProcedures procedures{}; + + // current procedure ID + int64_t currentProcedureID = 0; + // current procedure + vector* currentProcedure = nullptr; + // flag to signal that a loading process is ongoing + bool loadingIsActive = false; + // in ms + double currentLoadingTime = 0.0; + // time for next action in respect to currentLoadingTime + double currentDelay = 0; + // step number in the array of steps + uint64_t currentStep = 0; + +public: + /** + * Creates an instance of the LightPreset class. + * @param simVars pointer to the LightSimVars object for reading and writing + * the simulation variables. + */ + AircraftPreset(); + + /** + * Destructor + */ + ~AircraftPreset(); + + /** + * Called when SimConnect is initialized + */ + void initialize(); + + /** + * Callback used to update the LightPreset at each tick (dt). + * This is used to execute every action and task required to update the light Settings. + * @param deltaTime The time since the last tick + * @return True if successful, false otherwise. + */ + void onUpdate(double deltaTime); + + /** + * Called when SimConnect is shut down + */ + void shutdown(); + +private: + /** + * Reads the preset loading request variable. + * @return INT64 signifying the preset to be loaded + */ + inline FLOAT64 getLoadAircraftPresetRequest() const { + return get_named_variable_value(LoadAircraftPresetRequest); + } + + /** + * Sets the loading request value. Typically used to reset to 0 after the preset has been loaded. + * @param value usually loadFromData to 0 to reset the request. + */ + inline void setLoadAircraftPresetRequest(FLOAT64 value) const { + set_named_variable_value(LoadAircraftPresetRequest, value); + } + + /** + * Sets the curren progress in percent (0.0..1.0) + * @param value 0.0..1.0 progress in percent + */ + inline void setProgressAircraftPreset(FLOAT64 value) const { + set_named_variable_value(ProgressAircraftPreset, value); + } + + /** + * Sets the ID of the current procedure step to the LVAR + * @param value current procedure step ID + */ + inline void setProgressAircraftPresetId(FLOAT64 value) const { + set_named_variable_value(ProgressAircraftPresetId, value); + } + /** +* Retrieves the SIM ON GROUND var from the simulator. +* @return value true if one ground, false otherwise +*/ + inline bool getSimOnGround() const { + return static_cast(aircraft_varget(SimOnGround, m_Units->Bool, 1)); + } + +}; diff --git a/src/presets/src/Aircraft/AircraftProcedures.h b/src/presets/src/Aircraft/AircraftProcedures.h new file mode 100644 index 00000000000..7be03c031c8 --- /dev/null +++ b/src/presets/src/Aircraft/AircraftProcedures.h @@ -0,0 +1,244 @@ +#pragma once + +#include +#include +#include + +using namespace std; + +struct ProcedureStep { + std::string description; + // unique id for each step (will be assigned automatically in constructor) + int id; + // true if the procedure step is a pure condition check to wait for a certain state + bool isConditional; + // time to delay next step of execution of action - will be skipped if + // expected state is already set + double delayAfter; + // check if desired state is already set so the action can be skipped + std::string expectedStateCheckCode; + // calculator code to achieve the desired state + // if it is a conditional this calculator code needs to eval to true or false + std::string actionCode; +}; + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// Please remember to also update the EFB Presets page for the step description +// if you make any changes to this list. +// src/instruments/src/EFB/Presets/Widgets/AircraftPresets.tsx +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +// @formatter:off +static vector* TURNAROUND_CONFIG_ON = new vector{ + new ProcedureStep {"BAT1 On", 101, false, 100, "(L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)", "1 (>L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)"}, + new ProcedureStep {"BAT2 On", 102, false, 3000, "(L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)", "1 (>L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)"}, + new ProcedureStep {"EXT PWR On", 103, false, 3000, "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) " + "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) && " + "(L:A32NX_ENGINE_STATE:1) 1 == || " + "(L:A32NX_ENGINE_STATE:2) 1 == || " + "(A:EXTERNAL POWER ON:1, BOOL) ||", "(A:EXTERNAL POWER ON:1, BOOL) ! if{ 1 (>K:TOGGLE_EXTERNAL_POWER) }"}, + new ProcedureStep {"APU Master On", 104, false, 3000, "(L:A32NX_ENGINE_STATE:1) 1 == " + "(L:A32NX_ENGINE_STATE:2) 1 == && " + "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) 1 == ||", "1 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, + new ProcedureStep {"APU Start On", 105, false, 1000, "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) ! " + "(L:A32NX_OVHD_APU_START_PB_IS_AVAILABLE) ||", "1 (>L:A32NX_OVHD_APU_START_PB_IS_ON)"}, + new ProcedureStep {"AC BUS Avail Check", 106, true, 2000, "", "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)"}, + new ProcedureStep {"Nav & Logo Lt On", 107, false, 1000, "(A:LIGHT LOGO, Bool) (A:LIGHT NAV, Bool) &&", "1 (>K:2:LOGO_LIGHTS_SET) 1 (>K:2:NAV_LIGHTS_SET)"}, + new ProcedureStep {"ADIRS 1 Nav", 108, false, 500, "(L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB)"}, + new ProcedureStep {"ADIRS 2 Nav", 109, false, 500, "(L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB)"}, + new ProcedureStep {"ADIRS 3 Nav", 110, false, 1500, "(L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB)"}, + new ProcedureStep {"GND CTL On", 111, false, 1000, "(L:A32NX_ENGINE_STATE:1) 1 == " + "(L:A32NX_ENGINE_STATE:2) 1 == || " + "(L:A32NX_RCDR_GROUND_CONTROL_ON) 1 == ||", "1 (>L:A32NX_RCDR_GROUND_CONTROL_ON)"}, + new ProcedureStep{"Crew Oxy On", 112, false, 1000, "(L:PUSH_OVHD_OXYGEN_CREW) 0 ==", "0 (>L:PUSH_OVHD_OXYGEN_CREW)"}, + new ProcedureStep{"NO SMOKING Auto", 113, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION) 1 ==", "1 (>L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION)"}, + new ProcedureStep{"EMER EXT Lt Arm", 114, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION) 1 ==", "1 (>L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION)"}, + new ProcedureStep{"APU Avail Check", 115, true, 2000, "", "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) ! (L:A32NX_OVHD_APU_START_PB_IS_AVAILABLE) ||"}, + new ProcedureStep{"APU Bleed On", 116, false, 1000, "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) ! " + "(L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON) ||", "1 (>L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON)"}, +}; + +static vector* TURNAROUND_CONFIG_OFF = new vector{ + new ProcedureStep{"NO SMOKING Off", 117, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION) 2 ==", "2 (>L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION)"}, + new ProcedureStep{"EMER EXT Lt Off", 118, false, 1500, "(L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION) 2 ==", "2 (>L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION)"}, + new ProcedureStep{"Crew Oxy Off", 119, false, 1000, "(L:PUSH_OVHD_OXYGEN_CREW) 1 ==", "1 (>L:PUSH_OVHD_OXYGEN_CREW)"}, + new ProcedureStep{"GND CTL Off", 120, false, 1000, "(L:A32NX_RCDR_GROUND_CONTROL_ON) 0 ==", "0 (>L:A32NX_RCDR_GROUND_CONTROL_ON)"}, + new ProcedureStep{"ADIRS 3 Off", 121, false, 500, "(L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB)"}, + new ProcedureStep{"ADIRS 2 Off", 122, false, 500, "(L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB)"}, + new ProcedureStep{"ADIRS 1 Off", 123, false, 1000, "(L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB)"}, + new ProcedureStep{"Nav & Logo Lt Off", 124, false, 500, "(A:LIGHT LOGO, Bool) ! (A:LIGHT NAV, Bool) ! &&", "0 (>K:2:LOGO_LIGHTS_SET) 0 (>K:2:NAV_LIGHTS_SET)"}, + new ProcedureStep{"APU Bleed Off", 125, false, 1500, "(L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON)"}, + new ProcedureStep{"APU Master Off", 126, false, 2000, "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, + new ProcedureStep{"EXT PWR Off", 127, false, 3000, "(A:EXTERNAL POWER ON:1, BOOL) !", "(A:EXTERNAL POWER ON:1, BOOL) if{ 1 (>K:TOGGLE_EXTERNAL_POWER) }"}, + new ProcedureStep{"BAT2 Off", 128, false, 100, "(L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO) 0 ==", "0 (>L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)"}, + new ProcedureStep{"BAT1 Off", 129, false, 1000, "(L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO) 0 ==", "0 (>L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)"}, + new ProcedureStep{"AC BUS Off Check", 130, true, 2000, "", "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED) !"}, +}; + +static vector* PUSHBACK_CONFIG_ON = new vector{ + new ProcedureStep{"EXT PWR Off", 200, false, 3000, "(A:EXTERNAL POWER ON:1, BOOL) !", "(A:EXTERNAL POWER ON:1, BOOL) if{ 1 (>K:TOGGLE_EXTERNAL_POWER) }"}, + new ProcedureStep{"FUEL PUMP 2 On", 201, false, 100, "(A:FUELSYSTEM PUMP SWITCH:2, Bool)", "2 (>K:FUELSYSTEM_PUMP_ON)"}, + new ProcedureStep{"FUEL PUMP 5 On", 202, false, 500, "(A:FUELSYSTEM PUMP SWITCH:5, Bool)", "5 (>K:FUELSYSTEM_PUMP_ON)"}, + new ProcedureStep{"FUEL PUMP 1 On", 203, false, 100, "(A:FUELSYSTEM PUMP SWITCH:1, Bool)", "1 (>K:FUELSYSTEM_PUMP_ON)"}, + new ProcedureStep{"FUEL PUMP 4 On", 204, false, 500, "(A:FUELSYSTEM PUMP SWITCH:4, Bool)", "4 (>K:FUELSYSTEM_PUMP_ON)"}, + new ProcedureStep{"FUEL PUMP 3 On", 205, false, 100, "(A:FUELSYSTEM PUMP SWITCH:3, Bool)", "3 (>K:FUELSYSTEM_PUMP_ON)"}, + new ProcedureStep{"FUEL PUMP 6 On", 206, false, 2000, "(A:FUELSYSTEM PUMP SWITCH:6, Bool)", "6 (>K:FUELSYSTEM_PUMP_ON)"}, + new ProcedureStep{"PWS Auto", 207, false, 1000, "(L:A32NX_SWITCH_RADAR_PWS_POSITION) 1 ==", "1 (>L:A32NX_SWITCH_RADAR_PWS_POSITION)"}, + new ProcedureStep{"Transponder On", 208, false, 1000, "(L:A32NX_TRANSPONDER_MODE) 1 ==", "1 (>L:A32NX_TRANSPONDER_MODE)"}, + new ProcedureStep{"ATC ALT RPTG On", 209, false, 1000, "(L:A32NX_SWITCH_ATC_ALT) 1 ==", "1 (>L:A32NX_SWITCH_ATC_ALT)"}, + new ProcedureStep{"TCAS TRAFFIC ABV", 210, false, 2000, "(L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION)"}, + new ProcedureStep{"COCKPIT DOOR LCK", 211, false, 2000, "(L:A32NX_COCKPIT_DOOR_LOCKED) 1 ==", "1 (>L:A32NX_COCKPIT_DOOR_LOCKED)"}, + new ProcedureStep{"Strobe Auto", 212, false, 1000, "(A:LIGHT STROBE, Bool)", "1 (>L:STROBE_0_AUTO) 0 (>K:STROBES_ON)"}, + new ProcedureStep{"Beacon On", 213, false, 1000, "(A:LIGHT BEACON, Bool)", "0 (>K:BEACON_LIGHTS_ON)"}, + new ProcedureStep{"SEAT BELTS On", 214, false, 1000, "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL)", "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) ! if{ 1 (>K:CABIN_SEATBELTS_ALERT_SWITCH_TOGGLE) }"}, + new ProcedureStep{"ADIRS 1 Await Alignment", 215, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_1_STATE) 2 =="}, + new ProcedureStep{"ADIRS 2 Await Alignment", 216, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_2_STATE) 2 =="}, + new ProcedureStep{"ADIRS 3 Await Alignment", 217, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_3_STATE) 2 =="}, +}; + +static vector* PUSHBACK_CONFIG_OFF = new vector{ + new ProcedureStep{"Strobe Off", 218, false, 1000, "(A:LIGHT STROBE, Bool) !", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_OFF)"}, + new ProcedureStep{"Beacon Off", 219, false, 1000, "(A:LIGHT BEACON, Bool) !", "0 (>K:BEACON_LIGHTS_OFF)"}, + new ProcedureStep{"SEAT BELTS Off", 220, false, 2000, "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) !", "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) if{ 1 (>K:CABIN_SEATBELTS_ALERT_SWITCH_TOGGLE) }"}, + new ProcedureStep{"PWS Off", 221, false, 1000, "(L:A32NX_SWITCH_RADAR_PWS_POSITION) 0 ==", "0 (>L:A32NX_SWITCH_RADAR_PWS_POSITION)"}, + new ProcedureStep{"Transponder Off", 222, false, 1000, "(L:A32NX_TRANSPONDER_MODE) 0 ==", "0 (>L:A32NX_TRANSPONDER_MODE)"}, + new ProcedureStep{"ATC ALT RPTG Off",223, false, 1000, "(L:A32NX_SWITCH_ATC_ALT) 1 ==", "1 (>L:A32NX_SWITCH_ATC_ALT)"}, + new ProcedureStep{"TCAS TRAFFIC ABV",224, false, 1000, "(L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION)"}, + new ProcedureStep{"COCKPIT DOOR OP", 225, false, 2000, "(L:A32NX_COCKPIT_DOOR_LOCKED) 0 ==", "0 (>L:A32NX_COCKPIT_DOOR_LOCKED)"}, + new ProcedureStep{"FUEL PUMP 2 Off", 226, false, 100, "(A:FUELSYSTEM PUMP SWITCH:2, Bool) !", "2 (>K:FUELSYSTEM_PUMP_OFF)"}, + new ProcedureStep{"FUEL PUMP 5 Off", 227, false, 500, "(A:FUELSYSTEM PUMP SWITCH:5, Bool) !", "5 (>K:FUELSYSTEM_PUMP_OFF)"}, + new ProcedureStep{"FUEL PUMP 1 Off", 228, false, 100, "(A:FUELSYSTEM PUMP SWITCH:1, Bool) !", "1 (>K:FUELSYSTEM_PUMP_OFF)"}, + new ProcedureStep{"FUEL PUMP 4 Off", 229, false, 500, "(A:FUELSYSTEM PUMP SWITCH:4, Bool) !", "4 (>K:FUELSYSTEM_PUMP_OFF)"}, + new ProcedureStep{"FUEL PUMP 3 Off", 230, false, 100, "(A:FUELSYSTEM PUMP SWITCH:3, Bool) !", "3 (>K:FUELSYSTEM_PUMP_OFF)"}, + new ProcedureStep{"FUEL PUMP 6 Off", 231, false, 1000, "(A:FUELSYSTEM PUMP SWITCH:6, Bool) !", "6 (>K:FUELSYSTEM_PUMP_OFF)"}, +}; + +static vector* TAXI_CONFIG_ON = new vector{ + new ProcedureStep{"ENG MODE SEL START", 300, false, 3000, "(L:A32NX_ENGINE_STATE:1) 1 == " + "(L:A32NX_ENGINE_STATE:2) 1 == && " + "(K:TURBINE_IGNITION_SWITCH_SET1) 2 == " + "(K:TURBINE_IGNITION_SWITCH_SET2) 2 == && ||", "2 (>K:TURBINE_IGNITION_SWITCH_SET1) 2 (>K:TURBINE_IGNITION_SWITCH_SET2)"}, + new ProcedureStep{"ENG 2 ON", 301, false, 60000, "(A:FUELSYSTEM VALVE OPEN:2, Bool)", "2 (>K:FUELSYSTEM_VALVE_OPEN)"}, + new ProcedureStep{"ENG 2 Avail Check", 302, true, 5000, "", "(L:A32NX_ENGINE_STATE:2) 1 =="}, + new ProcedureStep{"ENG 1 ON", 303, false, 60000, "(A:FUELSYSTEM VALVE OPEN:1, Bool)", "1 (>K:FUELSYSTEM_VALVE_OPEN)"}, + new ProcedureStep{"ENG 1 Avail Check", 304, true, 5000, "", "(L:A32NX_ENGINE_STATE:1) 1 =="}, + new ProcedureStep{"ENG MODE SEL NORM", 305, false, 3000, "(A:TURB ENG IGNITION SWITCH EX1:1, Bool) 1 == " + "(A:TURB ENG IGNITION SWITCH EX1:2, Bool) 1 == &&", "1 (>K:TURBINE_IGNITION_SWITCH_SET1) 1 (>K:TURBINE_IGNITION_SWITCH_SET2)"}, + new ProcedureStep{"APU Bleed Off", 306, false, 2000, "(L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON)"}, + new ProcedureStep{"APU Master Off", 307, false, 2000, "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, + new ProcedureStep{"Autobrake Max", 308, false, 2000, "(L:A32NX_AUTOBRAKES_ARMED_MODE) 3 ==", "0 (>K:A32NX.AUTOBRAKE_SET_MAX)"}, + new ProcedureStep{"Spoiler Arm", 309, false, 2000, "(L:A32NX_SPOILERS_ARMED) 1 ==", "1 (>K:SPOILERS_ARM_SET)"}, + new ProcedureStep{"Rudder Trim Reset", 310, false, 2000, "(A:RUDDER TRIM, Radians) 0 ==", "0 (>K:RUDDER_TRIM_SET)"}, + new ProcedureStep{"Flaps 1", 311, false, 3000, "(L:A32NX_FLAPS_HANDLE_INDEX) 1 ==", "1 (>L:A32NX_FLAPS_HANDLE_INDEX)"}, + new ProcedureStep{"NOSE Lt Taxi", 312, false, 1000, "(A:CIRCUIT SWITCH ON:20, Bool)", "0 (>L:LIGHTING_LANDING_1) (A:CIRCUIT SWITCH ON:20, Bool) ! if{ 20 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + new ProcedureStep{"RWY TURN OFF Lt L On", 313, false, 0, "(A:CIRCUIT SWITCH ON:21, Bool)", "(A:CIRCUIT SWITCH ON:21, Bool) ! if{ 21 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + new ProcedureStep{"RWY TURN OFF Lt R On", 314, false, 1000, "(A:CIRCUIT SWITCH ON:22, Bool)", "(A:CIRCUIT SWITCH ON:22, Bool) ! if{ 22 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, +}; + +static vector* TAXI_CONFIG_OFF = new vector{ + new ProcedureStep{"NOSE Lt Taxi", 315, false, 1000, "(A:CIRCUIT SWITCH ON:20, Bool) !", "2 (>L:LIGHTING_LANDING_1) (A:CIRCUIT SWITCH ON:20, Bool) if{ 20 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + new ProcedureStep{"RWY TURN OFF Lt L Off", 316, false, 0, "(A:CIRCUIT SWITCH ON:21, Bool) !", "(A:CIRCUIT SWITCH ON:21, Bool) if{ 21 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + new ProcedureStep{"RWY TURN OFF Lt R Off", 317, false, 2000, "(A:CIRCUIT SWITCH ON:22, Bool) !", "(A:CIRCUIT SWITCH ON:22, Bool) if{ 22 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + new ProcedureStep{"Autobrake Off", 318, false, 2000, "(L:A32NX_AUTOBRAKES_ARMED_MODE) 0 ==", "0 (>K:A32NX.AUTOBRAKE_SET_DISARM)"}, + new ProcedureStep{"Spoiler Disarm", 319, false, 2000, "(L:A32NX_SPOILERS_ARMED) 0 ==", "0 (>K:SPOILERS_ARM_SET)"}, + new ProcedureStep{"Rudder Trim Reset", 320, false, 2000, "(A:RUDDER TRIM, Radians) 0 ==", "0 (>K:RUDDER_TRIM_SET)"}, + new ProcedureStep{"Flaps 0", 321, false, 2000, "(L:A32NX_FLAPS_HANDLE_INDEX) 0 ==", "0 (>L:A32NX_FLAPS_HANDLE_INDEX)"}, + new ProcedureStep{"ENG 1 Off", 322, false, 2000, "(A:FUELSYSTEM VALVE OPEN:1, Bool) !", "1 (>K:FUELSYSTEM_VALVE_CLOSE)"}, + new ProcedureStep{"ENG 2 Off", 323, false, 2000, "(A:FUELSYSTEM VALVE OPEN:2, Bool) !", "2 (>K:FUELSYSTEM_VALVE_CLOSE)"}, + new ProcedureStep{"ENG 1 N1 <3%", 324, true, 1000, "", "(L:A32NX_ENGINE_N1:1) 3 <"}, + new ProcedureStep{"ENG 2 N1 <3%", 325, true, 1000, "", "(L:A32NX_ENGINE_N1:2) 3 <"}, +}; + +static vector* TAKEOFF_CONFIG_ON = new vector{ + new ProcedureStep{"WX Radar On", 400, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_SYS) 0 ==", "0 (>L:XMLVAR_A320_WEATHERRADAR_SYS)"}, + new ProcedureStep{"WX Radar Mode", 401, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_MODE) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_MODE)"}, + new ProcedureStep{"TCAS Switch TA/RA", 402, false, 2000, "(L:A32NX_SWITCH_TCAS_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_POSITION)"}, + new ProcedureStep{"NOSE Lt Takeoff", 403, false, 1000, "(A:CIRCUIT SWITCH ON:17, Bool)", "(A:CIRCUIT SWITCH ON:17, Bool) ! if{ 17 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + new ProcedureStep{"LL Lt L On", 404, false, 0, "(A:CIRCUIT SWITCH ON:18, Bool)", "0 (>L:LIGHTING_LANDING_2) 0 (>L:LANDING_2_RETRACTED) (A:CIRCUIT SWITCH ON:18, Bool) ! if{ 18 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + new ProcedureStep{"LL Lt R On", 405, false, 1000, "(A:CIRCUIT SWITCH ON:19, Bool)", "0 (>L:LIGHTING_LANDING_3) 0 (>L:LANDING_3_RETRACTED) (A:CIRCUIT SWITCH ON:19, Bool) ! if{ 19 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, +}; + +static vector* TAKEOFF_CONFIG_OFF = new vector{ + new ProcedureStep{"LL Lt L Off", 406, false, 0, "(A:CIRCUIT SWITCH ON:18, Bool) ! (L:LANDING_2_RETRACTED) &&", "2 (>L:LIGHTING_LANDING_2) 1 (>L:LANDING_2_RETRACTED) (A:CIRCUIT SWITCH ON:18, Bool) if{ 18 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + new ProcedureStep{"LL Lt R Off", 407, false, 1000, "(A:CIRCUIT SWITCH ON:19, Bool) ! (L:LANDING_3_RETRACTED) &&", "2 (>L:LIGHTING_LANDING_3) 1 (>L:LANDING_3_RETRACTED) (A:CIRCUIT SWITCH ON:19, Bool) if{ 19 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + new ProcedureStep{"NOSE Lt Takeoff", 408, false, 2000, "(A:CIRCUIT SWITCH ON:17, Bool) !", "(A:CIRCUIT SWITCH ON:17, Bool) if{ 17 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + new ProcedureStep{"TCAS Switch TA/RA", 409, false, 1000, "(L:A32NX_SWITCH_TCAS_POSITION) 0 ==", "0 (>L:A32NX_SWITCH_TCAS_POSITION)"}, + new ProcedureStep{"WX Radar Off", 410, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_SYS) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_SYS)"}, + new ProcedureStep{"WX Radar Mode", 411, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_MODE) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_MODE)"}, +}; +// @formatter:on + +class AircraftProcedures { + vector* coldAndDark = new vector; + vector* turnaround = new vector; + vector* readyForPushback = new vector; + vector* readyForTaxi = new vector; + vector* readyForTakeoff = new vector; + // just to get a list of IDs + vector* all = new vector; + +public: + AircraftProcedures() { + // Map the procedure groups + +#ifdef DEBUG + // This is used to generate a list of IDs which can be printed to console to + // add them to the EFB code to display the current step. + all->insert(all->end(), TURNAROUND_CONFIG_ON->begin(), TURNAROUND_CONFIG_ON->end()); + all->insert(all->end(), PUSHBACK_CONFIG_ON->begin(), PUSHBACK_CONFIG_ON->end()); + all->insert(all->end(), TAXI_CONFIG_ON->begin(), TAXI_CONFIG_ON->end()); + all->insert(all->end(), TAKEOFF_CONFIG_ON->begin(), TAKEOFF_CONFIG_ON->end()); + all->insert(all->end(), TAKEOFF_CONFIG_OFF->begin(), TAKEOFF_CONFIG_OFF->end()); + all->insert(all->end(), TAXI_CONFIG_OFF->begin(), TAXI_CONFIG_OFF->end()); + all->insert(all->end(), PUSHBACK_CONFIG_OFF->begin(), PUSHBACK_CONFIG_OFF->end()); + all->insert(all->end(), TURNAROUND_CONFIG_OFF->begin(), TURNAROUND_CONFIG_OFF->end()); + + std::for_each(all->begin(), all->end(), [&](ProcedureStep* item) { + std::cout << item->id << " = " << item->description << std::endl; + }); +#endif + + coldAndDark->insert(coldAndDark->end(), TAKEOFF_CONFIG_OFF->begin(), TAKEOFF_CONFIG_OFF->end()); + coldAndDark->insert(coldAndDark->end(), TAXI_CONFIG_OFF->begin(), TAXI_CONFIG_OFF->end()); + coldAndDark->insert(coldAndDark->end(), PUSHBACK_CONFIG_OFF->begin(), PUSHBACK_CONFIG_OFF->end()); + coldAndDark->insert(coldAndDark->end(), TURNAROUND_CONFIG_OFF->begin(), TURNAROUND_CONFIG_OFF->end()); + + turnaround->insert(turnaround->end(), TAKEOFF_CONFIG_OFF->begin(), TAKEOFF_CONFIG_OFF->end()); + turnaround->insert(turnaround->end(), TAXI_CONFIG_OFF->begin(), TAXI_CONFIG_OFF->end()); + turnaround->insert(turnaround->end(), PUSHBACK_CONFIG_OFF->begin(), PUSHBACK_CONFIG_OFF->end()); + turnaround->insert(turnaround->end(), TURNAROUND_CONFIG_ON->begin(), TURNAROUND_CONFIG_ON->end()); + + readyForPushback->insert(readyForPushback->end(), TAKEOFF_CONFIG_OFF->begin(), TAKEOFF_CONFIG_OFF->end()); + readyForPushback->insert(readyForPushback->end(), TAXI_CONFIG_OFF->begin(), TAXI_CONFIG_OFF->end()); + readyForPushback->insert(readyForPushback->end(), TURNAROUND_CONFIG_ON->begin(), TURNAROUND_CONFIG_ON->end()); + readyForPushback->insert(readyForPushback->end(), PUSHBACK_CONFIG_ON->begin(), PUSHBACK_CONFIG_ON->end()); + + readyForTaxi->insert(readyForTaxi->end(), TAKEOFF_CONFIG_OFF->begin(), TAKEOFF_CONFIG_OFF->end()); + readyForTaxi->insert(readyForTaxi->end(), TURNAROUND_CONFIG_ON->begin(), TURNAROUND_CONFIG_ON->end()); + readyForTaxi->insert(readyForTaxi->end(), PUSHBACK_CONFIG_ON->begin(), PUSHBACK_CONFIG_ON->end()); + readyForTaxi->insert(readyForTaxi->end(), TAXI_CONFIG_ON->begin(), TAXI_CONFIG_ON->end()); + + readyForTakeoff->insert(readyForTakeoff->end(), TURNAROUND_CONFIG_ON->begin(), TURNAROUND_CONFIG_ON->end()); + readyForTakeoff->insert(readyForTakeoff->end(), PUSHBACK_CONFIG_ON->begin(), PUSHBACK_CONFIG_ON->end()); + readyForTakeoff->insert(readyForTakeoff->end(), TAXI_CONFIG_ON->begin(), TAXI_CONFIG_ON->end()); + readyForTakeoff->insert(readyForTakeoff->end(), TAKEOFF_CONFIG_ON->begin(), TAKEOFF_CONFIG_ON->end()); + } + + vector* getProcedure(int64_t pID) const { + switch (pID) { + case 1: + return coldAndDark; + case 2: + return turnaround; + case 3: + return readyForPushback; + case 4: + return readyForTaxi; + case 5: + return readyForTakeoff; + default: + return nullptr; + } + } + +}; diff --git a/src/presets/src/Lighting/LightPreset.cpp b/src/presets/src/Lighting/LightPreset.cpp new file mode 100644 index 00000000000..711a886f6d0 --- /dev/null +++ b/src/presets/src/Lighting/LightPreset.cpp @@ -0,0 +1,261 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#include +#include + +#include "LightPreset.h" + +void LightPreset::initialize() { + isInitialized = true; + std::cout << "PRESETS: LightPresets initialized" << std::endl; +} + +void LightPreset::onUpdate(__attribute__((unused)) double deltaTime) { + if (!isInitialized) { + return; + } + + // get aircraft AC power state + const bool isAC1powered = (bool) simVars->getElecAC1State(); + + if (isAC1powered) { + // read the LVAR used to signal loading or saving + const auto loadLightingPresetRequest = static_cast(simVars->getLoadLightingPresetRequest()); + const auto saveLightingPresetRequest = static_cast(simVars->getSaveLightingPresetRequest()); + + // load becomes priority in case both vars are set. + if (loadLightingPresetRequest) { + loadLightingPreset(loadLightingPresetRequest); + } + else if (saveLightingPresetRequest) { + saveLightingPreset(saveLightingPresetRequest); + } + + // reset the request signal vars + simVars->setLoadLightingPresetRequest(0); + simVars->setSaveLightingPresetRequest(0); + } +} + +void LightPreset::shutdown() { + isInitialized = false; + std::cout << "PRESETS: LightPresets shutdown" << std::endl; +} + +void LightPreset::loadLightingPreset(int64_t loadPresetRequest) { + std::cout << "PRESETS: Loading preset: " << loadPresetRequest << std::endl; + if (readFromStore(loadPresetRequest)) { + applyToAircraft(); + std::cout << "PRESETS: Lighting Preset: " << loadPresetRequest << " successfully loaded." + << std::endl; + return; + } + std::cout << "PRESETS: Loading Lighting Preset: " << loadPresetRequest << " failed." << std::endl; +} + +void LightPreset::saveLightingPreset(int64_t savePresetRequest) { + std::cout << "PRESETS: Save to Lighting Preset: " << savePresetRequest << std::endl; + readFromAircraft(); + if (saveToStore(savePresetRequest)) { + std::cout << "PRESETS: Lighting Preset: " << savePresetRequest << " successfully saved." + << std::endl; + return; + } + std::cout << "PRESETS: Saving Lighting Preset: " << savePresetRequest << " failed." << std::endl; +} + +void LightPreset::readFromAircraft() { + lightValues.efbBrightness = simVars->getEfbBrightness(); + lightValues.cabinLightLevel = simVars->getLightCabin(); + lightValues.ovhdIntegralLightLevel = simVars->getLightPotentiometer(86); + lightValues.glareshieldIntegralLightLevel = simVars->getLightPotentiometer(84); + lightValues.glareshieldLcdLightLevel = simVars->getLightPotentiometer(87); + lightValues.tableLightCptLevel = simVars->getLightPotentiometer(10); + lightValues.tableLightFoLevel = simVars->getLightPotentiometer(11); + lightValues.pfdBrtCptLevel = simVars->getLightPotentiometer(88); + lightValues.ndBrtCptLevel = simVars->getLightPotentiometer(89); + lightValues.wxTerrainBrtCptLevel = simVars->getLightPotentiometer(94); + lightValues.consoleLightCptLevel = simVars->getLightPotentiometer(8); + lightValues.pfdBrtFoLevel = simVars->getLightPotentiometer(90); + lightValues.ndBrtFoLevel = simVars->getLightPotentiometer(91); + lightValues.wxTerrainBrtFoLevel = simVars->getLightPotentiometer(95); + lightValues.consoleLightFoLevel = simVars->getLightPotentiometer(9); + lightValues.dcduLeftLightLevel = simVars->getDcduLightLevel(Left); + lightValues.dcduRightLightLevel = simVars->getDcduLightLevel(Right); + lightValues.mcduLeftLightLevel = simVars->getMcduLightLevel(Left); + lightValues.mcduRightLightLevel = simVars->getMcduLightLevel(Right); + lightValues.ecamUpperLightLevel = simVars->getLightPotentiometer(92); + lightValues.ecamLowerLightLevel = simVars->getLightPotentiometer(93); + lightValues.floodPnlLightLevel = simVars->getLightPotentiometer(83); + lightValues.pedestalIntegralLightLevel = simVars->getLightPotentiometer(85); + lightValues.floodPedLightLevel = simVars->getLightPotentiometer(76); +} + +void LightPreset::applyToAircraft() { + simVars->setEfbBrightness(lightValues.efbBrightness); + simVars->setLightCabin(lightValues.cabinLightLevel); + simVars->setLightPotentiometer(86, lightValues.ovhdIntegralLightLevel); + simVars->setLightPotentiometer(84, lightValues.glareshieldIntegralLightLevel); + simVars->setLightPotentiometer(87, lightValues.glareshieldLcdLightLevel); + simVars->setLightPotentiometer(10, lightValues.tableLightCptLevel); + simVars->setLightPotentiometer(11, lightValues.tableLightFoLevel); + simVars->setLightPotentiometer(88, lightValues.pfdBrtCptLevel); + simVars->setLightPotentiometer(89, lightValues.ndBrtCptLevel); + simVars->setLightPotentiometer(94, lightValues.wxTerrainBrtCptLevel); + simVars->setLightPotentiometer(8, lightValues.consoleLightCptLevel); + simVars->setLightPotentiometer(90, lightValues.pfdBrtFoLevel); + simVars->setLightPotentiometer(91, lightValues.ndBrtFoLevel); + simVars->setLightPotentiometer(95, lightValues.wxTerrainBrtFoLevel); + simVars->setLightPotentiometer(9, lightValues.consoleLightFoLevel); + simVars->setDcduLightLevel(Left, lightValues.dcduLeftLightLevel); + simVars->setDcduLightLevel(Right, lightValues.dcduRightLightLevel); + simVars->setMcduLightLevel(Left, lightValues.mcduLeftLightLevel); + simVars->setMcduLightLevel(Right, lightValues.mcduRightLightLevel); + simVars->setLightPotentiometer(92, lightValues.ecamUpperLightLevel); + simVars->setLightPotentiometer(93, lightValues.ecamLowerLightLevel); + simVars->setLightPotentiometer(83, lightValues.floodPnlLightLevel); + simVars->setLightPotentiometer(85, lightValues.pedestalIntegralLightLevel); + simVars->setLightPotentiometer(76, lightValues.floodPedLightLevel); +} + +bool LightPreset::readFromStore(int64_t presetNr) { + // create ini file and data structure + mINI::INIStructure ini; + mINI::INIFile iniFile(CONFIGURATION_FILEPATH); + + // load file + bool result = iniFile.read(ini); + + const std::string preset = "preset " + std::to_string(presetNr); + + // check if preset is available + // if not use a 50% default preset + if (!ini.has(preset)) { + loadFromData(DEFAULT_50); + return true; + } + + // reading data structure from ini + lightValues.efbBrightness = iniGetOrDefault(ini, preset, "efb_brightness", 80.0); + lightValues.cabinLightLevel = iniGetOrDefault(ini, preset, "cabin_light", 50.0); + lightValues.ovhdIntegralLightLevel = iniGetOrDefault(ini, preset, "ovhd_int_lt", 50.0); + lightValues.glareshieldIntegralLightLevel = iniGetOrDefault(ini, preset, "glareshield_int_lt", 50.0); + lightValues.glareshieldLcdLightLevel = iniGetOrDefault(ini, preset, "glareshield_lcd_lt", 50.0); + lightValues.tableLightCptLevel = iniGetOrDefault(ini, preset, "table_cpt_lt", 50.0); + lightValues.tableLightFoLevel = iniGetOrDefault(ini, preset, "table_fo_lt", 50.0); + lightValues.pfdBrtCptLevel = iniGetOrDefault(ini, preset, "pfd_cpt_lvl", 50.0); + lightValues.ndBrtCptLevel = iniGetOrDefault(ini, preset, "nd_cpt_lvl", 50.0); + lightValues.wxTerrainBrtCptLevel = iniGetOrDefault(ini, preset, "wx_cpt_lvl", 50.0); + lightValues.consoleLightCptLevel = iniGetOrDefault(ini, preset, "console_cpt_lt", 50.0); + lightValues.pfdBrtFoLevel = iniGetOrDefault(ini, preset, "pfd_fo_lvl", 50.0); + lightValues.ndBrtFoLevel = iniGetOrDefault(ini, preset, "nd_fo_lvl", 50.0); + lightValues.wxTerrainBrtFoLevel = iniGetOrDefault(ini, preset, "wx_fo_lvl", 50.0); + lightValues.consoleLightFoLevel = iniGetOrDefault(ini, preset, "console_fo_lt", 50.0); + lightValues.dcduLeftLightLevel = iniGetOrDefault(ini, preset, "dcdu_left_lvl", 50.0) / 100; + lightValues.dcduRightLightLevel = iniGetOrDefault(ini, preset, "dcdu_right_lvl", 50.0) / 100; + lightValues.mcduLeftLightLevel = iniGetOrDefault(ini, preset, "mcdu_left_lvl", 50.0) / 100; + lightValues.mcduRightLightLevel = iniGetOrDefault(ini, preset, "mcdu_right_lvl", 50.0) / 100; + lightValues.ecamUpperLightLevel = iniGetOrDefault(ini, preset, "ecam_upper_lvl", 50.0); + lightValues.ecamLowerLightLevel = iniGetOrDefault(ini, preset, "ecam_lower_lvl", 50.0); + lightValues.floodPnlLightLevel = iniGetOrDefault(ini, preset, "flood_pnl_lt", 50.0); + lightValues.pedestalIntegralLightLevel = iniGetOrDefault(ini, preset, "pedestal_int_lt", 50.0); + lightValues.floodPedLightLevel = iniGetOrDefault(ini, preset, "flood_ped_lvl", 50.0); + + return result; +} + +bool LightPreset::saveToStore(int64_t presetNr) { + // create ini file and data structure + mINI::INIStructure ini; + mINI::INIFile iniFile(CONFIGURATION_FILEPATH); + + // load file + bool result = iniFile.read(ini); + + // add/update preset + const std::string preset = "preset " + std::to_string(presetNr); + ini[preset]["efb_brightness"] = std::to_string(lightValues.efbBrightness); + ini[preset]["cabin_light"] = std::to_string(lightValues.cabinLightLevel); + ini[preset]["ovhd_int_lt"] = std::to_string(lightValues.ovhdIntegralLightLevel); + ini[preset]["glareshield_int_lt"] = std::to_string(lightValues.glareshieldIntegralLightLevel); + ini[preset]["glareshield_lcd_lt"] = std::to_string(lightValues.glareshieldLcdLightLevel); + ini[preset]["table_cpt_lt"] = std::to_string(lightValues.tableLightCptLevel); + ini[preset]["table_fo_lt"] = std::to_string(lightValues.tableLightFoLevel); + ini[preset]["pfd_cpt_lvl"] = std::to_string(lightValues.pfdBrtCptLevel); + ini[preset]["nd_cpt_lvl"] = std::to_string(lightValues.ndBrtCptLevel); + ini[preset]["wx_cpt_lvl"] = std::to_string(lightValues.wxTerrainBrtCptLevel); + ini[preset]["console_cpt_lt"] = std::to_string(lightValues.consoleLightCptLevel); + ini[preset]["pfd_fo_lvl"] = std::to_string(lightValues.pfdBrtFoLevel); + ini[preset]["nd_fo_lvl"] = std::to_string(lightValues.ndBrtFoLevel); + ini[preset]["wx_fo_lvl"] = std::to_string(lightValues.wxTerrainBrtFoLevel); + ini[preset]["console_fo_lt"] = std::to_string(lightValues.consoleLightFoLevel); + ini[preset]["dcdu_left_lvl"] = std::to_string(lightValues.dcduLeftLightLevel * 100); + ini[preset]["dcdu_right_lvl"] = std::to_string(lightValues.dcduRightLightLevel * 100); + ini[preset]["mcdu_left_lvl"] = std::to_string(lightValues.mcduLeftLightLevel * 100); + ini[preset]["mcdu_right_lvl"] = std::to_string(lightValues.mcduRightLightLevel * 100); + ini[preset]["ecam_upper_lvl"] = std::to_string(lightValues.ecamUpperLightLevel); + ini[preset]["ecam_lower_lvl"] = std::to_string(lightValues.ecamLowerLightLevel); + ini[preset]["flood_pnl_lt"] = std::to_string(lightValues.floodPnlLightLevel); + ini[preset]["pedestal_int_lt"] = std::to_string(lightValues.pedestalIntegralLightLevel); + ini[preset]["flood_ped_lvl"] = std::to_string(lightValues.floodPedLightLevel); + + result &= iniFile.write(ini, true); + + return result; +} + +void LightPreset::loadFromData(LightingValues lv) { + lightValues = lv; +} + +__attribute__((unused)) +std::string LightPreset::sprint() const { + std::ostringstream os; + os << "EFB Brightness: " << lightValues.efbBrightness << std::endl; + os << "Cabin Light: " << lightValues.cabinLightLevel << std::endl; + os << "Ovhd Int Lt: " << lightValues.ovhdIntegralLightLevel << std::endl; + os << "Glareshield Int Lt: " << lightValues.glareshieldIntegralLightLevel << std::endl; + os << "Glareshield Lcd Lt: " << lightValues.glareshieldLcdLightLevel << std::endl; + os << "Table Cpt Lt: " << lightValues.tableLightCptLevel << std::endl; + os << "Table FO Lt: " << lightValues.tableLightFoLevel << std::endl; + os << "PFD Cpt Lvl: " << lightValues.pfdBrtCptLevel << std::endl; + os << "ND Cpt Lvl: " << lightValues.ndBrtCptLevel << std::endl; + os << "WX Cpt Lvl: " << lightValues.wxTerrainBrtCptLevel << std::endl; + os << "Console Cpt Lt: " << lightValues.consoleLightCptLevel << std::endl; + os << "PFD FO Lvl: " << lightValues.pfdBrtFoLevel << std::endl; + os << "ND FO Lvl: " << lightValues.ndBrtFoLevel << std::endl; + os << "WX FO Lvl: " << lightValues.wxTerrainBrtFoLevel << std::endl; + os << "Console Fo Lt: " << lightValues.consoleLightFoLevel << std::endl; + os << "DCDU Left Lvl: " << lightValues.dcduLeftLightLevel << std::endl; + os << "DCDU Right Lvl: " << lightValues.dcduRightLightLevel << std::endl; + os << "MCDU Left Lvl: " << lightValues.mcduLeftLightLevel << std::endl; + os << "MCDU Right Lvl: " << lightValues.mcduRightLightLevel << std::endl; + os << "ECAM Upper Lvl: " << lightValues.ecamUpperLightLevel << std::endl; + os << "ECAM Lower Lvl: " << lightValues.ecamLowerLightLevel << std::endl; + os << "Floor Cpt Lt: " << lightValues.floodPnlLightLevel << std::endl; + os << "Pedestal Int Lt: " << lightValues.pedestalIntegralLightLevel << std::endl; + os << "Floor FO Lvl: " << lightValues.floodPedLightLevel << std::endl; + return os.str(); +} + +double LightPreset::iniGetOrDefault(const mINI::INIStructure &ini, + const std::string §ion, + const std::string &key, + const double defaultValue) { + if (ini.get(section).has(key)) { + // As MSFS wasm does not support exceptions (try/catch) we can't use + // std::stof here. Workaround with std::stringstreams. + std::stringstream input(ini.get(section).get(key)); + double value = defaultValue; + if (input >> value) { + return value; + } + else { + std::cout << "PRESETS: reading ini value for \"" + << "[" << section << "] " << key << " = " << ini.get(section).get(key) + << "\" failed." << std::endl; + } + } + return defaultValue; +} diff --git a/src/presets/src/Lighting/LightPreset.h b/src/presets/src/Lighting/LightPreset.h new file mode 100644 index 00000000000..aa4e5c64b61 --- /dev/null +++ b/src/presets/src/Lighting/LightPreset.h @@ -0,0 +1,230 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include + +#include "../Presets.h" +#include "../inih/ini.h" +#include "LightingSimVars.h" + +/** + * Data structure for holding all relevant lighting levels and states. + */ +struct LightingValues { + // EFB + double efbBrightness; // A32NX_EFB_BRIGHTNESS + // OVHD + double cabinLightLevel; // 7 (0, 50, 100) + double ovhdIntegralLightLevel; // 86 + // Glareshield + double glareshieldIntegralLightLevel; // 84 + double glareshieldLcdLightLevel; // 87 + double tableLightCptLevel; // 10 + double tableLightFoLevel; // 11 + // Instruments + double pfdBrtCptLevel; // 88 + double ndBrtCptLevel; // 89 + double wxTerrainBrtCptLevel; // 94 + double consoleLightCptLevel; // 8 (0, 50, 100) + double pfdBrtFoLevel; // 90 + double ndBrtFoLevel; // 91 + double wxTerrainBrtFoLevel; // 95 + double consoleLightFoLevel; // 9 (0, 50, 100) + // ISIS display has automatic brightness adjustment. + double dcduLeftLightLevel; // A32NX_PANEL_DCDU_L_BRIGHTNESS 0.0..1.0 + double dcduRightLightLevel; // A32NX_PANEL_DCDU_R_BRIGHTNESS 0.0..1.0 + double mcduLeftLightLevel; // A32NX_MCDU_L_BRIGHTNESS 0.0..1.0 + double mcduRightLightLevel; // A32NX_MCDU_R_BRIGHTNESS 0.0..1.0 + // Pedestal + double ecamUpperLightLevel; // 92 + double ecamLowerLightLevel; // 93 + double floodPnlLightLevel; // 83 + double pedestalIntegralLightLevel; // 85 + double floodPedLightLevel; // 76 +}; + +/** + * Class for handling light presets. + */ +class LightPreset { +private: + const std::string CONFIGURATION_FILEPATH = "\\work\\InteriorLightingPresets.ini"; + + bool isInitialized = false; + + LightingSimVars* simVars; + +public: + /** + * Currently stored lighting values. + */ + LightingValues lightValues{}; + + /** + * Creates an instance of the LightPreset class. + * @param simVars pointer to the LightSimVars object for reading and writing + * the simulation variables. + */ + LightPreset() : simVars(new LightingSimVars()) {}; + + /** + * Called when SimConnect is initialized + */ + void initialize(); + + /** + * Callback used to update the LightPreset at each tick (dt). + * This is used to execute every action and task required to update the light Settings. + * @param deltaTime The time since the last tick + */ + void onUpdate(__attribute__((unused)) double deltaTime); + + /** + * Called when SimConnect is shut down + */ + void shutdown(); + + /** + * Produces a string with the current settings and their values. + * @return string with the current settings and their values. + */ + __attribute__((unused)) + std::string sprint() const; + +private: + /** + * Loads a specified preset + * @param loadPresetRequest the number of the preset to be loaded + */ + void loadLightingPreset(int64_t loadPresetRequest); + + /** + * Save a specified preset + * @param savePresetRequest the number of the preset to be saved + */ + void saveLightingPreset(int64_t savePresetRequest); + + /** + * Read the current lighting level from the aircraft. + */ + void readFromAircraft(); + + /** + * Applies the currently loaded preset to the aircraft + */ + void applyToAircraft(); + + /** + * Reads a stored preset from the persistence store. + * @return true if successful, false otherwise. + */ + bool readFromStore(int64_t presetNr); + + /** + * Stores the current values into the persistent store. + * @return true if successful, false otherwise. + */ + bool saveToStore(int64_t presetNr); + + /** + * Load lighting level based on a given LightValue data structure + * @param lv a loadFromData of LightValue data + */ + void loadFromData(LightingValues lv); + + /** + * Convenience method to check for the existence of a key in a section and the option to + * provide a default value in case the key does not exist. + * Does not change the ini structure. + * @param ini mINI::INIStructure + * @param section section name as std::string + * @param key key name as std::string + * @param defaultValue a default value that is returned if the key does not exist + * @return the value of the key or the default value if the key does not exist + */ + static double + iniGetOrDefault(const mINI::INIStructure &ini, const std::string §ion, const std::string &key, + double defaultValue); + + // formatter:off + const LightingValues DEFAULT_50 = {50.0, + 50.0, + 50.0, + 50.0, + 50.0, + 50.0, + 50.0, + 50.0, + 50.0, + 50.0, + 50.0, + 50.0, + 50.0, + 50.0, + 50.0, + 0.5, + 0.5, + 0.5, + 0.5, + 50.0, + 50.0, + 50.0, + 50.0, + 50.0}; + + __attribute__((unused)) + const LightingValues DEFAULT_10 = {10.0, + 0.0, + 10.0, + 10.0, + 10.0, + 10.0, + 10.0, + 10.0, + 10.0, + 10.0, + 10.0, + 10.0, + 10.0, + 10.0, + 10.0, + 0.1, + 0.1, + 0.1, + 0.0, + 10.0, + 10.0, + 10.0, + 10.0, + 10.0}; + + __attribute__((unused)) + const LightingValues DEFAULT_100 = {100.0, + 100.0, + 100.0, + 100.0, + 100.0, + 100.0, + 100.0, + 100.0, + 100.0, + 100.0, + 100.0, + 100.0, + 100.0, + 100.0, + 100.0, + 1.0, + 1.0, + 1.0, + 1.0, + 100.0, + 100.0, + 100.0, + 100.0, + 100.0}; + // @formatter:on + +}; diff --git a/src/presets/src/Lighting/LightingSimVars.h b/src/presets/src/Lighting/LightingSimVars.h new file mode 100644 index 00000000000..e6d51c72ed7 --- /dev/null +++ b/src/presets/src/Lighting/LightingSimVars.h @@ -0,0 +1,240 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include + +#include "../Units.h" + +/** + * For instruments with are specific to the left (Cpt) or right (FO) side of the cockpit. + */ +enum Side { + Left, Right +}; + +/** + * A collection of SimVars and LVars for the A32NX for interior lighting + */ +class LightingSimVars { +public: + Units* m_Units; + + // Power state LVARs + ID ElecAC1{}; + + // Signal to load a preset. + ID LoadLightingPresetRequest{}; + ID SaveLightingPresetRequest{}; + + // Simvar light variables + ENUM lightPotentiometer{}; + + // LVAR Light variables + ID EfbBrightness{}; + ID DcduLeftLightLevel{}; + ID DcduRightLightLevel{}; + ID McduLeftLightLevel{}; + ID McduRightLightLevel{}; + + LightingSimVars() { + m_Units = new Units(); + this->initializeVars(); + } + + /** + * Initializes variables by registering them in SimConnect + */ + void initializeVars() { + // Power state LVar + ElecAC1 = register_named_variable("A32NX_ELEC_AC_1_BUS_IS_POWERED"); + + // Named Variables (LVARs) + LoadLightingPresetRequest = register_named_variable("A32NX_LOAD_LIGHTING_PRESET"); + this->setLoadLightingPresetRequest(0); + SaveLightingPresetRequest = register_named_variable("A32NX_SAVE_LIGHTING_PRESET"); + this->setSaveLightingPresetRequest(0); + + // Sim variables + lightPotentiometer = get_aircraft_var_enum("LIGHT POTENTIOMETER"); + + // Lighting LVARs + EfbBrightness = register_named_variable("A32NX_EFB_BRIGHTNESS"); + DcduLeftLightLevel = register_named_variable("A32NX_PANEL_DCDU_L_BRIGHTNESS"); + DcduRightLightLevel = register_named_variable("A32NX_PANEL_DCDU_R_BRIGHTNESS"); + McduLeftLightLevel = register_named_variable("A32NX_MCDU_L_BRIGHTNESS"); + McduRightLightLevel = register_named_variable("A32NX_MCDU_R_BRIGHTNESS"); + } + + /** + * Get the ElecAC1 state + * @return INT64 0 if AC1 bus is unpowered, 1 otherwise + */ + inline FLOAT64 getElecAC1State() const { + return get_named_variable_value(ElecAC1); + } + + /** + * Reads the preset loading request variable. + * @return INT64 signifying the preset to be loaded + */ + inline FLOAT64 getLoadLightingPresetRequest() const { + return get_named_variable_value(LoadLightingPresetRequest); + } + + /** + * Sets the loading request value. Typically used to reset to 0 after the preset has been loaded. + * @param value usually loadFromData to 0 to reset the request. + */ + inline void setLoadLightingPresetRequest(FLOAT64 value) const { + set_named_variable_value(LoadLightingPresetRequest, value); + } + + /** + * Reads the request preset save variable. + * @return INT64 signifying the preset to be loaded + */ + inline FLOAT64 getSaveLightingPresetRequest() const { + return get_named_variable_value(SaveLightingPresetRequest); + } + + /** + * Sets the save request value. Typically used to reset to 0 after the preset has been loaded. + * @param value usually loadFromData to 0 to reset the request. + */ + inline void setSaveLightingPresetRequest(FLOAT64 value) const { + set_named_variable_value(SaveLightingPresetRequest, value); + } + + /** + * Retrieves the EFB brightness setting from the simulator. + * @return value in percent over 100 (0..100) + */ + inline FLOAT64 getEfbBrightness() const { + return get_named_variable_value(EfbBrightness); + } + + /** + * Set the EFB brightness. + * @param value in percent over 100 (0..100) + */ + inline void setEfbBrightness(FLOAT64 value) const { + set_named_variable_value(EfbBrightness, value); + } + + /** + * Retrieves the DCDU brightness level from the simulator. + * @param s Side.Left or Side.Right + * @return value in percent (0.0 .. 1.0) + */ + FLOAT64 getDcduLightLevel(Side s) const { + switch (s) { + case Left: + return get_named_variable_value(DcduLeftLightLevel); + case Right: + return get_named_variable_value(DcduRightLightLevel); + } + } + + /** + * Sets the DCDU brightness level to the simulator. + * @param s Side.Left or Side.Right + */ + void setDcduLightLevel(Side s, FLOAT64 value) const { + switch (s) { + case Left: + set_named_variable_value(DcduLeftLightLevel, value); + break; + case Right: + set_named_variable_value(DcduRightLightLevel, value); + break; + } + } + + /** + * Retrieves the MCDU brightness level from the simulator. + * @param s Side.Left or Side.Right + * @return value in percent (0.0 .. 1.0) + */ + FLOAT64 getMcduLightLevel(Side s) const { + switch (s) { + case Left: + return get_named_variable_value(McduLeftLightLevel); + case Right: + return get_named_variable_value(McduRightLightLevel); + } + } + + /** + * Sets the MCDU brightness level to the simulator. + * @param s Side.Left or Side.Right + */ + void setMcduLightLevel(Side s, FLOAT64 value) const { + switch (s) { + case Left: + set_named_variable_value(McduLeftLightLevel, value); + break; + case Right: + set_named_variable_value(McduRightLightLevel, value); + break; + } + } + + /** + * Retrieves a light potentiometer setting from the simulator. + * @param index of the light potentiometer + * @return value in percent over 100 (0..100) + */ + inline FLOAT64 getLightPotentiometer(int index) const { + return aircraft_varget(lightPotentiometer, m_Units->Percent, index); + } + + /** + * Sets a light potentiometer setting to the simulator. + * @param index the light potentiometer index + * @param value in percent over 100 (0..100) + */ + static void setLightPotentiometer(int index, FLOAT64 value) { + std::string calculator_code; + calculator_code += std::to_string(value); + calculator_code += " "; + calculator_code += std::to_string(index); + calculator_code += " (>K:2:LIGHT_POTENTIOMETER_SET)"; + execute_calculator_code(calculator_code.c_str(), nullptr, nullptr, nullptr); + } + + /** + * Retrieves the switch position of the dome light switch. + * 0 = switch pos OFF, 50 = switch pos DIM, 100 = switch pos BRT + * @return value in percent over 100 (0..100) + */ + inline FLOAT64 getLightCabin() const { + return getLightPotentiometer(7); + } + + /** + * Sets the dome light switch in one of 3 positions. + * @param lvl 0 = OFF, 50 = DIM, 100 = BRT + */ + static void setLightCabin(FLOAT64 lvl) { + // cabin light level needs to either be 0, 50 or 100 for the switch position + // in the aircraft to work. + if (lvl <= 0.0) { + lvl = 0.0; + } + else if (lvl > 0.0 && lvl <= 50.0) { + lvl = 50.0; + } + else if ((lvl > 0.0 && lvl > 50.0)) { + lvl = 100.0; + } + // set the switch position via calculator code + std::string calculator_code; + calculator_code += std::to_string(lvl > 0 ? 1 : 0); + calculator_code += " (>K:2:CABIN_LIGHTS_SET) "; + calculator_code += std::to_string(lvl); // 0, 50% and 100% + calculator_code += " (>K:LIGHT_POTENTIOMETER_7_SET)"; + execute_calculator_code(calculator_code.c_str(), nullptr, nullptr, nullptr); + } +}; diff --git a/src/presets/src/Presets.cpp b/src/presets/src/Presets.cpp new file mode 100644 index 00000000000..f9581643b6f --- /dev/null +++ b/src/presets/src/Presets.cpp @@ -0,0 +1,234 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#include "Presets.h" +#include "Aircraft/AircraftPreset.h" +#include "Lighting/LightPreset.h" + +Presets PRESETS; + +/** + * Gauge Callback + * @see + * https://docs.flightsimulator.com/html/Content_Configuration/SimObjects/Aircraft_SimO/Instruments/C_C++_Gauges.htm?rhhlterm=_gauge_callback&rhsearch=_gauge_callback + */ +__attribute__((export_name("Presets_gauge_callback"))) extern "C" __attribute__((unused)) bool +Presets_gauge_callback(__attribute__((unused)) FsContext ctx, int service_id, void* pData) { + switch (service_id) { + case PANEL_SERVICE_PRE_INSTALL: { + return true; + } + case PANEL_SERVICE_POST_INSTALL: { + return PRESETS.initialize(); + } + case PANEL_SERVICE_PRE_DRAW: { + auto drawData = static_cast(pData); + return PRESETS.onUpdate(drawData->dt); + } + case PANEL_SERVICE_PRE_KILL: { + return PRESETS.shutdown();; + } + default: + break; + } + return false; +} + +bool Presets::initialize() { + std::cout << "PRESETS: Connecting to SimConnect..." << std::endl; + + lightPresetPtr = std::make_unique(); + aircraftPresetPtr = std::make_unique(); + + if (!SUCCEEDED(SimConnect_Open(&hSimConnect, "Presets", nullptr, 0, 0, 0))) { + std::cout << "PRESETS: SimConnect failed." << std::endl; + return false; + } + isConnected = true; + + // Simulation Data to local data structure mapping + const HRESULT result = SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::SimulationDataTypeId, "SIMULATION TIME", "NUMBER"); + + // initialize preset modules + lightPresetPtr->initialize(); + aircraftPresetPtr->initialize(); + + std::cout << "PRESETS: SimConnect connected." << std::endl; + return (result == S_OK); +} + +bool Presets::onUpdate(double deltaTime) { + if (isConnected) { + + // read simulation data from simconnect + simConnectRequestData(); + simConnectProcessMessages(); + + // detect pause + if (simulationData.simulationTime == previousSimulationTime || simulationData.simulationTime < 0.2) { + return true; + } + previousSimulationTime = simulationData.simulationTime; + + // update presets modules + lightPresetPtr->onUpdate(deltaTime); + aircraftPresetPtr->onUpdate(deltaTime); + + return true; + } + return false; +} + +bool Presets::shutdown() { + std::cout << "PRESETS: Disconnecting ..." << std::endl; + lightPresetPtr->shutdown(); + aircraftPresetPtr->shutdown(); + isConnected = false; + unregister_all_named_vars(); + std::cout << "PRESETS: Disconnected." << std::endl; + return SUCCEEDED(SimConnect_Close(hSimConnect)); +} + +bool Presets::simConnectRequestData() const { + HRESULT result = SimConnect_RequestDataOnSimObject(hSimConnect, + 0, + DataTypesID::SimulationDataTypeId, + SIMCONNECT_OBJECT_ID_USER, + SIMCONNECT_PERIOD_ONCE); + if (result != S_OK) { + return false; + } + return true; +} + +void Presets::simConnectProcessMessages() { + DWORD cbData; + SIMCONNECT_RECV* pData; + while (SUCCEEDED(SimConnect_GetNextDispatch(hSimConnect, &pData, &cbData))) { + simConnectProcessDispatchMessage(pData, &cbData); + } +} + +void Presets::simConnectProcessDispatchMessage(SIMCONNECT_RECV* pData, DWORD* cbData) { + switch (pData->dwID) { + case SIMCONNECT_RECV_ID_OPEN: + cout << "PRESETS: SimConnect connection established" << endl; + break; + + case SIMCONNECT_RECV_ID_QUIT: + cout << "PRESETS: Received SimConnect connection quit message" << endl; + break; + + case SIMCONNECT_RECV_ID_SIMOBJECT_DATA: + simConnectProcessSimObjectData(static_cast(pData)); + break; + + case SIMCONNECT_RECV_ID_EXCEPTION: + cout << "PRESETS: Exception in SimConnect connection: "; + cout << getSimConnectExceptionString( + static_cast(static_cast(pData)->dwException)); + cout << endl; + break; + + default: + break; + } +} + +void Presets::simConnectProcessSimObjectData(const SIMCONNECT_RECV_SIMOBJECT_DATA* data) { + // process depending on request id from SimConnect_RequestDataOnSimObject() + switch (data->dwRequestID) { + case 0: + // store aircraft data in local data structure + simulationData = *((SimulationData*) &data->dwData); + return; + + default: + cout << "PRESETS: Unknown request id in SimConnect connection: "; + cout << data->dwRequestID << endl; + return; + } +} + +std::string Presets::getSimConnectExceptionString(SIMCONNECT_EXCEPTION exception) { + switch (exception) { + case SIMCONNECT_EXCEPTION_NONE: + return "NONE"; + case SIMCONNECT_EXCEPTION_ERROR: + return "ERROR"; + case SIMCONNECT_EXCEPTION_SIZE_MISMATCH: + return "SIZE_MISMATCH"; + case SIMCONNECT_EXCEPTION_UNRECOGNIZED_ID: + return "UNRECOGNIZED_ID"; + case SIMCONNECT_EXCEPTION_UNOPENED: + return "UNOPENED"; + case SIMCONNECT_EXCEPTION_VERSION_MISMATCH: + return "VERSION_MISMATCH"; + case SIMCONNECT_EXCEPTION_TOO_MANY_GROUPS: + return "TOO_MANY_GROUPS"; + case SIMCONNECT_EXCEPTION_NAME_UNRECOGNIZED: + return "NAME_UNRECOGNIZED"; + case SIMCONNECT_EXCEPTION_TOO_MANY_EVENT_NAMES: + return "TOO_MANY_EVENT_NAMES"; + case SIMCONNECT_EXCEPTION_EVENT_ID_DUPLICATE: + return "EVENT_ID_DUPLICATE"; + case SIMCONNECT_EXCEPTION_TOO_MANY_MAPS: + return "TOO_MANY_MAPS"; + case SIMCONNECT_EXCEPTION_TOO_MANY_OBJECTS: + return "TOO_MANY_OBJECTS"; + case SIMCONNECT_EXCEPTION_TOO_MANY_REQUESTS: + return "TOO_MANY_REQUESTS"; + case SIMCONNECT_EXCEPTION_WEATHER_INVALID_PORT: + return "WEATHER_INVALID_PORT"; + case SIMCONNECT_EXCEPTION_WEATHER_INVALID_METAR: + return "WEATHER_INVALID_METAR"; + case SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_GET_OBSERVATION: + return "WEATHER_UNABLE_TO_GET_OBSERVATION"; + case SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_CREATE_STATION: + return "WEATHER_UNABLE_TO_CREATE_STATION"; + case SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_REMOVE_STATION: + return "WEATHER_UNABLE_TO_REMOVE_STATION"; + case SIMCONNECT_EXCEPTION_INVALID_DATA_TYPE: + return "INVALID_DATA_TYPE"; + case SIMCONNECT_EXCEPTION_INVALID_DATA_SIZE: + return "INVALID_DATA_SIZE"; + case SIMCONNECT_EXCEPTION_DATA_ERROR: + return "DATA_ERROR"; + case SIMCONNECT_EXCEPTION_INVALID_ARRAY: + return "INVALID_ARRAY"; + case SIMCONNECT_EXCEPTION_CREATE_OBJECT_FAILED: + return "CREATE_OBJECT_FAILED"; + case SIMCONNECT_EXCEPTION_LOAD_FLIGHTPLAN_FAILED: + return "LOAD_FLIGHTPLAN_FAILED"; + case SIMCONNECT_EXCEPTION_OPERATION_INVALID_FOR_OBJECT_TYPE: + return "OPERATION_INVALID_FOR_OBJECT_TYPE"; + case SIMCONNECT_EXCEPTION_ILLEGAL_OPERATION: + return "ILLEGAL_OPERATION"; + case SIMCONNECT_EXCEPTION_ALREADY_SUBSCRIBED: + return "ALREADY_SUBSCRIBED"; + case SIMCONNECT_EXCEPTION_INVALID_ENUM: + return "INVALID_ENUM"; + case SIMCONNECT_EXCEPTION_DEFINITION_ERROR: + return "DEFINITION_ERROR"; + case SIMCONNECT_EXCEPTION_DUPLICATE_ID: + return "DUPLICATE_ID"; + case SIMCONNECT_EXCEPTION_DATUM_ID: + return "DATUM_ID"; + case SIMCONNECT_EXCEPTION_OUT_OF_BOUNDS: + return "OUT_OF_BOUNDS"; + case SIMCONNECT_EXCEPTION_ALREADY_CREATED: + return "ALREADY_CREATED"; + case SIMCONNECT_EXCEPTION_OBJECT_OUTSIDE_REALITY_BUBBLE: + return "OBJECT_OUTSIDE_REALITY_BUBBLE"; + case SIMCONNECT_EXCEPTION_OBJECT_CONTAINER: + return "OBJECT_CONTAINER"; + case SIMCONNECT_EXCEPTION_OBJECT_AI: + return "OBJECT_AI"; + case SIMCONNECT_EXCEPTION_OBJECT_ATC: + return "OBJECT_ATC"; + case SIMCONNECT_EXCEPTION_OBJECT_SCHEDULE: + return "OBJECT_SCHEDULE"; + default: + return "UNKNOWN"; + } +} diff --git a/src/presets/src/Presets.h b/src/presets/src/Presets.h new file mode 100644 index 00000000000..80a35c59c46 --- /dev/null +++ b/src/presets/src/Presets.h @@ -0,0 +1,117 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#ifndef __INTELLISENSE__ +#define MODULE_EXPORT __attribute__((visibility("default"))) +#define MODULE_WASM_MODNAME(mod) __attribute__((import_module(mod))) +#else +#define MODULE_EXPORT +#define MODULE_WASM_MODNAME(mod) +#define __attribute__(x) +#define __restrict__ +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace std; + +// Required to map local data structures to simconnect data +enum DataTypesID { + SimulationDataTypeId +}; + +// Local data structure for simconnect data +struct SimulationData { + double simulationTime; +}; + +class LightPreset; +class AircraftPreset; + +class Presets { +private: + HANDLE hSimConnect; + + // Instance of local data structure for simconnect data + SimulationData simulationData = {}; + + /** + * Flag if connection has been initialized. + */ + bool isConnected = false; + + // Storing previous simulation allows for Pause detection + double previousSimulationTime = 0; + + std::unique_ptr lightPresetPtr; + std::unique_ptr aircraftPresetPtr; + +public: + + /** + * Initialize the gauge (instead of a constructor). + * Sets up data for the gauge and also connect to SimConnect. + * @return true if SimConnect was successfully connected, false otherwise. + */ + bool initialize(); + + /** + * Callback used to update the PRESETS at each tick (dt). + * This is used to execute every action and task required to update the gauge. + * @param deltaTime The time since the last tick + * @return True if successful, false otherwise. + */ + bool onUpdate(double deltaTime); + + /** + * Kills the PRESETS and unregisters all LVars + * @return True if successful, false otherwise. + */ + bool shutdown(); + +private: + + /** + * Requests simconnect data in preparation of reading it into a local data structure. + * @return true if request was successful, false otherwise + */ + bool simConnectRequestData() const; + + /** + * Reads simconnect data into local data structure after requesting it via + * simConnectRequestData. + * @return true if successful, false otherwise + */ + void simConnectProcessMessages(); + + /** + * Process received simconnect dispatch messages + * @param pData + * @param cbData + */ + void simConnectProcessDispatchMessage(SIMCONNECT_RECV* pData, DWORD* cbData); + + /** + * Process received simconnect data + * @param data + */ + void simConnectProcessSimObjectData(const SIMCONNECT_RECV_SIMOBJECT_DATA* data); + + /** + * Returns human-readable descriptions of simconnect exceptions + * @param exception + * @return string describing the exception + */ + static std::string getSimConnectExceptionString(SIMCONNECT_EXCEPTION exception); +}; diff --git a/src/presets/src/Units.h b/src/presets/src/Units.h new file mode 100644 index 00000000000..c4975e8d403 --- /dev/null +++ b/src/presets/src/Units.h @@ -0,0 +1,27 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +/** + * A collection of SimVar unit enums. + */ +class Units { + public: + ENUM Percent = get_units_enum("Percent"); + ENUM Number = get_units_enum("Number"); + ENUM Bool = get_units_enum("Bool"); + ENUM Pounds = get_units_enum("Pounds"); + ENUM Psi = get_units_enum("Psi"); + ENUM Pph = get_units_enum("Pounds per hour"); + ENUM Gallons = get_units_enum("Gallons"); + ENUM Feet = get_units_enum("Feet"); + ENUM FootPounds = get_units_enum("Foot pounds"); + ENUM FeetMin = get_units_enum("Feet per minute"); + ENUM Mach = get_units_enum("Mach"); + ENUM Millibars = get_units_enum("Millibars"); + ENUM SluggerSlugs = get_units_enum("Slug per cubic feet"); + ENUM Celsius = get_units_enum("Celsius"); + ENUM Hours = get_units_enum("Hours"); + ENUM Seconds = get_units_enum("Seconds"); +}; diff --git a/src/presets/src/inih/ini.h b/src/presets/src/inih/ini.h new file mode 100644 index 00000000000..44dd3d576c8 --- /dev/null +++ b/src/presets/src/inih/ini.h @@ -0,0 +1,761 @@ +/* + * The MIT License (MIT) + * Copyright (c) 2018 Danijel Durakovic + * + * 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. + * + */ + +/////////////////////////////////////////////////////////////////////////////// +// +// /mINI/ v0.9.10 +// An INI file reader and writer for the modern age. +// +/////////////////////////////////////////////////////////////////////////////// +// +// A tiny utility library for manipulating INI files with a straightforward +// API and a minimal footprint. It conforms to the (somewhat) standard INI +// format - sections and keys are case insensitive and all leading and +// trailing whitespace is ignored. Comments are lines that begin with a +// semicolon. Trailing comments are allowed on section lines. +// +// Files are read on demand, upon which data is kept in memory and the file +// is closed. This utility supports lazy writing, which only writes changes +// and updates to a file and preserves custom formatting and comments. A lazy +// write invoked by a write() call will read the output file, find what +// changes have been made and update the file accordingly. If you only need to +// generate files, use generate() instead. Section and key order is preserved +// on read, write and insert. +// +/////////////////////////////////////////////////////////////////////////////// +// +// /* BASIC USAGE EXAMPLE: */ +// +// /* read from file */ +// mINI::INIFile file("myfile.ini"); +// mINI::INIStructure ini; +// file.read(ini); +// +// /* read value; gets a reference to actual value in the structure. +// if key or section don't exist, a new empty value will be created */ +// std::string& value = ini["section"]["key"]; +// +// /* read value safely; gets a copy of value in the structure. +// does not alter the structure */ +// std::string value = ini.get("section").get("key"); +// +// /* set or update values */ +// ini["section"]["key"] = "value"; +// +// /* set multiple values */ +// ini["section2"].set({ +// {"key1", "value1"}, +// {"key2", "value2"} +// }); +// +// /* write updates back to file, preserving comments and formatting */ +// file.write(ini); +// +// /* or generate a file (overwrites the original) */ +// file.generate(ini); +// +/////////////////////////////////////////////////////////////////////////////// +// +// Long live the INI file!!! +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef MINI_INI_H_ +#define MINI_INI_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mINI +{ + namespace INIStringUtil + { + const char* const whitespaceDelimiters = " \t\n\r\f\v"; + inline void trim(std::string& str) + { + str.erase(str.find_last_not_of(whitespaceDelimiters) + 1); + str.erase(0, str.find_first_not_of(whitespaceDelimiters)); + } +#ifndef MINI_CASE_SENSITIVE + inline void toLower(std::string& str) + { + std::transform(str.begin(), str.end(), str.begin(), [](const char c) { + return static_cast(std::tolower(c)); + }); + } +#endif + inline void replace(std::string& str, std::string const& a, std::string const& b) + { + if (!a.empty()) + { + std::size_t pos = 0; + while ((pos = str.find(a, pos)) != std::string::npos) + { + str.replace(pos, a.size(), b); + pos += b.size(); + } + } + } +#ifdef _WIN32 + const char* const endl = "\r\n"; +#else + const char* const endl = "\n"; +#endif + }; + + template + class INIMap + { + private: + using T_DataIndexMap = std::unordered_map; + using T_DataItem = std::pair; + using T_DataContainer = std::vector; + using T_MultiArgs = typename std::vector>; + + T_DataIndexMap dataIndexMap; + T_DataContainer data; + + inline std::size_t setEmpty(std::string& key) + { + std::size_t index = data.size(); + dataIndexMap[key] = index; + data.emplace_back(key, T()); + return index; + } + + public: + using const_iterator = typename T_DataContainer::const_iterator; + + INIMap() { } + + INIMap(INIMap const& other) + { + std::size_t data_size = other.data.size(); + for (std::size_t i = 0; i < data_size; ++i) + { + auto const& key = other.data[i].first; + auto const& obj = other.data[i].second; + data.emplace_back(key, obj); + } + dataIndexMap = T_DataIndexMap(other.dataIndexMap); + } + + T& operator[](std::string key) + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + bool hasIt = (it != dataIndexMap.end()); + std::size_t index = (hasIt) ? it->second : setEmpty(key); + return data[index].second; + } + T get(std::string key) const + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + if (it == dataIndexMap.end()) + { + return T(); + } + return T(data[it->second].second); + } + bool has(std::string key) const + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + return (dataIndexMap.count(key) == 1); + } + void set(std::string key, T obj) + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + if (it != dataIndexMap.end()) + { + data[it->second].second = obj; + } + else + { + dataIndexMap[key] = data.size(); + data.emplace_back(key, obj); + } + } + void set(T_MultiArgs const& multiArgs) + { + for (auto const& it : multiArgs) + { + auto const& key = it.first; + auto const& obj = it.second; + set(key, obj); + } + } + bool remove(std::string key) + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + if (it != dataIndexMap.end()) + { + std::size_t index = it->second; + data.erase(data.begin() + index); + dataIndexMap.erase(it); + for (auto& it2 : dataIndexMap) + { + auto& vi = it2.second; + if (vi > index) + { + vi--; + } + } + return true; + } + return false; + } + void clear() + { + data.clear(); + dataIndexMap.clear(); + } + std::size_t size() const + { + return data.size(); + } + const_iterator begin() const { return data.begin(); } + const_iterator end() const { return data.end(); } + }; + + using INIStructure = INIMap>; + + namespace INIParser + { + using T_ParseValues = std::pair; + + enum class PDataType : char + { + PDATA_NONE, + PDATA_COMMENT, + PDATA_SECTION, + PDATA_KEYVALUE, + PDATA_UNKNOWN + }; + + inline PDataType parseLine(std::string line, T_ParseValues& parseData) + { + parseData.first.clear(); + parseData.second.clear(); + INIStringUtil::trim(line); + if (line.empty()) + { + return PDataType::PDATA_NONE; + } + char firstCharacter = line[0]; + if (firstCharacter == ';') + { + return PDataType::PDATA_COMMENT; + } + if (firstCharacter == '[') + { + auto commentAt = line.find_first_of(';'); + if (commentAt != std::string::npos) + { + line = line.substr(0, commentAt); + } + auto closingBracketAt = line.find_last_of(']'); + if (closingBracketAt != std::string::npos) + { + auto section = line.substr(1, closingBracketAt - 1); + INIStringUtil::trim(section); + parseData.first = section; + return PDataType::PDATA_SECTION; + } + } + auto lineNorm = line; + INIStringUtil::replace(lineNorm, "\\=", " "); + auto equalsAt = lineNorm.find_first_of('='); + if (equalsAt != std::string::npos) + { + auto key = line.substr(0, equalsAt); + INIStringUtil::trim(key); + INIStringUtil::replace(key, "\\=", "="); + auto value = line.substr(equalsAt + 1); + INIStringUtil::trim(value); + parseData.first = key; + parseData.second = value; + return PDataType::PDATA_KEYVALUE; + } + return PDataType::PDATA_UNKNOWN; + } + }; + + class INIReader + { + public: + using T_LineData = std::vector; + using T_LineDataPtr = std::shared_ptr; + + private: + std::ifstream fileReadStream; + T_LineDataPtr lineData; + + T_LineData readFile() + { + std::string fileContents; + fileReadStream.seekg(0, std::ios::end); + fileContents.resize(fileReadStream.tellg()); + fileReadStream.seekg(0, std::ios::beg); + std::size_t fileSize = fileContents.size(); + fileReadStream.read(&fileContents[0], fileSize); + fileReadStream.close(); + T_LineData output; + if (fileSize == 0) + { + return output; + } + std::string buffer; + buffer.reserve(50); + for (std::size_t i = 0; i < fileSize; ++i) + { + char& c = fileContents[i]; + if (c == '\n') + { + output.emplace_back(buffer); + buffer.clear(); + continue; + } + if (c != '\0' && c != '\r') + { + buffer += c; + } + } + output.emplace_back(buffer); + return output; + } + + public: + INIReader(std::string const& filename, bool keepLineData = false) + { + fileReadStream.open(filename, std::ios::in | std::ios::binary); + if (keepLineData) + { + lineData = std::make_shared(); + } + } + ~INIReader() { } + + bool operator>>(INIStructure& data) + { + if (!fileReadStream.is_open()) + { + return false; + } + T_LineData fileLines = readFile(); + std::string section; + bool inSection = false; + INIParser::T_ParseValues parseData; + for (auto const& line : fileLines) + { + auto parseResult = INIParser::parseLine(line, parseData); + if (parseResult == INIParser::PDataType::PDATA_SECTION) + { + inSection = true; + data[section = parseData.first]; + } + else if (inSection && parseResult == INIParser::PDataType::PDATA_KEYVALUE) + { + auto const& key = parseData.first; + auto const& value = parseData.second; + data[section][key] = value; + } + if (lineData && parseResult != INIParser::PDataType::PDATA_UNKNOWN) + { + if (parseResult == INIParser::PDataType::PDATA_KEYVALUE && !inSection) + { + continue; + } + lineData->emplace_back(line); + } + } + return true; + } + T_LineDataPtr getLines() + { + return lineData; + } + }; + + class INIGenerator + { + private: + std::ofstream fileWriteStream; + + public: + bool prettyPrint = false; + + INIGenerator(std::string const& filename) + { + fileWriteStream.open(filename, std::ios::out | std::ios::binary); + } + ~INIGenerator() { } + + bool operator<<(INIStructure const& data) + { + if (!fileWriteStream.is_open()) + { + return false; + } + if (!data.size()) + { + return true; + } + auto it = data.begin(); + for (;;) + { + auto const& section = it->first; + auto const& collection = it->second; + fileWriteStream + << "[" + << section + << "]"; + if (collection.size()) + { + fileWriteStream << INIStringUtil::endl; + auto it2 = collection.begin(); + for (;;) + { + auto key = it2->first; + INIStringUtil::replace(key, "=", "\\="); + auto value = it2->second; + INIStringUtil::trim(value); + fileWriteStream + << key + << ((prettyPrint) ? " = " : "=") + << value; + if (++it2 == collection.end()) + { + break; + } + fileWriteStream << INIStringUtil::endl; + } + } + if (++it == data.end()) + { + break; + } + fileWriteStream << INIStringUtil::endl; + if (prettyPrint) + { + fileWriteStream << INIStringUtil::endl; + } + } + return true; + } + }; + + class INIWriter + { + private: + using T_LineData = std::vector; + using T_LineDataPtr = std::shared_ptr; + + std::string filename; + + T_LineData getLazyOutput(T_LineDataPtr const& lineData, INIStructure& data, INIStructure& original) + { + T_LineData output; + INIParser::T_ParseValues parseData; + std::string sectionCurrent; + bool parsingSection = false; + bool continueToNextSection = false; + bool discardNextEmpty = false; + bool writeNewKeys = false; + std::size_t lastKeyLine = 0; + for (auto line = lineData->begin(); line != lineData->end(); ++line) + { + if (!writeNewKeys) + { + auto parseResult = INIParser::parseLine(*line, parseData); + if (parseResult == INIParser::PDataType::PDATA_SECTION) + { + if (parsingSection) + { + writeNewKeys = true; + parsingSection = false; + --line; + continue; + } + sectionCurrent = parseData.first; + if (data.has(sectionCurrent)) + { + parsingSection = true; + continueToNextSection = false; + discardNextEmpty = false; + output.emplace_back(*line); + lastKeyLine = output.size(); + } + else + { + continueToNextSection = true; + discardNextEmpty = true; + continue; + } + } + else if (parseResult == INIParser::PDataType::PDATA_KEYVALUE) + { + if (continueToNextSection) + { + continue; + } + if (data.has(sectionCurrent)) + { + auto& collection = data[sectionCurrent]; + auto const& key = parseData.first; + auto const& value = parseData.second; + if (collection.has(key)) + { + auto outputValue = collection[key]; + if (value == outputValue) + { + output.emplace_back(*line); + } + else + { + INIStringUtil::trim(outputValue); + auto lineNorm = *line; + INIStringUtil::replace(lineNorm, "\\=", " "); + auto equalsAt = lineNorm.find_first_of('='); + auto valueAt = lineNorm.find_first_not_of( + INIStringUtil::whitespaceDelimiters, + equalsAt + 1 + ); + std::string outputLine = line->substr(0, valueAt); + if (prettyPrint && equalsAt + 1 == valueAt) + { + outputLine += " "; + } + outputLine += outputValue; + output.emplace_back(outputLine); + } + lastKeyLine = output.size(); + } + } + } + else + { + if (discardNextEmpty && line->empty()) + { + discardNextEmpty = false; + } + else if (parseResult != INIParser::PDataType::PDATA_UNKNOWN) + { + output.emplace_back(*line); + } + } + } + if (writeNewKeys || std::next(line) == lineData->end()) + { + T_LineData linesToAdd; + if (data.has(sectionCurrent) && original.has(sectionCurrent)) + { + auto const& collection = data[sectionCurrent]; + auto const& collectionOriginal = original[sectionCurrent]; + for (auto const& it : collection) + { + auto key = it.first; + if (collectionOriginal.has(key)) + { + continue; + } + auto value = it.second; + INIStringUtil::replace(key, "=", "\\="); + INIStringUtil::trim(value); + linesToAdd.emplace_back( + key + ((prettyPrint) ? " = " : "=") + value + ); + } + } + if (!linesToAdd.empty()) + { + output.insert( + output.begin() + lastKeyLine, + linesToAdd.begin(), + linesToAdd.end() + ); + } + if (writeNewKeys) + { + writeNewKeys = false; + --line; + } + } + } + for (auto const& it : data) + { + auto const& section = it.first; + if (original.has(section)) + { + continue; + } + if (prettyPrint && output.size() > 0 && !output.back().empty()) + { + output.emplace_back(); + } + output.emplace_back("[" + section + "]"); + auto const& collection = it.second; + for (auto const& it2 : collection) + { + auto key = it2.first; + auto value = it2.second; + INIStringUtil::replace(key, "=", "\\="); + INIStringUtil::trim(value); + output.emplace_back( + key + ((prettyPrint) ? " = " : "=") + value + ); + } + } + return output; + } + + public: + bool prettyPrint = false; + + INIWriter(std::string const& filename) + : filename(filename) + { + } + ~INIWriter() { } + + bool operator<<(INIStructure& data) + { + struct stat buf; + bool fileExists = (stat(filename.c_str(), &buf) == 0); + if (!fileExists) + { + INIGenerator generator(filename); + generator.prettyPrint = prettyPrint; + return generator << data; + } + INIStructure originalData; + T_LineDataPtr lineData; + bool readSuccess = false; + { + INIReader reader(filename, true); + if ((readSuccess = reader >> originalData)) + { + lineData = reader.getLines(); + } + } + if (!readSuccess) + { + return false; + } + T_LineData output = getLazyOutput(lineData, data, originalData); + std::ofstream fileWriteStream(filename, std::ios::out | std::ios::binary); + if (fileWriteStream.is_open()) + { + if (output.size()) + { + auto line = output.begin(); + for (;;) + { + fileWriteStream << *line; + if (++line == output.end()) + { + break; + } + fileWriteStream << INIStringUtil::endl; + } + } + return true; + } + return false; + } + }; + + class INIFile + { + private: + std::string filename; + + public: + INIFile(std::string const& filename) + : filename(filename) + { } + + ~INIFile() { } + + bool read(INIStructure& data) const + { + if (data.size()) + { + data.clear(); + } + if (filename.empty()) + { + return false; + } + INIReader reader(filename); + return reader >> data; + } + bool generate(INIStructure const& data, bool pretty = false) const + { + if (filename.empty()) + { + return false; + } + INIGenerator generator(filename); + generator.prettyPrint = pretty; + return generator << data; + } + bool write(INIStructure& data, bool pretty = false) const + { + if (filename.empty()) + { + return false; + } + INIWriter writer(filename); + writer.prettyPrint = pretty; + return writer << data; + } + }; +} + +#endif // MINI_INI_H_ diff --git a/src/presets/src/inih/ini_type_conversion.h b/src/presets/src/inih/ini_type_conversion.h new file mode 100644 index 00000000000..014384dc0dd --- /dev/null +++ b/src/presets/src/inih/ini_type_conversion.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include + +namespace mINI { +class INITypeConversion { + public: + INITypeConversion() = delete; + + static bool getBoolean(mINI::INIStructure structure, const std::string& section, const std::string& key, bool defaultValue = false) { + if (!structure.has(section) || !structure.get(section).has(key)) { + return defaultValue; + } + return getBooleanFromString(structure.get(section).get(key)); + } + + static double getDouble(mINI::INIStructure structure, const std::string& section, const std::string& key, double defaultValue = 0.0) { + if (!structure.has(section) || !structure.get(section).has(key)) { + return defaultValue; + } + + // exceptions are not supported -> need to convert it without + double value; + std::stringstream stream(structure.get(section).get(key)); + stream >> value; + + // check if conversion worked + if (stream.fail()) { + return defaultValue; + } + return value; + } + + static int getInteger(mINI::INIStructure structure, const std::string& section, const std::string& key, int defaultValue = 0) { + if (!structure.has(section) || !structure.get(section).has(key)) { + return defaultValue; + } + + // exceptions are not supported -> need to convert it without + int value; + std::stringstream stream(structure.get(section).get(key)); + stream >> value; + + // check if conversion worked + if (stream.fail()) { + return defaultValue; + } + return value; + } + + private: + static bool getBooleanFromString(const std::string& value) { + // transform to lower case string + std::string local = value; + transform(local.begin(), local.end(), local.begin(), ::tolower); + + if (local == "1" || local == "true" || local == "yes") { + return true; + } else { + return false; + } + } +}; +}; // namespace mINI diff --git a/src/shared/src/MathUtils.ts b/src/shared/src/MathUtils.ts index 13c12b1311a..58f837c4d5b 100644 --- a/src/shared/src/MathUtils.ts +++ b/src/shared/src/MathUtils.ts @@ -377,4 +377,14 @@ export class MathUtils { }); return ret; } + + /** + * Returns the given value if the value is >=lower or <= upper. Otherwise returns the boundary value. + * @param value the value to be clamped + * @param lower lowest boundary value + * @param upper highest boundary value + */ + public static clamp(value, lower, upper) { + return Math.min(Math.max(value, lower), upper); + } } diff --git a/src/shared/src/ata.ts b/src/shared/src/ata.ts new file mode 100644 index 00000000000..f0f90adb19e --- /dev/null +++ b/src/shared/src/ata.ts @@ -0,0 +1,98 @@ +export const AtaChaptersTitle = { + 0: 'General', + 1: 'Maintenance Policy', + 2: 'Operations', + 3: 'Support', + 4: 'Airworthiness Limitations', + 5: 'Time Limits/Maintenance Checks', + 6: 'Dimensions And Areas', + 7: 'Lifting And Shoring', + 8: 'Leveling And Weighing', + 9: 'Towing And Taxiing', + 10: 'Parking, Mooring, Storage And Return To Service', + 11: 'Placards And Markings', + 12: 'Servicing', + 13: 'Hardware And General Tools', + 15: 'Aircrew Information', + 16: 'Change Of Role', + 18: 'Vibration And Noise Analysis (Helicopter Only)', + 20: 'Standard Practices- Airframe', + 21: 'Air Conditioning', + 22: 'Auto Flight', + 23: 'Communication', + 24: 'Electrical Power', + 25: 'Equipment /Furnishings', + 26: 'Fire Protection', + 27: 'Flight Controls', + 28: 'Fuel', + 29: 'Hydraulic Power', + 30: 'Ice And Rain Protection', + 31: 'Indicating / Recording System', + 32: 'Landing Gear', + 33: 'Lights', + 34: 'Navigation', + 35: 'Oxygen', + 36: 'Pneumatic', + 37: 'Vacuum', + 38: 'Water / Waste', + 39: 'Electrical - Electronic Panels And Multipurpose Components', + 40: 'Multisystem', + 41: 'Water Ballast', + 42: 'Integrated Modular Avionics', + 44: 'Cabin Systems', + 45: 'Onboard Maintenance Systems (Oms)', + 46: 'Information Systems', + 47: 'Inert Gas System', + 48: 'In Flight Fuel Dispensing', + 49: 'Airborne Auxiliary Power', + 50: 'Cargo And Accessory Compartments', + 51: 'Standard Practices And Structures - General', + 52: 'Doors', + 53: 'Fuselage', + 54: 'Nacelles/Pylons', + 55: 'Stabilizers', + 56: 'Windows', + 57: 'Wings', + 60: 'Standard Practices - Prop./Rotor', + 61: 'Propellers/ Propulsors', + 62: 'Main Rotor(S)', + 63: 'Main Rotor Drive(S)', + 64: 'Tail Rotor', + 65: 'Tail Rotor Drive', + 66: 'Folding Blades/Pylon', + 67: 'Rotors Flight Control', + 71: 'Power Plant', + 72: 'Engine', + 73: 'Engine - Fuel And Control', + 74: 'Ignition', + 75: 'Bleed Air', + 76: 'Engine Controls', + 77: 'Engine Indicating', + 78: 'Exhaust', + 79: 'Oil', + 80: 'Starting', + 81: 'Turbines (Reciprocating Engines)', + 82: 'Water Injection', + 83: 'Accessory Gear Box (Engine Driven)', + 84: 'Propulsion Augmentation', + 91: 'Charts', + 92: 'Electrical Power Multiplexing', + 93: 'Surveillance', + 94: 'Weapon System', + 95: 'Crew Escape And Safety', + 96: 'Missiles, Drones And Telemetry', + 97: 'Wiring Reporting', + 98: 'Meteorological And Atmospheric Research', + 99: 'Electronic Warfare System', + 115: 'Flight Simulator Systems', + 116: 'Flight Simulator Cuing System', +}; + +export const AtaChaptersDescription = Object.freeze({ + 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.', + 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.', + 34: 'The navigation systems provide data about the position, speed, heading, and altitude of the aircraft. Failures in a system such as the ADIRS can cause a loss of data sent to instrumentation.', +}); + +export type AtaChapterNumber = keyof typeof AtaChaptersTitle; diff --git a/src/shared/src/units.ts b/src/shared/src/units.ts new file mode 100644 index 00000000000..eb2714285fe --- /dev/null +++ b/src/shared/src/units.ts @@ -0,0 +1,134 @@ +import { NXDataStore } from './persistence'; + +// SI base units +export type Celsius = number; // derived unit +export type HectoPascal = number; // derived unit +export type KiloGram = number; +export type Metre = number; +export type Litre = number; + +// USCS base units +export type Fahrenheit = number; +export type Foot = number; +export type Pound = number; +export type Gallon = number; + +export type InchOfMercury = number; + +export class Units { + private static mMetricUnits: boolean; + + static get usingMetric(): boolean { + if (Units.mMetricUnits === undefined) { + NXDataStore.getAndSubscribe('CONFIG_USING_METRIC_UNIT', (_: string, value: string) => { + Units.mMetricUnits = value === '1'; + }); + } + return Units.mMetricUnits; + } + + static poundToKilogram(value: Pound): KiloGram { + return value / 2.204625; + } + + static kilogramToPound(value: KiloGram): Pound { + return value * 2.204625; + } + + static userToKilogram(value: Pound): KiloGram { + return Units.usingMetric ? value : Units.poundToKilogram(value); + } + + static kilogramToUser(value: KiloGram): Pound | KiloGram { + return Units.usingMetric ? value : Units.kilogramToPound(value); + } + + static get userWeightSuffixEis2(): 'kg' | 'lbs' { + // EIS uses S suffix on LB + return Units.usingMetric ? 'kg' : 'lbs'; + } + + static footToMetre(value: Foot): Metre { + return value / 3.28084; + } + + static metreToFoot(value: Metre): Foot { + return value * 3.28084; + } + + static userToMetre(value: Foot): Metre { + return Units.usingMetric ? value : Units.footToMetre(value); + } + + static metreToUser(value: Metre): Foot | Metre { + return Units.usingMetric ? value : Units.metreToFoot(value); + } + + static get userLengthSuffixEis2(): 'm' | 'ft' { + return Units.usingMetric ? 'm' : 'ft'; + } + + static fahrenheitToCelsius(value: Fahrenheit): Celsius { + return (value - 32) * 5 / 9; + } + + static celsiusToFahrenheit(value: Celsius): Fahrenheit { + return (value * 9 / 5) + 32; + } + + static userToCelsius(value: Fahrenheit): Celsius { + return Units.usingMetric ? value : Units.fahrenheitToCelsius(value); + } + + static celsiusToUser(value: Celsius): Fahrenheit | Celsius { + return Units.usingMetric ? value : Units.celsiusToFahrenheit(value); + } + + static get userTemperatureSuffixEis2(): '°C' | '°F' { + return Units.usingMetric ? '°C' : '°F'; + } + + static inchOfMercuryToHectopascal(value: InchOfMercury): HectoPascal { + return value * 33.863886666667; + } + + static hectopascalToInchOfMercury(value: HectoPascal): InchOfMercury { + return value / 33.863886666667; + } + + static userToHectopascal(value: InchOfMercury): HectoPascal { + return Units.usingMetric ? value : Units.inchOfMercuryToHectopascal(value); + } + + static hectopascalToUser(value: Celsius): InchOfMercury { + return Units.usingMetric ? value : Units.hectopascalToInchOfMercury(value); + } + + static hectopascalToUserString(value: Celsius): string { + return Units.usingMetric ? `${Math.round(value)}` : (Math.round(Units.hectopascalToInchOfMercury(value)) / 100).toFixed(2); + } + + static get userPressureSuffixEis2(): 'hPa' | 'in.Hg' { + return Units.usingMetric ? 'hPa' : 'in.Hg'; + } + + static gallonToLitre(value: number): Litre { + return value * 3.78541; + } + + static litreToGallon(value: number): Gallon { + return value / 3.78541; + } + + static litreToUser(value: number): Litre | Gallon { + return Units.usingMetric ? value : value * 0.264172052358148; + } + + static userToLitre(value: number): Litre { + return Units.usingMetric ? value : value / 0.264172052358148; + } + + static get userVolumeSuffixEis2(): 'l' | 'gal' { + return Units.usingMetric ? 'l' : 'gal'; + } +}