diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 0b4e0e4c75f..aa2984ca3ef 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -35,6 +35,8 @@ 1. [EFCS] Implement calculated yaw damper gain - @lukecologne (luke) 1. [EFCS] Decrease yaw damper at low speeds on ground, down to 0 below 40kts - @lukecologne (luke) 1. [FLIGHTMODEL] Fix pitch trim on approach - @donstim (donbikes) +1. [GENERAL] Added C++ WASM framework and migrated all flypad-backend code to it - @frankkopp (Frank Kopp) +1. [EFB] C++ WASM framework, extra-backend to replace flypad-backend, smooth dial turning for Lighting Presets - @frankkopp (Frank Kopp) ## 0.11.0 diff --git a/.gitignore b/.gitignore index e6b2590d94b..119f827aaa3 100644 --- a/.gitignore +++ b/.gitignore @@ -102,8 +102,9 @@ # explicit exclusions /.env node_modules/ -obj/ target/ +obj/ +cmake-build*/ *.tgz *.wasm cmake-build* diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 53f35b92192..a70345cf6a5 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -8,8 +8,13 @@ "${MSFS_SDK}/SimConnect SDK/include", "${workspaceFolder}/fbw-common/src/wasm/fbw_common/src/**", "${workspaceFolder}/fbw-a32nx/src/wasm/fbw_a320/src/**", - "${workspaceFolder}/fbw-a380x/src/wasm/fbw_a380/src/**" - ], + "${workspaceFolder}/fbw-a380x/src/wasm/fbw_a380/src/**", + "${workspaceFolder}/fbw-common/src/wasm/cpp-msfs-framework/**", + "${workspaceFolder}/fbw-common/src/wasm/extra-backend/**", + "${workspaceFolder}/fbw-a32nx/src/wasm/extra-backend-a32nx/src/**", + "${workspaceFolder}/fbw-a380x/src/wasm/extra-backend-a380x/src/**", + "${workspaceFolder}/fbw-a32nx/src/wasm/fadec_a320/src/**", + "${workspaceFolder}/fbw-a380x/src/wasm/fadec_a380/src/**" ], "defines": [ "_MSFS_WASM=1", "__wasi__", @@ -18,13 +23,12 @@ "_MBCS" ], "windowsSdkVersion": "10.0.18362.0", - //"compilerPath": "${MSFS_SDK}/WASM/llvm/bin/clang-cl.exe", "compilerArgs": [ "--sysroot ${MSFS_SDK}/WASM/wasi-sysroot", "-target wasm32-unknown-wasi" ], "cStandard": "c17", - "cppStandard": "c++17", + "cppStandard": "c++20", "intelliSenseMode": "msvc-x64" } ], diff --git a/.vscode/launch.json b/.vscode/launch.json index 81f14b18732..e41be59dd50 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,6 +8,7 @@ "name": "Attach", "type": "cppvsdbg", "request": "attach", + "processId": "${command:pickProcess}", "args": [], "stopAtEntry": false, "cwd": "${fileDirname}", @@ -45,4 +46,4 @@ } } ] -} + } diff --git a/CMakeLists.txt b/CMakeLists.txt index 20606ba7eba..7242f3614a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,27 +1,59 @@ -# This CMakeLists.txt file is used only for syntax highlighting and navigating -# through the code in an IDE. It is not used for building the project. - -cmake_minimum_required(VERSION 3.19) -project(flybywire-a32nx) +cmake_minimum_required(VERSION 3.18) +project(flybywire-aircraft C CXX) set(CMAKE_CXX_STANDARD 20) -set(MSFS_SDK "C:\\MSFS SDK") -set(FBW_ROOT ${CMAKE_SOURCE_DIR}) +set(CMAKE_VERBOSE_MAKEFILE OFF) +set(FBW_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) +set(FBW_COMMON ${FBW_ROOT}/fbw-common/src/wasm) + +# cmake helper scripts +include("${FBW_COMMON}/cpp-msfs-framework/cmake/TargetDefinition.cmake") + +# compiler refinement +set(COMPILER_FLAGS "-Wall -Wextra -Wno-unused-function -Wno-unused-command-line-argument -Wno-ignored-attributes -Wno-macro-redefined -target wasm32-unknown-wasi --sysroot \"${MSFS_SDK}/WASM/wasi-sysroot\" -mthread-model single -fno-exceptions -fms-extensions -fvisibility=hidden -ffunction-sections -fdata-sections -fno-stack-protector") + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMPILER_FLAGS}") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto -O2 -DNDEBUG") +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -DDEBUG") + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMPILER_FLAGS}") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto -O2 -DNDEBUG") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -DDEBUG") -# include sub directories +message("MSFS_SDK: " ${MSFS_SDK}) + +# add the include paths include_directories( - ${PROJECT_SOURCE_DIR}/fbw-a32nx/src/wasm/fadec_a320 - ${PROJECT_SOURCE_DIR}/fbw-a32nx/src/wasm/fbw_a320 - ${PROJECT_SOURCE_DIR}/fbw-a32nx/src/wasm/flypad-backend - ${PROJECT_SOURCE_DIR}/fbw-a380x/src/wasm/fadec_a380 - ${PROJECT_SOURCE_DIR}/fbw-a380x/src/wasm/fbw_a380 - ${PROJECT_SOURCE_DIR}/fbw-common/src/wasm/fadec_common - ${PROJECT_SOURCE_DIR}/fbw-common/src/wasm/fbw_common + "${MSFS_SDK}/WASM/include" + "${MSFS_SDK}/WASM/wasi-sysroot/include" + "${MSFS_SDK}/WASM/wasi-sysroot/include/c++/v1" + "${MSFS_SDK}/SimConnect SDK/include" + "${FBW_COMMON}/cpp-msfs-framework/lib" + "${FBW_COMMON}/cpp-msfs-framework/MsfsHandler" + "${FBW_COMMON}/cpp-msfs-framework/MsfsHandler/DataTypes" +) + +# add compiler definitions +add_definitions( + -D_MSFS_WASM=1 + -D__wasi__ + -D_LIBC_NO_EXCEPTIONS + -D_LIBCPP_HAS_NO_THREADS + -D_WINDLL + -D_MBCS + # ZERO_LVL=0 CRITICAL_LVL=1 ERROR_LVL=2 WARN_LVL=3 INFO_LVL=4 DEBUG_LVL=5 VERBOSE=6 TRACE_LVL=7 + -DLOG_LEVEL=4 + # EXAMPLES | NO_EXAMPLES + -DNO_EXAMPLES + #PROFILING | NO_PROFILING - for logging of profiling information of pre-, post-, update() calls + -DNO_PROFILING ) -add_subdirectory(./fbw-a32nx/src/wasm/fadec_a320) -add_subdirectory(./fbw-a32nx/src/wasm/fbw_a320) -add_subdirectory(./fbw-a32nx/src/wasm/flypad-backend) -add_subdirectory(./fbw-a380x/src/wasm/fadec_a380) -add_subdirectory(./fbw-a380x/src/wasm/fbw_a380) -add_subdirectory(./fbw-a380x/src/wasm/flypad-backend) +# add the common components +add_subdirectory(fbw-common/src/wasm) + +# add the A32NX components +add_subdirectory(fbw-a32nx/src/wasm) + +# add the A380X components +add_subdirectory(fbw-a380x/src/wasm) diff --git a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/panel.cfg b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/panel.cfg index dac83a63a5e..2b299cde8ea 100644 --- a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/panel.cfg +++ b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/panel.cfg @@ -133,7 +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=flypad-backend.wasm&wasm_gauge=FlyPadBackend,0,0,1,1 +htmlgauge03 = WasmInstrument/WasmInstrument.html?wasm_module=extra-backend-a32nx.wasm&wasm_gauge=Gauge_Extra_Backend,0,0,1,1 [VCockpit18] size_mm = 0,0 diff --git a/fbw-a32nx/src/systems/instruments/src/EFB/Efb.tsx b/fbw-a32nx/src/systems/instruments/src/EFB/Efb.tsx index 5b7ff0463fc..98f8217a208 100644 --- a/fbw-a32nx/src/systems/instruments/src/EFB/Efb.tsx +++ b/fbw-a32nx/src/systems/instruments/src/EFB/Efb.tsx @@ -211,20 +211,24 @@ const Efb = () => { // Automatically load a lighting preset useEffect(() => { - if (ac1BusIsPowered && autoLoadLightingPresetEnabled) { + if (ac1BusIsPowered && powerState === PowerStates.LOADED && autoLoadLightingPresetEnabled) { + // TIME OF DAY enum : 1 = Day ; 2 = Dusk/Dawn ; 3 = Night switch (timeOfDay) { case 1: if (autoLoadDayLightingPresetID !== 0) { + console.log('Auto-loading lighting preset: ', autoLoadDayLightingPresetID); setLoadLightingPresetVar(autoLoadDayLightingPresetID); } break; case 2: if (autoLoadDawnDuskLightingPresetID !== 0) { + console.log('Auto-loading lighting preset: ', autoLoadDawnDuskLightingPresetID); setLoadLightingPresetVar(autoLoadDawnDuskLightingPresetID); } break; case 3: if (autoLoadNightLightingPresetID !== 0) { + console.log('Auto-loading lighting preset: ', autoLoadNightLightingPresetID); setLoadLightingPresetVar(autoLoadNightLightingPresetID); } break; @@ -232,7 +236,7 @@ const Efb = () => { break; } } - }, [ac1BusIsPowered, autoLoadLightingPresetEnabled]); + }, [ac1BusIsPowered, powerState, autoLoadLightingPresetEnabled]); useInterval(() => { if (!autoFillChecklists) return; diff --git a/fbw-a32nx/src/systems/instruments/src/EFB/Presets/Widgets/Procedures.tsx b/fbw-a32nx/src/systems/instruments/src/EFB/Presets/Widgets/Procedures.tsx index c89f7eea31a..93ec78a03d2 100644 --- a/fbw-a32nx/src/systems/instruments/src/EFB/Presets/Widgets/Procedures.tsx +++ b/fbw-a32nx/src/systems/instruments/src/EFB/Presets/Widgets/Procedures.tsx @@ -68,7 +68,6 @@ export const StepDescription = new Map([ [2090, 'ATC ALT RPTG On'], [2100, 'TCAS TRAFFIC ABV'], [2110, 'COCKPIT DOOR LCK'], - [2120, 'Strobe Auto'], [2120, 'Strobe On'], [2121, 'Strobe Off'], [2122, 'Strobe Auto'], diff --git a/fbw-a32nx/src/wasm/CMakeLists.txt b/fbw-a32nx/src/wasm/CMakeLists.txt new file mode 100644 index 00000000000..cdf2102b7ba --- /dev/null +++ b/fbw-a32nx/src/wasm/CMakeLists.txt @@ -0,0 +1,13 @@ +# folder structure +set(OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel) + +# add compiler definitions +add_definitions(-DA32NX) + +add_subdirectory(extra-backend-a32nx) + +# FIXME: remove the if-clause as soon as all components are using CMake +if (WIN32) + add_subdirectory(fadec_a320) + add_subdirectory(fbw_a320) +endif () diff --git a/fbw-a32nx/src/wasm/README.md b/fbw-a32nx/src/wasm/README.md index 637540a9694..767f95b35cf 100644 --- a/fbw-a32nx/src/wasm/README.md +++ b/fbw-a32nx/src/wasm/README.md @@ -7,6 +7,6 @@ This directory contains the source Rust and C++ files for the WASM modules. ``` - fadec_a320 - fbw_a320 -- flypad-backend +- extra-backend-a32nx - systems ``` diff --git a/fbw-a32nx/src/wasm/extra-backend-a32nx/CMakeLists.txt b/fbw-a32nx/src/wasm/extra-backend-a32nx/CMakeLists.txt new file mode 100644 index 00000000000..df486a56e01 --- /dev/null +++ b/fbw-a32nx/src/wasm/extra-backend-a32nx/CMakeLists.txt @@ -0,0 +1,42 @@ +# extra-backend-a32nx CMakeLists.txt + +# add additional compiler definitions for the a32nx extra-backend build +add_definitions() + +# add the local include directories +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/src/AircraftPresets + ${CMAKE_CURRENT_SOURCE_DIR}/src/LightingPresets + ${FBW_COMMON}/cpp-msfs-framework/ + ${FBW_COMMON}/extra-backend/ +) + +# define the source files +set(SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/src/Gauge_Extra_Backend.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/LightingPresets/LightingPresets_A32NX.cpp + ${FBW_COMMON}/cpp-msfs-framework/Example/ExampleModule.cpp + ${FBW_COMMON}/extra-backend/Pushback/Pushback.cpp + ${FBW_COMMON}/extra-backend/AircraftPresets/AircraftPresets.cpp + ${FBW_COMMON}/extra-backend/LightingPresets/LightingPresets.cpp + ) +set(INCLUDE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/src/AircraftPresets/AircraftPresetProcedures_A32NX.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/LightingPresets/LightingPresets_A32NX.h + ${FBW_COMMON}/cpp-msfs-framework/Example/ExampleModule.h + ${FBW_COMMON}/cpp-msfs-framework/Example/longtext.h + ${FBW_COMMON}/extra-backend/Pushback/InertialDampener.hpp + ${FBW_COMMON}/extra-backend/Pushback/Pushback.h + ${FBW_COMMON}/extra-backend/AircraftPresets/AircraftPresets.h + ${FBW_COMMON}/extra-backend/AircraftPresets/PresetProcedures.h + ${FBW_COMMON}/extra-backend/AircraftPresets/ProcedureStep.h + ${FBW_COMMON}/extra-backend/LightingPresets/LightingPresets.h + ) + +# create the targets +add_library(extra-backend-a32nx OBJECT ${SOURCE_FILES} ${INCLUDE_FILES}) +add_wasm_library( + NAME extra-backend-a32nx + DEPENDENCIES extra-backend-a32nx cpp-msfs-framework-a32nx +) diff --git a/fbw-a32nx/src/wasm/extra-backend-a32nx/src/AircraftPresets/AircraftPresetProcedures_A32NX.h b/fbw-a32nx/src/wasm/extra-backend-a32nx/src/AircraftPresets/AircraftPresetProcedures_A32NX.h new file mode 100644 index 00000000000..ce019228819 --- /dev/null +++ b/fbw-a32nx/src/wasm/extra-backend-a32nx/src/AircraftPresets/AircraftPresetProcedures_A32NX.h @@ -0,0 +1,226 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFT_AIRCRAFTPRESETPROCEDURES_A32NX_H +#define FLYBYWIRE_AIRCRAFT_AIRCRAFTPRESETPROCEDURES_A32NX_H + +#include "AircraftPresets/PresetProceduresDefinition.h" +#include "AircraftPresets/ProcedureStep.h" + +/** + * A32NX specific aircraft procedures definition. + * + * @see AircraftProceduresDefinition + */ +class AircraftPresetProcedures_A32NX { + public: + const inline static PresetProceduresDefinition aircraftProcedureDefinition{ + // clang-format off + // @formatter:off + + .POWERED_CONFIG_ON { + // SOP: PRELIMINARY COCKPIT PREPARATION + ProcedureStep{"BAT1 On", 1010, false, 1000, "(L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)", "1 (>L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)"}, + ProcedureStep{"BAT2 On", 1020, false, 3000, "(L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)", "1 (>L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)"}, + + ProcedureStep{"EXT PWR On", 1030, 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) }"}, + + // if no Ext Pwr is available we start the APU here with a bat only fire test + ProcedureStep{"APU Fire Test On", 1035, false, 2000, "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)", "1 (>L:A32NX_FIRE_TEST_APU)"}, + ProcedureStep{"APU Fire Test Off", 1036, false, 2000, "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)", "0 (>L:A32NX_FIRE_TEST_APU)"}, + ProcedureStep{"APU Master On", 1040, false, 3000, "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)", "1 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, + ProcedureStep{"APU Start On", 1050, 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)"}, + + ProcedureStep{"Waiting on AC BUS Availability", 1060, true, 2000, "", "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)"}, + + // SOP: COCKPIT PREPARATION + ProcedureStep{"Crew Oxy On", 1120, false, 1000, "(L:PUSH_OVHD_OXYGEN_CREW) 0 ==", "0 (>L:PUSH_OVHD_OXYGEN_CREW)"}, + ProcedureStep{"GND CTL On", 1110, 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)"}, + ProcedureStep{"CVR Test On", 1115, false, 5000, "(L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)", "1 (>L:A32NX_RCDR_TEST)"}, + ProcedureStep{"CVR Test Off", 1116, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)", "0 (>L:A32NX_RCDR_TEST) 1 (>L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)"}, + + ProcedureStep{"ADIRS 1 Nav", 1080, false, 500, "(L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB)"}, + ProcedureStep{"ADIRS 2 Nav", 1090, false, 500, "(L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB)"}, + ProcedureStep{"ADIRS 3 Nav", 1100, false, 1500, "(L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB)"}, + + ProcedureStep{"Strobe Auto", 2122, false, 50, "(L:LIGHTING_STROBE_0) 1 ==", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_OFF)"}, + ProcedureStep{"Strobe Auto", 2122, false, 1000, "(L:LIGHTING_STROBE_0) 1 ==", "1 (>L:STROBE_0_AUTO) 0 (>K:STROBES_ON)"}, + ProcedureStep{"Nav & Logo Lt On", 1070, false, 1000, "(A:LIGHT LOGO, Bool) (A:LIGHT NAV, Bool) &&", "1 (>K:2:LOGO_LIGHTS_SET) 1 (>K:2:NAV_LIGHTS_SET)"}, + + ProcedureStep{"SEAT BELTS On", 2140, false, 1000, "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL)", "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) ! if{ 1 (>K:CABIN_SEATBELTS_ALERT_SWITCH_TOGGLE) }"}, + ProcedureStep{"NO SMOKING Auto", 1130, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION) 1 ==", "1 (>L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION)"}, + ProcedureStep{"EMER EXT Lt Arm", 1140, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION) 1 ==", "1 (>L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION)"}, + + // For the fire tests the FWC needs to be initialized + // The correct variables to wait for are: A32NX_FWS_FWC_1_NORMAL and A32NX_FWS_FWC_2_NORMAL. But + // as these are only on Exp this first iteration uses A32NX_FWC_FLIGHT_PHASE which also work on> + // master and is equivalent for this specific purpose. Will be changed when the FWC is on master. + ProcedureStep{"Waiting on FWC Initialization", 1065, true, 5000, "", "(L:A32NX_FWC_FLIGHT_PHASE)"}, + ProcedureStep{"Waiting...", 9999, false, 5000, "(L:A32NX_AIRCRAFT_PRESET_FWC_INIT_DONE)", "1 (>L:A32NX_AIRCRAFT_PRESET_FWC_INIT_DONE)"}, + + // APU fire test + ProcedureStep{"APU Fire Test On", 1035, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)", "1 (>L:A32NX_FIRE_TEST_APU)"}, + ProcedureStep{"APU Fire Test Off", 1036, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)", "0 (>L:A32NX_FIRE_TEST_APU) 1 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)"}, + + // After fire test we start the APU + ProcedureStep{"APU Master On", 1041, 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)"}, + ProcedureStep{"APU Start On", 1051, 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)"}, + + // ENG fire test + ProcedureStep{"ENG 1 Fire Test On", 2002, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)", "1 (>L:A32NX_FIRE_TEST_ENG1)"}, + ProcedureStep{"ENG 1 Fire Test Off", 2003, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)", "0 (>L:A32NX_FIRE_TEST_ENG1) 1 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)"}, + ProcedureStep{"ENG 2 Fire Test On", 2004, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)", "1 (>L:A32NX_FIRE_TEST_ENG2)"}, + ProcedureStep{"ENG 2 Fire Test Off", 2005, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)", "0 (>L:A32NX_FIRE_TEST_ENG2) 1 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)"}, + + ProcedureStep{"Waiting on APU Availability", 1150, true, 2000, "", "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) ! (L:A32NX_OVHD_APU_START_PB_IS_AVAILABLE) ||"}, + ProcedureStep{"APU Bleed On", 1160, 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)"} + }, + + .POWERED_CONFIG_OFF = { + ProcedureStep{"NO SMOKING Off", 1170, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION) 2 ==", "2 (>L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION)"}, + ProcedureStep{"EMER EXT Lt Off", 1180, false, 1500, "(L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION) 2 ==", "2 (>L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION)"}, + ProcedureStep{"GND CTL Off", 1200, false, 1000, "(L:A32NX_RCDR_GROUND_CONTROL_ON) 0 ==", "0 (>L:A32NX_RCDR_GROUND_CONTROL_ON)"}, + ProcedureStep{"SEAT BELTS Off", 2200, false, 2000, "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) !", "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) if{ 1 (>K:CABIN_SEATBELTS_ALERT_SWITCH_TOGGLE) }"}, + ProcedureStep{"Strobe Off", 2121, false, 1000, "(L:LIGHTING_STROBE_0) 2 ==", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_OFF)"}, + ProcedureStep{"Nav & Logo Lt Off", 1240, false, 500, "(A:LIGHT LOGO, Bool) ! (A:LIGHT NAV, Bool) ! &&", "0 (>K:2:LOGO_LIGHTS_SET) 0 (>K:2:NAV_LIGHTS_SET)"}, + ProcedureStep{"Crew Oxy Off", 1190, false, 1000, "(L:PUSH_OVHD_OXYGEN_CREW) 1 ==", "1 (>L:PUSH_OVHD_OXYGEN_CREW)"}, + ProcedureStep{"ADIRS 3 Off", 1210, false, 500, "(L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB)"}, + ProcedureStep{"ADIRS 2 Off", 1220, false, 500, "(L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB)"}, + ProcedureStep{"ADIRS 1 Off", 1230, false, 1000, "(L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB)"}, + ProcedureStep{"APU Bleed Off", 1250, false, 1500, "(L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON)"}, + ProcedureStep{"APU Master Off", 1260, false, 2000, "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, + ProcedureStep{"EXT PWR Off", 1270, false, 3000, "(A:EXTERNAL POWER ON:1, BOOL) !", "(A:EXTERNAL POWER ON:1, BOOL) if{ 1 (>K:TOGGLE_EXTERNAL_POWER) }"}, + ProcedureStep{"BAT2 Off", 1280, false, 100, "(L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO) 0 ==", "0 (>L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)"}, + ProcedureStep{"BAT1 Off", 1290, false, 1000, "(L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO) 0 ==", "0 (>L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)"}, + ProcedureStep{"AC BUS Off Check", 1300, true, 2000, "", "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED) !"}, + ProcedureStep{"CVR Test Reset", 1117, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)"}, + ProcedureStep{"APU Fire Test Reset", 1037, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)"}, + ProcedureStep{"ENG 1 Fire Test Reset", 2006, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)"}, + ProcedureStep{"ENG 2 Fire Test Reset", 2007, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)"}, + ProcedureStep{"FWC Init Reset", 1066, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FWC_INIT_DONE)"} + }, + + .PUSHBACK_CONFIG_ON = { + // SOP: BEFORE PUSHBACK OR START + ProcedureStep{"EXT PWR Off", 2000, false, 3000, "(A:EXTERNAL POWER ON:1, BOOL) !", "(A:EXTERNAL POWER ON:1, BOOL) if{ 1 (>K:TOGGLE_EXTERNAL_POWER) }"}, + ProcedureStep{"Beacon On", 2130, false, 2000, "(A:LIGHT BEACON, Bool)", "0 (>K:BEACON_LIGHTS_ON)"}, + ProcedureStep{"FUEL PUMP 2 On", 2010, false, 100, "(A:FUELSYSTEM PUMP SWITCH:2, Bool)", "2 (>K:FUELSYSTEM_PUMP_ON)"}, + ProcedureStep{"FUEL PUMP 5 On", 2020, false, 500, "(A:FUELSYSTEM PUMP SWITCH:5, Bool)", "5 (>K:FUELSYSTEM_PUMP_ON)"}, + ProcedureStep{"FUEL VALVE 9 On", 2030, false, 100, "(A:FUELSYSTEM VALVE SWITCH:9, Bool)", "9 (>K:FUELSYSTEM_VALVE_OPEN)"}, + ProcedureStep{"FUEL VALVE 10 On", 2040, false, 500, "(A:FUELSYSTEM VALVE SWITCH:10, Bool)", "10 (>K:FUELSYSTEM_VALVE_OPEN)"}, + ProcedureStep{"FUEL PUMP 3 On", 2050, false, 100, "(A:FUELSYSTEM PUMP SWITCH:3, Bool)", "3 (>K:FUELSYSTEM_PUMP_ON)"}, + ProcedureStep{"FUEL PUMP 6 On", 2060, false, 2000, "(A:FUELSYSTEM PUMP SWITCH:6, Bool)", "6 (>K:FUELSYSTEM_PUMP_ON)"}, + ProcedureStep{"COCKPIT DOOR LCK", 2110, false, 2000, "(L:A32NX_COCKPIT_DOOR_LOCKED) 1 ==", "1 (>L:A32NX_COCKPIT_DOOR_LOCKED)"}, + ProcedureStep{"Await ADIRS 1 Alignment", 2150, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_1_STATE) 2 =="}, + ProcedureStep{"Await ADIRS 2 Alignment", 2160, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_2_STATE) 2 =="}, + ProcedureStep{"Await ADIRS 3 Alignment", 2170, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_3_STATE) 2 =="}, + }, + + .PUSHBACK_CONFIG_OFF = { + ProcedureStep{"COCKPIT DOOR OP", 2250, false, 2000, "(L:A32NX_COCKPIT_DOOR_LOCKED) 0 ==", "0 (>L:A32NX_COCKPIT_DOOR_LOCKED)"}, + ProcedureStep{"FUEL PUMP 2 Off", 2260, false, 100, "(A:FUELSYSTEM PUMP SWITCH:2, Bool) !", "2 (>K:FUELSYSTEM_PUMP_OFF)"}, + ProcedureStep{"FUEL PUMP 5 Off", 2270, false, 500, "(A:FUELSYSTEM PUMP SWITCH:5, Bool) !", "5 (>K:FUELSYSTEM_PUMP_OFF)"}, + ProcedureStep{"FUEL VALVE 9 Off", 2280, false, 100, "(A:FUELSYSTEM VALVE SWITCH:9, Bool) !", "9 (>K:FUELSYSTEM_VALVE_CLOSE)"}, + ProcedureStep{"FUEL VALVE 10 Off",2290, false, 500, "(A:FUELSYSTEM VALVE SWITCH:10, Bool) !", "10 (>K:FUELSYSTEM_VALVE_CLOSE)"}, + ProcedureStep{"FUEL PUMP 3 Off", 2300, false, 100, "(A:FUELSYSTEM PUMP SWITCH:3, Bool) !", "3 (>K:FUELSYSTEM_PUMP_OFF)"}, + ProcedureStep{"FUEL PUMP 6 Off", 2310, false, 1000, "(A:FUELSYSTEM PUMP SWITCH:6, Bool) !", "6 (>K:FUELSYSTEM_PUMP_OFF)"}, + ProcedureStep{"Beacon Off", 2190, false, 1000, "(A:LIGHT BEACON, Bool) !", "0 (>K:BEACON_LIGHTS_OFF)"}, + }, + + .TAXI_CONFIG_ON = { + // SOP: ENGINE START + ProcedureStep{"ENG MODE SEL START", 3000, 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)"}, + ProcedureStep{"ENG 2 ON", 3010, false, 60000, "(A:FUELSYSTEM VALVE OPEN:2, Bool)", "2 (>K:FUELSYSTEM_VALVE_OPEN)"}, + ProcedureStep{"Await ENG 2 AVAIL", 3020, true, 5000, "", "(L:A32NX_ENGINE_STATE:2) 1 =="}, + ProcedureStep{"ENG 1 ON", 3030, false, 60000, "(A:FUELSYSTEM VALVE OPEN:1, Bool)", "1 (>K:FUELSYSTEM_VALVE_OPEN)"}, + ProcedureStep{"Await ENG 1 AVAIL", 3040, true, 5000, "", "(L:A32NX_ENGINE_STATE:1) 1 =="}, + // SOP: AFTER START + ProcedureStep{"ENG MODE SEL NORM", 3050, 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)"}, + ProcedureStep{"APU Bleed Off", 3060, false, 2000, "(L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON)"}, + ProcedureStep{"APU Master Off", 3070, false, 2000, "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, + ProcedureStep{"Spoiler Arm", 3090, false, 2000, "(L:A32NX_SPOILERS_ARMED) 1 ==", "1 (>K:SPOILERS_ARM_SET)"}, + ProcedureStep{"Rudder Trim Reset", 3100, false, 2000, "(A:RUDDER TRIM, Radians) 0 ==", "0 (>K:RUDDER_TRIM_SET)"}, + ProcedureStep{"Flaps 1", 3110, false, 3000, "(L:A32NX_FLAPS_HANDLE_INDEX) 1 ==", "1 (>L:A32NX_FLAPS_HANDLE_INDEX)"}, + // SOP: TAXI + ProcedureStep{"NOSE Lt Taxi", 3120, 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)"}, + ProcedureStep{"RWY TURN OFF Lt L On", 3130, false, 0, "(A:CIRCUIT SWITCH ON:21, Bool)", "(A:CIRCUIT SWITCH ON:21, Bool) ! if{ 21 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + ProcedureStep{"RWY TURN OFF Lt R On", 3140, false, 2000, "(A:CIRCUIT SWITCH ON:22, Bool)", "(A:CIRCUIT SWITCH ON:22, Bool) ! if{ 22 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + ProcedureStep{"PWS Auto", 2070, false, 1000, "(L:A32NX_SWITCH_RADAR_PWS_POSITION) 1 ==", "1 (>L:A32NX_SWITCH_RADAR_PWS_POSITION)"}, + ProcedureStep{"Transponder On", 2080, false, 1000, "(L:A32NX_TRANSPONDER_MODE) 1 ==", "1 (>L:A32NX_TRANSPONDER_MODE)"}, + ProcedureStep{"ATC ALT RPTG On", 2090, false, 1000, "(L:A32NX_SWITCH_ATC_ALT) 1 ==", "1 (>L:A32NX_SWITCH_ATC_ALT)"}, + ProcedureStep{"TCAS TRAFFIC ABV", 2100, false, 2000, "(L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION)"}, + ProcedureStep{"Autobrake Max", 3080, false, 2000, "(L:A32NX_AUTOBRAKES_ARMED_MODE) 3 ==", "3 (>L:A32NX_AUTOBRAKES_ARMED_MODE_SET)"}, + ProcedureStep{"TERR ON ND Capt. On", 3080, false, 2000, "(L:A32NX_EFIS_TERR_L_ACTIVE) 1 ==", "1 (>L:A32NX_EFIS_TERR_L_ACTIVE)"}, + ProcedureStep{"T.O Config", 3085, false, 200, "", "1 (>L:A32NX_BTN_TOCONFIG)"}, + ProcedureStep{"T.O Config", 3085, false, 2000, "", "0 (>L:A32NX_BTN_TOCONFIG)"}, + }, + + .TAXI_CONFIG_OFF = { + ProcedureStep{"TERR ON ND Capt. Off", 3080, false, 2000, "(L:A32NX_EFIS_TERR_L_ACTIVE) 0 ==", "0 (>L:A32NX_EFIS_TERR_L_ACTIVE)"}, + ProcedureStep{"Autobrake Off", 3180, false, 2000, "(L:A32NX_AUTOBRAKES_ARMED_MODE) 0 ==", "0 (>L:A32NX_AUTOBRAKES_ARMED_MODE_SET)"}, + ProcedureStep{"TCAS TRAFFIC ABV", 2240, false, 1000, "(L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION)"}, + ProcedureStep{"ATC ALT RPTG Off", 2230, false, 1000, "(L:A32NX_SWITCH_ATC_ALT) 1 ==", "1 (>L:A32NX_SWITCH_ATC_ALT)"}, + ProcedureStep{"Transponder Off", 2220, false, 1000, "(L:A32NX_TRANSPONDER_MODE) 0 ==", "0 (>L:A32NX_TRANSPONDER_MODE)"}, + ProcedureStep{"PWS Off", 2210, false, 1000, "(L:A32NX_SWITCH_RADAR_PWS_POSITION) 0 ==", "0 (>L:A32NX_SWITCH_RADAR_PWS_POSITION)"}, + ProcedureStep{"RWY TURN OFF Lt L Off", 3160, false, 0, "(A:CIRCUIT SWITCH ON:21, Bool) !", "(A:CIRCUIT SWITCH ON:21, Bool) if{ 21 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + ProcedureStep{"RWY TURN OFF Lt R Off", 3170, false, 2000, "(A:CIRCUIT SWITCH ON:22, Bool) !", "(A:CIRCUIT SWITCH ON:22, Bool) if{ 22 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + ProcedureStep{"NOSE Lt Taxi", 3150, 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)"}, + ProcedureStep{"Flaps 0", 3210, false, 2000, "(L:A32NX_FLAPS_HANDLE_INDEX) 0 ==", "0 (>L:A32NX_FLAPS_HANDLE_INDEX)"}, + ProcedureStep{"Rudder Trim Reset", 3200, false, 2000, "(A:RUDDER TRIM, Radians) 0 ==", "0 (>K:RUDDER_TRIM_SET)"}, + ProcedureStep{"Spoiler Disarm", 3190, false, 2000, "(L:A32NX_SPOILERS_ARMED) 0 ==", "0 (>K:SPOILERS_ARM_SET)"}, + ProcedureStep{"ENG 1 Off", 3220, false, 2000, "(A:FUELSYSTEM VALVE OPEN:1, Bool) !", "1 (>K:FUELSYSTEM_VALVE_CLOSE)"}, + ProcedureStep{"ENG 2 Off", 3230, false, 2000, "(A:FUELSYSTEM VALVE OPEN:2, Bool) !", "2 (>K:FUELSYSTEM_VALVE_CLOSE)"}, + ProcedureStep{"ENG 1 N1 <3%", 3240, true, 1000, "", "(L:A32NX_ENGINE_N1:1) 3 <"}, + ProcedureStep{"ENG 2 N1 <3%", 3250, true, 1000, "", "(L:A32NX_ENGINE_N1:2) 3 <"} + }, + + .TAKEOFF_CONFIG_ON = { + // SOP: TAXI + ProcedureStep{"WX Radar On", 4000, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_SYS) 0 ==", "0 (>L:XMLVAR_A320_WEATHERRADAR_SYS)"}, + ProcedureStep{"WX Radar Mode", 4010, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_MODE) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_MODE)"}, + // SOP: BEFORE TAKEOFF + ProcedureStep{"TCAS Switch TA/RA", 4020, false, 2000, "(L:A32NX_SWITCH_TCAS_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_POSITION)"}, + // unfortunately strobe 3-way switch control is weird, so we have to use a workaround and turn it off first + ProcedureStep{"Strobe On" , 2120, false, 50, "(L:LIGHTING_STROBE_0) 0 ==", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_OFF)"}, + ProcedureStep{"Strobe On", 2120, false, 1000, "(L:LIGHTING_STROBE_0) 0 ==", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_ON)"}, + ProcedureStep{"Cabin Ready", 2125, false, 1000, "", "1 (>L:A32NX_CABIN_READY)"}, + // SOP: TAKE OFF + ProcedureStep{"NOSE Lt Takeoff", 4030, false, 1000, "(A:CIRCUIT SWITCH ON:17, Bool)", "(A:CIRCUIT SWITCH ON:17, Bool) ! if{ 17 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + ProcedureStep{"LL Lt L On", 4040, 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)"}, + ProcedureStep{"LL Lt R On", 4050, 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)"}, + }, + + .TAKEOFF_CONFIG_OFF = { + ProcedureStep{"LL Lt L Off", 4060, 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)"}, + ProcedureStep{"LL Lt R Off", 4070, 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)"}, + ProcedureStep{"NOSE Lt Takeoff", 4080, false, 2000, "(A:CIRCUIT SWITCH ON:17, Bool) !", "(A:CIRCUIT SWITCH ON:17, Bool) if{ 17 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + // unfortunately strobe 3-way switch control is weird, so we have to use a workaround and turn it off first + ProcedureStep{"Strobe Auto", 2122, false, 50, "(L:LIGHTING_STROBE_0) 0 == (L:LIGHTING_STROBE_0) 1 == ||", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_OFF)"}, + ProcedureStep{"Strobe Auto", 2122, false, 1000, "(L:A32NX_ENGINE_STATE:1) 0 == (L:A32NX_ENGINE_STATE:2) 0 == && " + "(L:LIGHTING_STROBE_0) 1 == || ", "1 (>L:STROBE_0_AUTO) 0 (>K:STROBES_ON)"}, + ProcedureStep{"TCAS Switch TA/RA", 4090, false, 1000, "(L:A32NX_SWITCH_TCAS_POSITION) 0 ==", "0 (>L:A32NX_SWITCH_TCAS_POSITION)"}, + ProcedureStep{"WX Radar Mode", 4110, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_MODE) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_MODE)"}, + ProcedureStep{"WX Radar Off", 4100, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_SYS) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_SYS)"}, + } + + // @formatter:on + // clang-format on + }; +}; + +#endif // FLYBYWIRE_AIRCRAFT_AIRCRAFTPRESETPROCEDURES_A32NX_H diff --git a/fbw-a32nx/src/wasm/extra-backend-a32nx/src/Gauge_Extra_Backend.cpp b/fbw-a32nx/src/wasm/extra-backend-a32nx/src/Gauge_Extra_Backend.cpp new file mode 100644 index 00000000000..55695e293ec --- /dev/null +++ b/fbw-a32nx/src/wasm/extra-backend-a32nx/src/Gauge_Extra_Backend.cpp @@ -0,0 +1,68 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#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 "MsfsHandler.h" + +#ifdef EXAMPLES +#include "Example/ExampleModule.h" +#endif + +#include "AircraftPresets/AircraftPresetProcedures_A32NX.h" +#include "AircraftPresets/AircraftPresets.h" +#include "LightingPresets/LightingPresets_A32NX.h" +#include "Pushback/Pushback.h" + +MsfsHandler msfsHandler("Gauge_Extra_Backend_A32NX", "A32NX_"); +#ifdef EXAMPLES +ExampleModule exampleModule(msfsHandler); +#endif + +// ADD ADDITIONAL MODULES HERE +// This is the only place these have to be added - everything else is handled automatically +LightingPresets_A32NX lightingPresets(msfsHandler); +Pushback pushback(msfsHandler); +AircraftPresets aircraftPresets(msfsHandler, AircraftPresetProcedures_A32NX::aircraftProcedureDefinition); + +/** + * Gauge Callback + * There can by multiple gauges in a single wasm module. Just add another gauge callback function + * and register it in the panel.cfg file. + * + * Avoid putting any logic in the gauge callback function. Instead, create a new class and put + * the logic there. + * + * @see + * https://docs.flightsimulator.com/html/Content_Configuration/SimObjects/Aircraft_SimO/Instruments/C_C++_Gauges.htm?rhhlterm=_gauge_callback&rhsearch=_gauge_callback + */ +extern "C" { +[[maybe_unused]] MSFS_CALLBACK bool Gauge_Extra_Backend_gauge_callback([[maybe_unused]] FsContext ctx, int svcId, void* pData) { + switch (svcId) { + case PANEL_SERVICE_PRE_INSTALL: { + return msfsHandler.initialize(); + } + case PANEL_SERVICE_PRE_DRAW: { + return msfsHandler.update(static_cast(pData)); + } + case PANEL_SERVICE_PRE_KILL: { + return msfsHandler.shutdown(); + } + default: + break; + } + return false; +} +} diff --git a/fbw-a32nx/src/wasm/extra-backend-a32nx/src/LightingPresets/LightingPresets_A32NX.cpp b/fbw-a32nx/src/wasm/extra-backend-a32nx/src/LightingPresets/LightingPresets_A32NX.cpp new file mode 100644 index 00000000000..05572d1fbc1 --- /dev/null +++ b/fbw-a32nx/src/wasm/extra-backend-a32nx/src/LightingPresets/LightingPresets_A32NX.cpp @@ -0,0 +1,275 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include "LightingPresets_A32NX.h" +#include "UpdateMode.h" +#include "logging.h" + +/// +// DataManager Howto Note: +// ======================= + +// The LightingPresets_A32NX module uses the DataManager to get and set variables. +// Looking at the make_xxx_var functions, you can see that they are updated +// with different update cycles. +// +// Some variables are read from the sim at every tick: +// - A32NX_ELEC_AC_1_BUS_IS_POWERED +// +// Some variables are read and written from/to the sim at every tick: +// - A32NX_LIGHTING_PRESET_LOAD +// - A32NX_LIGHTING_PRESET_SAVE +// +// The rest are read on demand after the state of the above variables have been checked. +// +// This makes sure variables are only read or written to when really needed. And as +// LightingPresets_A32NX will be dormant most of the time, this is saving a lot of +// unnecessary reads/writes. +/// + +bool LightingPresets_A32NX::initialize() { + dataManager = &msfsHandler.getDataManager(); + + // Events for setting the aircraft variables + lightPotentiometerSetEvent = dataManager->make_sim_event("LIGHT_POTENTIOMETER_SET", NOTIFICATION_GROUP_1); + cabinLightSetEvent = dataManager->make_sim_event("CABIN_LIGHTS_SET", NOTIFICATION_GROUP_1); + + // Control LVARs - auto updated with every tick - LOAD/SAVE also auto written to sim + elecAC1Powered = dataManager->make_named_var("ELEC_AC_1_BUS_IS_POWERED", UNITS.Number, UpdateMode::AUTO_READ); + loadLightingPresetRequest = dataManager->make_named_var("LIGHTING_PRESET_LOAD", UNITS.Number, UpdateMode::AUTO_READ_WRITE); + saveLightingPresetRequest = dataManager->make_named_var("LIGHTING_PRESET_SAVE", UNITS.Number, UpdateMode::AUTO_READ_WRITE); + + // Lighting LVARs - manual update and write when load/saving is requested + efbBrightness = dataManager->make_named_var("EFB_BRIGHTNESS"); + dcduLeftLightLevel = dataManager->make_named_var("PANEL_DCDU_L_BRIGHTNESS"); + dcduRightLightLevel = dataManager->make_named_var("PANEL_DCDU_R_BRIGHTNESS"); + mcduLeftLightLevel = dataManager->make_named_var("MCDU_L_BRIGHTNESS"); + mcduRightLightLevel = dataManager->make_named_var("MCDU_R_BRIGHTNESS"); + + // Light Potentiometers - manual update and write when load/saving is requested + lightCabin = dataManager->make_aircraft_var("LIGHT CABIN", 0, "", cabinLightSetEvent, UNITS.Percent); + lightCabinLevel = getLightPotentiometerVar(7); + ovhdIntegralLightLevel = getLightPotentiometerVar(86); + glareshieldIntegralLightLevel = getLightPotentiometerVar(84); + glareshieldLcdLightLevel = getLightPotentiometerVar(87); + tableLightCptLevel = getLightPotentiometerVar(10); + tableLightFoLevel = getLightPotentiometerVar(11); + pfdBrtCptLevel = getLightPotentiometerVar(88); + ndBrtCptLevel = getLightPotentiometerVar(89); + wxTerrainBrtCptLevel = getLightPotentiometerVar(94); + consoleLightCptLevel = getLightPotentiometerVar(8); + pfdBrtFoLevel = getLightPotentiometerVar(90); + ndBrtFoLevel = getLightPotentiometerVar(91); + wxTerrainBrtFoLevel = getLightPotentiometerVar(95); + consoleLightFoLevel = getLightPotentiometerVar(9); + ecamUpperLightLevel = getLightPotentiometerVar(92); + ecamLowerLightLevel = getLightPotentiometerVar(93); + floodPnlLightLevel = getLightPotentiometerVar(83); + pedestalIntegralLightLevel = getLightPotentiometerVar(85); + floodPedLightLevel = getLightPotentiometerVar(76); + + loadLightingPresetRequest->setAsInt64(0); + saveLightingPresetRequest->setAsInt64(0); + + _isInitialized = true; + LOG_INFO("LightingPresets_A32NX initialized"); + return true; +} + +// ================================================================================================= +// PRIVATE METHODS +// ================================================================================================= + +void LightingPresets_A32NX::readFromAircraft() { + currentLightValues.efbBrightness = efbBrightness->readFromSim(); + currentLightValues.cabinLightLevel = lightCabinLevel->readFromSim(); + currentLightValues.ovhdIntegralLightLevel = ovhdIntegralLightLevel->readFromSim(); + currentLightValues.glareshieldIntegralLightLevel = glareshieldIntegralLightLevel->readFromSim(); + currentLightValues.glareshieldLcdLightLevel = glareshieldLcdLightLevel->readFromSim(); + currentLightValues.tableLightCptLevel = tableLightCptLevel->readFromSim(); + currentLightValues.tableLightFoLevel = tableLightFoLevel->readFromSim(); + currentLightValues.pfdBrtCptLevel = pfdBrtCptLevel->readFromSim(); + currentLightValues.ndBrtCptLevel = ndBrtCptLevel->readFromSim(); + currentLightValues.wxTerrainBrtCptLevel = wxTerrainBrtCptLevel->readFromSim(); + currentLightValues.consoleLightCptLevel = consoleLightCptLevel->readFromSim(); + currentLightValues.pfdBrtFoLevel = pfdBrtFoLevel->readFromSim(); + currentLightValues.ndBrtFoLevel = ndBrtFoLevel->readFromSim(); + currentLightValues.wxTerrainBrtFoLevel = wxTerrainBrtFoLevel->readFromSim(); + currentLightValues.consoleLightFoLevel = consoleLightFoLevel->readFromSim(); + currentLightValues.dcduLeftLightLevel = dcduLeftLightLevel->readFromSim(); + currentLightValues.dcduRightLightLevel = dcduLeftLightLevel->readFromSim(); + currentLightValues.mcduLeftLightLevel = mcduLeftLightLevel->readFromSim(); + currentLightValues.mcduRightLightLevel = mcduRightLightLevel->readFromSim(); + currentLightValues.ecamUpperLightLevel = ecamUpperLightLevel->readFromSim(); + currentLightValues.ecamLowerLightLevel = ecamLowerLightLevel->readFromSim(); + currentLightValues.floodPnlLightLevel = floodPnlLightLevel->readFromSim(); + currentLightValues.pedestalIntegralLightLevel = pedestalIntegralLightLevel->readFromSim(); + currentLightValues.floodPedLightLevel = floodPedLightLevel->readFromSim(); +} + +void LightingPresets_A32NX::applyToAircraft() { + efbBrightness->setAndWriteToSim(intermediateLightValues.efbBrightness); + setValidCabinLightValue(intermediateLightValues.cabinLightLevel); + ovhdIntegralLightLevel->setAndWriteToSim(intermediateLightValues.ovhdIntegralLightLevel); + glareshieldIntegralLightLevel->setAndWriteToSim(intermediateLightValues.glareshieldIntegralLightLevel); + glareshieldLcdLightLevel->setAndWriteToSim(intermediateLightValues.glareshieldLcdLightLevel); + tableLightCptLevel->setAndWriteToSim(intermediateLightValues.tableLightCptLevel); + tableLightFoLevel->setAndWriteToSim(intermediateLightValues.tableLightFoLevel); + pfdBrtCptLevel->setAndWriteToSim(intermediateLightValues.pfdBrtCptLevel); + ndBrtCptLevel->setAndWriteToSim(intermediateLightValues.ndBrtCptLevel); + wxTerrainBrtCptLevel->setAndWriteToSim(intermediateLightValues.wxTerrainBrtCptLevel); + consoleLightCptLevel->setAndWriteToSim(intermediateLightValues.consoleLightCptLevel); + pfdBrtFoLevel->setAndWriteToSim(intermediateLightValues.pfdBrtFoLevel); + ndBrtFoLevel->setAndWriteToSim(intermediateLightValues.ndBrtFoLevel); + wxTerrainBrtFoLevel->setAndWriteToSim(intermediateLightValues.wxTerrainBrtFoLevel); + consoleLightFoLevel->setAndWriteToSim(intermediateLightValues.consoleLightFoLevel); + dcduLeftLightLevel->setAndWriteToSim(intermediateLightValues.dcduLeftLightLevel); + dcduRightLightLevel->setAndWriteToSim(intermediateLightValues.dcduRightLightLevel); + mcduLeftLightLevel->setAndWriteToSim(intermediateLightValues.mcduLeftLightLevel); + mcduRightLightLevel->setAndWriteToSim(intermediateLightValues.mcduRightLightLevel); + ecamUpperLightLevel->setAndWriteToSim(intermediateLightValues.ecamUpperLightLevel); + ecamLowerLightLevel->setAndWriteToSim(intermediateLightValues.ecamLowerLightLevel); + floodPnlLightLevel->setAndWriteToSim(intermediateLightValues.floodPnlLightLevel); + pedestalIntegralLightLevel->setAndWriteToSim(intermediateLightValues.pedestalIntegralLightLevel); + floodPedLightLevel->setAndWriteToSim(intermediateLightValues.floodPedLightLevel); +} + +void LightingPresets_A32NX::loadFromIni(const mINI::INIStructure& ini, + const std::string& iniSectionName) { + // check if iniSectionName is available + // if not use a 50% default iniSectionName + if (!ini.has(iniSectionName)) { + intermediateLightValues = DEFAULT_50; + return; + } + + // reading data structure from ini + loadedLightValues.efbBrightness = iniGetOrDefault(ini, iniSectionName, "efb_brightness", 80.0); + loadedLightValues.cabinLightLevel = iniGetOrDefault(ini, iniSectionName, "cabin_light", 50.0); + loadedLightValues.ovhdIntegralLightLevel = iniGetOrDefault(ini, iniSectionName, "ovhd_int_lt", 50.0); + loadedLightValues.glareshieldIntegralLightLevel = iniGetOrDefault(ini, iniSectionName, "glareshield_int_lt", 50.0); + loadedLightValues.glareshieldLcdLightLevel = iniGetOrDefault(ini, iniSectionName, "glareshield_lcd_lt", 50.0); + loadedLightValues.tableLightCptLevel = iniGetOrDefault(ini, iniSectionName, "table_cpt_lt", 50.0); + loadedLightValues.tableLightFoLevel = iniGetOrDefault(ini, iniSectionName, "table_fo_lt", 50.0); + loadedLightValues.pfdBrtCptLevel = iniGetOrDefault(ini, iniSectionName, "pfd_cpt_lvl", 50.0); + loadedLightValues.ndBrtCptLevel = iniGetOrDefault(ini, iniSectionName, "nd_cpt_lvl", 50.0); + loadedLightValues.wxTerrainBrtCptLevel = iniGetOrDefault(ini, iniSectionName, "wx_cpt_lvl", 50.0); + loadedLightValues.consoleLightCptLevel = iniGetOrDefault(ini, iniSectionName, "console_cpt_lt", 50.0); + loadedLightValues.pfdBrtFoLevel = iniGetOrDefault(ini, iniSectionName, "pfd_fo_lvl", 50.0); + loadedLightValues.ndBrtFoLevel = iniGetOrDefault(ini, iniSectionName, "nd_fo_lvl", 50.0); + loadedLightValues.wxTerrainBrtFoLevel = iniGetOrDefault(ini, iniSectionName, "wx_fo_lvl", 50.0); + loadedLightValues.consoleLightFoLevel = iniGetOrDefault(ini, iniSectionName, "console_fo_lt", 50.0); + loadedLightValues.dcduLeftLightLevel = iniGetOrDefault(ini, iniSectionName, "dcdu_left_lvl", 50.0) / 100; + loadedLightValues.dcduRightLightLevel = iniGetOrDefault(ini, iniSectionName, "dcdu_right_lvl", 50.0) / 100; + loadedLightValues.mcduLeftLightLevel = iniGetOrDefault(ini, iniSectionName, "mcdu_left_lvl", 50.0) / 100; + loadedLightValues.mcduRightLightLevel = iniGetOrDefault(ini, iniSectionName, "mcdu_right_lvl", 50.0) / 100; + loadedLightValues.ecamUpperLightLevel = iniGetOrDefault(ini, iniSectionName, "ecam_upper_lvl", 50.0); + loadedLightValues.ecamLowerLightLevel = iniGetOrDefault(ini, iniSectionName, "ecam_lower_lvl", 50.0); + loadedLightValues.floodPnlLightLevel = iniGetOrDefault(ini, iniSectionName, "flood_pnl_lt", 50.0); + loadedLightValues.pedestalIntegralLightLevel = iniGetOrDefault(ini, iniSectionName, "pedestal_int_lt", 50.0); + loadedLightValues.floodPedLightLevel = iniGetOrDefault(ini, iniSectionName, "flood_ped_lvl", 50.0); +} + +void LightingPresets_A32NX::saveToIni(mINI::INIStructure& ini, const std::string& iniSectionName) const { + ini[iniSectionName]["efb_brightness"] = std::to_string(currentLightValues.efbBrightness); + ini[iniSectionName]["cabin_light"] = std::to_string(currentLightValues.cabinLightLevel); + ini[iniSectionName]["ovhd_int_lt"] = std::to_string(currentLightValues.ovhdIntegralLightLevel); + ini[iniSectionName]["glareshield_int_lt"] = std::to_string(currentLightValues.glareshieldIntegralLightLevel); + ini[iniSectionName]["glareshield_lcd_lt"] = std::to_string(currentLightValues.glareshieldLcdLightLevel); + ini[iniSectionName]["table_cpt_lt"] = std::to_string(currentLightValues.tableLightCptLevel); + ini[iniSectionName]["table_fo_lt"] = std::to_string(currentLightValues.tableLightFoLevel); + ini[iniSectionName]["pfd_cpt_lvl"] = std::to_string(currentLightValues.pfdBrtCptLevel); + ini[iniSectionName]["nd_cpt_lvl"] = std::to_string(currentLightValues.ndBrtCptLevel); + ini[iniSectionName]["wx_cpt_lvl"] = std::to_string(currentLightValues.wxTerrainBrtCptLevel); + ini[iniSectionName]["console_cpt_lt"] = std::to_string(currentLightValues.consoleLightCptLevel); + ini[iniSectionName]["pfd_fo_lvl"] = std::to_string(currentLightValues.pfdBrtFoLevel); + ini[iniSectionName]["nd_fo_lvl"] = std::to_string(currentLightValues.ndBrtFoLevel); + ini[iniSectionName]["wx_fo_lvl"] = std::to_string(currentLightValues.wxTerrainBrtFoLevel); + ini[iniSectionName]["console_fo_lt"] = std::to_string(currentLightValues.consoleLightFoLevel); + ini[iniSectionName]["dcdu_left_lvl"] = std::to_string(currentLightValues.dcduLeftLightLevel * 100); + ini[iniSectionName]["dcdu_right_lvl"] = std::to_string(currentLightValues.dcduRightLightLevel * 100); + ini[iniSectionName]["mcdu_left_lvl"] = std::to_string(currentLightValues.mcduLeftLightLevel * 100); + ini[iniSectionName]["mcdu_right_lvl"] = std::to_string(currentLightValues.mcduRightLightLevel * 100); + ini[iniSectionName]["ecam_upper_lvl"] = std::to_string(currentLightValues.ecamUpperLightLevel); + ini[iniSectionName]["ecam_lower_lvl"] = std::to_string(currentLightValues.ecamLowerLightLevel); + ini[iniSectionName]["flood_pnl_lt"] = std::to_string(currentLightValues.floodPnlLightLevel); + ini[iniSectionName]["pedestal_int_lt"] = std::to_string(currentLightValues.pedestalIntegralLightLevel); + ini[iniSectionName]["flood_ped_lvl"] = std::to_string(currentLightValues.floodPedLightLevel); +} + +[[maybe_unused]] std::string LightingPresets_A32NX::str() const { + std::ostringstream os; + os << "EFB Brightness: " << intermediateLightValues.efbBrightness << std::endl; + os << "Cabin Light: " << intermediateLightValues.cabinLightLevel << std::endl; + os << "Ovhd Int Lt: " << intermediateLightValues.ovhdIntegralLightLevel << std::endl; + os << "Glareshield Int Lt: " << intermediateLightValues.glareshieldIntegralLightLevel << std::endl; + os << "Glareshield Lcd Lt: " << intermediateLightValues.glareshieldLcdLightLevel << std::endl; + os << "Table Cpt Lt: " << intermediateLightValues.tableLightCptLevel << std::endl; + os << "Table FO Lt: " << intermediateLightValues.tableLightFoLevel << std::endl; + os << "PFD Cpt Lvl: " << intermediateLightValues.pfdBrtCptLevel << std::endl; + os << "ND Cpt Lvl: " << intermediateLightValues.ndBrtCptLevel << std::endl; + os << "WX Cpt Lvl: " << intermediateLightValues.wxTerrainBrtCptLevel << std::endl; + os << "Console Cpt Lt: " << intermediateLightValues.consoleLightCptLevel << std::endl; + os << "PFD FO Lvl: " << intermediateLightValues.pfdBrtFoLevel << std::endl; + os << "ND FO Lvl: " << intermediateLightValues.ndBrtFoLevel << std::endl; + os << "WX FO Lvl: " << intermediateLightValues.wxTerrainBrtFoLevel << std::endl; + os << "Console Fo Lt: " << intermediateLightValues.consoleLightFoLevel << std::endl; + os << "DCDU Left Lvl: " << intermediateLightValues.dcduLeftLightLevel << std::endl; + os << "DCDU Right Lvl: " << intermediateLightValues.dcduRightLightLevel << std::endl; + os << "MCDU Left Lvl: " << intermediateLightValues.mcduLeftLightLevel << std::endl; + os << "MCDU Right Lvl: " << intermediateLightValues.mcduRightLightLevel << std::endl; + os << "ECAM Upper Lvl: " << intermediateLightValues.ecamUpperLightLevel << std::endl; + os << "ECAM Lower Lvl: " << intermediateLightValues.ecamLowerLightLevel << std::endl; + os << "Floor Cpt Lt: " << intermediateLightValues.floodPnlLightLevel << std::endl; + os << "Pedestal Int Lt: " << intermediateLightValues.pedestalIntegralLightLevel << std::endl; + os << "Floor FO Lvl: " << intermediateLightValues.floodPedLightLevel << std::endl; + return os.str(); +} + +void LightingPresets_A32NX::setValidCabinLightValue(FLOAT64 level) { + // cabin light level needs to either be 0, 50 or 100 for the switch position + // in the aircraft to work. + if (level <= 0.0) { + level = 0.0; + } else if (level > 0.0 && level <= 50.0) { + level = 50.0; + } else if ((level > 0.0 && level > 50.0)) { + level = 100.0; + } + // cabin lights in the A32NX need to be controlled by two vars + // one for the switch position and one for the actual light + lightCabinLevel->setAndWriteToSim(level); + lightCabin->setAndWriteToSim(level > 0 ? 1 : 0); +} + +bool LightingPresets_A32NX::calculateIntermediateValues() { + // clang-format off + intermediateLightValues.efbBrightness = convergeValue( currentLightValues.efbBrightness,loadedLightValues.efbBrightness); + intermediateLightValues.cabinLightLevel = loadedLightValues.cabinLightLevel; + intermediateLightValues.ovhdIntegralLightLevel = convergeValue( currentLightValues.ovhdIntegralLightLevel,loadedLightValues.ovhdIntegralLightLevel); + intermediateLightValues.glareshieldIntegralLightLevel = convergeValue( currentLightValues.glareshieldIntegralLightLevel,loadedLightValues.glareshieldIntegralLightLevel); + intermediateLightValues.glareshieldLcdLightLevel = convergeValue( currentLightValues.glareshieldLcdLightLevel,loadedLightValues.glareshieldLcdLightLevel); + intermediateLightValues.tableLightCptLevel = convergeValue( currentLightValues.tableLightCptLevel,loadedLightValues.tableLightCptLevel); + intermediateLightValues.tableLightFoLevel = convergeValue( currentLightValues.tableLightFoLevel,loadedLightValues.tableLightFoLevel); + intermediateLightValues.pfdBrtCptLevel = convergeValue( currentLightValues.pfdBrtCptLevel,loadedLightValues.pfdBrtCptLevel); + intermediateLightValues.ndBrtCptLevel = convergeValue( currentLightValues.ndBrtCptLevel,loadedLightValues.ndBrtCptLevel); + intermediateLightValues.wxTerrainBrtCptLevel = convergeValue( currentLightValues.wxTerrainBrtCptLevel,loadedLightValues.wxTerrainBrtCptLevel); + intermediateLightValues.consoleLightCptLevel = convergeValue( currentLightValues.consoleLightCptLevel,loadedLightValues.consoleLightCptLevel); + intermediateLightValues.pfdBrtFoLevel = convergeValue( currentLightValues.pfdBrtFoLevel,loadedLightValues.pfdBrtFoLevel); + intermediateLightValues.ndBrtFoLevel = convergeValue( currentLightValues.ndBrtFoLevel,loadedLightValues.ndBrtFoLevel); + intermediateLightValues.wxTerrainBrtFoLevel = convergeValue( currentLightValues.wxTerrainBrtFoLevel,loadedLightValues.wxTerrainBrtFoLevel); + intermediateLightValues.consoleLightFoLevel = convergeValue( currentLightValues.consoleLightFoLevel,loadedLightValues.consoleLightFoLevel); + intermediateLightValues.dcduLeftLightLevel = convergeValue( currentLightValues.dcduLeftLightLevel,loadedLightValues.dcduLeftLightLevel); + intermediateLightValues.dcduRightLightLevel = convergeValue( currentLightValues.dcduRightLightLevel,loadedLightValues.dcduRightLightLevel); + intermediateLightValues.mcduLeftLightLevel = convergeValue( currentLightValues.mcduLeftLightLevel,loadedLightValues.mcduLeftLightLevel); + intermediateLightValues.mcduRightLightLevel = convergeValue( currentLightValues.mcduRightLightLevel,loadedLightValues.mcduRightLightLevel); + intermediateLightValues.ecamUpperLightLevel = convergeValue( currentLightValues.ecamUpperLightLevel,loadedLightValues.ecamUpperLightLevel); + intermediateLightValues.ecamLowerLightLevel = convergeValue( currentLightValues.ecamLowerLightLevel,loadedLightValues.ecamLowerLightLevel); + intermediateLightValues.floodPnlLightLevel = convergeValue( currentLightValues.floodPnlLightLevel,loadedLightValues.floodPnlLightLevel); + intermediateLightValues.pedestalIntegralLightLevel = convergeValue( currentLightValues.pedestalIntegralLightLevel,loadedLightValues.pedestalIntegralLightLevel); + intermediateLightValues.floodPedLightLevel = convergeValue( currentLightValues.floodPedLightLevel,loadedLightValues.floodPedLightLevel); + // clang-format on + return intermediateLightValues == loadedLightValues; +} diff --git a/fbw-a32nx/src/wasm/extra-backend-a32nx/src/LightingPresets/LightingPresets_A32NX.h b/fbw-a32nx/src/wasm/extra-backend-a32nx/src/LightingPresets/LightingPresets_A32NX.h new file mode 100644 index 00000000000..e6aa4b274c8 --- /dev/null +++ b/fbw-a32nx/src/wasm/extra-backend-a32nx/src/LightingPresets/LightingPresets_A32NX.h @@ -0,0 +1,161 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_LIGHTINGPRESETS_H +#define FLYBYWIRE_LIGHTINGPRESETS_H + +#include "LightingPresets/LightingPresets.h" +#include "math_utils.hpp" + +class MsfsHandler; + +// Struct to hold all relevant light levels for the A32NX +struct LightingValues_A32NX { + // EFB + FLOAT64 efbBrightness; // A32NX_EFB_BRIGHTNESS + // OVHD + FLOAT64 cabinLightLevel; // 7 (0, 50, 100) + FLOAT64 ovhdIntegralLightLevel; // 86 + // Glareshield + FLOAT64 glareshieldIntegralLightLevel; // 84 + FLOAT64 glareshieldLcdLightLevel; // 87 + FLOAT64 tableLightCptLevel; // 10 + FLOAT64 tableLightFoLevel; // 11 + // Instruments + FLOAT64 pfdBrtCptLevel; // 88 + FLOAT64 ndBrtCptLevel; // 89 + FLOAT64 wxTerrainBrtCptLevel; // 94 + FLOAT64 consoleLightCptLevel; // 8 (0, 50, 100) + FLOAT64 pfdBrtFoLevel; // 90 + FLOAT64 ndBrtFoLevel; // 91 + FLOAT64 wxTerrainBrtFoLevel; // 95 + FLOAT64 consoleLightFoLevel; // 9 (0, 50, 100) + // ISIS display has automatic brightness adjustment. + FLOAT64 dcduLeftLightLevel; // A32NX_PANEL_DCDU_L_BRIGHTNESS 0.0..1.0 + FLOAT64 dcduRightLightLevel; // A32NX_PANEL_DCDU_R_BRIGHTNESS 0.0..1.0 + FLOAT64 mcduLeftLightLevel; // A32NX_MCDU_L_BRIGHTNESS 0.0..1.0 + FLOAT64 mcduRightLightLevel; // A32NX_MCDU_R_BRIGHTNESS 0.0..1.0 + // Pedestal + FLOAT64 ecamUpperLightLevel; // 92 + FLOAT64 ecamLowerLightLevel; // 93 + FLOAT64 floodPnlLightLevel; // 83 + FLOAT64 pedestalIntegralLightLevel; // 85 + FLOAT64 floodPedLightLevel; // 76 +}; + +/** + * This module is responsible for the lighting presets. + * It stores and reads the current lighting preset from and to an ini-file in the work folder. + * + * It is controlled by two LVARs: + * - A32NX_LIGHTING_PRESET_LOAD + * - A32NX_LIGHTING_PRESET_SAVE + * + * If these are set to a number >0 the module will load or save the preset with the given number + * from and to the ini-file or create a new preset based on default (load) or current (save) + * lighting values. + */ +class LightingPresets_A32NX : public LightingPresets { + private: + // Lighting LVARs + NamedVariablePtr efbBrightness; + NamedVariablePtr dcduLeftLightLevel; + NamedVariablePtr dcduRightLightLevel; + NamedVariablePtr mcduLeftLightLevel; + NamedVariablePtr mcduRightLightLevel; + + // Lighting Aircraft Vars + AircraftVariablePtr lightCabin; + AircraftVariablePtr lightCabinLevel; + AircraftVariablePtr ovhdIntegralLightLevel; + AircraftVariablePtr glareshieldIntegralLightLevel; + AircraftVariablePtr glareshieldLcdLightLevel; + AircraftVariablePtr tableLightCptLevel; + AircraftVariablePtr tableLightFoLevel; + AircraftVariablePtr pfdBrtCptLevel; + AircraftVariablePtr ndBrtCptLevel; + AircraftVariablePtr wxTerrainBrtCptLevel; + AircraftVariablePtr consoleLightCptLevel; + AircraftVariablePtr pfdBrtFoLevel; + AircraftVariablePtr ndBrtFoLevel; + AircraftVariablePtr wxTerrainBrtFoLevel; + AircraftVariablePtr consoleLightFoLevel; + AircraftVariablePtr ecamUpperLightLevel; + AircraftVariablePtr ecamLowerLightLevel; + AircraftVariablePtr floodPnlLightLevel; + AircraftVariablePtr pedestalIntegralLightLevel; + AircraftVariablePtr floodPedLightLevel; + + ClientEventPtr cabinLightSetEvent; + + // THe current lighting values in the aircraft + LightingValues_A32NX currentLightValues{}; + // The lighting values that are loaded from the ini-file + LightingValues_A32NX loadedLightValues{}; + // The lighting values that are used to converge from the current values to the preset values + LightingValues_A32NX intermediateLightValues{}; + + public: + LightingPresets_A32NX() = delete; + + /** + * Creates a new LightingPresets_A32NX instance and takes a reference to the MsfsHandler instance. + * @param msfsHandler The MsfsHandler instance that is used to communicate with the simulator. + */ + explicit LightingPresets_A32NX(MsfsHandler& msfsHandler) : LightingPresets(msfsHandler) {} + + bool initialize() override; + + /** + * Produces a string with the current settings and their values. + * @return string with the current settings and their values. + */ + [[maybe_unused]] [[nodiscard]] std::string str() const; + + private: + void readFromAircraft() override; + void applyToAircraft() override; + bool calculateIntermediateValues() override; + void loadFromIni(const mINI::INIStructure& ini, const std::string& iniSectionName) override; + void saveToIni(mINI::INIStructure& ini, const std::string& iniSectionName) const override; + + /** + * cabin lights in the A32NX need to be controlled by two vars + * one for the switch position and one for the actual light + * @param level the level to set the lights to + */ + void setValidCabinLightValue(FLOAT64 level); + + const LightingValues_A32NX 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}; +}; + +inline bool operator==(const LightingValues_A32NX& p1, const LightingValues_A32NX& p2) { + const double epsilon = 0.1; + return helper::Math::almostEqual(p1.efbBrightness, p2.efbBrightness, epsilon) && + helper::Math::almostEqual(p1.cabinLightLevel, p2.cabinLightLevel, epsilon) && + helper::Math::almostEqual(p1.ovhdIntegralLightLevel, p2.ovhdIntegralLightLevel, epsilon) && + helper::Math::almostEqual(p1.glareshieldIntegralLightLevel, p2.glareshieldIntegralLightLevel, epsilon) && + helper::Math::almostEqual(p1.glareshieldLcdLightLevel, p2.glareshieldLcdLightLevel, epsilon) && + helper::Math::almostEqual(p1.tableLightCptLevel, p2.tableLightCptLevel, epsilon) && + helper::Math::almostEqual(p1.tableLightFoLevel, p2.tableLightFoLevel, epsilon) && + helper::Math::almostEqual(p1.pfdBrtCptLevel, p2.pfdBrtCptLevel, epsilon) && + helper::Math::almostEqual(p1.ndBrtCptLevel, p2.ndBrtCptLevel, epsilon) && + helper::Math::almostEqual(p1.wxTerrainBrtCptLevel, p2.wxTerrainBrtCptLevel, epsilon) && + helper::Math::almostEqual(p1.consoleLightCptLevel, p2.consoleLightCptLevel, epsilon) && + helper::Math::almostEqual(p1.pfdBrtFoLevel, p2.pfdBrtFoLevel, epsilon) && + helper::Math::almostEqual(p1.ndBrtFoLevel, p2.ndBrtFoLevel, epsilon) && + helper::Math::almostEqual(p1.wxTerrainBrtFoLevel, p2.wxTerrainBrtFoLevel, epsilon) && + helper::Math::almostEqual(p1.consoleLightFoLevel, p2.consoleLightFoLevel, epsilon) && + helper::Math::almostEqual(p1.dcduLeftLightLevel, p2.dcduLeftLightLevel, epsilon) && + helper::Math::almostEqual(p1.dcduRightLightLevel, p2.dcduRightLightLevel, epsilon) && + helper::Math::almostEqual(p1.mcduLeftLightLevel, p2.mcduLeftLightLevel, epsilon) && + helper::Math::almostEqual(p1.mcduRightLightLevel, p2.mcduRightLightLevel, epsilon) && + helper::Math::almostEqual(p1.ecamUpperLightLevel, p2.ecamUpperLightLevel, epsilon) && + helper::Math::almostEqual(p1.ecamLowerLightLevel, p2.ecamLowerLightLevel, epsilon) && + helper::Math::almostEqual(p1.floodPnlLightLevel, p2.floodPnlLightLevel, epsilon) && + helper::Math::almostEqual(p1.pedestalIntegralLightLevel, p2.pedestalIntegralLightLevel, epsilon) && + helper::Math::almostEqual(p1.floodPedLightLevel, p2.floodPedLightLevel, epsilon); +} + +#endif // FLYBYWIRE_LIGHTINGPRESETS_H diff --git a/fbw-a32nx/src/wasm/fadec_a320/CMakeLists.txt b/fbw-a32nx/src/wasm/fadec_a320/CMakeLists.txt index f0aab71a59e..d51e4342832 100644 --- a/fbw-a32nx/src/wasm/fadec_a320/CMakeLists.txt +++ b/fbw-a32nx/src/wasm/fadec_a320/CMakeLists.txt @@ -2,6 +2,7 @@ # through the code in an IDE. It is not used for building the project. cmake_minimum_required(VERSION 3.19) + project(flybywire-a32nx-fadec) set(CMAKE_CXX_STANDARD 20) @@ -14,12 +15,12 @@ include_directories("${MSFS_SDK}/WASM/wasi-sysroot/include") include_directories("${MSFS_SDK}/SimConnect SDK/include") include_directories( - ./src - ${FBW_ROOT}/fbw-common/src/wasm/fadec_common/src - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/inih + ./src + ${FBW_ROOT}/fbw-common/src/wasm/fadec_common/src + ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src + ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/inih ) add_executable(flybywire-a32nx-fadec - ./src/FadecGauge.cpp -) + ./src/FadecGauge.cpp + ) diff --git a/fbw-a32nx/src/wasm/fbw_a320/CMakeLists.txt b/fbw-a32nx/src/wasm/fbw_a320/CMakeLists.txt index a6300973eb1..6198a863c39 100644 --- a/fbw-a32nx/src/wasm/fbw_a320/CMakeLists.txt +++ b/fbw-a32nx/src/wasm/fbw_a320/CMakeLists.txt @@ -2,10 +2,10 @@ # through the code in an IDE. It is not used for building the project. cmake_minimum_required(VERSION 3.19) + project(flybywire-a32nx-fbw) set(CMAKE_CXX_STANDARD 20) -set(MSFS_SDK "C:\\MSFS SDK") set(CMAKE_CXX_FLAGS "-c -g -DDEBUG -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") @@ -14,68 +14,68 @@ include_directories("${MSFS_SDK}/WASM/wasi-sysroot/include") include_directories("${MSFS_SDK}/SimConnect SDK/include") include_directories( - ./src - ./src/inih - ./src/busStructures - ./src/elac - ./src/fac - ./src/failures - ./src/fcdc - ./src/interface - ./src/model - ./src/sec - ./src/utils - ${FBW_ROOT}/fbw-common/src/wasm/fadec_common/src/zlib - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/inih - ${FBW_ROOT}/fbw-common/src/wasm/fbw-common/src + ./src + ./src/inih + ./src/busStructures + ./src/elac + ./src/fac + ./src/failures + ./src/fcdc + ./src/interface + ./src/model + ./src/sec + ./src/utils + ${FBW_ROOT}/fbw-common/src/wasm/fadec_common/src/zlib + ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/inih + ${FBW_ROOT}/fbw-common/src/wasm/fbw-common/src ) add_executable(flybywire-a32nx-fbw - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/zlib/zfstream.cc - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/LocalVariable.cpp - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/ThrottleAxisMapping.cpp - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/InterpolatingLookupTable.cpp - src/interface/SimConnectInterface.cpp - src/elac/Elac.cpp - src/sec/Sec.cpp - src/fcdc/Fcdc.cpp - src/fac/Fac.cpp - src/failures/FailuresConsumer.cpp - src/utils/ConfirmNode.cpp - src/utils/SRFlipFLop.cpp - src/utils/PulseNode.cpp - src/utils/HysteresisNode.cpp - src/model/AutopilotLaws_data.cpp - src/model/AutopilotLaws.cpp - src/model/AutopilotStateMachine_data.cpp - src/model/AutopilotStateMachine.cpp - src/model/Autothrust_data.cpp - src/model/Autothrust.cpp - src/model/Double2MultiWord.cpp - src/model/ElacComputer_data.cpp - src/model/ElacComputer.cpp - src/model/SecComputer_data.cpp - src/model/SecComputer.cpp - src/model/PitchNormalLaw.cpp - src/model/PitchAlternateLaw.cpp - src/model/PitchDirectLaw.cpp - src/model/LateralNormalLaw.cpp - src/model/LateralDirectLaw.cpp - src/model/FacComputer_data.cpp - src/model/FacComputer.cpp - src/model/look1_binlxpw.cpp - src/model/look2_binlcpw.cpp - src/model/look2_binlxpw.cpp - src/model/mod_mvZvttxs.cpp - src/model/MultiWordIor.cpp - src/model/rt_modd.cpp - src/model/rt_remd.cpp - src/model/uMultiWord2Double.cpp - src/FlyByWireInterface.cpp - src/FlightDataRecorder.cpp - src/Arinc429.cpp - src/Arinc429Utils.cpp - src/SpoilersHandler.cpp - src/CalculatedRadioReceiver.cpp - src/main.cpp -) + ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/zlib/zfstream.cc + ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/LocalVariable.cpp + ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/ThrottleAxisMapping.cpp + ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/InterpolatingLookupTable.cpp + src/interface/SimConnectInterface.cpp + src/elac/Elac.cpp + src/sec/Sec.cpp + src/fcdc/Fcdc.cpp + src/fac/Fac.cpp + src/failures/FailuresConsumer.cpp + src/utils/ConfirmNode.cpp + src/utils/SRFlipFLop.cpp + src/utils/PulseNode.cpp + src/utils/HysteresisNode.cpp + src/model/AutopilotLaws_data.cpp + src/model/AutopilotLaws.cpp + src/model/AutopilotStateMachine_data.cpp + src/model/AutopilotStateMachine.cpp + src/model/Autothrust_data.cpp + src/model/Autothrust.cpp + src/model/Double2MultiWord.cpp + src/model/ElacComputer_data.cpp + src/model/ElacComputer.cpp + src/model/SecComputer_data.cpp + src/model/SecComputer.cpp + src/model/PitchNormalLaw.cpp + src/model/PitchAlternateLaw.cpp + src/model/PitchDirectLaw.cpp + src/model/LateralNormalLaw.cpp + src/model/LateralDirectLaw.cpp + src/model/FacComputer_data.cpp + src/model/FacComputer.cpp + src/model/look1_binlxpw.cpp + src/model/look2_binlcpw.cpp + src/model/look2_binlxpw.cpp + src/model/mod_mvZvttxs.cpp + src/model/MultiWordIor.cpp + src/model/rt_modd.cpp + src/model/rt_remd.cpp + src/model/uMultiWord2Double.cpp + src/FlyByWireInterface.cpp + src/FlightDataRecorder.cpp + src/Arinc429.cpp + src/Arinc429Utils.cpp + src/SpoilersHandler.cpp + src/CalculatedRadioReceiver.cpp + src/main.cpp + ) diff --git a/fbw-a32nx/src/wasm/fbw_a320/build.sh b/fbw-a32nx/src/wasm/fbw_a320/build.sh index bbd316894d6..53aefe573c5 100755 --- a/fbw-a32nx/src/wasm/fbw_a320/build.sh +++ b/fbw-a32nx/src/wasm/fbw_a320/build.sh @@ -138,12 +138,9 @@ clang++ \ "${DIR}/src/CalculatedRadioReceiver.cpp" \ "${DIR}/src/main.cpp" \ - # restore directory popd - - # link modules wasm-ld \ --no-entry \ diff --git a/fbw-a32nx/src/wasm/flypad-backend/CMakeLists.txt b/fbw-a32nx/src/wasm/flypad-backend/CMakeLists.txt deleted file mode 100644 index 1a9f582c695..00000000000 --- a/fbw-a32nx/src/wasm/flypad-backend/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -cmake_minimum_required(VERSION 3.19) -project(flybywire-a32nx-flypad-backend) - -set(CMAKE_CXX_STANDARD 20) -set(MSFS_SDK "C:\\MSFS SDK") - -set(CMAKE_CXX_FLAGS "-c -g -DDEBUG -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") - -include_directories("${MSFS_SDK}/WASM/include") -include_directories("${MSFS_SDK}/WASM/wasi-sysroot/include") -include_directories("${MSFS_SDK}/SimConnect SDK/include") - -include_directories( - ./src - ./src/Aircraft - ./src/Lighting - ./src/Pushback - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/inih -) - -add_executable(flybywire-a32nx-flypad-backend - ./src/FlyPadBackend.cpp - ./src/Aircraft/AircraftPreset.cpp - ./src/Lighting/LightPreset.cpp - ./src/Pushback/Pushback.cpp - ./src/Pushback/InertialDampener.cpp -) diff --git a/fbw-a32nx/src/wasm/flypad-backend/build.sh b/fbw-a32nx/src/wasm/flypad-backend/build.sh deleted file mode 100755 index 476f0a3bd26..00000000000 --- a/fbw-a32nx/src/wasm/flypad-backend/build.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash - -# get directory of this script relative to root -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -COMMON_DIR="${DIR}/../../../../fbw-common/src/wasm" -OUTPUT="${DIR}/../../../out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/flypad-backend.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} \ - -std=c++20 \ - -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 "${COMMON_DIR}/fbw_common/src/inih" \ - -I "${DIR}/src" \ - -I "${DIR}/src/Lighting" \ - -I "${DIR}/src/Aircraft" \ - -I "${DIR}/src/Pushback" \ - "${DIR}/src/FlyPadBackend.cpp" \ - "${DIR}/src/Lighting/LightPreset.cpp" \ - "${DIR}/src/Aircraft/AircraftPreset.cpp" \ - "${DIR}/src/Pushback/Pushback.cpp" \ - "${DIR}/src/Pushback/InertialDampener.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/fbw-a32nx/src/wasm/flypad-backend/src/Aircraft/AircraftPreset.cpp b/fbw-a32nx/src/wasm/flypad-backend/src/Aircraft/AircraftPreset.cpp deleted file mode 100644 index 58ca8d397f7..00000000000 --- a/fbw-a32nx/src/wasm/flypad-backend/src/Aircraft/AircraftPreset.cpp +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#include -#include - -#include "AircraftPreset.h" - -void AircraftPreset::initialize() { - LoadAircraftPresetRequest = register_named_variable("A32NX_AIRCRAFT_PRESET_LOAD"); - this->setLoadAircraftPresetRequest(0); - ProgressAircraftPreset = register_named_variable("A32NX_AIRCRAFT_PRESET_LOAD_PROGRESS"); - ProgressAircraftPresetId = register_named_variable("A32NX_AIRCRAFT_PRESET_LOAD_CURRENT_ID"); - SimOnGround = get_aircraft_var_enum("SIM ON GROUND"); - isInitialized = true; - std::cout << "FLYPAD_BACKEND: 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 << "FLYPAD_BACKEND: 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 - const std::vector* requestedProcedure = procedures.getProcedure(loadAircraftPresetRequest); - if (requestedProcedure == nullptr) { - std::cout << "FLYPAD_BACKEND: 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 << "FLYPAD_BACKEND: 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 << "FLYPAD_BACKEND: 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; - } - - // convenience tmp - const ProcedureStep* currentStepPtr = (*currentProcedure)[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) { - // update progress var - setProgressAircraftPreset(static_cast(currentStep) / currentProcedure->size()); - setProgressAircraftPresetId(currentStepPtr->id); - execute_calculator_code(currentStepPtr->actionCode.c_str(), &fvalue, &ivalue, &svalue); - std::cout << "FLYPAD_BACKEND: Aircraft Preset Step " << currentStep << " Condition: " - << currentStepPtr->description - << " (delay between tests: " << currentStepPtr->delayAfter << ")" << std::endl; - if (static_cast(fvalue)) { - currentDelay = 0; - currentStep++; - } - 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 << "FLYPAD_BACKEND: Aircraft Preset Step " << currentStep << " Test: " - << currentStepPtr->description << " TEST: \"" - << currentStepPtr->expectedStateCheckCode << "\"" << std::endl; -#endif - execute_calculator_code(currentStepPtr->expectedStateCheckCode.c_str(), &fvalue, &ivalue, &svalue); - if (static_cast(fvalue)) { -#ifdef DEBUG - std::cout << "FLYPAD_BACKEND: Aircraft Preset Step " << currentStep << " Skipping: " - << currentStepPtr->description << " TEST: \"" - << currentStepPtr->expectedStateCheckCode << "\"" << std::endl; -#endif - - currentDelay = 0; - currentStep++; - return; - } - } - - // update progress var - setProgressAircraftPreset(static_cast(currentStep) / currentProcedure->size()); - setProgressAircraftPresetId(currentStepPtr->id); - - // execute code to set expected state - std::cout << "FLYPAD_BACKEND: 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 << "FLYPAD_BACKEND: Aircraft Preset " << currentProcedureID << " loading cancelled!" - << std::endl; - loadingIsActive = false; - } -} - -void AircraftPreset::shutdown() { - isInitialized = false; - std::cout << "FLYPAD_BACKEND: AircraftPresets shutdown" << std::endl; -} diff --git a/fbw-a32nx/src/wasm/flypad-backend/src/Aircraft/AircraftPreset.h b/fbw-a32nx/src/wasm/flypad-backend/src/Aircraft/AircraftPreset.h deleted file mode 100644 index dcf6a5db3a5..00000000000 --- a/fbw-a32nx/src/wasm/flypad-backend/src/Aircraft/AircraftPreset.h +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#pragma once - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "../inih/ini.h" -#include "AircraftProcedures.h" -#include "../Units.h" - -/** - * Class for handling aircraft presets. - */ -class AircraftPreset { -private: - std::unique_ptr 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 - const std::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. - */ - AircraftPreset() { - m_Units = std::make_unique(); - } - - /** - * Destructor - */ - ~AircraftPreset() = default; - - /** - * 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 - */ - [[nodiscard]] - 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 - */ - [[nodiscard]] - inline bool getSimOnGround() const { return static_cast(aircraft_varget(SimOnGround, m_Units->Bool, 1)); } -}; diff --git a/fbw-a32nx/src/wasm/flypad-backend/src/Aircraft/AircraftProcedures.h b/fbw-a32nx/src/wasm/flypad-backend/src/Aircraft/AircraftProcedures.h deleted file mode 100644 index cc365184845..00000000000 --- a/fbw-a32nx/src/wasm/flypad-backend/src/Aircraft/AircraftProcedures.h +++ /dev/null @@ -1,313 +0,0 @@ -#pragma once - -#include -#include -#include -#ifdef DEBUG -#include -#endif - -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; -}; - -class AircraftProcedures { - - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // Please remember to also update the EFB Presets page for the step description - // if you make any changes to this list. - // src/systems/instruments/src/EFB/Presets/Widgets/AircraftPresets.tsx - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - static const inline std::vector POWERED_CONFIG_ON { - // SOP: PRELIMINARY COCKPIT PREPARATION - ProcedureStep{"BAT1 On", 1010, false, 1000, "(L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)", "1 (>L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)"}, - ProcedureStep{"BAT2 On", 1020, false, 3000, "(L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)", "1 (>L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)"}, - - ProcedureStep{"EXT PWR On", 1030, 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) }"}, - - // if no Ext Pwr is available we start the APU here with a bat only fire test - ProcedureStep{"APU Fire Test On", 1035, false, 2000, "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)", "1 (>L:A32NX_FIRE_TEST_APU)"}, - ProcedureStep{"APU Fire Test Off", 1036, false, 2000, "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)", "0 (>L:A32NX_FIRE_TEST_APU)"}, - ProcedureStep{"APU Master On", 1040, false, 3000, "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)", "1 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, - ProcedureStep{"APU Start On", 1050, 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)"}, - - ProcedureStep{"Waiting on AC BUS Availability", 1060, true, 2000, "", "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)"}, - - // SOP: COCKPIT PREPARATION - ProcedureStep{"Crew Oxy On", 1120, false, 1000, "(L:PUSH_OVHD_OXYGEN_CREW) 0 ==", "0 (>L:PUSH_OVHD_OXYGEN_CREW)"}, - ProcedureStep{"GND CTL On", 1110, 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)"}, - ProcedureStep{"CVR Test On", 1115, false, 5000, "(L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)", "1 (>L:A32NX_RCDR_TEST)"}, - ProcedureStep{"CVR Test Off", 1116, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)", "0 (>L:A32NX_RCDR_TEST) 1 (>L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)"}, - - ProcedureStep{"ADIRS 1 Nav", 1080, false, 500, "(L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB)"}, - ProcedureStep{"ADIRS 2 Nav", 1090, false, 500, "(L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB)"}, - ProcedureStep{"ADIRS 3 Nav", 1100, false, 1500, "(L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB)"}, - - ProcedureStep{"Strobe Auto", 2122, false, 50, "(L:LIGHTING_STROBE_0) 1 ==", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_OFF)"}, - ProcedureStep{"Strobe Auto", 2122, false, 1000, "(L:LIGHTING_STROBE_0) 1 ==", "1 (>L:STROBE_0_AUTO) 0 (>K:STROBES_ON)"}, - ProcedureStep{"Nav & Logo Lt On", 1070, false, 1000, "(A:LIGHT LOGO, Bool) (A:LIGHT NAV, Bool) &&", "1 (>K:2:LOGO_LIGHTS_SET) 1 (>K:2:NAV_LIGHTS_SET)"}, - - ProcedureStep{"SEAT BELTS On", 2140, false, 1000, "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL)", "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) ! if{ 1 (>K:CABIN_SEATBELTS_ALERT_SWITCH_TOGGLE) }"}, - ProcedureStep{"NO SMOKING Auto", 1130, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION) 1 ==", "1 (>L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION)"}, - ProcedureStep{"EMER EXT Lt Arm", 1140, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION) 1 ==", "1 (>L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION)"}, - - // For the fire tests the FWC needs to be initialized - // The correct variables to wait for are: A32NX_FWS_FWC_1_NORMAL and A32NX_FWS_FWC_2_NORMAL. But - // as these are only on Exp this first iteration uses A32NX_FWC_FLIGHT_PHASE which also work on> - // master and is equivalent for this specific purpose. Will be changed when the FWC is on master. - ProcedureStep{"Waiting on FWC Initialization", 1065, true, 5000, "", "(L:A32NX_FWC_FLIGHT_PHASE)"}, - ProcedureStep{"Waiting...", 9999, false, 5000, "(L:A32NX_AIRCRAFT_PRESET_FWC_INIT_DONE)", "1 (>L:A32NX_AIRCRAFT_PRESET_FWC_INIT_DONE)"}, - - // APU fire test - ProcedureStep{"APU Fire Test On", 1035, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)", "1 (>L:A32NX_FIRE_TEST_APU)"}, - ProcedureStep{"APU Fire Test Off", 1036, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)", "0 (>L:A32NX_FIRE_TEST_APU) 1 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)"}, - - // After fire test we start the APU - ProcedureStep{"APU Master On", 1041, 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)"}, - ProcedureStep{"APU Start On", 1051, 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)"}, - - // ENG fire test - ProcedureStep{"ENG 1 Fire Test On", 2002, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)", "1 (>L:A32NX_FIRE_TEST_ENG1)"}, - ProcedureStep{"ENG 1 Fire Test Off", 2003, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)", "0 (>L:A32NX_FIRE_TEST_ENG1) 1 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)"}, - ProcedureStep{"ENG 2 Fire Test On", 2004, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)", "1 (>L:A32NX_FIRE_TEST_ENG2)"}, - ProcedureStep{"ENG 2 Fire Test Off", 2005, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)", "0 (>L:A32NX_FIRE_TEST_ENG2) 1 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)"}, - - ProcedureStep{"Waiting on APU Availability", 1150, true, 2000, "", "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) ! (L:A32NX_OVHD_APU_START_PB_IS_AVAILABLE) ||"}, - ProcedureStep{"APU Bleed On", 1160, 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 const inline std::vector POWERED_CONFIG_OFF = { - ProcedureStep{"NO SMOKING Off", 1170, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION) 2 ==", "2 (>L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION)"}, - ProcedureStep{"EMER EXT Lt Off", 1180, false, 1500, "(L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION) 2 ==", "2 (>L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION)"}, - ProcedureStep{"GND CTL Off", 1200, false, 1000, "(L:A32NX_RCDR_GROUND_CONTROL_ON) 0 ==", "0 (>L:A32NX_RCDR_GROUND_CONTROL_ON)"}, - ProcedureStep{"SEAT BELTS Off", 2200, false, 2000, "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) !", "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) if{ 1 (>K:CABIN_SEATBELTS_ALERT_SWITCH_TOGGLE) }"}, - ProcedureStep{"Strobe Off", 2121, false, 1000, "(L:LIGHTING_STROBE_0) 2 ==", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_OFF)"}, - ProcedureStep{"Nav & Logo Lt Off", 1240, false, 500, "(A:LIGHT LOGO, Bool) ! (A:LIGHT NAV, Bool) ! &&", "0 (>K:2:LOGO_LIGHTS_SET) 0 (>K:2:NAV_LIGHTS_SET)"}, - ProcedureStep{"Crew Oxy Off", 1190, false, 1000, "(L:PUSH_OVHD_OXYGEN_CREW) 1 ==", "1 (>L:PUSH_OVHD_OXYGEN_CREW)"}, - ProcedureStep{"ADIRS 3 Off", 1210, false, 500, "(L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB)"}, - ProcedureStep{"ADIRS 2 Off", 1220, false, 500, "(L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB)"}, - ProcedureStep{"ADIRS 1 Off", 1230, false, 1000, "(L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB)"}, - ProcedureStep{"APU Bleed Off", 1250, false, 1500, "(L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON)"}, - ProcedureStep{"APU Master Off", 1260, false, 2000, "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, - ProcedureStep{"EXT PWR Off", 1270, false, 3000, "(A:EXTERNAL POWER ON:1, BOOL) !", "(A:EXTERNAL POWER ON:1, BOOL) if{ 1 (>K:TOGGLE_EXTERNAL_POWER) }"}, - ProcedureStep{"BAT2 Off", 1280, false, 100, "(L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO) 0 ==", "0 (>L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)"}, - ProcedureStep{"BAT1 Off", 1290, false, 1000, "(L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO) 0 ==", "0 (>L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)"}, - ProcedureStep{"AC BUS Off Check", 1300, true, 2000, "", "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED) !"}, - ProcedureStep{"CVR Test Reset", 1117, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)"}, - ProcedureStep{"APU Fire Test Reset", 1037, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)"}, - ProcedureStep{"ENG 1 Fire Test Reset", 2006, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)"}, - ProcedureStep{"ENG 2 Fire Test Reset", 2007, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)"}, - ProcedureStep{"FWC Init Reset", 1066, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FWC_INIT_DONE)"} -}; - - static const inline std::vector PUSHBACK_CONFIG_ON = { - // SOP: BEFORE PUSHBACK OR START - ProcedureStep{"EXT PWR Off", 2000, false, 3000, "(A:EXTERNAL POWER ON:1, BOOL) !", "(A:EXTERNAL POWER ON:1, BOOL) if{ 1 (>K:TOGGLE_EXTERNAL_POWER) }"}, - ProcedureStep{"Beacon On", 2130, false, 2000, "(A:LIGHT BEACON, Bool)", "0 (>K:BEACON_LIGHTS_ON)"}, - ProcedureStep{"FUEL PUMP 2 On", 2010, false, 100, "(A:FUELSYSTEM PUMP SWITCH:2, Bool)", "2 (>K:FUELSYSTEM_PUMP_ON)"}, - ProcedureStep{"FUEL PUMP 5 On", 2020, false, 500, "(A:FUELSYSTEM PUMP SWITCH:5, Bool)", "5 (>K:FUELSYSTEM_PUMP_ON)"}, - ProcedureStep{"FUEL VALVE 9 On", 2030, false, 100, "(A:FUELSYSTEM VALVE SWITCH:9, Bool)", "9 (>K:FUELSYSTEM_VALVE_OPEN)"}, - ProcedureStep{"FUEL VALVE 10 On", 2040, false, 500, "(A:FUELSYSTEM VALVE SWITCH:10, Bool)", "10 (>K:FUELSYSTEM_VALVE_OPEN)"}, - ProcedureStep{"FUEL PUMP 3 On", 2050, false, 100, "(A:FUELSYSTEM PUMP SWITCH:3, Bool)", "3 (>K:FUELSYSTEM_PUMP_ON)"}, - ProcedureStep{"FUEL PUMP 6 On", 2060, false, 2000, "(A:FUELSYSTEM PUMP SWITCH:6, Bool)", "6 (>K:FUELSYSTEM_PUMP_ON)"}, - ProcedureStep{"COCKPIT DOOR LCK", 2110, false, 2000, "(L:A32NX_COCKPIT_DOOR_LOCKED) 1 ==", "1 (>L:A32NX_COCKPIT_DOOR_LOCKED)"}, - ProcedureStep{"Await ADIRS 1 Alignment", 2150, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_1_STATE) 2 =="}, - ProcedureStep{"Await ADIRS 2 Alignment", 2160, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_2_STATE) 2 =="}, - ProcedureStep{"Await ADIRS 3 Alignment", 2170, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_3_STATE) 2 =="}, -}; - - static const inline std::vector PUSHBACK_CONFIG_OFF = { - ProcedureStep{"COCKPIT DOOR OP", 2250, false, 2000, "(L:A32NX_COCKPIT_DOOR_LOCKED) 0 ==", "0 (>L:A32NX_COCKPIT_DOOR_LOCKED)"}, - ProcedureStep{"FUEL PUMP 2 Off", 2260, false, 100, "(A:FUELSYSTEM PUMP SWITCH:2, Bool) !", "2 (>K:FUELSYSTEM_PUMP_OFF)"}, - ProcedureStep{"FUEL PUMP 5 Off", 2270, false, 500, "(A:FUELSYSTEM PUMP SWITCH:5, Bool) !", "5 (>K:FUELSYSTEM_PUMP_OFF)"}, - ProcedureStep{"FUEL VALVE 9 Off", 2280, false, 100, "(A:FUELSYSTEM VALVE SWITCH:9, Bool) !", "9 (>K:FUELSYSTEM_VALVE_CLOSE)"}, - ProcedureStep{"FUEL VALVE 10 Off",2290, false, 500, "(A:FUELSYSTEM VALVE SWITCH:10, Bool) !", "10 (>K:FUELSYSTEM_VALVE_CLOSE)"}, - ProcedureStep{"FUEL PUMP 3 Off", 2300, false, 100, "(A:FUELSYSTEM PUMP SWITCH:3, Bool) !", "3 (>K:FUELSYSTEM_PUMP_OFF)"}, - ProcedureStep{"FUEL PUMP 6 Off", 2310, false, 1000, "(A:FUELSYSTEM PUMP SWITCH:6, Bool) !", "6 (>K:FUELSYSTEM_PUMP_OFF)"}, - ProcedureStep{"Beacon Off", 2190, false, 1000, "(A:LIGHT BEACON, Bool) !", "0 (>K:BEACON_LIGHTS_OFF)"}, -}; - - static const inline std::vector TAXI_CONFIG_ON = { - // SOP: ENGINE START - ProcedureStep{"ENG MODE SEL START", 3000, 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)"}, - ProcedureStep{"ENG 2 ON", 3010, false, 60000, "(A:FUELSYSTEM VALVE OPEN:2, Bool)", "2 (>K:FUELSYSTEM_VALVE_OPEN)"}, - ProcedureStep{"Await ENG 2 AVAIL", 3020, true, 5000, "", "(L:A32NX_ENGINE_STATE:2) 1 =="}, - ProcedureStep{"ENG 1 ON", 3030, false, 60000, "(A:FUELSYSTEM VALVE OPEN:1, Bool)", "1 (>K:FUELSYSTEM_VALVE_OPEN)"}, - ProcedureStep{"Await ENG 1 AVAIL", 3040, true, 5000, "", "(L:A32NX_ENGINE_STATE:1) 1 =="}, - // SOP: AFTER START - ProcedureStep{"ENG MODE SEL NORM", 3050, 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)"}, - ProcedureStep{"APU Bleed Off", 3060, false, 2000, "(L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON)"}, - ProcedureStep{"APU Master Off", 3070, false, 2000, "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, - ProcedureStep{"Spoiler Arm", 3090, false, 2000, "(L:A32NX_SPOILERS_ARMED) 1 ==", "1 (>K:SPOILERS_ARM_SET)"}, - ProcedureStep{"Rudder Trim Reset", 3100, false, 2000, "(A:RUDDER TRIM, Radians) 0 ==", "0 (>K:RUDDER_TRIM_SET)"}, - ProcedureStep{"Flaps 1", 3110, false, 3000, "(L:A32NX_FLAPS_HANDLE_INDEX) 1 ==", "1 (>L:A32NX_FLAPS_HANDLE_INDEX)"}, - // SOP: TAXI - ProcedureStep{"NOSE Lt Taxi", 3120, 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)"}, - ProcedureStep{"RWY TURN OFF Lt L On", 3130, false, 0, "(A:CIRCUIT SWITCH ON:21, Bool)", "(A:CIRCUIT SWITCH ON:21, Bool) ! if{ 21 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, - ProcedureStep{"RWY TURN OFF Lt R On", 3140, false, 2000, "(A:CIRCUIT SWITCH ON:22, Bool)", "(A:CIRCUIT SWITCH ON:22, Bool) ! if{ 22 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, - ProcedureStep{"PWS Auto", 2070, false, 1000, "(L:A32NX_SWITCH_RADAR_PWS_POSITION) 1 ==", "1 (>L:A32NX_SWITCH_RADAR_PWS_POSITION)"}, - ProcedureStep{"Transponder On", 2080, false, 1000, "(L:A32NX_TRANSPONDER_MODE) 1 ==", "1 (>L:A32NX_TRANSPONDER_MODE)"}, - ProcedureStep{"ATC ALT RPTG On", 2090, false, 1000, "(L:A32NX_SWITCH_ATC_ALT) 1 ==", "1 (>L:A32NX_SWITCH_ATC_ALT)"}, - ProcedureStep{"TCAS TRAFFIC ABV", 2100, false, 2000, "(L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION)"}, - ProcedureStep{"Autobrake Max", 3080, false, 2000, "(L:A32NX_AUTOBRAKES_ARMED_MODE) 3 ==", "3 (>L:A32NX_AUTOBRAKES_ARMED_MODE_SET)"}, - ProcedureStep{"TERR ON ND Capt. On", 3080, false, 2000, "(L:A32NX_EFIS_TERR_L_ACTIVE) 1 ==", "1 (>L:A32NX_EFIS_TERR_L_ACTIVE)"}, - ProcedureStep{"T.O Config", 3085, false, 200, "", "1 (>L:A32NX_BTN_TOCONFIG)"}, - ProcedureStep{"T.O Config", 3085, false, 2000, "", "0 (>L:A32NX_BTN_TOCONFIG)"}, -}; - - static const inline std::vector TAXI_CONFIG_OFF = { - ProcedureStep{"TERR ON ND Capt. Off", 3080, false, 2000, "(L:A32NX_EFIS_TERR_L_ACTIVE) 0 ==", "0 (>L:A32NX_EFIS_TERR_L_ACTIVE)"}, - ProcedureStep{"Autobrake Off", 3180, false, 2000, "(L:A32NX_AUTOBRAKES_ARMED_MODE) 0 ==", "0 (>L:A32NX_AUTOBRAKES_ARMED_MODE_SET)"}, - ProcedureStep{"TCAS TRAFFIC ABV", 2240, false, 1000, "(L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION)"}, - ProcedureStep{"ATC ALT RPTG Off", 2230, false, 1000, "(L:A32NX_SWITCH_ATC_ALT) 1 ==", "1 (>L:A32NX_SWITCH_ATC_ALT)"}, - ProcedureStep{"Transponder Off", 2220, false, 1000, "(L:A32NX_TRANSPONDER_MODE) 0 ==", "0 (>L:A32NX_TRANSPONDER_MODE)"}, - ProcedureStep{"PWS Off", 2210, false, 1000, "(L:A32NX_SWITCH_RADAR_PWS_POSITION) 0 ==", "0 (>L:A32NX_SWITCH_RADAR_PWS_POSITION)"}, - ProcedureStep{"RWY TURN OFF Lt L Off", 3160, false, 0, "(A:CIRCUIT SWITCH ON:21, Bool) !", "(A:CIRCUIT SWITCH ON:21, Bool) if{ 21 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, - ProcedureStep{"RWY TURN OFF Lt R Off", 3170, false, 2000, "(A:CIRCUIT SWITCH ON:22, Bool) !", "(A:CIRCUIT SWITCH ON:22, Bool) if{ 22 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, - ProcedureStep{"NOSE Lt Taxi", 3150, 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)"}, - ProcedureStep{"Flaps 0", 3210, false, 2000, "(L:A32NX_FLAPS_HANDLE_INDEX) 0 ==", "0 (>L:A32NX_FLAPS_HANDLE_INDEX)"}, - ProcedureStep{"Rudder Trim Reset", 3200, false, 2000, "(A:RUDDER TRIM, Radians) 0 ==", "0 (>K:RUDDER_TRIM_SET)"}, - ProcedureStep{"Spoiler Disarm", 3190, false, 2000, "(L:A32NX_SPOILERS_ARMED) 0 ==", "0 (>K:SPOILERS_ARM_SET)"}, - ProcedureStep{"ENG 1 Off", 3220, false, 2000, "(A:FUELSYSTEM VALVE OPEN:1, Bool) !", "1 (>K:FUELSYSTEM_VALVE_CLOSE)"}, - ProcedureStep{"ENG 2 Off", 3230, false, 2000, "(A:FUELSYSTEM VALVE OPEN:2, Bool) !", "2 (>K:FUELSYSTEM_VALVE_CLOSE)"}, - ProcedureStep{"ENG 1 N1 <3%", 3240, true, 1000, "", "(L:A32NX_ENGINE_N1:1) 3 <"}, - ProcedureStep{"ENG 2 N1 <3%", 3250, true, 1000, "", "(L:A32NX_ENGINE_N1:2) 3 <"} -}; - - static const inline std::vector TAKEOFF_CONFIG_ON = { - // SOP: TAXI - ProcedureStep{"WX Radar On", 4000, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_SYS) 0 ==", "0 (>L:XMLVAR_A320_WEATHERRADAR_SYS)"}, - ProcedureStep{"WX Radar Mode", 4010, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_MODE) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_MODE)"}, - // SOP: BEFORE TAKEOFF - ProcedureStep{"TCAS Switch TA/RA", 4020, false, 2000, "(L:A32NX_SWITCH_TCAS_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_POSITION)"}, - // unfortunately strobe 3-way switch control is weird, so we have to use a workaround and turn it off first - ProcedureStep{"Strobe On" , 2120, false, 50, "(L:LIGHTING_STROBE_0) 0 ==", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_OFF)"}, - ProcedureStep{"Strobe On", 2120, false, 1000, "(L:LIGHTING_STROBE_0) 0 ==", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_ON)"}, - ProcedureStep{"Cabin Ready", 2125, false, 1000, "", "1 (>L:A32NX_CABIN_READY)"}, - // SOP: TAKE OFF - ProcedureStep{"NOSE Lt Takeoff", 4030, false, 1000, "(A:CIRCUIT SWITCH ON:17, Bool)", "(A:CIRCUIT SWITCH ON:17, Bool) ! if{ 17 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, - ProcedureStep{"LL Lt L On", 4040, 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)"}, - ProcedureStep{"LL Lt R On", 4050, 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 const inline std::vector TAKEOFF_CONFIG_OFF = { - ProcedureStep{"LL Lt L Off", 4060, 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)"}, - ProcedureStep{"LL Lt R Off", 4070, 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)"}, - ProcedureStep{"NOSE Lt Takeoff", 4080, false, 2000, "(A:CIRCUIT SWITCH ON:17, Bool) !", "(A:CIRCUIT SWITCH ON:17, Bool) if{ 17 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, - // unfortunately strobe 3-way switch control is weird, so we have to use a workaround and turn it off first - ProcedureStep{"Strobe Auto", 2122, false, 50, "(L:LIGHTING_STROBE_0) 1 == ||", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_OFF)"}, - ProcedureStep{"Strobe Auto", 2122, false, 1000, "(L:A32NX_ENGINE_STATE:1) 0 == (L:A32NX_ENGINE_STATE:2) 0 == && " - "(L:LIGHTING_STROBE_0) 1 == || ", "1 (>L:STROBE_0_AUTO) 0 (>K:STROBES_ON)"}, - ProcedureStep{"TCAS Switch TA/RA", 4090, false, 1000, "(L:A32NX_SWITCH_TCAS_POSITION) 0 ==", "0 (>L:A32NX_SWITCH_TCAS_POSITION)"}, - ProcedureStep{"WX Radar Mode", 4110, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_MODE) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_MODE)"}, - ProcedureStep{"WX Radar Off", 4100, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_SYS) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_SYS)"}, -}; - - std::vector coldAndDark; - std::vector powered; - std::vector readyForPushback; - std::vector readyForTaxi; - std::vector readyForTakeoff; - - static void insert(std::vector& dest, const std::vector& src) { - std::transform(begin(src), end(src), back_inserter(dest), [](const auto& procedure) { - return &procedure; - }); - } - -#ifdef DEBUG - static inline void printProcedure(const std::vector& procedures) { - for (const auto& p : procedures) { - std::cout << p.id << " = " << p.description << std::endl; - } - } -#endif - -public: - AircraftProcedures() { - -#ifdef DEBUG - // Map the procedure groups - // Print to console to add them to the EFB code to display the current step. - printProcedure(POWERED_CONFIG_ON); - printProcedure(PUSHBACK_CONFIG_ON); - printProcedure(TAXI_CONFIG_ON); - printProcedure(TAKEOFF_CONFIG_ON); - printProcedure(TAKEOFF_CONFIG_OFF); - printProcedure(TAXI_CONFIG_OFF); - printProcedure(PUSHBACK_CONFIG_OFF); - printProcedure(POWERED_CONFIG_OFF); -#endif - - insert(coldAndDark, TAKEOFF_CONFIG_OFF); - insert(coldAndDark, TAXI_CONFIG_OFF); - insert(coldAndDark, PUSHBACK_CONFIG_OFF); - insert(coldAndDark, POWERED_CONFIG_OFF); - - insert(powered, TAKEOFF_CONFIG_OFF); - insert(powered, TAXI_CONFIG_OFF); - insert(powered, PUSHBACK_CONFIG_OFF); - insert(powered, POWERED_CONFIG_ON); - - insert(readyForPushback, TAKEOFF_CONFIG_OFF); - insert(readyForPushback, TAXI_CONFIG_OFF); - insert(readyForPushback, POWERED_CONFIG_ON); - insert(readyForPushback, PUSHBACK_CONFIG_ON); - - insert(readyForTaxi, TAKEOFF_CONFIG_OFF); - insert(readyForTaxi, POWERED_CONFIG_ON); - insert(readyForTaxi, PUSHBACK_CONFIG_ON); - insert(readyForTaxi, TAXI_CONFIG_ON); - - insert(readyForTakeoff, POWERED_CONFIG_ON); - insert(readyForTakeoff, PUSHBACK_CONFIG_ON); - insert(readyForTakeoff, TAXI_CONFIG_ON); - insert(readyForTakeoff, TAKEOFF_CONFIG_ON); - } - - [[nodiscard]] - const std::vector* getProcedure(int64_t pID) const { - switch (pID) { - case 1: - return &coldAndDark; - case 2: - return &powered; - case 3: - return &readyForPushback; - case 4: - return &readyForTaxi; - case 5: - return &readyForTakeoff; - default: - return nullptr; - } - } -}; diff --git a/fbw-a32nx/src/wasm/flypad-backend/src/FlyPadBackend.cpp b/fbw-a32nx/src/wasm/flypad-backend/src/FlyPadBackend.cpp deleted file mode 100644 index 249203164e3..00000000000 --- a/fbw-a32nx/src/wasm/flypad-backend/src/FlyPadBackend.cpp +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#include "FlyPadBackend.h" -#include "Aircraft/AircraftPreset.h" -#include "Lighting/LightPreset.h" -#include "Pushback/Pushback.h" - -FlyPadBackend FLYPAD_BACKEND; - -/** - * 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("FlyPadBackend_gauge_callback"))) extern "C" __attribute__((unused)) bool -FlyPadBackend_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 FLYPAD_BACKEND.initialize(); - } - case PANEL_SERVICE_PRE_DRAW: { - auto drawData = static_cast(pData); - return FLYPAD_BACKEND.onUpdate(drawData->dt); - } - case PANEL_SERVICE_PRE_KILL: { - return FLYPAD_BACKEND.shutdown(); - } - default: - break; - } - return false; -} - -bool FlyPadBackend::initialize() { - std::cout << "FLYPAD_BACKEND: Connecting to SimConnect..." << std::endl; - - if (!SUCCEEDED(SimConnect_Open(&hSimConnect, "FlyPadBackend", nullptr, 0, 0, 0))) { - std::cout << "FLYPAD_BACKEND: SimConnect failed." << std::endl; - return false; - } - isConnected = true; - - // Create submodules and provide pointers to data required structures - lightPresetPtr = std::make_unique(); - aircraftPresetPtr = std::make_unique(); - pushbackPtr = std::make_unique(hSimConnect, &pushbackData); - - // Simulation data to local data structure mapping - HRESULT result = S_OK; - result &= SimConnect_AddToDataDefinition(hSimConnect, DataStructureIDs::SimulationDataID, "SIMULATION TIME", "NUMBER"); - - result &= SimConnect_AddToDataDefinition(hSimConnect, DataStructureIDs::PushbackDataID, "Pushback Wait", "BOOLEAN", SIMCONNECT_DATATYPE_INT64); - result &= SimConnect_AddToDataDefinition(hSimConnect, DataStructureIDs::PushbackDataID, "VELOCITY BODY Z", "FEET/SECOND", SIMCONNECT_DATATYPE_FLOAT64); - result &= SimConnect_AddToDataDefinition(hSimConnect, DataStructureIDs::PushbackDataID, "ROTATION VELOCITY BODY Y", "FEET/SECOND", SIMCONNECT_DATATYPE_FLOAT64); - result &= SimConnect_AddToDataDefinition(hSimConnect, DataStructureIDs::PushbackDataID, "ROTATION ACCELERATION BODY X", "RADIANS PER SECOND SQUARED", SIMCONNECT_DATATYPE_FLOAT64); - if (result != S_OK) { - std::cout << "FLYPAD_BACKEND: Data definition failed! " << std::endl; - } - - result &= SimConnect_MapClientEventToSimEvent(hSimConnect, Events::KEY_TUG_HEADING_EVENT, "KEY_TUG_HEADING"); - result &= SimConnect_MapClientEventToSimEvent(hSimConnect, Events::KEY_TUG_SPEED_EVENT, "KEY_TUG_SPEED"); - - // initialize submodules - lightPresetPtr->initialize(); - aircraftPresetPtr->initialize(); - pushbackPtr->initialize(); - - // read simulation data from simconnect - simConnectRequestData(); - - std::cout << "FLYPAD_BACKEND: SimConnect connected." << std::endl; - return (result == S_OK); -} - -bool FlyPadBackend::onUpdate(double deltaTime) { - if (isConnected) { - - simConnectProcessMessages(); - - // detect pause - if (simulationData.simulationTime == previousSimulationTime || simulationData.simulationTime < 0.2) { - return true; - } - previousSimulationTime = simulationData.simulationTime; - - // update sub modules - lightPresetPtr->onUpdate(deltaTime); - aircraftPresetPtr->onUpdate(deltaTime); - pushbackPtr->onUpdate(deltaTime); - - return true; - } - return false; -} - -bool FlyPadBackend::shutdown() { - std::cout << "FLYPAD_BACKEND: Disconnecting ..." << std::endl; - - // shutdown submodules - lightPresetPtr->shutdown(); - aircraftPresetPtr->shutdown(); - pushbackPtr->shutdown(); - - isConnected = false; - unregister_all_named_vars(); - std::cout << "FLYPAD_BACKEND: Disconnected." << std::endl; - return SUCCEEDED(SimConnect_Close(hSimConnect)); -} - -bool FlyPadBackend::simConnectRequestData() const { - HRESULT result = S_OK; - - // Request data for each data structure - remember to increase the request id. - result &= SimConnect_RequestDataOnSimObject(hSimConnect, 0, DataStructureIDs::SimulationDataID, SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_VISUAL_FRAME); - result &= SimConnect_RequestDataOnSimObject(hSimConnect, 1, DataStructureIDs::PushbackDataID, SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_VISUAL_FRAME); - - if (result != S_OK) { - return false; - } - - return true; -} - -void FlyPadBackend::simConnectProcessMessages() { - DWORD cbData; - SIMCONNECT_RECV* pData; - while (SUCCEEDED(SimConnect_GetNextDispatch(hSimConnect, &pData, &cbData))) { - simConnectProcessDispatchMessage(pData, &cbData); - } -} - -void FlyPadBackend::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; - - case 1: - // store aircraft data in local data structure - pushbackData = *((PushbackData*) &data->dwData); - return; - - default: - std::cout << "FLYPAD_BACKEND: Unknown request id in SimConnect connection: "; - std::cout << data->dwRequestID << std::endl; - return; - } -} - -void FlyPadBackend::simConnectProcessDispatchMessage(SIMCONNECT_RECV* pData, DWORD* cbData) { - switch (pData->dwID) { - case SIMCONNECT_RECV_ID_OPEN: - std::cout << "FLYPAD_BACKEND: SimConnect connection established" << std::endl; - break; - - case SIMCONNECT_RECV_ID_QUIT: - std::cout << "FLYPAD_BACKEND: Received SimConnect connection quit message" << std::endl; - break; - - case SIMCONNECT_RECV_ID_SIMOBJECT_DATA: - simConnectProcessSimObjectData(static_cast(pData)); - break; - - case SIMCONNECT_RECV_ID_EXCEPTION: - std::cout << "FLYPAD_BACKEND: Exception in SimConnect connection: "; - std::cout << getSimConnectExceptionString( - static_cast( - static_cast(pData)->dwException)); - std::cout << std::endl; - break; - - default: - break; - } -} - -std::string FlyPadBackend::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/fbw-a32nx/src/wasm/flypad-backend/src/FlyPadBackend.h b/fbw-a32nx/src/wasm/flypad-backend/src/FlyPadBackend.h deleted file mode 100644 index dab61ed0753..00000000000 --- a/fbw-a32nx/src/wasm/flypad-backend/src/FlyPadBackend.h +++ /dev/null @@ -1,131 +0,0 @@ -// 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 - -// IDs for data structures - must be mapped to data structs -enum DataStructureIDs { - SimulationDataID, - PushbackDataID -}; - -// Local data structure for simconnect data -struct SimulationData { - double simulationTime; -}; - -// Data structure for PushbackDataID -struct PushbackData { - INT64 pushbackWait; - FLOAT64 velBodyZ; - FLOAT64 rotVelBodyY; - FLOAT64 rotAccelBodyX; -}; - -enum Events { - KEY_TUG_HEADING_EVENT, - KEY_TUG_SPEED_EVENT -}; - -class LightPreset; -class AircraftPreset; -class Pushback; - -class FlyPadBackend { -private: - HANDLE hSimConnect; - - // Instance of local data structure for simconnect data - SimulationData simulationData = {}; - PushbackData pushbackData = {}; - - /** - * Flag if connection has been initialized. - */ - bool isConnected = false; - - // Storing previous simulation allows for Pause detection - double previousSimulationTime = 0; - - // Pointers to the flypad backend submodules - std::unique_ptr lightPresetPtr; - std::unique_ptr aircraftPresetPtr; - std::unique_ptr pushbackPtr; - -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/fbw-a32nx/src/wasm/flypad-backend/src/Lighting/LightPreset.cpp b/fbw-a32nx/src/wasm/flypad-backend/src/Lighting/LightPreset.cpp deleted file mode 100644 index 99e37190146..00000000000 --- a/fbw-a32nx/src/wasm/flypad-backend/src/Lighting/LightPreset.cpp +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#include -#include - -#include "LightPreset.h" - -void LightPreset::initialize() { - isInitialized = true; - std::cout << "FLYPAD_BACKEND: 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 << "FLYPAD_BACKEND: LightPresets shutdown" << std::endl; -} - -void LightPreset::loadLightingPreset(int64_t loadPresetRequest) { - std::cout << "FLYPAD_BACKEND: Loading preset: " << loadPresetRequest << std::endl; - if (readFromStore(loadPresetRequest)) { - applyToAircraft(); - std::cout << "FLYPAD_BACKEND: Lighting Preset: " << loadPresetRequest << " successfully loaded." - << std::endl; - return; - } - std::cout << "FLYPAD_BACKEND: Loading Lighting Preset: " << loadPresetRequest << " failed." - << std::endl; -} - -void LightPreset::saveLightingPreset(int64_t savePresetRequest) { - std::cout << "FLYPAD_BACKEND: Save to Lighting Preset: " << savePresetRequest << std::endl; - readFromAircraft(); - if (saveToStore(savePresetRequest)) { - std::cout << "FLYPAD_BACKEND: Lighting Preset: " << savePresetRequest << " successfully saved." - << std::endl; - return; - } - std::cout << "FLYPAD_BACKEND: 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 << "FLYPAD_BACKEND: reading ini value for \"" - << "[" << section << "] " << key << " = " << ini.get(section).get(key) - << "\" failed." << std::endl; - } - } - return defaultValue; -} diff --git a/fbw-a32nx/src/wasm/flypad-backend/src/Lighting/LightPreset.h b/fbw-a32nx/src/wasm/flypad-backend/src/Lighting/LightPreset.h deleted file mode 100644 index ff2771d8104..00000000000 --- a/fbw-a32nx/src/wasm/flypad-backend/src/Lighting/LightPreset.h +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#pragma once - -#include -#include - -#include "../FlyPadBackend.h" -#include "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; - - std::unique_ptr 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 = std::make_unique(); - }; - - /** - * Destructor - */ - ~LightPreset() = default; - - /** - * 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. - */ - [[nodiscard]] __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/fbw-a32nx/src/wasm/flypad-backend/src/Lighting/LightingSimVars.h b/fbw-a32nx/src/wasm/flypad-backend/src/Lighting/LightingSimVars.h deleted file mode 100644 index d397f6c3da3..00000000000 --- a/fbw-a32nx/src/wasm/flypad-backend/src/Lighting/LightingSimVars.h +++ /dev/null @@ -1,250 +0,0 @@ -// 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_LIGHTING_PRESET_LOAD"); - this->setLoadLightingPresetRequest(0); - SaveLightingPresetRequest = register_named_variable("A32NX_LIGHTING_PRESET_SAVE"); - 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 - */ - [[nodiscard]] - inline FLOAT64 getElecAC1State() const { - return get_named_variable_value(ElecAC1); - } - - /** - * Reads the preset loading request variable. - * @return INT64 signifying the preset to be loaded - */ - [[nodiscard]] - 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. - */ - [[nodiscard]] - 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 - */ - [[nodiscard]] - 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. - */ - [[nodiscard]] - 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) - */ - [[nodiscard]] - 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) - */ - [[nodiscard]] - 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) - */ - [[nodiscard]] - 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) - */ - [[nodiscard]] - 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) - */ - [[nodiscard]] - 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/fbw-a32nx/src/wasm/flypad-backend/src/Pushback/InertialDampener.cpp b/fbw-a32nx/src/wasm/flypad-backend/src/Pushback/InertialDampener.cpp deleted file mode 100644 index e060655bccc..00000000000 --- a/fbw-a32nx/src/wasm/flypad-backend/src/Pushback/InertialDampener.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#include - -#include "InertialDampener.h" - -InertialDampener::InertialDampener(double startValue, double accelStepSize) { - this->lastValue = startValue; - this->accelStepSize = accelStepSize; -} - -double InertialDampener::updateSpeed(double newTargetValue) { - if (round(newTargetValue, 1) == round(lastValue, 1)) { - return newTargetValue; - } - if (newTargetValue > this->lastValue) { - this->lastValue += this->accelStepSize; - } - else if (newTargetValue < this->lastValue) { - this->lastValue -= this->accelStepSize; - } - return this->lastValue; -} - -double InertialDampener::round(double value, int decimalPrecision) { - const double p = std::pow(10, decimalPrecision); - return std::round(value * p) / p; -} diff --git a/fbw-a32nx/src/wasm/flypad-backend/src/Pushback/Pushback.cpp b/fbw-a32nx/src/wasm/flypad-backend/src/Pushback/Pushback.cpp deleted file mode 100644 index 4ab3891d03e..00000000000 --- a/fbw-a32nx/src/wasm/flypad-backend/src/Pushback/Pushback.cpp +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#include -#include - -#include "InertialDampener.h" -#include "Pushback.h" - -using namespace std::chrono; - -static constexpr double SPEED_RATIO = 18.0; -static constexpr double TURN_SPEED_RATIO = 0.16; - -Pushback::Pushback(HANDLE hdl, PushbackData* data) { - hSimConnect = hdl; - pushbackDataPtr = data; - m_Units = std::make_unique(); - inertialDampenerPtr = std::make_unique(0.0, 0.15); -} - -void Pushback::initialize() { - // LVARs are initialized here - pushbackSystemEnabled = register_named_variable("A32NX_PUSHBACK_SYSTEM_ENABLED"); - updateDelta = register_named_variable("A32NX_PUSHBACK_UPDT_DELTA"); - parkingBrakeEngaged = register_named_variable("A32NX_PARK_BRAKE_LEVER_POS"); - tugCommandedSpeedFactor = register_named_variable("A32NX_PUSHBACK_SPD_FACTOR"); - tugCommandedHeadingFactor = register_named_variable("A32NX_PUSHBACK_HDG_FACTOR"); - tugCommandedSpeed = register_named_variable("A32NX_PUSHBACK_SPD"); - tugCommandedHeading = register_named_variable("A32NX_PUSHBACK_HDG"); - tugInertiaSpeed = register_named_variable("A32NX_PUSHBACK_INERTIA_SPD"); - rotXOut = register_named_variable("A32NX_PUSHBACK_R_X_OUT"); - - // Read only Simvars - pushbackAttached = get_aircraft_var_enum("Pushback Attached"); - simOnGround = get_aircraft_var_enum("SIM ON GROUND"); - aircraftHeading = get_aircraft_var_enum("PLANE HEADING DEGREES TRUE"); - windVelBodyZ = get_aircraft_var_enum("RELATIVE WIND VELOCITY BODY Z"); - - // Writable Simvars in FlyPadBackend via simconnect data definitions - - isInitialized = true; - std::cout << "FLYPAD_BACKEND: Pushback initialized" << std::endl; -} - -void Pushback::onUpdate(double deltaTime) { - if (!isInitialized || !isPushbackSystemEnabled() || !isPushbackAttached() || !isSimOnGround()) { - return; - } - - // auto start = high_resolution_clock::now(); - - // Calculate movement data and update shared data (debug info) - set_named_variable_value(updateDelta, deltaTime); - - const FLOAT64 tugCmdSpdFactor = getTugCmdSpdFactor(); - const bool parkBrakeEngaged = isParkingBrakeEngaged(); - - const FLOAT64 tugCmdSpd = tugCmdSpdFactor * (parkBrakeEngaged ? (SPEED_RATIO / 10) : SPEED_RATIO); - set_named_variable_value(tugCommandedSpeed, tugCmdSpd); // debug - - const FLOAT64 inertiaSpeed = inertialDampenerPtr->updateSpeed(tugCmdSpd); - set_named_variable_value(tugInertiaSpeed, inertiaSpeed); // debug - - const FLOAT64 computedHdg = angleAdd(getAircraftTrueHeading(), -50 * getTugCmdHdgFactor()); - set_named_variable_value(tugCommandedHeading, computedHdg); // debug - - const FLOAT64 computedRotationVelocity = sgn(tugCmdSpd) - * getTugCmdHdgFactor() - * (parkBrakeEngaged ? (TURN_SPEED_RATIO / 10) : TURN_SPEED_RATIO); - - // As we might use the elevator for taxiing we compensate for wind to avoid - // the aircraft lifting any gears. - const FLOAT64 windCounterRotAccel = getWindVelBodyZ() / 2000.0; - FLOAT64 movementCounterRotAccel = windCounterRotAccel; - if (inertiaSpeed > 0) { - movementCounterRotAccel -= 0.5; - } - else if (inertiaSpeed < 0) { - movementCounterRotAccel += 1.0; - } - else { - movementCounterRotAccel = 0.0; - } - set_named_variable_value(rotXOut, movementCounterRotAccel); // debug - - // K:KEY_TUG_HEADING expects an unsigned integer scaling 360° to 0 to 2^32-1 (0xffffffff / 360) - static const int32_t headingToInt32 = 0xffffffff / 360; - const auto convertedComputedHeading = static_cast(static_cast(computedHdg * headingToInt32)); - - // send K:KEY_TUG_HEADING event - HRESULT result = SimConnect_TransmitClientEvent( - hSimConnect, - 0, - Events::KEY_TUG_HEADING_EVENT, - convertedComputedHeading, - SIMCONNECT_GROUP_PRIORITY_HIGHEST, - SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); - - // K:KEY_TUG_SPEED - seems to actually do nothing - // result &= SimConnect_TransmitClientEvent(hSimConnect, - // 0, Events::KEY_TUG_SPEED_EVENT, inertiaSpeed, - // SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); - - // Update sim data - pushbackDataPtr->pushbackWait = inertiaSpeed == 0 ? 1 : 0; - pushbackDataPtr->velBodyZ = inertiaSpeed; - pushbackDataPtr->rotVelBodyY = computedRotationVelocity; - pushbackDataPtr->rotAccelBodyX = movementCounterRotAccel; - result &= SimConnect_SetDataOnSimObject( - hSimConnect, - DataStructureIDs::PushbackDataID, - SIMCONNECT_OBJECT_ID_USER, - 0, - 0, - sizeof(*pushbackDataPtr), - pushbackDataPtr); - - // check result of data request - if (result != S_OK) { - std::cout << "FLYPAD_BACKEND (Pushback): Writing to sim failed! " << std::endl; - } - - // auto elapsed = duration_cast(high_resolution_clock::now() - start); - // std::cout << "FLYPAD_BACKEND (Pushback): Elapsed = " << elapsed.count() << " micro seconds" - // << std::endl; -} - -void Pushback::shutdown() { - isInitialized = false; - std::cout << "FLYPAD_BACKEND (Pushback): Pushback shutdown" << std::endl; -} diff --git a/fbw-a32nx/src/wasm/flypad-backend/src/Pushback/Pushback.h b/fbw-a32nx/src/wasm/flypad-backend/src/Pushback/Pushback.h deleted file mode 100644 index cb38bcdda36..00000000000 --- a/fbw-a32nx/src/wasm/flypad-backend/src/Pushback/Pushback.h +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#pragma once - -#include -#include -#include -#include - -#include - - -#include "Units.h" -#include "FlyPadBackend.h" -#include "InertialDampener.h" - -class InertialDampener; - -#ifdef __cpp_lib_math_constants -#include -constexpr double PI = std::numbers::pi; -#else -constexpr double PI = 3.14159265358979323846; -#endif - -/** - * Class for handling aircraft presets. - */ -class Pushback { - -private: - HANDLE hSimConnect; - bool isInitialized = false; - - PushbackData* pushbackDataPtr; - - std::unique_ptr m_Units; - std::unique_ptr inertialDampenerPtr; - - // LVARs - ID pushbackSystemEnabled{}; - ID pushbackPaused{}; - ID tugCommandedHeadingFactor{}; - ID tugCommandedHeading{}; - ID tugCommandedSpeedFactor{}; - ID tugCommandedSpeed{}; - ID tugInertiaSpeed{}; - ID parkingBrakeEngaged{}; - ID updateDelta{}; - ID rotXInput{}; - ID rotXOut{}; - - // Sim-vars - ENUM simOnGround{}; - ENUM pushbackAttached{}; - ENUM aircraftHeading{}; - ENUM windVelBodyZ{}; - -public: - /** - * Creates an instance of the Pushback class. - */ - Pushback(HANDLE hdl, PushbackData* data); - - /** - * Destructor - */ - ~Pushback() = default; - - /** - * 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: - // @formatter:off - // LVAR getter - [[nodiscard]] inline bool isPushbackPaused() const { return static_cast(get_named_variable_value(pushbackPaused)); } - [[nodiscard]] inline bool isPushbackSystemEnabled() const { return static_cast(get_named_variable_value(pushbackSystemEnabled)); } - [[nodiscard]] inline bool isParkingBrakeEngaged() const { return static_cast(get_named_variable_value(parkingBrakeEngaged)); } - [[nodiscard]] inline FLOAT64 getTugCmdSpdFactor() const { return static_cast(get_named_variable_value(tugCommandedSpeedFactor)); } - [[nodiscard]] inline FLOAT64 getTugCmdHdgFactor() const { return static_cast(get_named_variable_value(tugCommandedHeadingFactor)); } - - // Simvar getter - [[nodiscard]] inline bool isPushbackAttached() const { return static_cast(aircraft_varget(pushbackAttached, m_Units->Bool, 0)); } - [[nodiscard]] inline bool isSimOnGround() const { return static_cast(aircraft_varget(simOnGround, m_Units->Bool, 0)); } - [[nodiscard]] inline FLOAT64 getAircraftTrueHeading() const { - return (180.0 / PI) * static_cast(aircraft_varget(aircraftHeading, m_Units->Number, 0)); - } - [[nodiscard]] inline FLOAT64 getWindVelBodyZ() const { return static_cast(aircraft_varget(windVelBodyZ, m_Units->FeetSec, 0)); } - // Sim data getter - [[nodiscard]] inline bool isPushbackWaiting() const { return static_cast(pushbackDataPtr->pushbackWait); } - // @formatter:on - - /** - * Adds two angles with wrap around to result in 0-360° - * @param a - positive or negative angle - * @param b - positive or negative angle - */ - static double angleAdd(double a, double b) { - double r = a + b; - while (r > 360.0) { - r -= 360.0; - } - while (r < 0.0) { - r += 360.0; - } - return r; - }; - - /** - * Returns the signum (sign) of the given value. - * @tparam T - * @param val - * @return sign of value or 0 when value==0 - */ - template - int sgn(T val) { - return (T(0) < val) - (val < T(0)); - } - -}; diff --git a/fbw-a32nx/src/wasm/flypad-backend/src/Units.h b/fbw-a32nx/src/wasm/flypad-backend/src/Units.h deleted file mode 100644 index 68ba3ccda61..00000000000 --- a/fbw-a32nx/src/wasm/flypad-backend/src/Units.h +++ /dev/null @@ -1,27 +0,0 @@ -// 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 FeetSec = get_units_enum("Feet per second"); - ENUM Mach = get_units_enum("Mach"); - ENUM Millibars = get_units_enum("Millibars"); - ENUM Celsius = get_units_enum("Celsius"); - ENUM Hours = get_units_enum("Hours"); - ENUM Seconds = get_units_enum("Seconds"); -}; diff --git a/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/panel.cfg b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/panel.cfg index 50300370b44..ca22aa5d1b0 100644 --- a/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/panel.cfg +++ b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/panel.cfg @@ -150,6 +150,6 @@ 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=flypad-backend.wasm&wasm_gauge=FlyPadBackend,0,0,1,1 +htmlgauge03=WasmInstrument/WasmInstrument.html?wasm_module=extra-backend-a380x.wasm&wasm_gauge=Gauge_Extra_Backend,0,0,1,1 # Uncomment when A380 ExtrasHost exists in monorepo #htmlgauge04=A380X/ExtrasHost/extras-host.html,0,0,1,1 diff --git a/fbw-a380x/src/wasm/CMakeLists.txt b/fbw-a380x/src/wasm/CMakeLists.txt new file mode 100644 index 00000000000..b68e233e5c0 --- /dev/null +++ b/fbw-a380x/src/wasm/CMakeLists.txt @@ -0,0 +1,13 @@ +# folder structure +set(OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/) + +# add compiler definitions +add_definitions(-DA380X) + +add_subdirectory(extra-backend-a380x) + +# FIXME: remove the if-clause as soon as all components are using CMake +if (WIN32) + add_subdirectory(fadec_a380) + add_subdirectory(fbw_a380) +endif () diff --git a/fbw-a380x/src/wasm/build.sh b/fbw-a380x/src/wasm/build.sh deleted file mode 100644 index 8a4c27724bd..00000000000 --- a/fbw-a380x/src/wasm/build.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/bash - -# get directory of this script relative to root -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -COMMON_DIR="${DIR}/../../../../fbw-common/src/wasm" -OUTPUT="${DIR}/../../../out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/fadec.wasm" - -if [ "$1" == "--debug" ]; then - WASMLD_ARGS="" - CLANG_ARGS="-g -DEBUG" -else - WASMLD_ARGS="-O2 --lto-O2 --strip-debug" - CLANG_ARGS="-flto -O2 -DNDEBUG" -fi - -set -ex - -# create temporary folder for o files -mkdir -p "${DIR}/obj" -# clean old object files out if they exist -rm -f "${DIR}/obj/*.o" -pushd "${DIR}/obj" - -# compile c++ code for the A32NX -clang++ \ - -c \ - ${CLANG_ARGS} \ - -std=c++20 \ - -Wno-unused-command-line-argument \ - -Wno-ignored-attributes \ - -Wno-macro-redefined \ - --sysroot "${MSFS_SDK}/WASM/wasi-sysroot" \ - -target wasm32-unknown-wasi \ - -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 "${COMMON_DIR}/fadec_common/src" \ - -I "${COMMON_DIR}/fbw_common/src/inih" \ - -I "${DIR}/common" \ - "${DIR}/src/FadecGauge.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 \ - ${DIR}/obj/*.o \ - -o $OUTPUT \ No newline at end of file diff --git a/fbw-a380x/src/wasm/extra-backend-a380x/CMakeLists.txt b/fbw-a380x/src/wasm/extra-backend-a380x/CMakeLists.txt new file mode 100644 index 00000000000..5f174ba6ca0 --- /dev/null +++ b/fbw-a380x/src/wasm/extra-backend-a380x/CMakeLists.txt @@ -0,0 +1,43 @@ +# extra-backend-a380x CMakeLists.txt + +# add additional compiler definitions for the a380x extra-backend build +add_definitions() + +# add the local include directories +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/src/AircraftPresets + ${CMAKE_CURRENT_SOURCE_DIR}/src/LightingPresets + ${FBW_COMMON}/cpp-msfs-framework/ + ${FBW_COMMON}/extra-backend/ +) + +# define the source files +set(SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/src/Gauge_Extra_Backend.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/LightingPresets/LightingPresets_A380X.cpp + ${FBW_COMMON}/cpp-msfs-framework/Example/ExampleModule.cpp + ${FBW_COMMON}/extra-backend/Pushback/Pushback.cpp + ${FBW_COMMON}/extra-backend/AircraftPresets/AircraftPresets.cpp + ${FBW_COMMON}/extra-backend/LightingPresets/LightingPresets.cpp + ) + +set(INCLUDE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/src/AircraftPresets/AircraftPresetProcedures_A380X.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/LightingPresets/LightingPresets_A380X.h + ${FBW_COMMON}/cpp-msfs-framework/Example/ExampleModule.h + ${FBW_COMMON}/cpp-msfs-framework/Example/longtext.h + ${FBW_COMMON}/extra-backend/Pushback/InertialDampener.hpp + ${FBW_COMMON}/extra-backend/Pushback/Pushback.h + ${FBW_COMMON}/extra-backend/AircraftPresets/AircraftPresets.h + ${FBW_COMMON}/extra-backend/AircraftPresets/PresetProcedures.h + ${FBW_COMMON}/extra-backend/AircraftPresets/ProcedureStep.h + ${FBW_COMMON}/extra-backend/LightingPresets/LightingPresets.h + ) + +# create the targets +add_library(extra-backend-a380x OBJECT ${SOURCE_FILES} ${INCLUDE_FILES}) +add_wasm_library( + NAME extra-backend-a380x + DEPENDENCIES extra-backend-a380x cpp-msfs-framework-a380x +) diff --git a/fbw-a380x/src/wasm/extra-backend-a380x/src/AircraftPresets/AircraftPresetProcedures_A380X.h b/fbw-a380x/src/wasm/extra-backend-a380x/src/AircraftPresets/AircraftPresetProcedures_A380X.h new file mode 100644 index 00000000000..37d96c16dcc --- /dev/null +++ b/fbw-a380x/src/wasm/extra-backend-a380x/src/AircraftPresets/AircraftPresetProcedures_A380X.h @@ -0,0 +1,221 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFT_AIRCRAFTPRESETPROCEDURES_A32NX_H +#define FLYBYWIRE_AIRCRAFT_AIRCRAFTPRESETPROCEDURES_A32NX_H + +#include "AircraftPresets/PresetProceduresDefinition.h" +#include "AircraftPresets/ProcedureStep.h" + +/** + * A380X specific aircraft procedures definition. + * + * @see AircraftProceduresDefinition + * + * FIXME: This is not yet adapted to the A380X - not all necessary systems and APIs are available yet. + */ +class AircraftPresetProcedures_A380X { + public: + const inline static PresetProceduresDefinition aircraftProcedureDefinition{ + // clang-format off + // @formatter:off + + .POWERED_CONFIG_ON { + // SOP: PRELIMINARY COCKPIT PREPARATION + ProcedureStep{"BAT1 On", 1010, false, 1000, "(L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)", "1 (>L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)"}, + ProcedureStep{"BAT2 On", 1020, false, 3000, "(L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)", "1 (>L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)"}, + + ProcedureStep{"EXT PWR On", 1030, 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) }"}, + + // if no Ext Pwr is available we start the APU here with a bat only fire test + ProcedureStep{"APU Fire Test On", 1035, false, 2000, "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)", "1 (>L:A32NX_FIRE_TEST_APU)"}, + ProcedureStep{"APU Fire Test Off", 1036, false, 2000, "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)", "0 (>L:A32NX_FIRE_TEST_APU)"}, + ProcedureStep{"APU Master On", 1040, false, 3000, "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)", "1 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, + ProcedureStep{"APU Start On", 1050, 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)"}, + + ProcedureStep{"Waiting on AC BUS Availability", 1060, true, 2000, "", "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)"}, + + // SOP: COCKPIT PREPARATION + ProcedureStep{"Crew Oxy On", 1120, false, 1000, "(L:PUSH_OVHD_OXYGEN_CREW) 0 ==", "0 (>L:PUSH_OVHD_OXYGEN_CREW)"}, + ProcedureStep{"GND CTL On", 1110, 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)"}, + ProcedureStep{"CVR Test On", 1115, false, 5000, "(L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)", "1 (>L:A32NX_RCDR_TEST)"}, + ProcedureStep{"CVR Test Off", 1116, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)", "0 (>L:A32NX_RCDR_TEST) 1 (>L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)"}, + + ProcedureStep{"ADIRS 1 Nav", 1080, false, 500, "(L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB)"}, + ProcedureStep{"ADIRS 2 Nav", 1090, false, 500, "(L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB)"}, + ProcedureStep{"ADIRS 3 Nav", 1100, false, 1500, "(L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB)"}, + + ProcedureStep{"Strobe Auto", 2120, false, 1000, "(A:LIGHT STROBE, Bool)", "1 (>L:STROBE_0_AUTO) 0 (>K:STROBES_ON)"}, + ProcedureStep{"Nav & Logo Lt On", 1070, false, 1000, "(A:LIGHT LOGO, Bool) (A:LIGHT NAV, Bool) &&", "1 (>K:2:LOGO_LIGHTS_SET) 1 (>K:2:NAV_LIGHTS_SET)"}, + + ProcedureStep{"SEAT BELTS On", 2140, false, 1000, "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL)", "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) ! if{ 1 (>K:CABIN_SEATBELTS_ALERT_SWITCH_TOGGLE) }"}, + ProcedureStep{"NO SMOKING Auto", 1130, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION) 1 ==", "1 (>L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION)"}, + ProcedureStep{"EMER EXT Lt Arm", 1140, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION) 1 ==", "1 (>L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION)"}, + + // For the fire tests the FWC needs to be initialized + // The correct variables to wait for are: A32NX_FWS_FWC_1_NORMAL and A32NX_FWS_FWC_2_NORMAL. But + // as these are only on Exp this first iteration uses A32NX_FWC_FLIGHT_PHASE which also work on> + // master and is equivalent for this specific purpose. Will be changed when the FWC is on master. + ProcedureStep{"Waiting on FWC Initialization", 1065, true, 5000, "", "(L:A32NX_FWC_FLIGHT_PHASE)"}, + ProcedureStep{"Waiting...", 9999, false, 5000, "(L:A32NX_AIRCRAFT_PRESET_FWC_INIT_DONE)", "1 (>L:A32NX_AIRCRAFT_PRESET_FWC_INIT_DONE)"}, + + // APU fire test + ProcedureStep{"APU Fire Test On", 1035, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)", "1 (>L:A32NX_FIRE_TEST_APU)"}, + ProcedureStep{"APU Fire Test Off", 1036, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)", "0 (>L:A32NX_FIRE_TEST_APU) 1 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)"}, + + // After fire test we start the APU + ProcedureStep{"APU Master On", 1041, 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)"}, + ProcedureStep{"APU Start On", 1051, 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)"}, + + // ENG fire test + ProcedureStep{"ENG 1 Fire Test On", 2002, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)", "1 (>L:A32NX_FIRE_TEST_ENG1)"}, + ProcedureStep{"ENG 1 Fire Test Off", 2003, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)", "0 (>L:A32NX_FIRE_TEST_ENG1) 1 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)"}, + ProcedureStep{"ENG 2 Fire Test On", 2004, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)", "1 (>L:A32NX_FIRE_TEST_ENG2)"}, + ProcedureStep{"ENG 2 Fire Test Off", 2005, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)", "0 (>L:A32NX_FIRE_TEST_ENG2) 1 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)"}, + + ProcedureStep{"Waiting on APU Availability", 1150, true, 2000, "", "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) ! (L:A32NX_OVHD_APU_START_PB_IS_AVAILABLE) ||"}, + ProcedureStep{"APU Bleed On", 1160, 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)"} + }, + + .POWERED_CONFIG_OFF = { + ProcedureStep{"NO SMOKING Off", 1170, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION) 2 ==", "2 (>L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION)"}, + ProcedureStep{"EMER EXT Lt Off", 1180, false, 1500, "(L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION) 2 ==", "2 (>L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION)"}, + ProcedureStep{"GND CTL Off", 1200, false, 1000, "(L:A32NX_RCDR_GROUND_CONTROL_ON) 0 ==", "0 (>L:A32NX_RCDR_GROUND_CONTROL_ON)"}, + ProcedureStep{"SEAT BELTS Off", 2200, false, 2000, "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) !", "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) if{ 1 (>K:CABIN_SEATBELTS_ALERT_SWITCH_TOGGLE) }"}, + ProcedureStep{"Strobe Off", 2180, false, 1000, "(A:LIGHT STROBE, Bool) !", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_OFF)"}, + ProcedureStep{"Nav & Logo Lt Off", 1240, false, 500, "(A:LIGHT LOGO, Bool) ! (A:LIGHT NAV, Bool) ! &&", "0 (>K:2:LOGO_LIGHTS_SET) 0 (>K:2:NAV_LIGHTS_SET)"}, + ProcedureStep{"Crew Oxy Off", 1190, false, 1000, "(L:PUSH_OVHD_OXYGEN_CREW) 1 ==", "1 (>L:PUSH_OVHD_OXYGEN_CREW)"}, + ProcedureStep{"ADIRS 3 Off", 1210, false, 500, "(L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB)"}, + ProcedureStep{"ADIRS 2 Off", 1220, false, 500, "(L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB)"}, + ProcedureStep{"ADIRS 1 Off", 1230, false, 1000, "(L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB)"}, + ProcedureStep{"APU Bleed Off", 1250, false, 1500, "(L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON)"}, + ProcedureStep{"APU Master Off", 1260, false, 2000, "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, + ProcedureStep{"EXT PWR Off", 1270, false, 3000, "(A:EXTERNAL POWER ON:1, BOOL) !", "(A:EXTERNAL POWER ON:1, BOOL) if{ 1 (>K:TOGGLE_EXTERNAL_POWER) }"}, + ProcedureStep{"BAT2 Off", 1280, false, 100, "(L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO) 0 ==", "0 (>L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)"}, + ProcedureStep{"BAT1 Off", 1290, false, 1000, "(L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO) 0 ==", "0 (>L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)"}, + ProcedureStep{"AC BUS Off Check", 1300, true, 2000, "", "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED) !"}, + ProcedureStep{"CVR Test Reset", 1117, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)"}, + ProcedureStep{"APU Fire Test Reset", 1037, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)"}, + ProcedureStep{"ENG 1 Fire Test Reset", 2006, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)"}, + ProcedureStep{"ENG 2 Fire Test Reset", 2007, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)"}, + ProcedureStep{"FWC Init Reset", 1066, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FWC_INIT_DONE)"} + }, + + .PUSHBACK_CONFIG_ON = { + // SOP: BEFORE PUSHBACK OR START + ProcedureStep{"EXT PWR Off", 2000, false, 3000, "(A:EXTERNAL POWER ON:1, BOOL) !", "(A:EXTERNAL POWER ON:1, BOOL) if{ 1 (>K:TOGGLE_EXTERNAL_POWER) }"}, + ProcedureStep{"Beacon On", 2130, false, 1000, "(A:LIGHT BEACON, Bool)", "0 (>K:BEACON_LIGHTS_ON)"}, + ProcedureStep{"FUEL PUMP 2 On", 2010, false, 100, "(A:FUELSYSTEM PUMP SWITCH:2, Bool)", "2 (>K:FUELSYSTEM_PUMP_ON)"}, + ProcedureStep{"FUEL PUMP 5 On", 2020, false, 500, "(A:FUELSYSTEM PUMP SWITCH:5, Bool)", "5 (>K:FUELSYSTEM_PUMP_ON)"}, + ProcedureStep{"FUEL PUMP 1 On", 2030, false, 100, "(A:FUELSYSTEM PUMP SWITCH:1, Bool)", "1 (>K:FUELSYSTEM_PUMP_ON)"}, + ProcedureStep{"FUEL PUMP 4 On", 2040, false, 500, "(A:FUELSYSTEM PUMP SWITCH:4, Bool)", "4 (>K:FUELSYSTEM_PUMP_ON)"}, + ProcedureStep{"FUEL PUMP 3 On", 2050, false, 100, "(A:FUELSYSTEM PUMP SWITCH:3, Bool)", "3 (>K:FUELSYSTEM_PUMP_ON)"}, + ProcedureStep{"FUEL PUMP 6 On", 2060, false, 2000, "(A:FUELSYSTEM PUMP SWITCH:6, Bool)", "6 (>K:FUELSYSTEM_PUMP_ON)"}, + ProcedureStep{"COCKPIT DOOR LCK", 2110, false, 2000, "(L:A32NX_COCKPIT_DOOR_LOCKED) 1 ==", "1 (>L:A32NX_COCKPIT_DOOR_LOCKED)"}, + ProcedureStep{"Await ADIRS 1 Alignment", 2150, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_1_STATE) 2 =="}, + ProcedureStep{"Await ADIRS 2 Alignment", 2160, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_2_STATE) 2 =="}, + ProcedureStep{"Await ADIRS 3 Alignment", 2170, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_3_STATE) 2 =="}, + }, + + .PUSHBACK_CONFIG_OFF = { + ProcedureStep{"COCKPIT DOOR OP", 2250, false, 2000, "(L:A32NX_COCKPIT_DOOR_LOCKED) 0 ==", "0 (>L:A32NX_COCKPIT_DOOR_LOCKED)"}, + ProcedureStep{"FUEL PUMP 2 Off", 2260, false, 100, "(A:FUELSYSTEM PUMP SWITCH:2, Bool) !", "2 (>K:FUELSYSTEM_PUMP_OFF)"}, + ProcedureStep{"FUEL PUMP 5 Off", 2270, false, 500, "(A:FUELSYSTEM PUMP SWITCH:5, Bool) !", "5 (>K:FUELSYSTEM_PUMP_OFF)"}, + ProcedureStep{"FUEL PUMP 1 Off", 2280, false, 100, "(A:FUELSYSTEM PUMP SWITCH:1, Bool) !", "1 (>K:FUELSYSTEM_PUMP_OFF)"}, + ProcedureStep{"FUEL PUMP 4 Off", 2290, false, 500, "(A:FUELSYSTEM PUMP SWITCH:4, Bool) !", "4 (>K:FUELSYSTEM_PUMP_OFF)"}, + ProcedureStep{"FUEL PUMP 3 Off", 2300, false, 100, "(A:FUELSYSTEM PUMP SWITCH:3, Bool) !", "3 (>K:FUELSYSTEM_PUMP_OFF)"}, + ProcedureStep{"FUEL PUMP 6 Off", 2310, false, 1000, "(A:FUELSYSTEM PUMP SWITCH:6, Bool) !", "6 (>K:FUELSYSTEM_PUMP_OFF)"}, + ProcedureStep{"Beacon Off", 2190, false, 1000, "(A:LIGHT BEACON, Bool) !", "0 (>K:BEACON_LIGHTS_OFF)"}, + }, + + .TAXI_CONFIG_ON = { + // SOP: ENGINE START + ProcedureStep{"ENG MODE SEL START", 3000, 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)"}, + ProcedureStep{"ENG 2 ON", 3010, false, 60000, "(A:FUELSYSTEM VALVE OPEN:2, Bool)", "2 (>K:FUELSYSTEM_VALVE_OPEN)"}, + ProcedureStep{"Await ENG 2 AVAIL", 3020, true, 5000, "", "(L:A32NX_ENGINE_STATE:2) 1 =="}, + ProcedureStep{"ENG 1 ON", 3030, false, 60000, "(A:FUELSYSTEM VALVE OPEN:1, Bool)", "1 (>K:FUELSYSTEM_VALVE_OPEN)"}, + ProcedureStep{"Await ENG 1 AVAIL", 3040, true, 5000, "", "(L:A32NX_ENGINE_STATE:1) 1 =="}, + // SOP: AFTER START + ProcedureStep{"ENG MODE SEL NORM", 3050, 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)"}, + ProcedureStep{"APU Bleed Off", 3060, false, 2000, "(L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON)"}, + ProcedureStep{"APU Master Off", 3070, false, 2000, "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, + ProcedureStep{"Spoiler Arm", 3090, false, 2000, "(L:A32NX_SPOILERS_ARMED) 1 ==", "1 (>K:SPOILERS_ARM_SET)"}, + ProcedureStep{"Rudder Trim Reset", 3100, false, 2000, "(A:RUDDER TRIM, Radians) 0 ==", "0 (>K:RUDDER_TRIM_SET)"}, + ProcedureStep{"Flaps 1", 3110, false, 3000, "(L:A32NX_FLAPS_HANDLE_INDEX) 1 ==", "1 (>L:A32NX_FLAPS_HANDLE_INDEX)"}, + // SOP: TAXI + ProcedureStep{"NOSE Lt Taxi", 3120, 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)"}, + ProcedureStep{"RWY TURN OFF Lt L On", 3130, false, 0, "(A:CIRCUIT SWITCH ON:21, Bool)", "(A:CIRCUIT SWITCH ON:21, Bool) ! if{ 21 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + ProcedureStep{"RWY TURN OFF Lt R On", 3140, false, 2000, "(A:CIRCUIT SWITCH ON:22, Bool)", "(A:CIRCUIT SWITCH ON:22, Bool) ! if{ 22 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + ProcedureStep{"PWS Auto", 2070, false, 1000, "(L:A32NX_SWITCH_RADAR_PWS_POSITION) 1 ==", "1 (>L:A32NX_SWITCH_RADAR_PWS_POSITION)"}, + ProcedureStep{"Transponder On", 2080, false, 1000, "(L:A32NX_TRANSPONDER_MODE) 1 ==", "1 (>L:A32NX_TRANSPONDER_MODE)"}, + ProcedureStep{"ATC ALT RPTG On", 2090, false, 1000, "(L:A32NX_SWITCH_ATC_ALT) 1 ==", "1 (>L:A32NX_SWITCH_ATC_ALT)"}, + ProcedureStep{"TCAS TRAFFIC ABV", 2100, false, 2000, "(L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION)"}, + ProcedureStep{"Autobrake Max", 3080, false, 2000, "(L:A32NX_AUTOBRAKES_ARMED_MODE) 3 ==", "3 (>L:A32NX_AUTOBRAKES_ARMED_MODE_SET)"}, + ProcedureStep{"TERR ON ND Capt. On", 3080, false, 2000, "(L:A32NX_EFIS_TERR_L_ACTIVE) 1 ==", "1 (>L:A32NX_EFIS_TERR_L_ACTIVE)"}, + ProcedureStep{"T.O. Config", 3085, false, 2000, "", "1 (>L:A32NX_TO_CONFIG_NORMAL)"}, + }, + + .TAXI_CONFIG_OFF = { + ProcedureStep{"TERR ON ND Capt. Off", 3080, false, 2000, "(L:A32NX_EFIS_TERR_L_ACTIVE) 0 ==", "0 (>L:A32NX_EFIS_TERR_L_ACTIVE)"}, + ProcedureStep{"Autobrake Off", 3180, false, 2000, "(L:A32NX_AUTOBRAKES_ARMED_MODE) 0 ==", "0 (>L:A32NX_AUTOBRAKES_ARMED_MODE_SET)"}, + ProcedureStep{"TCAS TRAFFIC ABV", 2240, false, 1000, "(L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION)"}, + ProcedureStep{"ATC ALT RPTG Off", 2230, false, 1000, "(L:A32NX_SWITCH_ATC_ALT) 1 ==", "1 (>L:A32NX_SWITCH_ATC_ALT)"}, + ProcedureStep{"Transponder Off", 2220, false, 1000, "(L:A32NX_TRANSPONDER_MODE) 0 ==", "0 (>L:A32NX_TRANSPONDER_MODE)"}, + ProcedureStep{"PWS Off", 2210, false, 1000, "(L:A32NX_SWITCH_RADAR_PWS_POSITION) 0 ==", "0 (>L:A32NX_SWITCH_RADAR_PWS_POSITION)"}, + ProcedureStep{"RWY TURN OFF Lt L Off", 3160, false, 0, "(A:CIRCUIT SWITCH ON:21, Bool) !", "(A:CIRCUIT SWITCH ON:21, Bool) if{ 21 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + ProcedureStep{"RWY TURN OFF Lt R Off", 3170, false, 2000, "(A:CIRCUIT SWITCH ON:22, Bool) !", "(A:CIRCUIT SWITCH ON:22, Bool) if{ 22 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + ProcedureStep{"NOSE Lt Taxi", 3150, 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)"}, + ProcedureStep{"Flaps 0", 3210, false, 2000, "(L:A32NX_FLAPS_HANDLE_INDEX) 0 ==", "0 (>L:A32NX_FLAPS_HANDLE_INDEX)"}, + ProcedureStep{"Rudder Trim Reset", 3200, false, 2000, "(A:RUDDER TRIM, Radians) 0 ==", "0 (>K:RUDDER_TRIM_SET)"}, + ProcedureStep{"Spoiler Disarm", 3190, false, 2000, "(L:A32NX_SPOILERS_ARMED) 0 ==", "0 (>K:SPOILERS_ARM_SET)"}, + ProcedureStep{"ENG 1 Off", 3220, false, 2000, "(A:FUELSYSTEM VALVE OPEN:1, Bool) !", "1 (>K:FUELSYSTEM_VALVE_CLOSE)"}, + ProcedureStep{"ENG 2 Off", 3230, false, 2000, "(A:FUELSYSTEM VALVE OPEN:2, Bool) !", "2 (>K:FUELSYSTEM_VALVE_CLOSE)"}, + ProcedureStep{"ENG 1 N1 <3%", 3240, true, 1000, "", "(L:A32NX_ENGINE_N1:1) 3 <"}, + ProcedureStep{"ENG 2 N1 <3%", 3250, true, 1000, "", "(L:A32NX_ENGINE_N1:2) 3 <"} + }, + + .TAKEOFF_CONFIG_ON = { + // SOP: TAXI + ProcedureStep{"WX Radar On", 4000, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_SYS) 0 ==", "0 (>L:XMLVAR_A320_WEATHERRADAR_SYS)"}, + ProcedureStep{"WX Radar Mode", 4010, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_MODE) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_MODE)"}, + // SOP: BEFORE TAKEOFF + ProcedureStep{"TCAS Switch TA/RA", 4020, false, 2000, "(L:A32NX_SWITCH_TCAS_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_POSITION)"}, + ProcedureStep{"Strobe On", 2120, false, 1000, "(A:LIGHT STROBE, Bool)", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_ON)"}, + ProcedureStep{"Cabin Ready", 2125, false, 1000, "", "1 (>L:A32NX_CABIN_READY)"}, + // SOP: TAKE OFF + ProcedureStep{"NOSE Lt Takeoff", 4030, false, 1000, "(A:CIRCUIT SWITCH ON:17, Bool)", "(A:CIRCUIT SWITCH ON:17, Bool) ! if{ 17 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + ProcedureStep{"LL Lt L On", 4040, 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)"}, + ProcedureStep{"LL Lt R On", 4050, 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)"}, + }, + + .TAKEOFF_CONFIG_OFF = { + ProcedureStep{"LL Lt L Off", 4060, 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)"}, + ProcedureStep{"LL Lt R Off", 4070, 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)"}, + ProcedureStep{"NOSE Lt Takeoff", 4080, false, 2000, "(A:CIRCUIT SWITCH ON:17, Bool) !", "(A:CIRCUIT SWITCH ON:17, Bool) if{ 17 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, + ProcedureStep{"Strobe Auto", 2180, false, 1000, "(A:LIGHT STROBE, Bool) !", "1 (>L:STROBE_0_AUTO) 0 (>K:STROBES_OFF)"}, + ProcedureStep{"TCAS Switch TA/RA", 4090, false, 1000, "(L:A32NX_SWITCH_TCAS_POSITION) 0 ==", "0 (>L:A32NX_SWITCH_TCAS_POSITION)"}, + ProcedureStep{"WX Radar Mode", 4110, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_MODE) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_MODE)"}, + ProcedureStep{"WX Radar Off", 4100, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_SYS) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_SYS)"}, + } + + // @formatter:on + // clang-format on + }; +}; + +#endif // FLYBYWIRE_AIRCRAFT_AIRCRAFTPRESETPROCEDURES_A32NX_H diff --git a/fbw-a380x/src/wasm/extra-backend-a380x/src/Gauge_Extra_Backend.cpp b/fbw-a380x/src/wasm/extra-backend-a380x/src/Gauge_Extra_Backend.cpp new file mode 100644 index 00000000000..64fcb332571 --- /dev/null +++ b/fbw-a380x/src/wasm/extra-backend-a380x/src/Gauge_Extra_Backend.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#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 "MsfsHandler.h" + +#ifdef EXAMPLES +#include "Example/ExampleModule.h" +#endif + +#include "AircraftPresets/AircraftPresetProcedures_A380X.h" +#include "AircraftPresets/AircraftPresets.h" +#include "LightingPresets/LightingPresets_A380X.h" +#include "Pushback/Pushback.h" + + +// FIXME: Using the A32NX_ prefix is a workaround until the A380X_ prefix is used +// in the A380X EFB +MsfsHandler msfsHandler("Gauge_Extra_Backend_A380X", "A32NX_"); +#ifdef EXAMPLES +ExampleModule exampleModule(msfsHandler); +#endif + +// FIXME: As the A380X does not yet provide the "A...._IS_READY" LVAR the modules will +// not be working as expected. They will return from their update() method immediately. + +// ADD ADDITIONAL MODULES HERE +// This is the only place these have to be added - everything else is handled automatically +LightingPresets_A380X lightingPresets(msfsHandler); +Pushback pushback(msfsHandler); +AircraftPresets aircraftPresets(msfsHandler, AircraftPresetProcedures_A380X::aircraftProcedureDefinition); + +/** + * Gauge Callback + * There can by multiple gauges in a single wasm module. Just add another gauge callback function + * and register it in the panel.cfg file. + * + * Avoid putting any logic in the gauge callback function. Instead, create a new class and put + * the logic there. + * + * @see + * https://docs.flightsimulator.com/html/Content_Configuration/SimObjects/Aircraft_SimO/Instruments/C_C++_Gauges.htm?rhhlterm=_gauge_callback&rhsearch=_gauge_callback + */ +extern "C" { +[[maybe_unused]] MSFS_CALLBACK bool Gauge_Extra_Backend_gauge_callback([[maybe_unused]] FsContext ctx, int svcId, void* pData) { + switch (svcId) { + case PANEL_SERVICE_PRE_INSTALL: { + return msfsHandler.initialize(); + } + case PANEL_SERVICE_PRE_DRAW: { + return msfsHandler.update(static_cast(pData)); + } + case PANEL_SERVICE_PRE_KILL: { + return msfsHandler.shutdown(); + } + default: + break; + } + return false; +} +} diff --git a/fbw-a380x/src/wasm/extra-backend-a380x/src/LightingPresets/LightingPresets_A380X.cpp b/fbw-a380x/src/wasm/extra-backend-a380x/src/LightingPresets/LightingPresets_A380X.cpp new file mode 100644 index 00000000000..b7de9b812bb --- /dev/null +++ b/fbw-a380x/src/wasm/extra-backend-a380x/src/LightingPresets/LightingPresets_A380X.cpp @@ -0,0 +1,275 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include "LightingPresets_A380X.h" +#include "UpdateMode.h" +#include "logging.h" + +/// +// DataManager Howto Note: +// ======================= + +// The LightingPresets_A32NX module uses the DataManager to get and set variables. +// Looking at the make_xxx_var functions, you can see that they are updated +// with different update cycles. +// +// Some variables are read from the sim at every tick: +// - A32NX_ELEC_AC_1_BUS_IS_POWERED +// +// Some variables are read and written from/to the sim at every tick: +// - A32NX_LIGHTING_PRESET_LOAD +// - A32NX_LIGHTING_PRESET_SAVE +// +// The rest are read on demand after the state of the above variables have been checked. +// +// This makes sure variables are only read or written to when really needed. And as +// LightingPresets_A32NX will be dormant most of the time, this is saving a lot of +// unnecessary reads/writes. +/// + +bool LightingPresets_A380X::initialize() { + dataManager = &msfsHandler.getDataManager(); + + // Events for setting the aircraft variables + lightPotentiometerSetEvent = dataManager->make_sim_event("LIGHT_POTENTIOMETER_SET", NOTIFICATION_GROUP_1); + cabinLightSetEvent = dataManager->make_sim_event("CABIN_LIGHTS_SET", NOTIFICATION_GROUP_1); + + // Control LVARs - auto updated with every tick - LOAD/SAVE also auto written to sim + elecAC1Powered = dataManager->make_named_var("ELEC_AC_1_BUS_IS_POWERED", UNITS.Number, UpdateMode::AUTO_READ); + loadLightingPresetRequest = dataManager->make_named_var("LIGHTING_PRESET_LOAD", UNITS.Number, UpdateMode::AUTO_READ_WRITE); + saveLightingPresetRequest = dataManager->make_named_var("LIGHTING_PRESET_SAVE", UNITS.Number, UpdateMode::AUTO_READ_WRITE); + + // Lighting LVARs - manual update and write when load/saving is requested + efbBrightness = dataManager->make_named_var("EFB_BRIGHTNESS", UNITS.Number); + dcduLeftLightLevel = dataManager->make_named_var("PANEL_DCDU_L_BRIGHTNESS", UNITS.Number); + dcduRightLightLevel = dataManager->make_named_var("PANEL_DCDU_R_BRIGHTNESS", UNITS.Number); + mcduLeftLightLevel = dataManager->make_named_var("MCDU_L_BRIGHTNESS", UNITS.Number); + mcduRightLightLevel = dataManager->make_named_var("MCDU_R_BRIGHTNESS", UNITS.Number); + + // Light Potentiometers - manual update and write when load/saving is requested + lightCabin = dataManager->make_aircraft_var("LIGHT CABIN", 0, "", cabinLightSetEvent, UNITS.Percent); + lightCabinLevel = getLightPotentiometerVar(7); + ovhdIntegralLightLevel = getLightPotentiometerVar(86); + glareshieldIntegralLightLevel = getLightPotentiometerVar(84); + glareshieldLcdLightLevel = getLightPotentiometerVar(87); + tableLightCptLevel = getLightPotentiometerVar(10); + tableLightFoLevel = getLightPotentiometerVar(11); + pfdBrtCptLevel = getLightPotentiometerVar(88); + ndBrtCptLevel = getLightPotentiometerVar(89); + wxTerrainBrtCptLevel = getLightPotentiometerVar(94); + consoleLightCptLevel = getLightPotentiometerVar(8); + pfdBrtFoLevel = getLightPotentiometerVar(90); + ndBrtFoLevel = getLightPotentiometerVar(91); + wxTerrainBrtFoLevel = getLightPotentiometerVar(95); + consoleLightFoLevel = getLightPotentiometerVar(9); + ecamUpperLightLevel = getLightPotentiometerVar(92); + ecamLowerLightLevel = getLightPotentiometerVar(93); + floodPnlLightLevel = getLightPotentiometerVar(83); + pedestalIntegralLightLevel = getLightPotentiometerVar(85); + floodPedLightLevel = getLightPotentiometerVar(76); + + loadLightingPresetRequest->setAsInt64(0); + saveLightingPresetRequest->setAsInt64(0); + + _isInitialized = true; + LOG_INFO("LightingPresets_A380X initialized"); + return true; +} + +// ================================================================================================= +// PRIVATE METHODS +// ================================================================================================= + +void LightingPresets_A380X::readFromAircraft() { + currentLightValues.efbBrightness = efbBrightness->readFromSim(); + currentLightValues.cabinLightLevel = lightCabinLevel->readFromSim(); + currentLightValues.ovhdIntegralLightLevel = ovhdIntegralLightLevel->readFromSim(); + currentLightValues.glareshieldIntegralLightLevel = glareshieldIntegralLightLevel->readFromSim(); + currentLightValues.glareshieldLcdLightLevel = glareshieldLcdLightLevel->readFromSim(); + currentLightValues.tableLightCptLevel = tableLightCptLevel->readFromSim(); + currentLightValues.tableLightFoLevel = tableLightFoLevel->readFromSim(); + currentLightValues.pfdBrtCptLevel = pfdBrtCptLevel->readFromSim(); + currentLightValues.ndBrtCptLevel = ndBrtCptLevel->readFromSim(); + currentLightValues.wxTerrainBrtCptLevel = wxTerrainBrtCptLevel->readFromSim(); + currentLightValues.consoleLightCptLevel = consoleLightCptLevel->readFromSim(); + currentLightValues.pfdBrtFoLevel = pfdBrtFoLevel->readFromSim(); + currentLightValues.ndBrtFoLevel = ndBrtFoLevel->readFromSim(); + currentLightValues.wxTerrainBrtFoLevel = wxTerrainBrtFoLevel->readFromSim(); + currentLightValues.consoleLightFoLevel = consoleLightFoLevel->readFromSim(); + currentLightValues.dcduLeftLightLevel = dcduLeftLightLevel->readFromSim(); + currentLightValues.dcduRightLightLevel = dcduLeftLightLevel->readFromSim(); + currentLightValues.mcduLeftLightLevel = mcduLeftLightLevel->readFromSim(); + currentLightValues.mcduRightLightLevel = mcduRightLightLevel->readFromSim(); + currentLightValues.ecamUpperLightLevel = ecamUpperLightLevel->readFromSim(); + currentLightValues.ecamLowerLightLevel = ecamLowerLightLevel->readFromSim(); + currentLightValues.floodPnlLightLevel = floodPnlLightLevel->readFromSim(); + currentLightValues.pedestalIntegralLightLevel = pedestalIntegralLightLevel->readFromSim(); + currentLightValues.floodPedLightLevel = floodPedLightLevel->readFromSim(); +} + +void LightingPresets_A380X::applyToAircraft() { + efbBrightness->setAndWriteToSim(intermediateLightValues.efbBrightness); + setValidCabinLightValue(intermediateLightValues.cabinLightLevel); + ovhdIntegralLightLevel->setAndWriteToSim(intermediateLightValues.ovhdIntegralLightLevel); + glareshieldIntegralLightLevel->setAndWriteToSim(intermediateLightValues.glareshieldIntegralLightLevel); + glareshieldLcdLightLevel->setAndWriteToSim(intermediateLightValues.glareshieldLcdLightLevel); + tableLightCptLevel->setAndWriteToSim(intermediateLightValues.tableLightCptLevel); + tableLightFoLevel->setAndWriteToSim(intermediateLightValues.tableLightFoLevel); + pfdBrtCptLevel->setAndWriteToSim(intermediateLightValues.pfdBrtCptLevel); + ndBrtCptLevel->setAndWriteToSim(intermediateLightValues.ndBrtCptLevel); + wxTerrainBrtCptLevel->setAndWriteToSim(intermediateLightValues.wxTerrainBrtCptLevel); + consoleLightCptLevel->setAndWriteToSim(intermediateLightValues.consoleLightCptLevel); + pfdBrtFoLevel->setAndWriteToSim(intermediateLightValues.pfdBrtFoLevel); + ndBrtFoLevel->setAndWriteToSim(intermediateLightValues.ndBrtFoLevel); + wxTerrainBrtFoLevel->setAndWriteToSim(intermediateLightValues.wxTerrainBrtFoLevel); + consoleLightFoLevel->setAndWriteToSim(intermediateLightValues.consoleLightFoLevel); + dcduLeftLightLevel->setAndWriteToSim(intermediateLightValues.dcduLeftLightLevel); + dcduRightLightLevel->setAndWriteToSim(intermediateLightValues.dcduRightLightLevel); + mcduLeftLightLevel->setAndWriteToSim(intermediateLightValues.mcduLeftLightLevel); + mcduRightLightLevel->setAndWriteToSim(intermediateLightValues.mcduRightLightLevel); + ecamUpperLightLevel->setAndWriteToSim(intermediateLightValues.ecamUpperLightLevel); + ecamLowerLightLevel->setAndWriteToSim(intermediateLightValues.ecamLowerLightLevel); + floodPnlLightLevel->setAndWriteToSim(intermediateLightValues.floodPnlLightLevel); + pedestalIntegralLightLevel->setAndWriteToSim(intermediateLightValues.pedestalIntegralLightLevel); + floodPedLightLevel->setAndWriteToSim(intermediateLightValues.floodPedLightLevel); +} + +void LightingPresets_A380X::loadFromIni(const mINI::INIStructure& ini, + const std::string& iniSectionName) { + // check if iniSectionName is available + // if not use a 50% default iniSectionName + if (!ini.has(iniSectionName)) { + intermediateLightValues = DEFAULT_50; + return; + } + + // reading data structure from ini + loadedLightValues.efbBrightness = iniGetOrDefault(ini, iniSectionName, "efb_brightness", 80.0); + loadedLightValues.cabinLightLevel = iniGetOrDefault(ini, iniSectionName, "cabin_light", 50.0); + loadedLightValues.ovhdIntegralLightLevel = iniGetOrDefault(ini, iniSectionName, "ovhd_int_lt", 50.0); + loadedLightValues.glareshieldIntegralLightLevel = iniGetOrDefault(ini, iniSectionName, "glareshield_int_lt", 50.0); + loadedLightValues.glareshieldLcdLightLevel = iniGetOrDefault(ini, iniSectionName, "glareshield_lcd_lt", 50.0); + loadedLightValues.tableLightCptLevel = iniGetOrDefault(ini, iniSectionName, "table_cpt_lt", 50.0); + loadedLightValues.tableLightFoLevel = iniGetOrDefault(ini, iniSectionName, "table_fo_lt", 50.0); + loadedLightValues.pfdBrtCptLevel = iniGetOrDefault(ini, iniSectionName, "pfd_cpt_lvl", 50.0); + loadedLightValues.ndBrtCptLevel = iniGetOrDefault(ini, iniSectionName, "nd_cpt_lvl", 50.0); + loadedLightValues.wxTerrainBrtCptLevel = iniGetOrDefault(ini, iniSectionName, "wx_cpt_lvl", 50.0); + loadedLightValues.consoleLightCptLevel = iniGetOrDefault(ini, iniSectionName, "console_cpt_lt", 50.0); + loadedLightValues.pfdBrtFoLevel = iniGetOrDefault(ini, iniSectionName, "pfd_fo_lvl", 50.0); + loadedLightValues.ndBrtFoLevel = iniGetOrDefault(ini, iniSectionName, "nd_fo_lvl", 50.0); + loadedLightValues.wxTerrainBrtFoLevel = iniGetOrDefault(ini, iniSectionName, "wx_fo_lvl", 50.0); + loadedLightValues.consoleLightFoLevel = iniGetOrDefault(ini, iniSectionName, "console_fo_lt", 50.0); + loadedLightValues.dcduLeftLightLevel = iniGetOrDefault(ini, iniSectionName, "dcdu_left_lvl", 50.0) / 100; + loadedLightValues.dcduRightLightLevel = iniGetOrDefault(ini, iniSectionName, "dcdu_right_lvl", 50.0) / 100; + loadedLightValues.mcduLeftLightLevel = iniGetOrDefault(ini, iniSectionName, "mcdu_left_lvl", 50.0) / 100; + loadedLightValues.mcduRightLightLevel = iniGetOrDefault(ini, iniSectionName, "mcdu_right_lvl", 50.0) / 100; + loadedLightValues.ecamUpperLightLevel = iniGetOrDefault(ini, iniSectionName, "ecam_upper_lvl", 50.0); + loadedLightValues.ecamLowerLightLevel = iniGetOrDefault(ini, iniSectionName, "ecam_lower_lvl", 50.0); + loadedLightValues.floodPnlLightLevel = iniGetOrDefault(ini, iniSectionName, "flood_pnl_lt", 50.0); + loadedLightValues.pedestalIntegralLightLevel = iniGetOrDefault(ini, iniSectionName, "pedestal_int_lt", 50.0); + loadedLightValues.floodPedLightLevel = iniGetOrDefault(ini, iniSectionName, "flood_ped_lvl", 50.0); +} + +void LightingPresets_A380X::saveToIni(mINI::INIStructure& ini, const std::string& iniSectionName) const { + ini[iniSectionName]["efb_brightness"] = std::to_string(currentLightValues.efbBrightness); + ini[iniSectionName]["cabin_light"] = std::to_string(currentLightValues.cabinLightLevel); + ini[iniSectionName]["ovhd_int_lt"] = std::to_string(currentLightValues.ovhdIntegralLightLevel); + ini[iniSectionName]["glareshield_int_lt"] = std::to_string(currentLightValues.glareshieldIntegralLightLevel); + ini[iniSectionName]["glareshield_lcd_lt"] = std::to_string(currentLightValues.glareshieldLcdLightLevel); + ini[iniSectionName]["table_cpt_lt"] = std::to_string(currentLightValues.tableLightCptLevel); + ini[iniSectionName]["table_fo_lt"] = std::to_string(currentLightValues.tableLightFoLevel); + ini[iniSectionName]["pfd_cpt_lvl"] = std::to_string(currentLightValues.pfdBrtCptLevel); + ini[iniSectionName]["nd_cpt_lvl"] = std::to_string(currentLightValues.ndBrtCptLevel); + ini[iniSectionName]["wx_cpt_lvl"] = std::to_string(currentLightValues.wxTerrainBrtCptLevel); + ini[iniSectionName]["console_cpt_lt"] = std::to_string(currentLightValues.consoleLightCptLevel); + ini[iniSectionName]["pfd_fo_lvl"] = std::to_string(currentLightValues.pfdBrtFoLevel); + ini[iniSectionName]["nd_fo_lvl"] = std::to_string(currentLightValues.ndBrtFoLevel); + ini[iniSectionName]["wx_fo_lvl"] = std::to_string(currentLightValues.wxTerrainBrtFoLevel); + ini[iniSectionName]["console_fo_lt"] = std::to_string(currentLightValues.consoleLightFoLevel); + ini[iniSectionName]["dcdu_left_lvl"] = std::to_string(currentLightValues.dcduLeftLightLevel * 100); + ini[iniSectionName]["dcdu_right_lvl"] = std::to_string(currentLightValues.dcduRightLightLevel * 100); + ini[iniSectionName]["mcdu_left_lvl"] = std::to_string(currentLightValues.mcduLeftLightLevel * 100); + ini[iniSectionName]["mcdu_right_lvl"] = std::to_string(currentLightValues.mcduRightLightLevel * 100); + ini[iniSectionName]["ecam_upper_lvl"] = std::to_string(currentLightValues.ecamUpperLightLevel); + ini[iniSectionName]["ecam_lower_lvl"] = std::to_string(currentLightValues.ecamLowerLightLevel); + ini[iniSectionName]["flood_pnl_lt"] = std::to_string(currentLightValues.floodPnlLightLevel); + ini[iniSectionName]["pedestal_int_lt"] = std::to_string(currentLightValues.pedestalIntegralLightLevel); + ini[iniSectionName]["flood_ped_lvl"] = std::to_string(currentLightValues.floodPedLightLevel); +} + +[[maybe_unused]] std::string LightingPresets_A380X::str() const { + std::ostringstream os; + os << "EFB Brightness: " << intermediateLightValues.efbBrightness << std::endl; + os << "Cabin Light: " << intermediateLightValues.cabinLightLevel << std::endl; + os << "Ovhd Int Lt: " << intermediateLightValues.ovhdIntegralLightLevel << std::endl; + os << "Glareshield Int Lt: " << intermediateLightValues.glareshieldIntegralLightLevel << std::endl; + os << "Glareshield Lcd Lt: " << intermediateLightValues.glareshieldLcdLightLevel << std::endl; + os << "Table Cpt Lt: " << intermediateLightValues.tableLightCptLevel << std::endl; + os << "Table FO Lt: " << intermediateLightValues.tableLightFoLevel << std::endl; + os << "PFD Cpt Lvl: " << intermediateLightValues.pfdBrtCptLevel << std::endl; + os << "ND Cpt Lvl: " << intermediateLightValues.ndBrtCptLevel << std::endl; + os << "WX Cpt Lvl: " << intermediateLightValues.wxTerrainBrtCptLevel << std::endl; + os << "Console Cpt Lt: " << intermediateLightValues.consoleLightCptLevel << std::endl; + os << "PFD FO Lvl: " << intermediateLightValues.pfdBrtFoLevel << std::endl; + os << "ND FO Lvl: " << intermediateLightValues.ndBrtFoLevel << std::endl; + os << "WX FO Lvl: " << intermediateLightValues.wxTerrainBrtFoLevel << std::endl; + os << "Console Fo Lt: " << intermediateLightValues.consoleLightFoLevel << std::endl; + os << "DCDU Left Lvl: " << intermediateLightValues.dcduLeftLightLevel << std::endl; + os << "DCDU Right Lvl: " << intermediateLightValues.dcduRightLightLevel << std::endl; + os << "MCDU Left Lvl: " << intermediateLightValues.mcduLeftLightLevel << std::endl; + os << "MCDU Right Lvl: " << intermediateLightValues.mcduRightLightLevel << std::endl; + os << "ECAM Upper Lvl: " << intermediateLightValues.ecamUpperLightLevel << std::endl; + os << "ECAM Lower Lvl: " << intermediateLightValues.ecamLowerLightLevel << std::endl; + os << "Floor Cpt Lt: " << intermediateLightValues.floodPnlLightLevel << std::endl; + os << "Pedestal Int Lt: " << intermediateLightValues.pedestalIntegralLightLevel << std::endl; + os << "Floor FO Lvl: " << intermediateLightValues.floodPedLightLevel << std::endl; + return os.str(); +} + +void LightingPresets_A380X::setValidCabinLightValue(FLOAT64 level) { + // cabin light level needs to either be 0, 50 or 100 for the switch position + // in the aircraft to work. + if (level <= 0.0) { + level = 0.0; + } else if (level > 0.0 && level <= 50.0) { + level = 50.0; + } else if ((level > 0.0 && level > 50.0)) { + level = 100.0; + } + // cabin lights in the A32NX need to be controlled by two vars + // one for the switch position and one for the actual light + lightCabinLevel->setAndWriteToSim(level); + lightCabin->setAndWriteToSim(level > 0 ? 1 : 0); +} + +bool LightingPresets_A380X::calculateIntermediateValues() { + // clang-format off + intermediateLightValues.efbBrightness = convergeValue( currentLightValues.efbBrightness,loadedLightValues.efbBrightness); + intermediateLightValues.cabinLightLevel = loadedLightValues.cabinLightLevel; + intermediateLightValues.ovhdIntegralLightLevel = convergeValue( currentLightValues.ovhdIntegralLightLevel,loadedLightValues.ovhdIntegralLightLevel); + intermediateLightValues.glareshieldIntegralLightLevel = convergeValue( currentLightValues.glareshieldIntegralLightLevel,loadedLightValues.glareshieldIntegralLightLevel); + intermediateLightValues.glareshieldLcdLightLevel = convergeValue( currentLightValues.glareshieldLcdLightLevel,loadedLightValues.glareshieldLcdLightLevel); + intermediateLightValues.tableLightCptLevel = convergeValue( currentLightValues.tableLightCptLevel,loadedLightValues.tableLightCptLevel); + intermediateLightValues.tableLightFoLevel = convergeValue( currentLightValues.tableLightFoLevel,loadedLightValues.tableLightFoLevel); + intermediateLightValues.pfdBrtCptLevel = convergeValue( currentLightValues.pfdBrtCptLevel,loadedLightValues.pfdBrtCptLevel); + intermediateLightValues.ndBrtCptLevel = convergeValue( currentLightValues.ndBrtCptLevel,loadedLightValues.ndBrtCptLevel); + intermediateLightValues.wxTerrainBrtCptLevel = convergeValue( currentLightValues.wxTerrainBrtCptLevel,loadedLightValues.wxTerrainBrtCptLevel); + intermediateLightValues.consoleLightCptLevel = convergeValue( currentLightValues.consoleLightCptLevel,loadedLightValues.consoleLightCptLevel); + intermediateLightValues.pfdBrtFoLevel = convergeValue( currentLightValues.pfdBrtFoLevel,loadedLightValues.pfdBrtFoLevel); + intermediateLightValues.ndBrtFoLevel = convergeValue( currentLightValues.ndBrtFoLevel,loadedLightValues.ndBrtFoLevel); + intermediateLightValues.wxTerrainBrtFoLevel = convergeValue( currentLightValues.wxTerrainBrtFoLevel,loadedLightValues.wxTerrainBrtFoLevel); + intermediateLightValues.consoleLightFoLevel = convergeValue( currentLightValues.consoleLightFoLevel,loadedLightValues.consoleLightFoLevel); + intermediateLightValues.dcduLeftLightLevel = convergeValue( currentLightValues.dcduLeftLightLevel,loadedLightValues.dcduLeftLightLevel); + intermediateLightValues.dcduRightLightLevel = convergeValue( currentLightValues.dcduRightLightLevel,loadedLightValues.dcduRightLightLevel); + intermediateLightValues.mcduLeftLightLevel = convergeValue( currentLightValues.mcduLeftLightLevel,loadedLightValues.mcduLeftLightLevel); + intermediateLightValues.mcduRightLightLevel = convergeValue( currentLightValues.mcduRightLightLevel,loadedLightValues.mcduRightLightLevel); + intermediateLightValues.ecamUpperLightLevel = convergeValue( currentLightValues.ecamUpperLightLevel,loadedLightValues.ecamUpperLightLevel); + intermediateLightValues.ecamLowerLightLevel = convergeValue( currentLightValues.ecamLowerLightLevel,loadedLightValues.ecamLowerLightLevel); + intermediateLightValues.floodPnlLightLevel = convergeValue( currentLightValues.floodPnlLightLevel,loadedLightValues.floodPnlLightLevel); + intermediateLightValues.pedestalIntegralLightLevel = convergeValue( currentLightValues.pedestalIntegralLightLevel,loadedLightValues.pedestalIntegralLightLevel); + intermediateLightValues.floodPedLightLevel = convergeValue( currentLightValues.floodPedLightLevel,loadedLightValues.floodPedLightLevel); + // clang-format on + return intermediateLightValues == loadedLightValues; +} diff --git a/fbw-a380x/src/wasm/extra-backend-a380x/src/LightingPresets/LightingPresets_A380X.h b/fbw-a380x/src/wasm/extra-backend-a380x/src/LightingPresets/LightingPresets_A380X.h new file mode 100644 index 00000000000..4de5e2e6a2b --- /dev/null +++ b/fbw-a380x/src/wasm/extra-backend-a380x/src/LightingPresets/LightingPresets_A380X.h @@ -0,0 +1,164 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_LIGHTINGPRESETS_H +#define FLYBYWIRE_LIGHTINGPRESETS_H + +#include "LightingPresets/LightingPresets.h" + +#include "math_utils.hpp" + +class MsfsHandler; + +// FIXME: This is not yet adapted to the A380X - not all necessary systems and APIs are available yet. + +// Struct to hold all relevant light levels for the A32NX +struct LightingValues_A380X { + // EFB + FLOAT64 efbBrightness; // A32NX_EFB_BRIGHTNESS + // OVHD + FLOAT64 cabinLightLevel; // 7 (0, 50, 100) + FLOAT64 ovhdIntegralLightLevel; // 86 + // Glareshield + FLOAT64 glareshieldIntegralLightLevel; // 84 + FLOAT64 glareshieldLcdLightLevel; // 87 + FLOAT64 tableLightCptLevel; // 10 + FLOAT64 tableLightFoLevel; // 11 + // Instruments + FLOAT64 pfdBrtCptLevel; // 88 + FLOAT64 ndBrtCptLevel; // 89 + FLOAT64 wxTerrainBrtCptLevel; // 94 + FLOAT64 consoleLightCptLevel; // 8 (0, 50, 100) + FLOAT64 pfdBrtFoLevel; // 90 + FLOAT64 ndBrtFoLevel; // 91 + FLOAT64 wxTerrainBrtFoLevel; // 95 + FLOAT64 consoleLightFoLevel; // 9 (0, 50, 100) + // ISIS display has automatic brightness adjustment. + FLOAT64 dcduLeftLightLevel; // A32NX_PANEL_DCDU_L_BRIGHTNESS 0.0..1.0 + FLOAT64 dcduRightLightLevel; // A32NX_PANEL_DCDU_R_BRIGHTNESS 0.0..1.0 + FLOAT64 mcduLeftLightLevel; // A32NX_MCDU_L_BRIGHTNESS 0.0..1.0 + FLOAT64 mcduRightLightLevel; // A32NX_MCDU_R_BRIGHTNESS 0.0..1.0 + // Pedestal + FLOAT64 ecamUpperLightLevel; // 92 + FLOAT64 ecamLowerLightLevel; // 93 + FLOAT64 floodPnlLightLevel; // 83 + FLOAT64 pedestalIntegralLightLevel; // 85 + FLOAT64 floodPedLightLevel; // 76 +}; + +/** + * This module is responsible for the lighting presets. + * It stores and reads the current lighting preset from and to an ini-file in the work folder. + * + * It is controlled by two LVARs: + * - A32NX_LIGHTING_PRESET_LOAD + * - A32NX_LIGHTING_PRESET_SAVE + * + * If these are set to a number >0 the module will load or save the preset with the given number + * from and to the ini-file or create a new preset based on default (load) or current (save) + * lighting values. + */ +class LightingPresets_A380X : public LightingPresets { + private: + // Lighting LVARs + NamedVariablePtr efbBrightness; + NamedVariablePtr dcduLeftLightLevel; + NamedVariablePtr dcduRightLightLevel; + NamedVariablePtr mcduLeftLightLevel; + NamedVariablePtr mcduRightLightLevel; + + // Lighting Aircraft Vars + AircraftVariablePtr lightCabin; + AircraftVariablePtr lightCabinLevel; + AircraftVariablePtr ovhdIntegralLightLevel; + AircraftVariablePtr glareshieldIntegralLightLevel; + AircraftVariablePtr glareshieldLcdLightLevel; + AircraftVariablePtr tableLightCptLevel; + AircraftVariablePtr tableLightFoLevel; + AircraftVariablePtr pfdBrtCptLevel; + AircraftVariablePtr ndBrtCptLevel; + AircraftVariablePtr wxTerrainBrtCptLevel; + AircraftVariablePtr consoleLightCptLevel; + AircraftVariablePtr pfdBrtFoLevel; + AircraftVariablePtr ndBrtFoLevel; + AircraftVariablePtr wxTerrainBrtFoLevel; + AircraftVariablePtr consoleLightFoLevel; + AircraftVariablePtr ecamUpperLightLevel; + AircraftVariablePtr ecamLowerLightLevel; + AircraftVariablePtr floodPnlLightLevel; + AircraftVariablePtr pedestalIntegralLightLevel; + AircraftVariablePtr floodPedLightLevel; + + ClientEventPtr cabinLightSetEvent; + + // THe current lighting values in the aircraft + LightingValues_A380X currentLightValues{}; + // The lighting values that are loaded from the ini-file + LightingValues_A380X loadedLightValues{}; + // The lighting values that are used to converge from the current values to the preset values + LightingValues_A380X intermediateLightValues{}; + + public: + LightingPresets_A380X() = delete; + + /** + * Creates a new LightingPresets_A32NX instance and takes a reference to the MsfsHandler instance. + * @param msfsHandler The MsfsHandler instance that is used to communicate with the simulator. + */ + explicit LightingPresets_A380X(MsfsHandler& msfsHandler) : LightingPresets(msfsHandler) {} + + bool initialize() override; + + /** + * Produces a string with the current settings and their values. + * @return string with the current settings and their values. + */ + [[maybe_unused]] [[nodiscard]] std::string str() const; + + private: + void readFromAircraft() override; + void applyToAircraft() override; + bool calculateIntermediateValues() override; + void loadFromIni(const mINI::INIStructure& ini, const std::string& iniSectionName) override; + void saveToIni(mINI::INIStructure& ini, const std::string& iniSectionName) const override; + + /** + * cabin lights in the A32NX need to be controlled by two vars + * one for the switch position and one for the actual light + * @param level the level to set the lights to + */ + void setValidCabinLightValue(FLOAT64 level); + + const LightingValues_A380X 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}; +}; + +inline bool operator==(const LightingValues_A380X& p1, const LightingValues_A380X& p2) { + const double epsilon = 0.1; + return helper::Math::almostEqual(p1.efbBrightness, p2.efbBrightness, epsilon) && + helper::Math::almostEqual(p1.cabinLightLevel, p2.cabinLightLevel, epsilon) && + helper::Math::almostEqual(p1.ovhdIntegralLightLevel, p2.ovhdIntegralLightLevel, epsilon) && + helper::Math::almostEqual(p1.glareshieldIntegralLightLevel, p2.glareshieldIntegralLightLevel, epsilon) && + helper::Math::almostEqual(p1.glareshieldLcdLightLevel, p2.glareshieldLcdLightLevel, epsilon) && + helper::Math::almostEqual(p1.tableLightCptLevel, p2.tableLightCptLevel, epsilon) && + helper::Math::almostEqual(p1.tableLightFoLevel, p2.tableLightFoLevel, epsilon) && + helper::Math::almostEqual(p1.pfdBrtCptLevel, p2.pfdBrtCptLevel, epsilon) && + helper::Math::almostEqual(p1.ndBrtCptLevel, p2.ndBrtCptLevel, epsilon) && + helper::Math::almostEqual(p1.wxTerrainBrtCptLevel, p2.wxTerrainBrtCptLevel, epsilon) && + helper::Math::almostEqual(p1.consoleLightCptLevel, p2.consoleLightCptLevel, epsilon) && + helper::Math::almostEqual(p1.pfdBrtFoLevel, p2.pfdBrtFoLevel, epsilon) && + helper::Math::almostEqual(p1.ndBrtFoLevel, p2.ndBrtFoLevel, epsilon) && + helper::Math::almostEqual(p1.wxTerrainBrtFoLevel, p2.wxTerrainBrtFoLevel, epsilon) && + helper::Math::almostEqual(p1.consoleLightFoLevel, p2.consoleLightFoLevel, epsilon) && + helper::Math::almostEqual(p1.dcduLeftLightLevel, p2.dcduLeftLightLevel, epsilon) && + helper::Math::almostEqual(p1.dcduRightLightLevel, p2.dcduRightLightLevel, epsilon) && + helper::Math::almostEqual(p1.mcduLeftLightLevel, p2.mcduLeftLightLevel, epsilon) && + helper::Math::almostEqual(p1.mcduRightLightLevel, p2.mcduRightLightLevel, epsilon) && + helper::Math::almostEqual(p1.ecamUpperLightLevel, p2.ecamUpperLightLevel, epsilon) && + helper::Math::almostEqual(p1.ecamLowerLightLevel, p2.ecamLowerLightLevel, epsilon) && + helper::Math::almostEqual(p1.floodPnlLightLevel, p2.floodPnlLightLevel, epsilon) && + helper::Math::almostEqual(p1.pedestalIntegralLightLevel, p2.pedestalIntegralLightLevel, epsilon) && + helper::Math::almostEqual(p1.floodPedLightLevel, p2.floodPedLightLevel, epsilon); +} + +#endif // FLYBYWIRE_LIGHTINGPRESETS_H diff --git a/fbw-a380x/src/wasm/fadec_a380/CMakeLists.txt b/fbw-a380x/src/wasm/fadec_a380/CMakeLists.txt index 5d61b91acfe..9e33d48733a 100644 --- a/fbw-a380x/src/wasm/fadec_a380/CMakeLists.txt +++ b/fbw-a380x/src/wasm/fadec_a380/CMakeLists.txt @@ -2,10 +2,10 @@ # through the code in an IDE. It is not used for building the project. cmake_minimum_required(VERSION 3.19) -project(flybywire-a32nx-fadec) + +project(flybywire-a380x-fadec) set(CMAKE_CXX_STANDARD 20) -set(MSFS_SDK "C:\\MSFS SDK") set(CMAKE_CXX_FLAGS "-c -g -DDEBUG -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") @@ -14,11 +14,11 @@ include_directories("${MSFS_SDK}/WASM/wasi-sysroot/include") include_directories("${MSFS_SDK}/SimConnect SDK/include") include_directories( - ./src - ${FBW_ROOT}/fbw-common/src/wasm/fadec_common/src - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src + ./src + ${FBW_ROOT}/fbw-common/src/wasm/fadec_common/src + ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src ) add_executable(flybywire-a380x-fadec - ./src/FadecGauge.cpp -) + ./src/FadecGauge.cpp + ) diff --git a/fbw-a380x/src/wasm/fbw_a380/CMakeLists.txt b/fbw-a380x/src/wasm/fbw_a380/CMakeLists.txt index 2f918f9d91a..9a832cf1cbc 100644 --- a/fbw-a380x/src/wasm/fbw_a380/CMakeLists.txt +++ b/fbw-a380x/src/wasm/fbw_a380/CMakeLists.txt @@ -2,10 +2,10 @@ # through the code in an IDE. It is not used for building the project. cmake_minimum_required(VERSION 3.19) -project(flybywire-a32nx-fbw) + +project(flybywire-a380x-fbw) set(CMAKE_CXX_STANDARD 20) -set(MSFS_SDK "C:\\MSFS SDK") set(CMAKE_CXX_FLAGS "-c -g -DDEBUG -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") @@ -14,68 +14,68 @@ include_directories("${MSFS_SDK}/WASM/wasi-sysroot/include") include_directories("${MSFS_SDK}/SimConnect SDK/include") include_directories( - ./src - ./src/inih - ./src/busStructures - ./src/elac - ./src/fac - ./src/failures - ./src/fcdc - ./src/interface - ./src/model - ./src/sec - ./src/utils - ${FBW_ROOT}/fbw-common/src/wasm/fadec_common/src/zlib - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/inih - ${FBW_ROOT}/fbw-common/src/wasm/fbw-common/src + ./src + ./src/inih + ./src/busStructures + ./src/elac + ./src/fac + ./src/failures + ./src/fcdc + ./src/interface + ./src/model + ./src/sec + ./src/utils + ${FBW_ROOT}/fbw-common/src/wasm/fadec_common/src/zlib + ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/inih + ${FBW_ROOT}/fbw-common/src/wasm/fbw-common/src ) add_executable(flybywire-a380x-fbw - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/zlib/zfstream.cc - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/LocalVariable.cpp - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/ThrottleAxisMapping.cpp - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/InterpolatingLookupTable.cpp - src/interface/SimConnectInterface.cpp -# src/elac/Elac.cpp - src/sec/Sec.cpp -# src/fcdc/Fcdc.cpp - src/fac/Fac.cpp - src/failures/FailuresConsumer.cpp - src/utils/ConfirmNode.cpp - src/utils/SRFlipFLop.cpp - src/utils/PulseNode.cpp - src/utils/HysteresisNode.cpp - src/model/AutopilotLaws_data.cpp - src/model/AutopilotLaws.cpp - src/model/AutopilotStateMachine_data.cpp - src/model/AutopilotStateMachine.cpp - src/model/Autothrust_data.cpp - src/model/Autothrust.cpp - src/model/Double2MultiWord.cpp -# src/model/ElacComputer_data.cpp -# src/model/ElacComputer.cpp -# src/model/SecComputer_data.cpp -# src/model/SecComputer.cpp -# src/model/PitchNormalLaw.cpp -# src/model/PitchAlternateLaw.cpp -# src/model/PitchDirectLaw.cpp -# src/model/LateralNormalLaw.cpp -# src/model/LateralDirectLaw.cpp - src/model/FacComputer_data.cpp - src/model/FacComputer.cpp - src/model/look1_binlxpw.cpp - src/model/look2_binlcpw.cpp - src/model/look2_binlxpw.cpp - src/model/mod_mvZvttxs.cpp - src/model/MultiWordIor.cpp - src/model/rt_modd.cpp - src/model/rt_remd.cpp - src/model/uMultiWord2Double.cpp - src/FlyByWireInterface.cpp - src/FlightDataRecorder.cpp -# src/Arinc429.cpp - src/Arinc429Utils.cpp - src/SpoilersHandler.cpp - src/CalculatedRadioReceiver.cpp - src/main.cpp -) + ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/zlib/zfstream.cc + ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/LocalVariable.cpp + ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/ThrottleAxisMapping.cpp + ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/InterpolatingLookupTable.cpp + src/interface/SimConnectInterface.cpp + # src/elac/Elac.cpp + src/sec/Sec.cpp + # src/fcdc/Fcdc.cpp + src/fac/Fac.cpp + src/failures/FailuresConsumer.cpp + src/utils/ConfirmNode.cpp + src/utils/SRFlipFLop.cpp + src/utils/PulseNode.cpp + src/utils/HysteresisNode.cpp + src/model/AutopilotLaws_data.cpp + src/model/AutopilotLaws.cpp + src/model/AutopilotStateMachine_data.cpp + src/model/AutopilotStateMachine.cpp + src/model/Autothrust_data.cpp + src/model/Autothrust.cpp + src/model/Double2MultiWord.cpp + # src/model/ElacComputer_data.cpp + # src/model/ElacComputer.cpp + # src/model/SecComputer_data.cpp + # src/model/SecComputer.cpp + # src/model/PitchNormalLaw.cpp + # src/model/PitchAlternateLaw.cpp + # src/model/PitchDirectLaw.cpp + # src/model/LateralNormalLaw.cpp + # src/model/LateralDirectLaw.cpp + src/model/FacComputer_data.cpp + src/model/FacComputer.cpp + src/model/look1_binlxpw.cpp + src/model/look2_binlcpw.cpp + src/model/look2_binlxpw.cpp + src/model/mod_mvZvttxs.cpp + src/model/MultiWordIor.cpp + src/model/rt_modd.cpp + src/model/rt_remd.cpp + src/model/uMultiWord2Double.cpp + src/FlyByWireInterface.cpp + src/FlightDataRecorder.cpp + # src/Arinc429.cpp + src/Arinc429Utils.cpp + src/SpoilersHandler.cpp + src/CalculatedRadioReceiver.cpp + src/main.cpp + ) diff --git a/fbw-a380x/src/wasm/flypad-backend/CMakeLists.txt b/fbw-a380x/src/wasm/flypad-backend/CMakeLists.txt deleted file mode 100644 index cbb04e98b92..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -cmake_minimum_required(VERSION 3.19) -project(flybywire-a32nx-flypad-backend) - -set(CMAKE_CXX_STANDARD 20) -set(MSFS_SDK "C:\\MSFS SDK") - -set(CMAKE_CXX_FLAGS "-c -g -DDEBUG -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") - -include_directories("${MSFS_SDK}/WASM/include") -include_directories("${MSFS_SDK}/WASM/wasi-sysroot/include") -include_directories("${MSFS_SDK}/SimConnect SDK/include") - -include_directories( - ./src - ./src/Aircraft - ./src/Lighting - ./src/Pushback - ${FBW_ROOT}/fbw-common/src/wasm/fbw_common/src/inih -) - -add_executable(flybywire-a380x-flypad-backend - ./src/FlyPadBackend.cpp - ./src/Aircraft/AircraftPreset.cpp - ./src/Lighting/LightPreset.cpp - ./src/Pushback/Pushback.cpp - ./src/Pushback/InertialDampener.cpp -) diff --git a/fbw-a380x/src/wasm/flypad-backend/build.sh b/fbw-a380x/src/wasm/flypad-backend/build.sh deleted file mode 100755 index f25ebe70a60..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/build.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/bash - -# get directory of this script relative to root -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -COMMON_DIR="${DIR}/../../../../fbw-common/src/wasm" -OUTPUT="${DIR}/../../../out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/flypad-backend.wasm" - -if [ "$1" == "--debug" ]; then - WASMLD_ARGS="" - CLANG_ARGS="-g" -else - WASMLD_ARGS="-O2 --lto-O2 --strip-debug" - CLANG_ARGS="-flto -O2 -DNDEBUG" -fi - -set -ex - -# create temporary folder for o files -mkdir -p "${DIR}/obj" -# clean old object files out if they exist -rm -f "${DIR}/obj/*.o" -pushd "${DIR}/obj" - -# compile c++ code -clang++ \ - -c \ - ${CLANG_ARGS} \ - -std=c++20 \ - -Wno-unused-command-line-argument \ - -Wno-ignored-attributes \ - -Wno-macro-redefined \ - --sysroot "${MSFS_SDK}/WASM/wasi-sysroot" \ - -target wasm32-unknown-wasi \ - -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 "${COMMON_DIR}/fbw_common/src/inih" \ - -I "${DIR}/src" \ - -I "${DIR}/src/Lighting" \ - -I "${DIR}/src/Aircraft" \ - -I "${DIR}/src/Pushback" \ - "${DIR}/src/FlyPadBackend.cpp" \ - "${DIR}/src/Lighting/LightPreset.cpp" \ - "${DIR}/src/Aircraft/AircraftPreset.cpp" \ - "${DIR}/src/Pushback/Pushback.cpp" \ - "${DIR}/src/Pushback/InertialDampener.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 \ - -lc++ -lc++abi \ - ${DIR}/obj/*.o \ - -o $OUTPUT diff --git a/fbw-a380x/src/wasm/flypad-backend/src/Aircraft/AircraftPreset.cpp b/fbw-a380x/src/wasm/flypad-backend/src/Aircraft/AircraftPreset.cpp deleted file mode 100644 index 58ca8d397f7..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/src/Aircraft/AircraftPreset.cpp +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#include -#include - -#include "AircraftPreset.h" - -void AircraftPreset::initialize() { - LoadAircraftPresetRequest = register_named_variable("A32NX_AIRCRAFT_PRESET_LOAD"); - this->setLoadAircraftPresetRequest(0); - ProgressAircraftPreset = register_named_variable("A32NX_AIRCRAFT_PRESET_LOAD_PROGRESS"); - ProgressAircraftPresetId = register_named_variable("A32NX_AIRCRAFT_PRESET_LOAD_CURRENT_ID"); - SimOnGround = get_aircraft_var_enum("SIM ON GROUND"); - isInitialized = true; - std::cout << "FLYPAD_BACKEND: 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 << "FLYPAD_BACKEND: 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 - const std::vector* requestedProcedure = procedures.getProcedure(loadAircraftPresetRequest); - if (requestedProcedure == nullptr) { - std::cout << "FLYPAD_BACKEND: 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 << "FLYPAD_BACKEND: 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 << "FLYPAD_BACKEND: 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; - } - - // convenience tmp - const ProcedureStep* currentStepPtr = (*currentProcedure)[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) { - // update progress var - setProgressAircraftPreset(static_cast(currentStep) / currentProcedure->size()); - setProgressAircraftPresetId(currentStepPtr->id); - execute_calculator_code(currentStepPtr->actionCode.c_str(), &fvalue, &ivalue, &svalue); - std::cout << "FLYPAD_BACKEND: Aircraft Preset Step " << currentStep << " Condition: " - << currentStepPtr->description - << " (delay between tests: " << currentStepPtr->delayAfter << ")" << std::endl; - if (static_cast(fvalue)) { - currentDelay = 0; - currentStep++; - } - 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 << "FLYPAD_BACKEND: Aircraft Preset Step " << currentStep << " Test: " - << currentStepPtr->description << " TEST: \"" - << currentStepPtr->expectedStateCheckCode << "\"" << std::endl; -#endif - execute_calculator_code(currentStepPtr->expectedStateCheckCode.c_str(), &fvalue, &ivalue, &svalue); - if (static_cast(fvalue)) { -#ifdef DEBUG - std::cout << "FLYPAD_BACKEND: Aircraft Preset Step " << currentStep << " Skipping: " - << currentStepPtr->description << " TEST: \"" - << currentStepPtr->expectedStateCheckCode << "\"" << std::endl; -#endif - - currentDelay = 0; - currentStep++; - return; - } - } - - // update progress var - setProgressAircraftPreset(static_cast(currentStep) / currentProcedure->size()); - setProgressAircraftPresetId(currentStepPtr->id); - - // execute code to set expected state - std::cout << "FLYPAD_BACKEND: 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 << "FLYPAD_BACKEND: Aircraft Preset " << currentProcedureID << " loading cancelled!" - << std::endl; - loadingIsActive = false; - } -} - -void AircraftPreset::shutdown() { - isInitialized = false; - std::cout << "FLYPAD_BACKEND: AircraftPresets shutdown" << std::endl; -} diff --git a/fbw-a380x/src/wasm/flypad-backend/src/Aircraft/AircraftPreset.h b/fbw-a380x/src/wasm/flypad-backend/src/Aircraft/AircraftPreset.h deleted file mode 100644 index dcf6a5db3a5..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/src/Aircraft/AircraftPreset.h +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#pragma once - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "../inih/ini.h" -#include "AircraftProcedures.h" -#include "../Units.h" - -/** - * Class for handling aircraft presets. - */ -class AircraftPreset { -private: - std::unique_ptr 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 - const std::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. - */ - AircraftPreset() { - m_Units = std::make_unique(); - } - - /** - * Destructor - */ - ~AircraftPreset() = default; - - /** - * 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 - */ - [[nodiscard]] - 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 - */ - [[nodiscard]] - inline bool getSimOnGround() const { return static_cast(aircraft_varget(SimOnGround, m_Units->Bool, 1)); } -}; diff --git a/fbw-a380x/src/wasm/flypad-backend/src/Aircraft/AircraftProcedures.h b/fbw-a380x/src/wasm/flypad-backend/src/Aircraft/AircraftProcedures.h deleted file mode 100644 index 22ffc2fb270..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/src/Aircraft/AircraftProcedures.h +++ /dev/null @@ -1,306 +0,0 @@ -#pragma once - -#include -#include -#include -#ifdef DEBUG -#include -#endif - -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; -}; - -class AircraftProcedures { - - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // Please remember to also update the EFB Presets page for the step description - // if you make any changes to this list. - // src/systems/instruments/src/EFB/Presets/Widgets/AircraftPresets.tsx - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - static const inline std::vector POWERED_CONFIG_ON { - // SOP: PRELIMINARY COCKPIT PREPARATION - ProcedureStep{"BAT1 On", 1010, false, 1000, "(L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)", "1 (>L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)"}, - ProcedureStep{"BAT2 On", 1020, false, 3000, "(L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)", "1 (>L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)"}, - - ProcedureStep{"EXT PWR On", 1030, 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) }"}, - - // if no Ext Pwr is available we start the APU here with a bat only fire test - ProcedureStep{"APU Fire Test On", 1035, false, 2000, "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)", "1 (>L:A32NX_FIRE_TEST_APU)"}, - ProcedureStep{"APU Fire Test Off", 1036, false, 2000, "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)", "0 (>L:A32NX_FIRE_TEST_APU)"}, - ProcedureStep{"APU Master On", 1040, false, 3000, "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)", "1 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, - ProcedureStep{"APU Start On", 1050, 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)"}, - - ProcedureStep{"Waiting on AC BUS Availability", 1060, true, 2000, "", "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED)"}, - - // SOP: COCKPIT PREPARATION - ProcedureStep{"Crew Oxy On", 1120, false, 1000, "(L:PUSH_OVHD_OXYGEN_CREW) 0 ==", "0 (>L:PUSH_OVHD_OXYGEN_CREW)"}, - ProcedureStep{"GND CTL On", 1110, 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)"}, - ProcedureStep{"CVR Test On", 1115, false, 5000, "(L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)", "1 (>L:A32NX_RCDR_TEST)"}, - ProcedureStep{"CVR Test Off", 1116, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)", "0 (>L:A32NX_RCDR_TEST) 1 (>L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)"}, - - ProcedureStep{"ADIRS 1 Nav", 1080, false, 500, "(L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB)"}, - ProcedureStep{"ADIRS 2 Nav", 1090, false, 500, "(L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB)"}, - ProcedureStep{"ADIRS 3 Nav", 1100, false, 1500, "(L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB) 1 ==", "1 (>L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB)"}, - - ProcedureStep{"Strobe Auto", 2120, false, 1000, "(A:LIGHT STROBE, Bool)", "1 (>L:STROBE_0_AUTO) 0 (>K:STROBES_ON)"}, - ProcedureStep{"Nav & Logo Lt On", 1070, false, 1000, "(A:LIGHT LOGO, Bool) (A:LIGHT NAV, Bool) &&", "1 (>K:2:LOGO_LIGHTS_SET) 1 (>K:2:NAV_LIGHTS_SET)"}, - - ProcedureStep{"SEAT BELTS On", 2140, false, 1000, "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL)", "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) ! if{ 1 (>K:CABIN_SEATBELTS_ALERT_SWITCH_TOGGLE) }"}, - ProcedureStep{"NO SMOKING Auto", 1130, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION) 1 ==", "1 (>L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION)"}, - ProcedureStep{"EMER EXT Lt Arm", 1140, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION) 1 ==", "1 (>L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION)"}, - - // For the fire tests the FWC needs to be initialized - // The correct variables to wait for are: A32NX_FWS_FWC_1_NORMAL and A32NX_FWS_FWC_2_NORMAL. But - // as these are only on Exp this first iteration uses A32NX_FWC_FLIGHT_PHASE which also work on> - // master and is equivalent for this specific purpose. Will be changed when the FWC is on master. - ProcedureStep{"Waiting on FWC Initialization", 1065, true, 5000, "", "(L:A32NX_FWC_FLIGHT_PHASE)"}, - ProcedureStep{"Waiting...", 9999, false, 5000, "(L:A32NX_AIRCRAFT_PRESET_FWC_INIT_DONE)", "1 (>L:A32NX_AIRCRAFT_PRESET_FWC_INIT_DONE)"}, - - // APU fire test - ProcedureStep{"APU Fire Test On", 1035, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)", "1 (>L:A32NX_FIRE_TEST_APU)"}, - ProcedureStep{"APU Fire Test Off", 1036, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)", "0 (>L:A32NX_FIRE_TEST_APU) 1 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)"}, - - // After fire test we start the APU - ProcedureStep{"APU Master On", 1041, 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)"}, - ProcedureStep{"APU Start On", 1051, 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)"}, - - // ENG fire test - ProcedureStep{"ENG 1 Fire Test On", 2002, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)", "1 (>L:A32NX_FIRE_TEST_ENG1)"}, - ProcedureStep{"ENG 1 Fire Test Off", 2003, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)", "0 (>L:A32NX_FIRE_TEST_ENG1) 1 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)"}, - ProcedureStep{"ENG 2 Fire Test On", 2004, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)", "1 (>L:A32NX_FIRE_TEST_ENG2)"}, - ProcedureStep{"ENG 2 Fire Test Off", 2005, false, 2000, "(L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)", "0 (>L:A32NX_FIRE_TEST_ENG2) 1 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)"}, - - ProcedureStep{"Waiting on APU Availability", 1150, true, 2000, "", "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) ! (L:A32NX_OVHD_APU_START_PB_IS_AVAILABLE) ||"}, - ProcedureStep{"APU Bleed On", 1160, 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 const inline std::vector POWERED_CONFIG_OFF = { - ProcedureStep{"NO SMOKING Off", 1170, false, 1000, "(L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION) 2 ==", "2 (>L:XMLVAR_SWITCH_OVHD_INTLT_NOSMOKING_POSITION)"}, - ProcedureStep{"EMER EXT Lt Off", 1180, false, 1500, "(L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION) 2 ==", "2 (>L:XMLVAR_SWITCH_OVHD_INTLT_EMEREXIT_POSITION)"}, - ProcedureStep{"GND CTL Off", 1200, false, 1000, "(L:A32NX_RCDR_GROUND_CONTROL_ON) 0 ==", "0 (>L:A32NX_RCDR_GROUND_CONTROL_ON)"}, - ProcedureStep{"SEAT BELTS Off", 2200, false, 2000, "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) !", "(A:CABIN SEATBELTS ALERT SWITCH:1, BOOL) if{ 1 (>K:CABIN_SEATBELTS_ALERT_SWITCH_TOGGLE) }"}, - ProcedureStep{"Strobe Off", 2180, false, 1000, "(A:LIGHT STROBE, Bool) !", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_OFF)"}, - ProcedureStep{"Nav & Logo Lt Off", 1240, false, 500, "(A:LIGHT LOGO, Bool) ! (A:LIGHT NAV, Bool) ! &&", "0 (>K:2:LOGO_LIGHTS_SET) 0 (>K:2:NAV_LIGHTS_SET)"}, - ProcedureStep{"Crew Oxy Off", 1190, false, 1000, "(L:PUSH_OVHD_OXYGEN_CREW) 1 ==", "1 (>L:PUSH_OVHD_OXYGEN_CREW)"}, - ProcedureStep{"ADIRS 3 Off", 1210, false, 500, "(L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB)"}, - ProcedureStep{"ADIRS 2 Off", 1220, false, 500, "(L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB)"}, - ProcedureStep{"ADIRS 1 Off", 1230, false, 1000, "(L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB) 0 ==", "0 (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB)"}, - ProcedureStep{"APU Bleed Off", 1250, false, 1500, "(L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON)"}, - ProcedureStep{"APU Master Off", 1260, false, 2000, "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, - ProcedureStep{"EXT PWR Off", 1270, false, 3000, "(A:EXTERNAL POWER ON:1, BOOL) !", "(A:EXTERNAL POWER ON:1, BOOL) if{ 1 (>K:TOGGLE_EXTERNAL_POWER) }"}, - ProcedureStep{"BAT2 Off", 1280, false, 100, "(L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO) 0 ==", "0 (>L:A32NX_OVHD_ELEC_BAT_2_PB_IS_AUTO)"}, - ProcedureStep{"BAT1 Off", 1290, false, 1000, "(L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO) 0 ==", "0 (>L:A32NX_OVHD_ELEC_BAT_1_PB_IS_AUTO)"}, - ProcedureStep{"AC BUS Off Check", 1300, true, 2000, "", "(L:A32NX_ELEC_AC_1_BUS_IS_POWERED) !"}, - ProcedureStep{"CVR Test Reset", 1117, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_CVR_TEST_DONE)"}, - ProcedureStep{"APU Fire Test Reset", 1037, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_APU_DONE)"}, - ProcedureStep{"ENG 1 Fire Test Reset", 2006, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG1_DONE)"}, - ProcedureStep{"ENG 2 Fire Test Reset", 2007, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FIRE_TEST_ENG2_DONE)"}, - ProcedureStep{"FWC Init Reset", 1066, false, 0, "", "0 (>L:A32NX_AIRCRAFT_PRESET_FWC_INIT_DONE)"} -}; - - static const inline std::vector PUSHBACK_CONFIG_ON = { - // SOP: BEFORE PUSHBACK OR START - ProcedureStep{"EXT PWR Off", 2000, false, 3000, "(A:EXTERNAL POWER ON:1, BOOL) !", "(A:EXTERNAL POWER ON:1, BOOL) if{ 1 (>K:TOGGLE_EXTERNAL_POWER) }"}, - ProcedureStep{"Beacon On", 2130, false, 1000, "(A:LIGHT BEACON, Bool)", "0 (>K:BEACON_LIGHTS_ON)"}, - ProcedureStep{"FUEL PUMP 2 On", 2010, false, 100, "(A:FUELSYSTEM PUMP SWITCH:2, Bool)", "2 (>K:FUELSYSTEM_PUMP_ON)"}, - ProcedureStep{"FUEL PUMP 5 On", 2020, false, 500, "(A:FUELSYSTEM PUMP SWITCH:5, Bool)", "5 (>K:FUELSYSTEM_PUMP_ON)"}, - ProcedureStep{"FUEL PUMP 1 On", 2030, false, 100, "(A:FUELSYSTEM PUMP SWITCH:1, Bool)", "1 (>K:FUELSYSTEM_PUMP_ON)"}, - ProcedureStep{"FUEL PUMP 4 On", 2040, false, 500, "(A:FUELSYSTEM PUMP SWITCH:4, Bool)", "4 (>K:FUELSYSTEM_PUMP_ON)"}, - ProcedureStep{"FUEL PUMP 3 On", 2050, false, 100, "(A:FUELSYSTEM PUMP SWITCH:3, Bool)", "3 (>K:FUELSYSTEM_PUMP_ON)"}, - ProcedureStep{"FUEL PUMP 6 On", 2060, false, 2000, "(A:FUELSYSTEM PUMP SWITCH:6, Bool)", "6 (>K:FUELSYSTEM_PUMP_ON)"}, - ProcedureStep{"COCKPIT DOOR LCK", 2110, false, 2000, "(L:A32NX_COCKPIT_DOOR_LOCKED) 1 ==", "1 (>L:A32NX_COCKPIT_DOOR_LOCKED)"}, - ProcedureStep{"Await ADIRS 1 Alignment", 2150, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_1_STATE) 2 =="}, - ProcedureStep{"Await ADIRS 2 Alignment", 2160, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_2_STATE) 2 =="}, - ProcedureStep{"Await ADIRS 3 Alignment", 2170, true, 2000, "", "(L:A32NX_ADIRS_ADIRU_3_STATE) 2 =="}, -}; - - static const inline std::vector PUSHBACK_CONFIG_OFF = { - ProcedureStep{"COCKPIT DOOR OP", 2250, false, 2000, "(L:A32NX_COCKPIT_DOOR_LOCKED) 0 ==", "0 (>L:A32NX_COCKPIT_DOOR_LOCKED)"}, - ProcedureStep{"FUEL PUMP 2 Off", 2260, false, 100, "(A:FUELSYSTEM PUMP SWITCH:2, Bool) !", "2 (>K:FUELSYSTEM_PUMP_OFF)"}, - ProcedureStep{"FUEL PUMP 5 Off", 2270, false, 500, "(A:FUELSYSTEM PUMP SWITCH:5, Bool) !", "5 (>K:FUELSYSTEM_PUMP_OFF)"}, - ProcedureStep{"FUEL PUMP 1 Off", 2280, false, 100, "(A:FUELSYSTEM PUMP SWITCH:1, Bool) !", "1 (>K:FUELSYSTEM_PUMP_OFF)"}, - ProcedureStep{"FUEL PUMP 4 Off", 2290, false, 500, "(A:FUELSYSTEM PUMP SWITCH:4, Bool) !", "4 (>K:FUELSYSTEM_PUMP_OFF)"}, - ProcedureStep{"FUEL PUMP 3 Off", 2300, false, 100, "(A:FUELSYSTEM PUMP SWITCH:3, Bool) !", "3 (>K:FUELSYSTEM_PUMP_OFF)"}, - ProcedureStep{"FUEL PUMP 6 Off", 2310, false, 1000, "(A:FUELSYSTEM PUMP SWITCH:6, Bool) !", "6 (>K:FUELSYSTEM_PUMP_OFF)"}, - ProcedureStep{"Beacon Off", 2190, false, 1000, "(A:LIGHT BEACON, Bool) !", "0 (>K:BEACON_LIGHTS_OFF)"}, -}; - - static const inline std::vector TAXI_CONFIG_ON = { - // SOP: ENGINE START - ProcedureStep{"ENG MODE SEL START", 3000, 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)"}, - ProcedureStep{"ENG 2 ON", 3010, false, 60000, "(A:FUELSYSTEM VALVE OPEN:2, Bool)", "2 (>K:FUELSYSTEM_VALVE_OPEN)"}, - ProcedureStep{"Await ENG 2 AVAIL", 3020, true, 5000, "", "(L:A32NX_ENGINE_STATE:2) 1 =="}, - ProcedureStep{"ENG 1 ON", 3030, false, 60000, "(A:FUELSYSTEM VALVE OPEN:1, Bool)", "1 (>K:FUELSYSTEM_VALVE_OPEN)"}, - ProcedureStep{"Await ENG 1 AVAIL", 3040, true, 5000, "", "(L:A32NX_ENGINE_STATE:1) 1 =="}, - // SOP: AFTER START - ProcedureStep{"ENG MODE SEL NORM", 3050, 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)"}, - ProcedureStep{"APU Bleed Off", 3060, false, 2000, "(L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_PNEU_APU_BLEED_PB_IS_ON)"}, - ProcedureStep{"APU Master Off", 3070, false, 2000, "(L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON) 0 ==", "0 (>L:A32NX_OVHD_APU_MASTER_SW_PB_IS_ON)"}, - ProcedureStep{"Spoiler Arm", 3090, false, 2000, "(L:A32NX_SPOILERS_ARMED) 1 ==", "1 (>K:SPOILERS_ARM_SET)"}, - ProcedureStep{"Rudder Trim Reset", 3100, false, 2000, "(A:RUDDER TRIM, Radians) 0 ==", "0 (>K:RUDDER_TRIM_SET)"}, - ProcedureStep{"Flaps 1", 3110, false, 3000, "(L:A32NX_FLAPS_HANDLE_INDEX) 1 ==", "1 (>L:A32NX_FLAPS_HANDLE_INDEX)"}, - // SOP: TAXI - ProcedureStep{"NOSE Lt Taxi", 3120, 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)"}, - ProcedureStep{"RWY TURN OFF Lt L On", 3130, false, 0, "(A:CIRCUIT SWITCH ON:21, Bool)", "(A:CIRCUIT SWITCH ON:21, Bool) ! if{ 21 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, - ProcedureStep{"RWY TURN OFF Lt R On", 3140, false, 2000, "(A:CIRCUIT SWITCH ON:22, Bool)", "(A:CIRCUIT SWITCH ON:22, Bool) ! if{ 22 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, - ProcedureStep{"PWS Auto", 2070, false, 1000, "(L:A32NX_SWITCH_RADAR_PWS_POSITION) 1 ==", "1 (>L:A32NX_SWITCH_RADAR_PWS_POSITION)"}, - ProcedureStep{"Transponder On", 2080, false, 1000, "(L:A32NX_TRANSPONDER_MODE) 1 ==", "1 (>L:A32NX_TRANSPONDER_MODE)"}, - ProcedureStep{"ATC ALT RPTG On", 2090, false, 1000, "(L:A32NX_SWITCH_ATC_ALT) 1 ==", "1 (>L:A32NX_SWITCH_ATC_ALT)"}, - ProcedureStep{"TCAS TRAFFIC ABV", 2100, false, 2000, "(L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION)"}, - ProcedureStep{"Autobrake Max", 3080, false, 2000, "(L:A32NX_AUTOBRAKES_ARMED_MODE) 3 ==", "3 (>L:A32NX_AUTOBRAKES_ARMED_MODE_SET)"}, - ProcedureStep{"TERR ON ND Capt. On", 3080, false, 2000, "(L:A32NX_EFIS_TERR_L_ACTIVE) 1 ==", "1 (>L:A32NX_EFIS_TERR_L_ACTIVE)"}, - ProcedureStep{"T.O. Config", 3085, false, 2000, "", "1 (>L:A32NX_TO_CONFIG_NORMAL)"}, -}; - - static const inline std::vector TAXI_CONFIG_OFF = { - ProcedureStep{"TERR ON ND Capt. Off", 3080, false, 2000, "(L:A32NX_EFIS_TERR_L_ACTIVE) 0 ==", "0 (>L:A32NX_EFIS_TERR_L_ACTIVE)"}, - ProcedureStep{"Autobrake Off", 3180, false, 2000, "(L:A32NX_AUTOBRAKES_ARMED_MODE) 0 ==", "0 (>L:A32NX_AUTOBRAKES_ARMED_MODE_SET)"}, - ProcedureStep{"TCAS TRAFFIC ABV", 2240, false, 1000, "(L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_TRAFFIC_POSITION)"}, - ProcedureStep{"ATC ALT RPTG Off", 2230, false, 1000, "(L:A32NX_SWITCH_ATC_ALT) 1 ==", "1 (>L:A32NX_SWITCH_ATC_ALT)"}, - ProcedureStep{"Transponder Off", 2220, false, 1000, "(L:A32NX_TRANSPONDER_MODE) 0 ==", "0 (>L:A32NX_TRANSPONDER_MODE)"}, - ProcedureStep{"PWS Off", 2210, false, 1000, "(L:A32NX_SWITCH_RADAR_PWS_POSITION) 0 ==", "0 (>L:A32NX_SWITCH_RADAR_PWS_POSITION)"}, - ProcedureStep{"RWY TURN OFF Lt L Off", 3160, false, 0, "(A:CIRCUIT SWITCH ON:21, Bool) !", "(A:CIRCUIT SWITCH ON:21, Bool) if{ 21 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, - ProcedureStep{"RWY TURN OFF Lt R Off", 3170, false, 2000, "(A:CIRCUIT SWITCH ON:22, Bool) !", "(A:CIRCUIT SWITCH ON:22, Bool) if{ 22 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, - ProcedureStep{"NOSE Lt Taxi", 3150, 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)"}, - ProcedureStep{"Flaps 0", 3210, false, 2000, "(L:A32NX_FLAPS_HANDLE_INDEX) 0 ==", "0 (>L:A32NX_FLAPS_HANDLE_INDEX)"}, - ProcedureStep{"Rudder Trim Reset", 3200, false, 2000, "(A:RUDDER TRIM, Radians) 0 ==", "0 (>K:RUDDER_TRIM_SET)"}, - ProcedureStep{"Spoiler Disarm", 3190, false, 2000, "(L:A32NX_SPOILERS_ARMED) 0 ==", "0 (>K:SPOILERS_ARM_SET)"}, - ProcedureStep{"ENG 1 Off", 3220, false, 2000, "(A:FUELSYSTEM VALVE OPEN:1, Bool) !", "1 (>K:FUELSYSTEM_VALVE_CLOSE)"}, - ProcedureStep{"ENG 2 Off", 3230, false, 2000, "(A:FUELSYSTEM VALVE OPEN:2, Bool) !", "2 (>K:FUELSYSTEM_VALVE_CLOSE)"}, - ProcedureStep{"ENG 1 N1 <3%", 3240, true, 1000, "", "(L:A32NX_ENGINE_N1:1) 3 <"}, - ProcedureStep{"ENG 2 N1 <3%", 3250, true, 1000, "", "(L:A32NX_ENGINE_N1:2) 3 <"} -}; - - static const inline std::vector TAKEOFF_CONFIG_ON = { - // SOP: TAXI - ProcedureStep{"WX Radar On", 4000, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_SYS) 0 ==", "0 (>L:XMLVAR_A320_WEATHERRADAR_SYS)"}, - ProcedureStep{"WX Radar Mode", 4010, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_MODE) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_MODE)"}, - // SOP: BEFORE TAKEOFF - ProcedureStep{"TCAS Switch TA/RA", 4020, false, 2000, "(L:A32NX_SWITCH_TCAS_POSITION) 2 ==", "2 (>L:A32NX_SWITCH_TCAS_POSITION)"}, - ProcedureStep{"Strobe On", 2120, false, 1000, "(A:LIGHT STROBE, Bool)", "0 (>L:STROBE_0_AUTO) 0 (>K:STROBES_ON)"}, - ProcedureStep{"Cabin Ready", 2125, false, 1000, "", "1 (>L:A32NX_CABIN_READY)"}, - // SOP: TAKE OFF - ProcedureStep{"NOSE Lt Takeoff", 4030, false, 1000, "(A:CIRCUIT SWITCH ON:17, Bool)", "(A:CIRCUIT SWITCH ON:17, Bool) ! if{ 17 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, - ProcedureStep{"LL Lt L On", 4040, 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)"}, - ProcedureStep{"LL Lt R On", 4050, 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 const inline std::vector TAKEOFF_CONFIG_OFF = { - ProcedureStep{"LL Lt L Off", 4060, 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)"}, - ProcedureStep{"LL Lt R Off", 4070, 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)"}, - ProcedureStep{"NOSE Lt Takeoff", 4080, false, 2000, "(A:CIRCUIT SWITCH ON:17, Bool) !", "(A:CIRCUIT SWITCH ON:17, Bool) if{ 17 (>K:ELECTRICAL_CIRCUIT_TOGGLE)"}, - ProcedureStep{"Strobe Auto", 2180, false, 1000, "(A:LIGHT STROBE, Bool) !", "1 (>L:STROBE_0_AUTO) 0 (>K:STROBES_OFF)"}, - ProcedureStep{"TCAS Switch TA/RA", 4090, false, 1000, "(L:A32NX_SWITCH_TCAS_POSITION) 0 ==", "0 (>L:A32NX_SWITCH_TCAS_POSITION)"}, - ProcedureStep{"WX Radar Mode", 4110, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_MODE) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_MODE)"}, - ProcedureStep{"WX Radar Off", 4100, false, 1000, "(L:XMLVAR_A320_WEATHERRADAR_SYS) 1 ==", "1 (>L:XMLVAR_A320_WEATHERRADAR_SYS)"}, -}; - - std::vector coldAndDark; - std::vector powered; - std::vector readyForPushback; - std::vector readyForTaxi; - std::vector readyForTakeoff; - - static void insert(std::vector& dest, const std::vector& src) { - std::transform(begin(src), end(src), back_inserter(dest), [](const auto& procedure) { - return &procedure; - }); - } - -#ifdef DEBUG - static inline void printProcedure(const std::vector& procedures) { - for (const auto& p : procedures) { - std::cout << p.id << " = " << p.description << std::endl; - } - } -#endif - -public: - AircraftProcedures() { - -#ifdef DEBUG - // Map the procedure groups - // Print to console to add them to the EFB code to display the current step. - printProcedure(POWERED_CONFIG_ON); - printProcedure(PUSHBACK_CONFIG_ON); - printProcedure(TAXI_CONFIG_ON); - printProcedure(TAKEOFF_CONFIG_ON); - printProcedure(TAKEOFF_CONFIG_OFF); - printProcedure(TAXI_CONFIG_OFF); - printProcedure(PUSHBACK_CONFIG_OFF); - printProcedure(POWERED_CONFIG_OFF); -#endif - - insert(coldAndDark, TAKEOFF_CONFIG_OFF); - insert(coldAndDark, TAXI_CONFIG_OFF); - insert(coldAndDark, PUSHBACK_CONFIG_OFF); - insert(coldAndDark, POWERED_CONFIG_OFF); - - insert(powered, TAKEOFF_CONFIG_OFF); - insert(powered, TAXI_CONFIG_OFF); - insert(powered, PUSHBACK_CONFIG_OFF); - insert(powered, POWERED_CONFIG_ON); - - insert(readyForPushback, TAKEOFF_CONFIG_OFF); - insert(readyForPushback, TAXI_CONFIG_OFF); - insert(readyForPushback, POWERED_CONFIG_ON); - insert(readyForPushback, PUSHBACK_CONFIG_ON); - - insert(readyForTaxi, TAKEOFF_CONFIG_OFF); - insert(readyForTaxi, POWERED_CONFIG_ON); - insert(readyForTaxi, PUSHBACK_CONFIG_ON); - insert(readyForTaxi, TAXI_CONFIG_ON); - - insert(readyForTakeoff, POWERED_CONFIG_ON); - insert(readyForTakeoff, PUSHBACK_CONFIG_ON); - insert(readyForTakeoff, TAXI_CONFIG_ON); - insert(readyForTakeoff, TAKEOFF_CONFIG_ON); - } - - [[nodiscard]] - const std::vector* getProcedure(int64_t pID) const { - switch (pID) { - case 1: - return &coldAndDark; - case 2: - return &powered; - case 3: - return &readyForPushback; - case 4: - return &readyForTaxi; - case 5: - return &readyForTakeoff; - default: - return nullptr; - } - } -}; diff --git a/fbw-a380x/src/wasm/flypad-backend/src/FlyPadBackend.cpp b/fbw-a380x/src/wasm/flypad-backend/src/FlyPadBackend.cpp deleted file mode 100644 index ae6cbe1616a..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/src/FlyPadBackend.cpp +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#include "FlyPadBackend.h" -#include "Aircraft/AircraftPreset.h" -#include "Lighting/LightPreset.h" -#include "Pushback/Pushback.h" - -FlyPadBackend FLYPAD_BACKEND; - -/** - * 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("FlyPadBackend_gauge_callback"))) extern "C" __attribute__((unused)) bool -FlyPadBackend_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 FLYPAD_BACKEND.initialize(); - } - case PANEL_SERVICE_PRE_DRAW: { - auto drawData = static_cast(pData); - return FLYPAD_BACKEND.onUpdate(drawData->dt); - } - case PANEL_SERVICE_PRE_KILL: { - return FLYPAD_BACKEND.shutdown(); - } - default: - break; - } - return false; -} - -bool FlyPadBackend::initialize() { - std::cout << "FLYPAD_BACKEND: Connecting to SimConnect..." << std::endl; - - if (!SUCCEEDED(SimConnect_Open(&hSimConnect, "FlyPadBackend", nullptr, 0, 0, 0))) { - std::cout << "FLYPAD_BACKEND: SimConnect failed." << std::endl; - return false; - } - isConnected = true; - - // Create submodules and provide pointers to data required structures - lightPresetPtr = std::make_unique(); - aircraftPresetPtr = std::make_unique(); - pushbackPtr = std::make_unique(hSimConnect, &pushbackData); - - // Simulation data to local data structure mapping - HRESULT result = S_OK; - result &= SimConnect_AddToDataDefinition(hSimConnect, DataStructureIDs::SimulationDataID, "SIMULATION TIME", "NUMBER"); - - result &= SimConnect_AddToDataDefinition(hSimConnect, DataStructureIDs::PushbackDataID, "Pushback Wait", "BOOLEAN", SIMCONNECT_DATATYPE_INT64); - result &= SimConnect_AddToDataDefinition(hSimConnect, DataStructureIDs::PushbackDataID, "VELOCITY BODY Z", "FEET/SECOND", SIMCONNECT_DATATYPE_FLOAT64); - result &= SimConnect_AddToDataDefinition(hSimConnect, DataStructureIDs::PushbackDataID, "ROTATION VELOCITY BODY Y", "FEET/SECOND", SIMCONNECT_DATATYPE_FLOAT64); - result &= SimConnect_AddToDataDefinition(hSimConnect, DataStructureIDs::PushbackDataID, "ROTATION ACCELERATION BODY X", "RADIANS PER SECOND SQUARED", SIMCONNECT_DATATYPE_FLOAT64); - if (result != S_OK) { - std::cout << "FLYPAD_BACKEND: Data definition failed! " << std::endl; - } - - result &= SimConnect_MapClientEventToSimEvent(hSimConnect, Events::KEY_TUG_HEADING_EVENT, "KEY_TUG_HEADING"); - result &= SimConnect_MapClientEventToSimEvent(hSimConnect, Events::KEY_TUG_SPEED_EVENT, "KEY_TUG_SPEED"); - - // initialize submodules - lightPresetPtr->initialize(); - aircraftPresetPtr->initialize(); - pushbackPtr->initialize(); - - // read simulation data from simconnect - simConnectRequestData(); - - std::cout << "FLYPAD_BACKEND: SimConnect connected." << std::endl; - return (result == S_OK); -} - -bool FlyPadBackend::onUpdate(double deltaTime) { - if (isConnected) { - simConnectProcessMessages(); - - // detect pause - if (simulationData.simulationTime == previousSimulationTime || simulationData.simulationTime < 0.2) { - return true; - } - previousSimulationTime = simulationData.simulationTime; - - // update sub modules - lightPresetPtr->onUpdate(deltaTime); - aircraftPresetPtr->onUpdate(deltaTime); - pushbackPtr->onUpdate(deltaTime); - - return true; - } - return false; -} - -bool FlyPadBackend::shutdown() { - std::cout << "FLYPAD_BACKEND: Disconnecting ..." << std::endl; - - // shutdown submodules - lightPresetPtr->shutdown(); - aircraftPresetPtr->shutdown(); - pushbackPtr->shutdown(); - - isConnected = false; - unregister_all_named_vars(); - std::cout << "FLYPAD_BACKEND: Disconnected." << std::endl; - return SUCCEEDED(SimConnect_Close(hSimConnect)); -} - -bool FlyPadBackend::simConnectRequestData() const { - HRESULT result = S_OK; - - // Request data for each data structure - remember to increase the request id. - result &= SimConnect_RequestDataOnSimObject(hSimConnect, 0, DataStructureIDs::SimulationDataID, SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_VISUAL_FRAME); - result &= SimConnect_RequestDataOnSimObject(hSimConnect, 1, DataStructureIDs::PushbackDataID, SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_VISUAL_FRAME); - - if (result != S_OK) { - return false; - } - - return true; -} - -void FlyPadBackend::simConnectProcessMessages() { - DWORD cbData; - SIMCONNECT_RECV* pData; - while (SUCCEEDED(SimConnect_GetNextDispatch(hSimConnect, &pData, &cbData))) { - simConnectProcessDispatchMessage(pData, &cbData); - } -} - -void FlyPadBackend::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; - - case 1: - // store aircraft data in local data structure - pushbackData = *((PushbackData*) &data->dwData); - return; - - default: - std::cout << "FLYPAD_BACKEND: Unknown request id in SimConnect connection: "; - std::cout << data->dwRequestID << std::endl; - return; - } -} - -void FlyPadBackend::simConnectProcessDispatchMessage(SIMCONNECT_RECV* pData, DWORD* cbData) { - switch (pData->dwID) { - case SIMCONNECT_RECV_ID_OPEN: - std::cout << "FLYPAD_BACKEND: SimConnect connection established" << std::endl; - break; - - case SIMCONNECT_RECV_ID_QUIT: - std::cout << "FLYPAD_BACKEND: Received SimConnect connection quit message" << std::endl; - break; - - case SIMCONNECT_RECV_ID_SIMOBJECT_DATA: - simConnectProcessSimObjectData(static_cast(pData)); - break; - - case SIMCONNECT_RECV_ID_EXCEPTION: - std::cout << "FLYPAD_BACKEND: Exception in SimConnect connection: "; - std::cout << getSimConnectExceptionString( - static_cast( - static_cast(pData)->dwException)); - std::cout << std::endl; - break; - - default: - break; - } -} - -std::string FlyPadBackend::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/fbw-a380x/src/wasm/flypad-backend/src/FlyPadBackend.h b/fbw-a380x/src/wasm/flypad-backend/src/FlyPadBackend.h deleted file mode 100644 index dab61ed0753..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/src/FlyPadBackend.h +++ /dev/null @@ -1,131 +0,0 @@ -// 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 - -// IDs for data structures - must be mapped to data structs -enum DataStructureIDs { - SimulationDataID, - PushbackDataID -}; - -// Local data structure for simconnect data -struct SimulationData { - double simulationTime; -}; - -// Data structure for PushbackDataID -struct PushbackData { - INT64 pushbackWait; - FLOAT64 velBodyZ; - FLOAT64 rotVelBodyY; - FLOAT64 rotAccelBodyX; -}; - -enum Events { - KEY_TUG_HEADING_EVENT, - KEY_TUG_SPEED_EVENT -}; - -class LightPreset; -class AircraftPreset; -class Pushback; - -class FlyPadBackend { -private: - HANDLE hSimConnect; - - // Instance of local data structure for simconnect data - SimulationData simulationData = {}; - PushbackData pushbackData = {}; - - /** - * Flag if connection has been initialized. - */ - bool isConnected = false; - - // Storing previous simulation allows for Pause detection - double previousSimulationTime = 0; - - // Pointers to the flypad backend submodules - std::unique_ptr lightPresetPtr; - std::unique_ptr aircraftPresetPtr; - std::unique_ptr pushbackPtr; - -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/fbw-a380x/src/wasm/flypad-backend/src/Lighting/LightPreset.cpp b/fbw-a380x/src/wasm/flypad-backend/src/Lighting/LightPreset.cpp deleted file mode 100644 index 99e37190146..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/src/Lighting/LightPreset.cpp +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#include -#include - -#include "LightPreset.h" - -void LightPreset::initialize() { - isInitialized = true; - std::cout << "FLYPAD_BACKEND: 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 << "FLYPAD_BACKEND: LightPresets shutdown" << std::endl; -} - -void LightPreset::loadLightingPreset(int64_t loadPresetRequest) { - std::cout << "FLYPAD_BACKEND: Loading preset: " << loadPresetRequest << std::endl; - if (readFromStore(loadPresetRequest)) { - applyToAircraft(); - std::cout << "FLYPAD_BACKEND: Lighting Preset: " << loadPresetRequest << " successfully loaded." - << std::endl; - return; - } - std::cout << "FLYPAD_BACKEND: Loading Lighting Preset: " << loadPresetRequest << " failed." - << std::endl; -} - -void LightPreset::saveLightingPreset(int64_t savePresetRequest) { - std::cout << "FLYPAD_BACKEND: Save to Lighting Preset: " << savePresetRequest << std::endl; - readFromAircraft(); - if (saveToStore(savePresetRequest)) { - std::cout << "FLYPAD_BACKEND: Lighting Preset: " << savePresetRequest << " successfully saved." - << std::endl; - return; - } - std::cout << "FLYPAD_BACKEND: 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 << "FLYPAD_BACKEND: reading ini value for \"" - << "[" << section << "] " << key << " = " << ini.get(section).get(key) - << "\" failed." << std::endl; - } - } - return defaultValue; -} diff --git a/fbw-a380x/src/wasm/flypad-backend/src/Lighting/LightPreset.h b/fbw-a380x/src/wasm/flypad-backend/src/Lighting/LightPreset.h deleted file mode 100644 index ff2771d8104..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/src/Lighting/LightPreset.h +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#pragma once - -#include -#include - -#include "../FlyPadBackend.h" -#include "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; - - std::unique_ptr 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 = std::make_unique(); - }; - - /** - * Destructor - */ - ~LightPreset() = default; - - /** - * 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. - */ - [[nodiscard]] __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/fbw-a380x/src/wasm/flypad-backend/src/Lighting/LightingSimVars.h b/fbw-a380x/src/wasm/flypad-backend/src/Lighting/LightingSimVars.h deleted file mode 100644 index d397f6c3da3..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/src/Lighting/LightingSimVars.h +++ /dev/null @@ -1,250 +0,0 @@ -// 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_LIGHTING_PRESET_LOAD"); - this->setLoadLightingPresetRequest(0); - SaveLightingPresetRequest = register_named_variable("A32NX_LIGHTING_PRESET_SAVE"); - 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 - */ - [[nodiscard]] - inline FLOAT64 getElecAC1State() const { - return get_named_variable_value(ElecAC1); - } - - /** - * Reads the preset loading request variable. - * @return INT64 signifying the preset to be loaded - */ - [[nodiscard]] - 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. - */ - [[nodiscard]] - 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 - */ - [[nodiscard]] - 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. - */ - [[nodiscard]] - 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) - */ - [[nodiscard]] - 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) - */ - [[nodiscard]] - 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) - */ - [[nodiscard]] - 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) - */ - [[nodiscard]] - 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) - */ - [[nodiscard]] - 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/fbw-a380x/src/wasm/flypad-backend/src/Pushback/InertialDampener.cpp b/fbw-a380x/src/wasm/flypad-backend/src/Pushback/InertialDampener.cpp deleted file mode 100644 index e060655bccc..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/src/Pushback/InertialDampener.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#include - -#include "InertialDampener.h" - -InertialDampener::InertialDampener(double startValue, double accelStepSize) { - this->lastValue = startValue; - this->accelStepSize = accelStepSize; -} - -double InertialDampener::updateSpeed(double newTargetValue) { - if (round(newTargetValue, 1) == round(lastValue, 1)) { - return newTargetValue; - } - if (newTargetValue > this->lastValue) { - this->lastValue += this->accelStepSize; - } - else if (newTargetValue < this->lastValue) { - this->lastValue -= this->accelStepSize; - } - return this->lastValue; -} - -double InertialDampener::round(double value, int decimalPrecision) { - const double p = std::pow(10, decimalPrecision); - return std::round(value * p) / p; -} diff --git a/fbw-a380x/src/wasm/flypad-backend/src/Pushback/InertialDampener.h b/fbw-a380x/src/wasm/flypad-backend/src/Pushback/InertialDampener.h deleted file mode 100644 index f9b866114a3..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/src/Pushback/InertialDampener.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#pragma once - -/** - * The InertialDampener provides a dampened output based on the current input - * and an internal state value. The output value increases or decreases from the - * internal state value towards the input value with the given acceleration value. - */ -class InertialDampener { - -private: - double lastValue{}; - double accelStepSize{}; - -public: - - /** - * Creates a new instance of the InertialDampener - * @param startValue initial value to avoid a too large delta for the first usage - * @param accelStepSize value which will be added/subtracted to/from the internal - * state towards the input value. - */ - InertialDampener(double startValue, double accelStepSize); - - /** - * Destructor - */ - ~InertialDampener() = default; - - /** - * Given a target value this returns a value increased or decreased from the last - * returned value towards the new target value. The value is increased or decreased - * by the accelStepSize provided when creating the instance. - * @param newTargetValue - * @return new value loser to newTarget value by accelStepSize - */ - double updateSpeed(double newTargetValue); - -private: - static double round(double value, int decimalPrecision); -}; diff --git a/fbw-a380x/src/wasm/flypad-backend/src/Pushback/Pushback.cpp b/fbw-a380x/src/wasm/flypad-backend/src/Pushback/Pushback.cpp deleted file mode 100644 index 4ab3891d03e..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/src/Pushback/Pushback.cpp +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#include -#include - -#include "InertialDampener.h" -#include "Pushback.h" - -using namespace std::chrono; - -static constexpr double SPEED_RATIO = 18.0; -static constexpr double TURN_SPEED_RATIO = 0.16; - -Pushback::Pushback(HANDLE hdl, PushbackData* data) { - hSimConnect = hdl; - pushbackDataPtr = data; - m_Units = std::make_unique(); - inertialDampenerPtr = std::make_unique(0.0, 0.15); -} - -void Pushback::initialize() { - // LVARs are initialized here - pushbackSystemEnabled = register_named_variable("A32NX_PUSHBACK_SYSTEM_ENABLED"); - updateDelta = register_named_variable("A32NX_PUSHBACK_UPDT_DELTA"); - parkingBrakeEngaged = register_named_variable("A32NX_PARK_BRAKE_LEVER_POS"); - tugCommandedSpeedFactor = register_named_variable("A32NX_PUSHBACK_SPD_FACTOR"); - tugCommandedHeadingFactor = register_named_variable("A32NX_PUSHBACK_HDG_FACTOR"); - tugCommandedSpeed = register_named_variable("A32NX_PUSHBACK_SPD"); - tugCommandedHeading = register_named_variable("A32NX_PUSHBACK_HDG"); - tugInertiaSpeed = register_named_variable("A32NX_PUSHBACK_INERTIA_SPD"); - rotXOut = register_named_variable("A32NX_PUSHBACK_R_X_OUT"); - - // Read only Simvars - pushbackAttached = get_aircraft_var_enum("Pushback Attached"); - simOnGround = get_aircraft_var_enum("SIM ON GROUND"); - aircraftHeading = get_aircraft_var_enum("PLANE HEADING DEGREES TRUE"); - windVelBodyZ = get_aircraft_var_enum("RELATIVE WIND VELOCITY BODY Z"); - - // Writable Simvars in FlyPadBackend via simconnect data definitions - - isInitialized = true; - std::cout << "FLYPAD_BACKEND: Pushback initialized" << std::endl; -} - -void Pushback::onUpdate(double deltaTime) { - if (!isInitialized || !isPushbackSystemEnabled() || !isPushbackAttached() || !isSimOnGround()) { - return; - } - - // auto start = high_resolution_clock::now(); - - // Calculate movement data and update shared data (debug info) - set_named_variable_value(updateDelta, deltaTime); - - const FLOAT64 tugCmdSpdFactor = getTugCmdSpdFactor(); - const bool parkBrakeEngaged = isParkingBrakeEngaged(); - - const FLOAT64 tugCmdSpd = tugCmdSpdFactor * (parkBrakeEngaged ? (SPEED_RATIO / 10) : SPEED_RATIO); - set_named_variable_value(tugCommandedSpeed, tugCmdSpd); // debug - - const FLOAT64 inertiaSpeed = inertialDampenerPtr->updateSpeed(tugCmdSpd); - set_named_variable_value(tugInertiaSpeed, inertiaSpeed); // debug - - const FLOAT64 computedHdg = angleAdd(getAircraftTrueHeading(), -50 * getTugCmdHdgFactor()); - set_named_variable_value(tugCommandedHeading, computedHdg); // debug - - const FLOAT64 computedRotationVelocity = sgn(tugCmdSpd) - * getTugCmdHdgFactor() - * (parkBrakeEngaged ? (TURN_SPEED_RATIO / 10) : TURN_SPEED_RATIO); - - // As we might use the elevator for taxiing we compensate for wind to avoid - // the aircraft lifting any gears. - const FLOAT64 windCounterRotAccel = getWindVelBodyZ() / 2000.0; - FLOAT64 movementCounterRotAccel = windCounterRotAccel; - if (inertiaSpeed > 0) { - movementCounterRotAccel -= 0.5; - } - else if (inertiaSpeed < 0) { - movementCounterRotAccel += 1.0; - } - else { - movementCounterRotAccel = 0.0; - } - set_named_variable_value(rotXOut, movementCounterRotAccel); // debug - - // K:KEY_TUG_HEADING expects an unsigned integer scaling 360° to 0 to 2^32-1 (0xffffffff / 360) - static const int32_t headingToInt32 = 0xffffffff / 360; - const auto convertedComputedHeading = static_cast(static_cast(computedHdg * headingToInt32)); - - // send K:KEY_TUG_HEADING event - HRESULT result = SimConnect_TransmitClientEvent( - hSimConnect, - 0, - Events::KEY_TUG_HEADING_EVENT, - convertedComputedHeading, - SIMCONNECT_GROUP_PRIORITY_HIGHEST, - SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); - - // K:KEY_TUG_SPEED - seems to actually do nothing - // result &= SimConnect_TransmitClientEvent(hSimConnect, - // 0, Events::KEY_TUG_SPEED_EVENT, inertiaSpeed, - // SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); - - // Update sim data - pushbackDataPtr->pushbackWait = inertiaSpeed == 0 ? 1 : 0; - pushbackDataPtr->velBodyZ = inertiaSpeed; - pushbackDataPtr->rotVelBodyY = computedRotationVelocity; - pushbackDataPtr->rotAccelBodyX = movementCounterRotAccel; - result &= SimConnect_SetDataOnSimObject( - hSimConnect, - DataStructureIDs::PushbackDataID, - SIMCONNECT_OBJECT_ID_USER, - 0, - 0, - sizeof(*pushbackDataPtr), - pushbackDataPtr); - - // check result of data request - if (result != S_OK) { - std::cout << "FLYPAD_BACKEND (Pushback): Writing to sim failed! " << std::endl; - } - - // auto elapsed = duration_cast(high_resolution_clock::now() - start); - // std::cout << "FLYPAD_BACKEND (Pushback): Elapsed = " << elapsed.count() << " micro seconds" - // << std::endl; -} - -void Pushback::shutdown() { - isInitialized = false; - std::cout << "FLYPAD_BACKEND (Pushback): Pushback shutdown" << std::endl; -} diff --git a/fbw-a380x/src/wasm/flypad-backend/src/Pushback/Pushback.h b/fbw-a380x/src/wasm/flypad-backend/src/Pushback/Pushback.h deleted file mode 100644 index cb38bcdda36..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/src/Pushback/Pushback.h +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) 2022 FlyByWire Simulations -// SPDX-License-Identifier: GPL-3.0 - -#pragma once - -#include -#include -#include -#include - -#include - - -#include "Units.h" -#include "FlyPadBackend.h" -#include "InertialDampener.h" - -class InertialDampener; - -#ifdef __cpp_lib_math_constants -#include -constexpr double PI = std::numbers::pi; -#else -constexpr double PI = 3.14159265358979323846; -#endif - -/** - * Class for handling aircraft presets. - */ -class Pushback { - -private: - HANDLE hSimConnect; - bool isInitialized = false; - - PushbackData* pushbackDataPtr; - - std::unique_ptr m_Units; - std::unique_ptr inertialDampenerPtr; - - // LVARs - ID pushbackSystemEnabled{}; - ID pushbackPaused{}; - ID tugCommandedHeadingFactor{}; - ID tugCommandedHeading{}; - ID tugCommandedSpeedFactor{}; - ID tugCommandedSpeed{}; - ID tugInertiaSpeed{}; - ID parkingBrakeEngaged{}; - ID updateDelta{}; - ID rotXInput{}; - ID rotXOut{}; - - // Sim-vars - ENUM simOnGround{}; - ENUM pushbackAttached{}; - ENUM aircraftHeading{}; - ENUM windVelBodyZ{}; - -public: - /** - * Creates an instance of the Pushback class. - */ - Pushback(HANDLE hdl, PushbackData* data); - - /** - * Destructor - */ - ~Pushback() = default; - - /** - * 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: - // @formatter:off - // LVAR getter - [[nodiscard]] inline bool isPushbackPaused() const { return static_cast(get_named_variable_value(pushbackPaused)); } - [[nodiscard]] inline bool isPushbackSystemEnabled() const { return static_cast(get_named_variable_value(pushbackSystemEnabled)); } - [[nodiscard]] inline bool isParkingBrakeEngaged() const { return static_cast(get_named_variable_value(parkingBrakeEngaged)); } - [[nodiscard]] inline FLOAT64 getTugCmdSpdFactor() const { return static_cast(get_named_variable_value(tugCommandedSpeedFactor)); } - [[nodiscard]] inline FLOAT64 getTugCmdHdgFactor() const { return static_cast(get_named_variable_value(tugCommandedHeadingFactor)); } - - // Simvar getter - [[nodiscard]] inline bool isPushbackAttached() const { return static_cast(aircraft_varget(pushbackAttached, m_Units->Bool, 0)); } - [[nodiscard]] inline bool isSimOnGround() const { return static_cast(aircraft_varget(simOnGround, m_Units->Bool, 0)); } - [[nodiscard]] inline FLOAT64 getAircraftTrueHeading() const { - return (180.0 / PI) * static_cast(aircraft_varget(aircraftHeading, m_Units->Number, 0)); - } - [[nodiscard]] inline FLOAT64 getWindVelBodyZ() const { return static_cast(aircraft_varget(windVelBodyZ, m_Units->FeetSec, 0)); } - // Sim data getter - [[nodiscard]] inline bool isPushbackWaiting() const { return static_cast(pushbackDataPtr->pushbackWait); } - // @formatter:on - - /** - * Adds two angles with wrap around to result in 0-360° - * @param a - positive or negative angle - * @param b - positive or negative angle - */ - static double angleAdd(double a, double b) { - double r = a + b; - while (r > 360.0) { - r -= 360.0; - } - while (r < 0.0) { - r += 360.0; - } - return r; - }; - - /** - * Returns the signum (sign) of the given value. - * @tparam T - * @param val - * @return sign of value or 0 when value==0 - */ - template - int sgn(T val) { - return (T(0) < val) - (val < T(0)); - } - -}; diff --git a/fbw-a380x/src/wasm/flypad-backend/src/Units.h b/fbw-a380x/src/wasm/flypad-backend/src/Units.h deleted file mode 100644 index 68ba3ccda61..00000000000 --- a/fbw-a380x/src/wasm/flypad-backend/src/Units.h +++ /dev/null @@ -1,27 +0,0 @@ -// 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 FeetSec = get_units_enum("Feet per second"); - ENUM Mach = get_units_enum("Mach"); - ENUM Millibars = get_units_enum("Millibars"); - ENUM Celsius = get_units_enum("Celsius"); - ENUM Hours = get_units_enum("Hours"); - ENUM Seconds = get_units_enum("Seconds"); -}; diff --git a/fbw-common/src/wasm/CMakeLists.txt b/fbw-common/src/wasm/CMakeLists.txt new file mode 100644 index 00000000000..3bc18ceb393 --- /dev/null +++ b/fbw-common/src/wasm/CMakeLists.txt @@ -0,0 +1,2 @@ +# add the submodules +add_subdirectory(cpp-msfs-framework) diff --git a/fbw-common/src/wasm/cpp-msfs-framework/CMakeLists.txt b/fbw-common/src/wasm/cpp-msfs-framework/CMakeLists.txt new file mode 100644 index 00000000000..9d5270a6008 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/CMakeLists.txt @@ -0,0 +1,45 @@ +# define the source files +set(SOURCE_FILES + MsfsHandler/DataManager.cpp + MsfsHandler/DataTypes/AircraftVariable.cpp + MsfsHandler/DataTypes/CacheableVariable.cpp + MsfsHandler/DataTypes/ClientEvent.cpp + MsfsHandler/DataTypes/NamedVariable.cpp + MsfsHandler/MsfsHandler.cpp + ) +set(INCLUDE_FILES + MsfsHandler/DataManager.h + MsfsHandler/DataTypes/AircraftVariable.h + MsfsHandler/DataTypes/CacheableVariable.h + MsfsHandler/DataTypes/ClientDataAreaVariable.hpp + MsfsHandler/DataTypes/ClientEvent.h + MsfsHandler/DataTypes/DataDefinitionVariable.hpp + MsfsHandler/DataTypes/DataObjectBase.hpp + MsfsHandler/DataTypes/ManagedDataObjectBase.hpp + MsfsHandler/DataTypes/NamedVariable.h + MsfsHandler/DataTypes/SimObjectBase.hpp + MsfsHandler/DataTypes/StreamingClientDataAreaVariable.hpp + MsfsHandler/Module.h + MsfsHandler/MsfsHandler.h + MsfsHandler/SimconnectExceptionStrings.h + MsfsHandler/SimUnits.h + lib/Callback.h + lib/IDGenerator.h + lib/fingerprint.hpp + lib/inih/ini.h + lib/inih/ini_type_conversion.h + lib/logging.h + lib/math_utils.hpp + lib/ProfileBuffer.hpp + lib/ScopedTimer.hpp + lib/SimpleProfiler.hpp + lib/simple_assert.h + lib/string_utils.hpp + lib/quantity.hpp) + +# create the targets for all aircrafts +add_library(cpp-msfs-framework-a32nx OBJECT ${SOURCE_FILES} ${INCLUDE_FILES}) +target_compile_definitions(cpp-msfs-framework-a32nx PUBLIC A32NX) + +add_library(cpp-msfs-framework-a380x OBJECT ${SOURCE_FILES} ${INCLUDE_FILES}) +target_compile_definitions(cpp-msfs-framework-a380x PUBLIC A380X) diff --git a/fbw-common/src/wasm/cpp-msfs-framework/Example/ExampleModule.cpp b/fbw-common/src/wasm/cpp-msfs-framework/Example/ExampleModule.cpp new file mode 100644 index 00000000000..82c1124a818 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/Example/ExampleModule.cpp @@ -0,0 +1,520 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifdef EXAMPLES + +#include +#include + +#include "AircraftVariable.h" +#include "ClientEvent.h" +#include "ExampleModule.h" +#include "NamedVariable.h" +#include "fingerprint.hpp" +#include "logging.h" +#include "longtext.h" +#include "math_utils.hpp" + +bool ExampleModule::initialize() { + dataManager = &msfsHandler.getDataManager(); + + /* + * Update mode of a variable - last 4 optional parameters of the make_... calls: + * + * UpdateMode::AUTO_READ: automatically update from sim at every tick when update criteria are met + * UpdateMode::AUTO_WRITE: automatically write to sim at every tick + * maxAgeTime: maximum age of the variable in seconds (influences update reads) + * maxAgeTicks: maximum age of the variable in ticks (influences update reads) + * + * default is "false, false, 0, 0" + */ + + +#ifdef LVAR_EXAMPLES + // LVARS + // requested multiple times to demonstrate de-duplication - also shows optional parameters + // use this to familiarise yourself with the different parameters + debugLVARPtr = dataManager->make_named_var("DEBUG_LVAR", UNITS.Hours, UpdateMode::AUTO_READ_WRITE, 0, 0); + // debugLVARPtr->setEpsilon(1.0); // only read when difference is >1.0 + // debugLVARPtr->addCallback([&, this]() { LOG_INFO("Callback: DEBUG_LVAR value changed to " + std::to_string(debugLVARPtr->get())); }); + // these are unique and not the same as the first + debugLVAR2Ptr = dataManager->make_named_var("DEBUG_LVAR", UNITS.Minutes, UpdateMode::AUTO_READ, 0, 1000); + debugLVAR3Ptr = dataManager->make_named_var("DEBUG_LVAR", UNITS.Seconds, UpdateMode::AUTO_READ, 5.0, 0); + // this is a duplicate of the first one, so should be the same pointer + debugLVAR4Ptr = dataManager->make_named_var("DEBUG_LVAR", UNITS.Hours, UpdateMode::NO_AUTO_UPDATE, 0, 0); +#endif + +#if defined(SIM_EVENT_EXAMPLE) || defined(AIRCRAFT_VAR_EXAMPLE) || defined(INDEXED_AIRCRAFT_VAR_EXAMPLE) + // Sim Events + beaconLightSetEventPtr = dataManager->make_client_event("BEACON_LIGHTS_SET", true, NOTIFICATION_GROUP_0); + beaconLightSetCallbackID = beaconLightSetEventPtr->addCallback( + [&, this](const int number, const DWORD param0, const DWORD param1, const DWORD param2, const DWORD param3, const DWORD param4) { + LOG_INFO("Callback: BEACON_LIGHTS_SET event received with " + std::to_string(number) + + " params:" + " 0: " + std::to_string(param0) + " 1: " + std::to_string(param1) + " 2: " + std::to_string(param2) + + " 3: " + std::to_string(param3) + " 4: " + std::to_string(param4) + " beaconLt: " + this->beaconLightSetEventPtr->str()); + }); + beaconLightSetCallback2ID = beaconLightSetEventPtr->addCallback( + [&, this](const int number, const DWORD param0, const DWORD param1, const DWORD param2, const DWORD param3, const DWORD param4) { + LOG_INFO("Callback 2: BEACON_LIGHTS_SET event received with " + std::to_string(number) + + " params:" + " 0: " + std::to_string(param0) + " 1: " + std::to_string(param1) + " 2: " + std::to_string(param2) + + " 3: " + std::to_string(param3) + " 4: " + std::to_string(param4) + " beaconLt: " + this->beaconLightSetEventPtr->str()); + }); + + // Event with callback example + lightPotentiometerSetEventPtr = dataManager->make_sim_event("LIGHT_POTENTIOMETER_SET", NOTIFICATION_GROUP_0); + lightPotentiometerSetCallbackID = + lightPotentiometerSetEventPtr->addCallback([=]([[maybe_unused]] int number, + [[maybe_unused]] DWORD param0, + [[maybe_unused]] DWORD param1, + [[maybe_unused]] DWORD param2, + [[maybe_unused]] DWORD param3, + [[maybe_unused]] DWORD param4) { + if (param0 == 99) + return; + LOG_DEBUG("Callback 1: LIGHT_POTENTIOMETER_SET event received with " + std::to_string(number) + + " params:" + " 0: " + std::to_string(param0) + " 1: " + std::to_string(param1) + " 2: " + std::to_string(param2) + + " 3: " + std::to_string(param3) + " 4: " + std::to_string(param4)); + }); + + // Second event with the same name - this should be de-duplicated + lightPotentiometerSetEvent2Ptr = dataManager->make_sim_event("LIGHT_POTENTIOMETER_SET", NOTIFICATION_GROUP_0); + lightPotentiometerSetCallback2ID = + lightPotentiometerSetEvent2Ptr->addCallback([=]([[maybe_unused]] int number, + [[maybe_unused]] DWORD param0, + [[maybe_unused]] DWORD param1, + [[maybe_unused]] DWORD param2, + [[maybe_unused]] DWORD param3, + [[maybe_unused]] DWORD param4) { + if (param0 == 99) + return; + LOG_DEBUG("Callback 2: LIGHT_POTENTIOMETER_SET event received with " + std::to_string(number) + + " params:" + " 0: " + std::to_string(param0) + " 1: " + std::to_string(param1) + " 2: " + std::to_string(param2) + + " 3: " + std::to_string(param3) + " 4: " + std::to_string(param4)); + }); +#endif + +#ifdef AIRCRAFT_VAR_EXAMPLE + // Aircraft variables - requested multiple times to demonstrate de-duplication + // to test change the units to either use the same units (will be deduplicated) or different units + // in which case the variables will be unique. + beaconLightSwitchPtr = + dataManager->make_aircraft_var("LIGHT BEACON", 0, "", beaconLightSetEventPtr, UNITS.Percent, UpdateMode::AUTO_READ, 0, 0); + beaconLightSwitch2Ptr = + dataManager->make_aircraft_var("LIGHT BEACON", 0, "", beaconLightSetEventPtr, UNITS.Bool, UpdateMode::AUTO_READ, 5.0, 0); + // using make_simple_aircraft_var() to demonstrate the same thing + beaconLightSwitch3Ptr = dataManager->make_simple_aircraft_var("LIGHT BEACON", UNITS.PercentOver100); + + // using event name for execute_calculator_code example + strobeLightSwitchPtr = + dataManager->make_aircraft_var("LIGHT STROBE", 0, "STROBES_SET", nullptr, UNITS.Bool, UpdateMode::AUTO_READ, 0, 0); +#endif + +#ifdef INDEXED_AIRCRAFT_VAR_EXAMPLE + // A:FUELSYSTEM PUMP SWITCH:#ID# - demonstrates variable with index + // clang-format off + fuelPumpSwitch1Ptr = dataManager->make_aircraft_var("FUELSYSTEM PUMP SWITCH", 2, "", beaconLightSetEventPtr, UNITS.Bool, UpdateMode::AUTO_READ, 0, 0); + fuelPumpSwitch2Ptr = dataManager->make_aircraft_var("FUELSYSTEM PUMP SWITCH", 3, "", beaconLightSetEventPtr, UNITS.Bool, UpdateMode::AUTO_READ, 0, 0); + // clang-format on +#endif + +#ifdef DATA_DEFINITION_EXAMPLE + // ======================================== + // Data definition variables + std::vector exampleDataDef = { + {"LIGHT STROBE", 0, UNITS.Bool}, + {"LIGHT WING", 0, UNITS.Bool}, + {"ZULU TIME"}, + {"LOCAL TIME"}, + {"ABSOLUTE TIME"}, + // The sim crashes if the string datatype is too short for the string + {"TITLE", 0, UNITS.None, SIMCONNECT_DATATYPE_STRING256}, + }; + exampleDataPtr = dataManager->make_datadefinition_var("EXAMPLE DATA", exampleDataDef, UpdateMode::AUTO_READ, 0, 0); + // Alternative to use autoRead it is possible to set the SIMCONNECT_PERIOD. + // this is probably very efficient for data definitions areas if every change needs to be read + // or if the sim should only send data when it has changed. + // See + // https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Structures_And_Enumerations/SIMCONNECT_CLIENT_DATA_PERIOD.htm?rhhlterm=SIMCONNECT_CLIENT_DATA_PERIOD&rhsearch=SIMCONNECT_CLIENT_DATA_PERIOD + if (!exampleDataPtr->requestPeriodicDataFromSim(SIMCONNECT_PERIOD_VISUAL_FRAME)) { + LOG_ERROR("Failed to request periodic data from sim"); + } +#endif + +#ifdef CLIENT_DATA_AREA_EXAMPLE + // ======================================== + // Client data area owned by this module + exampleClientDataPtr = dataManager->make_clientdataarea_var("EXAMPLE CLIENT DATA", UpdateMode::AUTO_WRITE); + exampleClientDataPtr->allocateClientDataArea(sizeof(ExampleClientData)); + + // Client data area owned by an external module + exampleClientData2Ptr = dataManager->make_clientdataarea_var("EXAMPLE 2 CLIENT DATA"); + exampleClientData2Ptr->setSkipChangeCheck(true); + // this is probably very efficient for client data areas if every change needs to be read + if (!exampleClientData2Ptr->requestPeriodicDataFromSim(SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET)) { + LOG_ERROR("Failed to request periodic data from sim"); + } +#endif + +#ifdef BIG_CLIENT_DATA_EXAMPLE + // ======================================== + // Big client data area owned by an external module + bigClientDataPtr = dataManager->make_clientdataarea_var("BIG CLIENT DATA"); + bigClientDataPtr->setSkipChangeCheck(true); + bigClientDataPtr->addCallback([&]() { + // Big Client Data + std::cout << "--- CALLBACK: BIG CLIENT DATA (External - reading)" << std::endl; + std::cout << bigClientDataPtr->str() << std::endl; + std::cout << "Big Client Data data: " << std::endl; + auto s = std::string_view((const char*)&bigClientDataPtr->data().dataChunk, 100); + std::cout << bigClientDataPtr->data().dataChunk.size() << " bytes: " << s << " ... " << std::endl; + std::cout << "Fingerprint: " + << Fingerprint::fingerPrintFVN( + std::vector(bigClientDataPtr->data().dataChunk.begin(), bigClientDataPtr->data().dataChunk.end())) + << std::endl; + }); + if (!bigClientDataPtr->requestPeriodicDataFromSim(SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET)) { + LOG_ERROR("Failed to request periodic data from sim"); + } +#endif + +#ifdef STREAM_RECEIVE_EXAMPLE + // ======================================== + // Metadata for the StreamingClientDataAreaVariable test + streamReceiverMetaDataPtr = dataManager->make_clientdataarea_var("STREAM RECEIVER META DATA"); + streamReceiverMetaDataPtr->setSkipChangeCheck(true); + streamReceiverMetaDataPtr->addCallback([&]() { + streamReceiverTimerStart = std::chrono::high_resolution_clock::now(); + streamReveicerDataPtr->reserve(streamReceiverMetaDataPtr->data().size); + std::cout << "--- CALLBACK: STREAM RECEIVER META DATA (External - reading)" << std::endl; + std::cout << streamReceiverMetaDataPtr->str() << std::endl; + std::cout << "STREAM RECEIVER DATA META DATA size = " << streamReceiverMetaDataPtr->data().size + << " fingerprint = " << streamReceiverMetaDataPtr->data().hash << std::endl; + }); + if (!streamReceiverMetaDataPtr->requestPeriodicDataFromSim(SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET)) { + LOG_ERROR("Failed to request periodic data from sim"); + } + + // ============================================== + // StreamingClientDataAreaVariable receiving test + streamReveicerDataPtr = dataManager->make_streamingclientdataarea_var("STREAM RECEIVER DATA"); + streamReveicerDataPtr->setSkipChangeCheck(true); + streamReveicerDataPtr->addCallback([&]() { + streamReceiverTimerEnd = + std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - streamReceiverTimerStart); + std::cout << "--- CALLBACK: STREAM RECEIVER DATA (External - reading)" << std::endl; + std::cout << streamReveicerDataPtr->str() << std::endl; + const uint64_t fingerPrintFvn = Fingerprint::fingerPrintFVN(streamReveicerDataPtr->getData()); + std::cout << "STREAM RECEIVER DATA " + << " size = " << streamReveicerDataPtr->getData().size() << " bytes = " << streamReveicerDataPtr->getReceivedBytes() + << " chunks = " << streamReveicerDataPtr->getReceivedChunks() << " fingerprint = " << std::setw(21) << fingerPrintFvn + << " (match = " << std::boolalpha << (fingerPrintFvn == streamReceiverMetaDataPtr->data().hash) << ")" + << " time = " << std::setw(10) << streamReceiverTimerEnd.count() << " ns" << std::endl; + std::cout << "Content: " + << "[" << std::string(streamReveicerDataPtr->getData().begin(), streamReveicerDataPtr->getData().begin() + 100) << " ... ]" + << std::endl; + }); + if (!SUCCEEDED(streamReveicerDataPtr->requestPeriodicDataFromSim(SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET))) { + LOG_ERROR("Failed to request periodic data from sim"); + } +#endif + +#ifdef STREAM_SEND_EXAMPLE + // ============================================ + // StreamingClientDataAreaVariable sending test + streamSenderMetaDataPtr = dataManager->make_clientdataarea_var("STREAM SENDER META DATA"); + streamSenderMetaDataPtr->allocateClientDataArea(); + streamSenderDataPtr = dataManager->make_streamingclientdataarea_var("STREAM SENDER DATA"); + streamSenderDataPtr->allocateClientDataArea(); + streamSenderDataPtr->getData().assign(longText.begin(), longText.end()); +#endif + +#ifdef KEY_EVENT_EXAMPLE + // ====================== + // Key event tests + // Test callback a member method using this-> + [[maybe_unused]] auto keyEventId = dataManager->addKeyEventCallback( + KEY_BEACON_LIGHTS_SET, [&, this](DWORD param0, DWORD param1, DWORD param2, DWORD param3, DWORD param4) { + this->keyEventTest(param0, param1, param2, param3, param4); + }); + // Test a second callback using a lambda + [[maybe_unused]] auto keyEventId2 = + dataManager->addKeyEventCallback(KEY_BEACON_LIGHTS_SET, [&](DWORD param0, DWORD param1, DWORD param2, DWORD param3, DWORD param4) { + std::cout << "Callback 2: KEY_BEACON_LIGHTS_SET " + << " param0 = " << param0 << " param1 = " << param1 << " param2 = " << param2 << " param3 = " << param3 + << " param4 = " << param4 << std::endl; + }); +#endif + +#ifdef CUSTOM_EVENT_EXAMPLE + // ====================== + // Client Event tests + // Simple custom client event - no mappings + clientEventPtr = dataManager->make_custom_event("A32NX.MY_CUSTOM_EVENT"); +#endif + +#ifdef SYSTEM_EVENT_EXAMPLE + // ====================== + // System Events + // Create the client event with the registerToSim flag set to false, so we can add + // client event to system event. When this is set to true (default) the client event + // will be registered to the sim either as a custom event or a mapped event (if the event name exists) and an + // error will be thrown if you try to register the system event to the sim. + systemEventPtr = dataManager->make_system_event("A32NX.SYSTEM_EVENT_VIEW", "View"); + systemEventCallbackId = systemEventPtr->addCallback( + [&](const int number, const DWORD param0, const DWORD param1, const DWORD param2, const DWORD param3, const DWORD param4) { + std::cout << "--- CALLBACK: A32NX.SYSTEM_EVENT_VIEW" << std::endl; + std::cout << systemEventPtr->str() << std::endl; + std::cout << "A32NX.SYSTEM_EVENT_VIEW" + << " number = " << number << " param0 = " << param0 << " param1 = " << param1 << " param2 = " << param2 + << " param3 = " << param3 << " param4 = " << param4 << std::endl; + std::cout << std::endl; + }); +#endif + +#ifdef MASK_KEYBOARD_EXAMPLE + inputEventPtr = dataManager->make_custom_event("A32NX.MASK_KEYBOARD", NOTIFICATION_GROUP_0); + inputEventCallbackId = inputEventPtr->addCallback( + [&](const int number, const DWORD param0, const DWORD param1, const DWORD param2, const DWORD param3, const DWORD param4) { + std::cout << "--- CALLBACK: A32NX.MASK_KEYBOARD" << std::endl; + std::cout << inputEventPtr->str() << std::endl; + std::cout << "A32NX.MASK_KEYBOARD" + << " number = " << number << " param0 = " << param0 << " param1 = " << param1 << " param2 = " << param2 + << " param3 = " << param3 << " param4 = " << param4 << std::endl; + std::cout << std::endl; + }); + // Masking seems not to work - the sim still receives the key events + inputEventPtr->mapInputDownEvent("VK_LCONTROL+e", INPUT_GROUP_0, true); + inputEventPtr->mapInputDownEvent("VK_RCONTROL+e", INPUT_GROUP_0, true); + inputEventPtr->setInputGroupPriority(INPUT_GROUP_0, SIMCONNECT_GROUP_PRIORITY_HIGHEST); +#endif + + _isInitialized = true; + LOG_INFO("ExampleModule initialized"); + return true; +} + +bool ExampleModule::preUpdate([[maybe_unused]] sGaugeDrawData* pData) { + // empty could be used to manually update variables if they are not autoRead + return true; +} + +bool ExampleModule::update([[maybe_unused]] sGaugeDrawData* pData) { + if (!_isInitialized) { + LOG_ERROR("ExampleModule::update() - not initialized"); + return false; + } + + // Do not do anything if the sim is not running - this is not required but is a good idea + // It is ready after the click on "READY TO FLY" + if (!msfsHandler.getAircraftIsReadyVar()) + return true; + + // Un-throttled tests +#ifdef STREAM_RECEIVE_EXAMPLE + // if (streamReceiverMetaDataPtr->hasChanged()) { + // // STREAM RECEIVER DATA Meta Data + // LOG_INFO("--- HUGE CLIENT META DATA (External - reading)"); + // streamReveicerDataPtr->reserve(streamReceiverMetaDataPtr->data().size); + // std::cout << streamReceiverMetaDataPtr->str() << std::endl; + // std::cout << "STREAM RECEIVER DATA size: " << streamReceiverMetaDataPtr->data().size << std::endl; + // std::cout << "STREAM RECEIVER DATA hash: " << streamReceiverMetaDataPtr->data().hash << std::endl; + // } + + // if (streamReveicerDataPtr->hasChanged()) { + // LOG_INFO("--- STREAM RECEIVER DATA (External - reading)"); + // std::cout << streamReveicerDataPtr->str() << std::endl; + // std::cout << "STREAM RECEIVER DATA size: " << streamReveicerDataPtr->getData().size() << std::endl; + // std::string s(streamReveicerDataPtr->getData().data(), streamReveicerDataPtr->getData().size()); + // auto fingerprint = fingerPrintFVN(s); + // std::cout << "Fingerprint: " << fingerprint << std::endl; + // std::cout << "Fingerprint is " << (fingerprint == streamReceiverMetaDataPtr->data().hash ? "equal" : "not equal") << std::endl; + // } +#endif + + // Use this to throttle output frequency while you are debugging + if (msfsHandler.getTickCounter() % 100 == 0) { + [[maybe_unused]] const FLOAT64 timeStamp = msfsHandler.getTimeStamp(); + [[maybe_unused]] const UINT64 tickCounter = msfsHandler.getTickCounter(); + + std::cout << "==== tickCounter = " << tickCounter << " timeStamp = " << timeStamp << " =============================" << std::endl; + + LOG_DEBUG("A32NX_IS_READY = " + std::string(msfsHandler.getAircraftIsReadyVar() ? "true" : "false") + + " A32NX_DEVELOPER_STATE = " + std::to_string(msfsHandler.getAircraftDevelopmentStateVar())); + +#ifdef CUSTOM_EVENT_EXAMPLE + // ====================== + // Client Event Tests + + if (tickCounter % 2000 == 1000) { + if (!clientEventPtr->isRegisteredToSim()) clientEventPtr->mapToSimEvent(); + // this will trigger an SimConnect error if the event is already registered to this group + clientEventPtr->addClientEventToNotificationGroup(NOTIFICATION_GROUP_0); + clientEventCallbackId = clientEventPtr->addCallback( + [&, this](const int number, const DWORD param0, const DWORD param1, const DWORD param2, const DWORD param3, const DWORD param4) { + std::cout << "--- CALLBACK: A32NX.MY_CUSTOM_EVENT" << std::endl; + std::cout << clientEventPtr->str() << std::endl; + std::cout << "CUSTOM_EVENT " + << " number = " << number << " param0 = " << param0 << " param1 = " << param1 << " param2 = " << param2 + << " param3 = " << param3 << " param4 = " << param4 << std::endl; + std::cout << std::endl; + }); + clientEventPtr->mapInputDownUpEvent("VK_COMMA", INPUT_GROUP_0); + clientEventPtr->mapInputDownUpEvent("joystick:1:button:7", INPUT_GROUP_0); + clientEventPtr->setInputGroupState(INPUT_GROUP_0, SIMCONNECT_STATE_ON); + } + if (tickCounter % 2000 == 0) { + clientEventPtr->removeCallback(clientEventCallbackId); + clientEventPtr->unmapInputEvent("VK_COMMA", INPUT_GROUP_0); + // this will trigger a SimConnect exception UNRECOGNIZED_ID but actually will remove the mapping + // without this adding the mapping again will lead to two events being sent for the input + // likely a bug in SimConnect/MSFS + clientEventPtr->unmapInputEvent("joystick:1:button:7", INPUT_GROUP_0); + clientEventPtr->removeClientEventFromNotificationGroup(NOTIFICATION_GROUP_0); + } + clientEventPtr->trigger(999); +#endif + +#ifdef LVAR_EXAMPLES + // clang-format off + + // difference if using different units + LOG_INFO("--- LVAR EXAMPLE"); + LOG_INFO("timeStamp = " + std::to_string(timeStamp) + " / ticks = " + std::to_string(msfsHandler.getTickCounter())); + + LOG_INFO("debugLVARPtr DEBUG_LVAR (hours) = " + std::to_string(debugLVARPtr->get())); + LOG_INFO("debugLVAR2Ptr DEBUG_LVAR (minutes) = " + std::to_string(debugLVAR2Ptr->get())); + LOG_INFO("debugLVAR3Ptr DEBUG_LVAR (seconds) = " + std::to_string(debugLVAR3Ptr->get())); + + // this second read of the duplicate should not trigger a read from the sim + // enable LOG_TRACE to see the read in updateFromSim() + LOG_INFO("debugLVAR4Ptr DEBUG_LVAR (hours) = " + std::to_string(debugLVAR4Ptr->get())); + + // testing doubled LVARs + std::cout << "Pointer Address of duplicates: debugLVARPtr=" << std::addressof(*debugLVARPtr) << " debugLVAR4Ptr=" << std::addressof(*debugLVAR4Ptr) << std::endl; + + + LOG_INFO("Setting DEBUG_LVAR to tickCounter = " + std::to_string(tickCounter)); + debugLVARPtr->set(tickCounter); + + // manual changing in the sim's LocalVariables dialog - uncomment below and comment out above + // + // std::cout << "debugLVARPtr = " << debugLVARPtr->get() << " changed? " << (debugLVARPtr->hasChanged() ? "yes" : "no") << " debugLVARPtr time = " << msfsHandlerPtr.getTimeStamp() << " tick = " << msfsHandlerPtr.getTickCounter() << std::endl; + // std::cout << "debugLVAR2Ptr = " << debugLVAR2Ptr->get() << " changed? " << (debugLVAR2Ptr->hasChanged() ? "yes" : "no") << " debugLVAR2Ptr time = " << msfsHandlerPtr.getTimeStamp() << " tick = " << msfsHandlerPtr.getTickCounter() << std::endl; + + // Set a variable which does not auto write - uncomment below and comment out above + // debugLVARPtr->setAndWriteToSim(debugLVARPtr->get() + 1); + + // clang-format on +#endif + +#ifdef AIRCRAFT_VAR_EXAMPLE + // Read vars which auto update each tick + std::cout << "beaconLightSwitchPtr = " << beaconLightSwitchPtr->get() << " changed? " + << (beaconLightSwitchPtr->hasChanged() ? "yes" : "no") << " beaconLightSwitchPtr time = " << msfsHandler.getTimeStamp() + << " tick = " << msfsHandler.getTickCounter() << std::endl; + std::cout << "beaconLightSwitch2Ptr = " << beaconLightSwitch2Ptr->get() << " changed? " + << (beaconLightSwitch2Ptr->hasChanged() ? "yes" : "no") << " beaconLightSwitch2Ptr time = " << msfsHandler.getTimeStamp() + << " tick = " << msfsHandler.getTickCounter() << std::endl; + std::cout << "beaconLightSwitch3Ptr = " << beaconLightSwitch3Ptr->updateFromSim(timeStamp, tickCounter) << " changed? " + << (beaconLightSwitch3Ptr->hasChanged() ? "yes" : "no") << " beaconLightSwitch3Ptr time = " << msfsHandler.getTimeStamp() + << " tick = " << msfsHandler.getTickCounter() << std::endl; + std::cout << "strobeLightSwitchPtr = " << strobeLightSwitchPtr->get() << " changed? " + << (strobeLightSwitchPtr->hasChanged() ? "yes" : "no") << " strobeLightSwitchPtr time = " << msfsHandler.getTimeStamp() + << " tick = " << msfsHandler.getTickCounter() << std::endl; + + // Test writing an aircraft variable by toggling the strobe light switch + // Immediate write + // strobeLightSwitchPtr->setAndWriteToSim(strobeLightSwitchPtr->get() == 0.0 ? 1.0 : 0.0); + + // Trigger event + // beaconLightSetKeyEventPtr->trigger_ex1(beaconLightSwitchPtr->get() == 0.0 ? 1.0 : 0.0); + // autoWrite in postUpdate + // beaconLightSwitch2Ptr->set(beaconLightSwitch2Ptr->get() == 0.0 ? 1.0 : 0.0); +#endif + +#ifdef INDEXED_AIRCRAFT_VAR_EXAMPLE + std::cout << "fuelPumpSwitch1Ptr = " << fuelPumpSwitch1Ptr->get() << " changed? " << (fuelPumpSwitch2Ptr->hasChanged() ? "yes" : "no") + << " time = " << msfsHandler.getTimeStamp() << " tick = " << msfsHandler.getTickCounter() << std::endl; + + std::cout << "fuelPumpSwitch2Ptr = " << fuelPumpSwitch2Ptr->get() << " changed? " << (fuelPumpSwitch2Ptr->hasChanged() ? "yes" : "no") + << " time = " << msfsHandler.getTimeStamp() << " tick = " << msfsHandler.getTickCounter() << std::endl; +#endif + +#ifdef DATA_DEFINITION_EXAMPLE + // testing data definition variables + LOG_INFO("--- TEST SIMOBJECT DATA"); + std::cout << exampleDataPtr->str() << std::endl; + + LOG_INFO("--- DataDefinition Example)"); + exampleDataPtr->requestUpdateFromSim(timeStamp, tickCounter); + std::cout << "strobeLightSwitch = " << exampleDataPtr->data().strobeLightSwitch << std::endl; + std::cout << "wingLightSwitch = " << exampleDataPtr->data().wingLightSwitch << std::endl; + std::cout << "zuluTime = " << exampleDataPtr->data().zuluTime << std::endl; + std::cout << "localTime = " << exampleDataPtr->data().localTime << std::endl; + std::cout << "absoluteTime = " << INT64(exampleDataPtr->data().absoluteTime) << std::endl; + std::cout << "aircraftTTitle = " << exampleDataPtr->data().aircraftTTitle << std::endl; +#endif + +#ifdef CLIENT_DATA_AREA_EXAMPLE + // Testing client data variables + // Can be tested together with https://github.com/frankkopp/fbw-cpp-framework-test + + // This local data sent to other clients + LOG_INFO("--- EXAMPLE CLIENT DATA (Owning - sending)"); + std::cout << exampleClientDataPtr->str() << std::endl; + std::cout << "FLOAT64 " << exampleClientDataPtr->data().aFloat64 << std::endl; + std::cout << "FLOAT32 " << exampleClientDataPtr->data().aFloat32 << std::endl; + std::cout << "INT64 " << exampleClientDataPtr->data().anInt64 << std::endl; + std::cout << "INT32 " << exampleClientDataPtr->data().anInt32 << std::endl; + std::cout << "INT16 " << exampleClientDataPtr->data().anInt16 << std::endl; + std::cout << "INT8 " << int(exampleClientDataPtr->data().anInt8) << std::endl; + exampleClientDataPtr->data().aFloat64 += 0.1; + exampleClientDataPtr->data().aFloat32 += 0.1f; + exampleClientDataPtr->data().anInt64++; + exampleClientDataPtr->data().anInt32++; + exampleClientDataPtr->data().anInt16++; + exampleClientDataPtr->data().anInt8++; + // exampleClientDataPtr->writeDataToSim(); + + // This is external data from an external client + LOG_INFO("--- EXAMPLE 2 CLIENT DATA (External - reading)"); + std::cout << exampleClientData2Ptr->str() << std::endl; + // if (!exampleClientData2Ptr->requestUpdateFromSim(timeStamp, tickCounter)) { + // LOG_ERROR("ExampleModule::update() - exampleClientData2Ptr->requestUpdateFromSim() failed"); + // } + std::cout << "INT8 " << int(exampleClientData2Ptr->data().anInt8) << std::endl; + std::cout << "INT16 " << exampleClientData2Ptr->data().anInt16 << std::endl; + std::cout << "INT32 " << exampleClientData2Ptr->data().anInt32 << std::endl; + std::cout << "INT64 " << exampleClientData2Ptr->data().anInt64 << std::endl; + std::cout << "FLOAT32 " << exampleClientData2Ptr->data().aFloat32 << std::endl; + std::cout << "FLOAT64 " << exampleClientData2Ptr->data().aFloat64 << std::endl; +#endif + +#ifdef STREAM_SEND_EXAMPLE + // clang-format off + // ====================== + // Sending large data to the sim + streamSenderMetaDataPtr->data().size = streamSenderDataPtr->getData().size(); + streamSenderMetaDataPtr->data().hash = Fingerprint::fingerPrintFVN(streamSenderDataPtr->getData()); + streamSenderMetaDataPtr->writeDataToSim(); + LOG_DEBUG("--- STREAM SENDER DATA - writing: " + std::to_string(streamSenderMetaDataPtr->data().size) + " bytes + fingerprint: " + std::to_string(streamSenderMetaDataPtr->data().hash)); + streamSenderDataPtr->writeDataToSim(); + // clang-format on +#endif + + } // update throttle + + return true; +} + +bool ExampleModule::postUpdate([[maybe_unused]] sGaugeDrawData* pData) { + return true; +} + +bool ExampleModule::shutdown() { + _isInitialized = false; + std::cout << "ExampleModule::shutdown()" << std::endl; + return true; +} + +#endif diff --git a/fbw-common/src/wasm/cpp-msfs-framework/Example/ExampleModule.h b/fbw-common/src/wasm/cpp-msfs-framework/Example/ExampleModule.h new file mode 100644 index 00000000000..82262eb49cc --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/Example/ExampleModule.h @@ -0,0 +1,201 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifdef EXAMPLES + +#ifndef FLYBYWIRE_EXAMPLEMODULE_H +#define FLYBYWIRE_EXAMPLEMODULE_H + +#include +#include + +#include "DataManager.h" +#include "Module.h" +#include "ClientEvent.h" + +// Uncomment the following to enable/disable the examples +// +//#define LVAR_EXAMPLES +//#define AIRCRAFT_VAR_EXAMPLE +//#define INDEXED_AIRCRAFT_VAR_EXAMPLE +//#define DATA_DEFINITION_EXAMPLE +//#define CLIENT_DATA_AREA_EXAMPLE +//#define BIG_CLIENT_DATA_EXAMPLE +//#define STREAM_RECEIVE_EXAMPLE +//#define STREAM_SEND_EXAMPLE +//#define SIM_EVENT_EXAMPLE +//#define KEY_EVENT_EXAMPLE +//#define CUSTOM_EVENT_EXAMPLE +//#define SYSTEM_EVENT_EXAMPLE +//#define MASK_KEYBOARD_EXAMPLE + +class MsfsHandler; + +/** + * This is an example and test module which is used to demonstrate the usage of the module system + * and to debug the module and DataManager system. + * It should have no effect on the simulation - it should not write to the sim other than while testing + * Should be commented out from the Gauge - remove -DEXAMPLES compiler flag. + */ +class ExampleModule : public Module { + private: + // Notification group(s) for the module + enum NotificationGroup { NOTIFICATION_GROUP_0 }; + + // Input group(s) for the module + enum InputGroup { INPUT_GROUP_0 }; + + // Convenience pointer to the data manager + DataManager* dataManager{}; + +#ifdef LVAR_EXAMPLES + // LVARs + NamedVariablePtr debugLVARPtr{}; + NamedVariablePtr debugLVAR2Ptr{}; + NamedVariablePtr debugLVAR3Ptr{}; + NamedVariablePtr debugLVAR4Ptr{}; +#endif + +#ifdef AIRCRAFT_VAR_EXAMPLE + // Sim-vars + AircraftVariablePtr beaconLightSwitchPtr; + AircraftVariablePtr beaconLightSwitch2Ptr; + AircraftVariablePtr beaconLightSwitch3Ptr; + AircraftVariablePtr strobeLightSwitchPtr; +#endif + +#ifdef INDEXED_AIRCRAFT_VAR_EXAMPLE + AircraftVariablePtr fuelPumpSwitch1Ptr; + AircraftVariablePtr fuelPumpSwitch2Ptr; +#endif + +#ifdef DATA_DEFINITION_EXAMPLE + // DataDefinition variables + struct ExampleData { + [[maybe_unused]] FLOAT64 strobeLightSwitch; + [[maybe_unused]] FLOAT64 wingLightSwitch; + [[maybe_unused]] FLOAT64 zuluTime; // E:ZULU TIME + [[maybe_unused]] FLOAT64 localTime; // E:LOCAL TIME + [[maybe_unused]] FLOAT64 absoluteTime; // E:ABSOLUTE TIME + // if the string is longer than 256 characters, it will overwrite the subsequent variables + // and the sim might crash. It seems to be ok when the string is last in the struct. + // Then the string is truncated to the size but seem to have no other effect (due to the memcpy + // being restricted to the size of the struct). + [[maybe_unused]] char aircraftTTitle[256] = ""; + }; + std::shared_ptr> exampleDataPtr; +#endif + +#ifdef CLIENT_DATA_AREA_EXAMPLE + // ClientDataArea variables + struct ExampleClientData { + [[maybe_unused]] FLOAT64 aFloat64; + [[maybe_unused]] FLOAT32 aFloat32; + [[maybe_unused]] INT64 anInt64; + [[maybe_unused]] INT32 anInt32; + [[maybe_unused]] INT16 anInt16; + [[maybe_unused]] INT8 anInt8; + } __attribute__((packed)); + std::shared_ptr> exampleClientDataPtr; + + // Second ClientDataArea variable identical to the first one for testing + struct ExampleClientData2 { + [[maybe_unused]] INT8 anInt8; + [[maybe_unused]] INT16 anInt16; + [[maybe_unused]] INT32 anInt32; + [[maybe_unused]] INT64 anInt64; + [[maybe_unused]] FLOAT32 aFloat32; + [[maybe_unused]] FLOAT64 aFloat64; + } __attribute__((packed)); + std::shared_ptr> exampleClientData2Ptr; +#endif + +#ifdef BIG_CLIENT_DATA_EXAMPLE + // ClientDataArea variable for testing + struct BigClientData { + std::array dataChunk; + } __attribute__((packed)); + std::shared_ptr> bigClientDataPtr; +#endif + +#if defined(STREAM_RECEIVE_EXAMPLE) || defined(STREAM_SEND_EXAMPLE) + // Meta data struct for StreamingClientDataAreaVariable + struct StreamingDataMetaData { + UINT64 size; + UINT64 hash; + } __attribute__((packed)); +#endif + +#ifdef STREAM_RECEIVE_EXAMPLE + // ClientDataBufferedArea variable for testing receiving + std::shared_ptr> streamReceiverMetaDataPtr; + std::shared_ptr> streamReveicerDataPtr; +#endif + +#ifdef STREAM_SEND_EXAMPLE + // ClientDataBufferedArea variable for testing sending + std::shared_ptr> streamSenderMetaDataPtr; + std::shared_ptr> streamSenderDataPtr; +#endif + +#if defined(SIM_EVENT_EXAMPLE) || defined(AIRCRAFT_VAR_EXAMPLE) || defined(INDEXED_AIRCRAFT_VAR_EXAMPLE) + // Events + ClientEventPtr beaconLightSetEventPtr; + [[maybe_unused]] CallbackID beaconLightSetCallbackID{}; + [[maybe_unused]] CallbackID beaconLightSetCallback2ID{}; + ClientEventPtr lightPotentiometerSetEventPtr; + [[maybe_unused]] CallbackID lightPotentiometerSetCallbackID{}; + ClientEventPtr lightPotentiometerSetEvent2Ptr; + [[maybe_unused]] CallbackID lightPotentiometerSetCallback2ID{}; +#endif + +#ifdef CUSTOM_EVENT_EXAMPLE + // Custom Event + ClientEventPtr clientEventPtr; + [[maybe_unused]] CallbackID clientEventCallbackId{}; +#endif + +#ifdef SYSTEM_EVENT_EXAMPLE + // System Event + ClientEventPtr systemEventPtr; + [[maybe_unused]] CallbackID systemEventCallbackId{}; +#endif + +#ifdef MASK_KEYBOARD_EXAMPLE + ClientEventPtr inputEventPtr; + [[maybe_unused]] CallbackID inputEventCallbackId{}; +#endif + + public: + ExampleModule() = delete; + + /** + * Creates a new ExampleModule instance and takes a reference to the MsfsHandler instance. + * @param msfsHandlerPtr The MsfsHandler instance that is used to communicate with the simulator. + */ + explicit ExampleModule(MsfsHandler& msfsHandlerPtr) : Module(msfsHandlerPtr){}; + + bool initialize() override; + bool preUpdate(sGaugeDrawData* pData) override; + bool update(sGaugeDrawData* pData) override; + bool postUpdate(sGaugeDrawData* pData) override; + bool shutdown() override; + + private: +#ifdef KEY_EVENT_EXAMPLE + // key event test function + void keyEventTest(DWORD param0, DWORD param1, DWORD param2, DWORD param3, DWORD param4) { + std::cout << "ExampleModule::keyEventTest() - param0 = " << param0 << " param1 = " << param1 << " param2 = " << param2 + << " param3 = " << param3 << " param4 = " << param4 << std::endl; + } +#endif + +#ifdef STREAM_RECEIVE_EXAMPLE + std::chrono::time_point> streamReceiverTimerStart; + std::chrono::duration streamReceiverTimerEnd; +#endif +}; + +#endif // FLYBYWIRE_EXAMPLEMODULE_H + +#endif // EXAMPLES diff --git a/fbw-common/src/wasm/cpp-msfs-framework/Example/longtext.h b/fbw-common/src/wasm/cpp-msfs-framework/Example/longtext.h new file mode 100644 index 00000000000..ba1da6fd9cd --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/Example/longtext.h @@ -0,0 +1,638 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FBW_CPP_FRAMEWORK_TEST_LONGTEXT_H +#define FBW_CPP_FRAMEWORK_TEST_LONGTEXT_H + +#include + +[[maybe_unused]] static std::string longText = R"(Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata san)"; + +#endif //FBW_CPP_FRAMEWORK_TEST_LONGTEXT_H + diff --git a/fbw-common/src/wasm/cpp-msfs-framework/GUIDELINES.md b/fbw-common/src/wasm/cpp-msfs-framework/GUIDELINES.md new file mode 100644 index 00000000000..0c9c6eabdcc --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/GUIDELINES.md @@ -0,0 +1,77 @@ +# FlyByWire Simulations - C++ WASM Development Guidelines + +## Purpose +Reading and troubleshooting other developers code is hard in general. Also, +developers new to the project have a hard time to get started with developing +new features and functionalities in the FlyByWire Simulations code base. + +This document aims to provide a set of guidelines for the C++ WASM development in +the FlyByWire Simulations code base which aims to readability, maintainability of +the code base which is also easily accessible for new developers. + +## C++ Code Style +The FlyByWire Cpp code style is based on the clang-format Chromium style +( see`.clang-format`). + +## Commenting +Good commenting is a very important part of writing readable and maintainable +software. This is especially true in a multi-developer project with very different +developer backgrounds and level of experience amongst its developers. +The FlyByWire Open-Source project certainly falls into this category. + +As a general rule code should be commented so that other developers should not +have to read the actual code to understand what classes, methods and members do +and why they exist in the first place. + +In C++ in general, it should be sufficient to read the header file to understand +the purpose and usage of a class, method/function or any member. There should be +little need to read the code in the definition files (cpp) to know what a class +or any of its members does or how it should be used. +This is especially important for any public members of a class. + +If you write comments think of the following questions: + +- What? Why? How to use? +- What does the code do? + - Give a short general description of what the code does +- What implications / side effects does this code have? +- Why does the code do it? + - Explain the purpose of the code + - e.g. why is this method/function needed and where is it used? + - e.g. why is this variable needed and where is it used? +- How to use the code? + - Explain how to use the code + - e.g. how to use the method/function? +- How does the code actually do it conceptually (optional)? + - Although good code speaks for itself it is helpful to explain in very + broad strokes how the code does it esp. if the code has more complex logic + +Modern IDEs like VSCode or JetBrain IDEs provide a lot of features to help +developers by simply hovering over a class, function, variable, etc. to get a +quick look at the documentation. Have this in mind when writing comments. + +It is good practice to comment the code as you write it. Often it is easier to +start by writing parts of the documentation first. E.g. writing the header files +first by declaring members and document these often helps to achieve a better +and faster implementation. + +## Logging + +A very simple logging framework is in place in the logging.h header file. + +This logging framework will be improved and extended over time. + +Some notes on logging: +- challenge is to find a logging framework that does not use exceptions or + threading (which is not supported in MSFS WASM) +- MSFS does not easily allow to attach a debugger for C++ and tends to crash if one does +- MSFS has no permanent logging to analyse CTDs +- Logging should not be excessive but allow to see where the code is at +- Logging should print any warning and errors to the console to make it easier + to find issues later. + +## Unit Testing +Unit testing is a very important part of writing maintainable and readable code. + +A C++ Unit-testing framework has not yet been chosen or added to the project. +This is a TODO. diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataManager.cpp b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataManager.cpp new file mode 100644 index 00000000000..71509bd900a --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataManager.cpp @@ -0,0 +1,345 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#include "DataManager.h" +#include "MsfsHandler.h" +#include "SimconnectExceptionStrings.h" +#include "UpdateMode.h" + +bool DataManager::initialize(HANDLE simConnectHandle) { + hSimConnect = simConnectHandle; + isInitialized = true; + return true; +} + +bool DataManager::preUpdate([[maybe_unused]] sGaugeDrawData* pData) const { + LOG_TRACE("DataManager::preUpdate()"); + + if (!isInitialized) { + LOG_ERROR("DataManager::preUpdate() called but DataManager is not initialized"); + return false; + } + + // get current time stamp and tick counter + const FLOAT64 timeStamp = msfsHandlerPtr->getTimeStamp(); + const UINT64 tickCounter = msfsHandlerPtr->getTickCounter(); + + // get all variables set to automatically read + for (const auto& var : variables) { + if (var.second->isAutoRead()) { + var.second->updateFromSim(timeStamp, tickCounter); + } + } + + // request all data definitions set to automatically read + for (const auto& simObject : simObjects) { + if (simObject.second->isAutoRead()) { + if (!simObject.second->requestUpdateFromSim(timeStamp, tickCounter)) { + LOG_ERROR("DataManager::preUpdate(): requestUpdateFromSim() failed for " + simObject.second->getName()); + } + } + } + + // get requested sim object data + getRequestedData(); + + LOG_TRACE("DataManager::preUpdate() - done"); + return true; +} + +bool DataManager::update([[maybe_unused]] sGaugeDrawData* pData) const { + if (!isInitialized) { + LOG_ERROR("DataManager::update() called but DataManager is not initialized"); + return false; + } + // empty + return true; +} + +bool DataManager::postUpdate([[maybe_unused]] sGaugeDrawData* pData) const { + LOG_TRACE("DataManager::postUpdate()"); + + if (!isInitialized) { + LOG_ERROR("DataManager::postUpdate() called but DataManager is not initialized"); + return false; + } + + // write all variables set to automatically write + for (const auto& var : variables) { + if (var.second->isAutoWrite()) { + var.second->updateToSim(); + } + } + + // write all data definitions set to automatically write + for (const auto& ddv : simObjects) { + if (ddv.second->isAutoWrite()) { + if (!ddv.second->writeDataToSim()) { + LOG_ERROR("DataManager::postUpdate(): updateDataToSim() failed for " + ddv.second->getName()); + } + } + } + + LOG_TRACE("DataManager::postUpdate() - done"); + return true; +} + +bool DataManager::shutdown() { + isInitialized = false; + variables.clear(); + simObjects.clear(); + clientEvents.clear(); + LOG_INFO("DataManager::shutdown()"); + return true; +} + +void DataManager::getRequestedData() const { + DWORD cbData; + SIMCONNECT_RECV* ptrData; + while (SUCCEEDED(SimConnect_GetNextDispatch(hSimConnect, &ptrData, &cbData))) { + processDispatchMessage(ptrData, &cbData); + } +} + +// ================================================================================================= +// Generators / make_ functions +// ================================================================================================= + +NamedVariablePtr DataManager::make_named_var(const std::string& varName, + SimUnit unit, + UpdateMode updateMode, + FLOAT64 maxAgeTime, + UINT64 maxAgeTicks) { + // The uniqueName is used in the map of all named variables and needs to + // contain all the information to identify the variable and the expected value + // uniquely. This is because the same variable can be used in different places + // with different expected values via SimUnits. + const std::string uniqueName{varName + ":" + unit.name}; + + // Check if variable already exists + // Check which update method and frequency to use - if two variables are the same + // then use the update method and frequency of the automated one with faster + // update frequency + const auto pair = variables.find(uniqueName); + if (pair != variables.end()) { + if (!pair->second->isAutoRead() && (updateMode & UpdateMode::AUTO_READ)) { + pair->second->setAutoRead(true); + } + if (pair->second->getMaxAgeTime() > maxAgeTime) { + pair->second->setMaxAgeTime(maxAgeTime); + } + if (pair->second->getMaxAgeTicks() > maxAgeTicks) { + pair->second->setMaxAgeTicks(maxAgeTicks); + } + if (!pair->second->isAutoWrite() & (updateMode & UpdateMode::AUTO_WRITE)) { + pair->second->setAutoWrite(true); + } + LOG_DEBUG("DataManager::make_named_var(): already exists: " + pair->second->str()); + return std::dynamic_pointer_cast(pair->second); + } + + // Create new var and store it in the map + NamedVariablePtr var = NamedVariablePtr(new NamedVariable(varName, unit, updateMode, maxAgeTime, maxAgeTicks)); + variables[uniqueName] = var; + + LOG_DEBUG("DataManager::make_named_var(): created variable " + var->str()); + return var; +} + +AircraftVariablePtr DataManager::make_aircraft_var(const std::string& varName, + int index, + std::string setterEventName, + const ClientEventPtr& setterEvent, + SimUnit unit, + UpdateMode updateMode, + FLOAT64 maxAgeTime, + UINT64 maxAgeTicks) { + // The uniqueName is used in the map of all named variables and needs to + // contain all the information to identify the variable and the expected value + // uniquely. This is because the same variable can be used in different places + // with different expected values via Index and SimUnits. + const std::string uniqueName{varName + ":" + std::to_string(index) + ":" + unit.name}; + + // Check if variable already exists + // Check which update method and frequency to use - if two variables are the same + // use the update method and frequency of the automated one with faster update frequency + const auto pair = variables.find(uniqueName); + if (pair != variables.end()) { + if (!pair->second->isAutoRead() && (updateMode & UpdateMode::AUTO_READ)) { + pair->second->setAutoRead(true); + } + if (pair->second->getMaxAgeTime() > maxAgeTime) { + pair->second->setMaxAgeTime(maxAgeTime); + } + if (pair->second->getMaxAgeTicks() > maxAgeTicks) { + pair->second->setMaxAgeTicks(maxAgeTicks); + } + if (!pair->second->isAutoWrite() & (updateMode & UpdateMode::AUTO_WRITE)) { + pair->second->setAutoWrite(true); + } + LOG_DEBUG("DataManager::make_aircraft_var(): already exists: " + pair->second->str()); + return std::dynamic_pointer_cast(pair->second); + } + + // Create new var and store it in the map + AircraftVariablePtr var = + setterEventName.empty() + ? AircraftVariablePtr(new AircraftVariable(varName, index, setterEvent, unit, updateMode, maxAgeTime, maxAgeTicks)) + : AircraftVariablePtr( + new AircraftVariable(varName, index, std::move(setterEventName), unit, updateMode, maxAgeTime, maxAgeTicks)); + variables[uniqueName] = var; + + LOG_DEBUG("DataManager::make_aircraft_var(): created variable " + var->str()); + return var; +} + +ClientEventPtr DataManager::make_client_event(const std::string& clientEventName, + bool registerToSim, + SIMCONNECT_NOTIFICATION_GROUP_ID notificationGroupId) { + // find existing event instance for this event + for (const auto& event : clientEvents) { + if (event.second->getClientEventName() == clientEventName) { + LOG_DEBUG("DataManager::make_event(): already exists: " + event.second->str()); + return event.second; + } + } + + // create a new event instance + ClientEventPtr clientEvent = ClientEventPtr(new ClientEvent(hSimConnect, clientEventIDGen.getNextId(), clientEventName)); + clientEvents[clientEvent->getClientEventId()] = clientEvent; + if (registerToSim) { + clientEvent->mapToSimEvent(); + } + if (notificationGroupId != SIMCONNECT_UNUSED) { + clientEvent->addClientEventToNotificationGroup(notificationGroupId); + } + LOG_DEBUG("DataManager::make_client_event(): created client event " + clientEvent->str()); + return clientEvent; +} + +// ================================================================================================= +// Key Events +// ================================================================================================= + +KeyEventCallbackID DataManager::addKeyEventCallback(KeyEventID keyEventId, const KeyEventCallbackFunction& callback) { + auto id = keyEventCallbackIDGen.getNextId(); + if (!keyEventCallbacks.contains(keyEventId)) { + keyEventCallbacks.insert({keyEventId, {{id, callback}}}); + } else { + keyEventCallbacks[keyEventId].insert({id, callback}); + } + LOG_DEBUG("Added callback to key event " + std::to_string(keyEventId) + " with ID " + std::to_string(id) + " and " + + std::to_string(keyEventCallbacks[keyEventId].size()) + " callbacks"); + return id; +} + +// FIXME: Double check if this is correct +bool DataManager::removeKeyEventCallback(KeyEventID keyEventId, KeyEventCallbackID callbackId) { + const auto eventPair = keyEventCallbacks.find(keyEventId); + if (eventPair != keyEventCallbacks.end()) { + const auto callbackPair = eventPair->second.find(callbackId); + if (callbackPair != eventPair->second.end()) { + eventPair->second.erase(callbackPair); + LOG_DEBUG("Removed callback from key event " + std::to_string(keyEventId) + " with ID " + std::to_string(callbackId) + " and " + + std::to_string(eventPair->second.size()) + " callbacks left"); + if (eventPair->second.empty()) { + keyEventCallbacks.erase(eventPair); + } + return true; + } + } + LOG_WARN("Failed to remove callback from key event" + std::to_string(keyEventId) + "with ID " + std::to_string(callbackId)); + return false; +} + +bool DataManager::sendKeyEvent(KeyEventID keyEventId, DWORD param0, DWORD param1, DWORD param2, DWORD param3, DWORD param4) { + auto result = trigger_key_event_EX1(keyEventId, param0, param1, param2, param3, param4); + if (result == 0) { + LOG_VERBOSE("Sent key event " + std::to_string(keyEventId) + " with params " + std::to_string(param0) + ", " + std::to_string(param1) + + ", " + std::to_string(param2) + ", " + std::to_string(param3) + ", " + std::to_string(param4)); + return true; + } + LOG_WARN("Failed to send key event " + std::to_string(keyEventId) + " with params " + std::to_string(param0) + ", " + + std::to_string(param1) + ", " + std::to_string(param2) + ", " + std::to_string(param3) + ", " + std::to_string(param4) + + " with error code " + std::to_string(result)); + return false; +} + +void DataManager::processKeyEvent(KeyEventID keyEventId, UINT32 evdata0, UINT32 evdata1, UINT32 evdata2, UINT32 evdata3, UINT32 evdata4) { + const auto eventPair = keyEventCallbacks.find(keyEventId); + if (eventPair != keyEventCallbacks.end()) { + for (const auto& callbackPair : eventPair->second) { + callbackPair.second(evdata0, evdata1, evdata2, evdata3, evdata4); + } + } +} + +// ================================================================================================= +// Private methods +// ================================================================================================= + +void DataManager::processDispatchMessage(SIMCONNECT_RECV* pRecv, [[maybe_unused]] DWORD* cbData) const { + switch (pRecv->dwID) { + case SIMCONNECT_RECV_ID_SIMOBJECT_DATA: // fallthrough + case SIMCONNECT_RECV_ID_CLIENT_DATA: + processSimObjectData(pRecv); + break; + + case SIMCONNECT_RECV_ID_EVENT: + processEvent(reinterpret_cast(pRecv)); + break; + + case SIMCONNECT_RECV_ID_EVENT_EX1: + processEvent(reinterpret_cast(pRecv)); + break; + + case SIMCONNECT_RECV_ID_OPEN: + LOG_INFO("DataManager: SimConnect connection established"); + break; + + case SIMCONNECT_RECV_ID_QUIT: + LOG_INFO("DataManager: Received SimConnect connection quit message"); + break; + + case SIMCONNECT_RECV_ID_EXCEPTION: { + auto* const pException = reinterpret_cast(pRecv); + std::ignore = pException; + LOG_ERROR("DataManager: Exception in SimConnect connection: " + + SimconnectExceptionStrings::getSimConnectExceptionString(static_cast(pException->dwException)) + + " send_id:" + std::to_string(pException->dwSendID) + " index:" + std::to_string(pException->dwIndex)); + break; + } + + default: + LOG_WARN("DataManager: Unknown/Unimplemented SimConnect message received: " + std::to_string(pRecv->dwID)); + break; + } +} + +void DataManager::processSimObjectData(SIMCONNECT_RECV* pData) const { + const auto pSimobjectData = reinterpret_cast(pData); + const auto pair = simObjects.find(pSimobjectData->dwRequestID); + if (pair != simObjects.end()) { + pair->second->processSimData(pData, msfsHandlerPtr->getTimeStamp(), msfsHandlerPtr->getTickCounter()); + return; + } + LOG_ERROR("DataManager::processSimObjectData() - unknown request id: " + std::to_string(pSimobjectData->dwRequestID)); +} + +void DataManager::processEvent(const SIMCONNECT_RECV_EVENT* pRecv) const { + const auto pair = clientEvents.find(pRecv->uEventID); + if (pair != clientEvents.end()) { + pair->second->processEvent(pRecv->dwData); + return; + } + LOG_WARN("DataManager::processEvent() - unknown event id: " + std::to_string(pRecv->uEventID)); +} + +void DataManager::processEvent(const SIMCONNECT_RECV_EVENT_EX1* pRecv) const { + const auto pair = clientEvents.find(pRecv->uEventID); + if (pair != clientEvents.end()) { + pair->second->processEvent(pRecv->dwData0, pRecv->dwData1, pRecv->dwData2, pRecv->dwData3, pRecv->dwData4); + return; + } + LOG_WARN("DataManager::processEvent() - unknown event id: " + std::to_string(pRecv->uEventID)); +} diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataManager.h b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataManager.h new file mode 100644 index 00000000000..64f02cd6824 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataManager.h @@ -0,0 +1,551 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_DATAMANAGER_H +#define FLYBYWIRE_DATAMANAGER_H + +#include +#include +#include +#include + +#include +#include +#include + +#include "IDGenerator.h" +#include "SimUnits.h" +#include "logging.h" +#include "simple_assert.h" + +#include "AircraftVariable.h" +#include "ClientDataAreaVariable.hpp" +#include "DataDefinitionVariable.hpp" +#include "NamedVariable.h" +#include "StreamingClientDataAreaVariable.hpp" +#include "UpdateMode.h" + +// Forward declarations +class MsfsHandler; + +// convenience typedefs +using CacheableVariablePtr = std::shared_ptr; +using NamedVariablePtr = std::shared_ptr; +using AircraftVariablePtr = std::shared_ptr; +using SimObjectBasePtr = std::shared_ptr; +using ClientEventPtr = std::shared_ptr; +template +using DataDefinitionVariablePtr = std::shared_ptr>; +template +using ClientDataAreaVariablePtr = std::shared_ptr>; +template +using StreamingClientDataAreaVariablePtr = std::shared_ptr>; + +// Used to identify a key event +using KeyEventID = ID32; + +// Used for callback registration to allow removal of callbacks +using KeyEventCallbackID = UINT64; + +/** + * Defines a callback function for a key event + * @param number of parameters to use + * @param parameters 0-4 to pass to the callback function + */ +using KeyEventCallbackFunction = std::function; + +/** + * @brief The DataManager class is responsible for managing all variables and events. + * + * It is used to register variables and events and to update them. + * If possible, it de-duplicates variables and events and only creates one instance of each if multiple + * modules use the same variable.

+ * + * It is still possible to use the SDK and Simconnect directly but it is recommended to use the + * DataManager instead as the data manager is able to de-duplicate variables and events and automatically + * update and write back variables from and to the sim. + */ +class DataManager { + private: + // Backreference to the MsfsHandler instance. + const MsfsHandler* msfsHandlerPtr; + + // Handle to the simconnect instance. + HANDLE hSimConnect{}; + + // A map of all registered variables. + // Map over the variable name to quickly find the variable. + // De-duplication of variables happens via this map. So each aspect of a variable needs to be + // part of the unique name - e.g. the index or unit. + std::map variables{}; + + // A map of all registered SimObjects. + // Map over the request id to quickly find the SimObject. + std::map simObjects{}; + + // A map of all registered events. + // Map over the event id to quickly find the event - make creating an event a bit less efficient. + std::map clientEvents{}; + + // Map of callback vectors to be called when a key event is triggered in the sim. + std::map> keyEventCallbacks{}; + + // Flag to indicate if the data manager is initialized. + bool isInitialized = false; + + // Instances of an IDGenerator to generate unique IDs for variables and events. + IDGenerator dataDefIDGen{}; + IDGenerator dataReqIDGen{}; + IDGenerator clientDataIDGen{}; + IDGenerator clientEventIDGen{}; + IDGenerator keyEventCallbackIDGen{}; + + public: + /** + * @brief Creates an instance of the DataManager. + */ + explicit DataManager(MsfsHandler* msfsHdl) : msfsHandlerPtr(msfsHdl) {} + + DataManager() = delete; // no default constructor + DataManager(const DataManager&) = delete; // no copy constructor + DataManager& operator=(const DataManager&) = delete; // no copy assignment + DataManager(DataManager&&) = delete; // no move constructor + DataManager& operator=(DataManager&&) = delete; // no move assignment + + ~DataManager() = default; + + // =============================================================================================== + // Sim Loop Methods + // =============================================================================================== + + /** + * @brief Initializes the data manager.
+ * This method must be called before any other method of the data manager. + * Usually called in the MsfsHandler initialization. + * @param hdl Handle to the simconnect instance + */ + bool initialize(HANDLE simConnectHandle); + + /** + * @brief Called by the MsfsHandler update() method.
+ * Updates all variables marked for automatic reading.
+ * Calls SimConnect_GetNextDispatch to retrieve all messages from the simconnect queue. + * @param pData Pointer to the data structure of gauge pre-draw event + * @return true if successful, false otherwise + */ + bool preUpdate([[maybe_unused]] sGaugeDrawData* pData) const; + + /** + * @brief Called by the MsfsHandler update() method. + * @param pData Pointer to the data structure of gauge pre-draw event + * @return true if successful, false otherwise + */ + bool update(sGaugeDrawData* pData) const; + + /** + * @brief Called by the MsfsHandler update() method.
+ * Writes all variables marked for automatic writing back to the sim. + * @param pData Pointer to the data structure of gauge pre-draw event + * @return true if successful, false otherwise + */ + bool postUpdate(sGaugeDrawData* pData) const; + + /** + * @brief Called by the MsfsHandler shutdown() method.
+ * Can be used for any extra cleanup. + * @return true if successful, false otherwise + */ + bool shutdown(); + + /** + * @brief Ask the sim to send the requested data for this tick.
+ * It will loop until all requested data has been received for this tick. + * Will be called at the end of preUpdate() whenever preUpdate() is called. + * Request data by calling any of the DataDefinitions::request...() methods + * on the data definition variable. + */ + void getRequestedData() const; + + // =============================================================================================== + // Generators / make_ methods + // =============================================================================================== + + /** + * @brief Creates a new named variable (LVAR) and adds it to the list of managed variables.

+ * + * The NamedVariable is a variable which is mapped to a LVAR. It is the simplest variable type and + * can be used to store and retrieve custom numeric data from the sim.

+ * + * OBS: A prefix will be added to the variable name depending on aircraft type. + * E.g. "A32NX_" for the A32NX. Do not add this prefix yourself. + * + * @param varName Name of the variable in the sim + * @param unit optional SimUnit of the variable (default=Number) + * @param updateMode optional DataManager update mode of the variable (default=UpdateMode::NO_AUTO_UPDATE) + * @param maxAgeTime optional maximum age of the variable in seconds (default=0) + * @param maxAgeTicks optional maximum age of the variable in ticks (default=0) + * @return A shared pointer to the variable + * @see SimUnits.h for available units + */ + [[nodiscard]] NamedVariablePtr make_named_var(const std::string& varName, + SimUnit unit = UNITS.Number, + UpdateMode updateMode = UpdateMode::NO_AUTO_UPDATE, + FLOAT64 maxAgeTime = 0.0, + UINT64 maxAgeTicks = 0); + + /** + * @brief Creates a new AircraftVariable and adds it to the list of managed variables.

+ * + * The AircraftVariable is a variable which is mapped to an aircraft simvar. As simvars are + * read-only it is required to use an event to write the variable back to the sim.

+ * + * To create a writable variable, use either the setterEventName or setterEvent parameter. + * If both are set, the setterEvent will be used. + * + * @param varName Name of the variable in the sim + * @param index Index of the indexed variable in the sim (default=0) + * @param setterEventName the name of the event to set the variable with an event or calculator code (default="") + * @param setterEvent an instance of an event variable to set the variable with an event or calculator code (default=nullptr) + * @param unit SimUnit of the variable (default=Number) + * @param updateMode optional DataManager update mode of the variable (default=UpdateMode::NO_AUTO_UPDATE) + * @param maxAgeTime optional maximum age of the variable in seconds (default=0) + * @param maxAgeTicks optional maximum age of the variable in ticks (default=0) + * @return A shared pointer to the variable + * @see SimUnits.h for available units + */ + [[nodiscard]] AircraftVariablePtr make_aircraft_var(const std::string& varName, + int index = 0, + std::string setterEventName = "", + const ClientEventPtr& setterEvent = nullptr, + SimUnit unit = UNITS.Number, + UpdateMode updateMode = UpdateMode::NO_AUTO_UPDATE, + FLOAT64 maxAgeTime = 0.0, + UINT64 maxAgeTicks = 0); + + /** + * @brief Creates a new readonly non-indexed AircraftVariable and adds it to the list of managed variables. + * This is a convenience method for make_aircraft_var() to create a variable that is read-only and + * does not have an index. + * + * @param varName Name of the variable in the sim + * @param unit SimUnit of the variable (default=Number) + * @param updateMode optional DataManager update mode of the variable (default=UpdateMode::NO_AUTO_UPDATE) + * @param maxAgeTime optional maximum age of the variable in seconds (default=0) + * @param maxAgeTicks optional maximum age of the variable in ticks (default=0) + * @return A shared pointer to the variable + * @see SimUnits.h for available units + */ + [[nodiscard]] AircraftVariablePtr make_simple_aircraft_var(const std::string& varName, + SimUnit unit = UNITS.Number, + bool autoReading = false, + FLOAT64 maxAgeTime = 0.0, + UINT64 maxAgeTicks = 0) { + LOG_DEBUG("DataManager::make_simple_aircraft_var(): call make_aircraft_var() to create variable " + var->str()); + return make_aircraft_var(varName, 0, "", nullptr, unit, autoReading ? UpdateMode::AUTO_READ : UpdateMode::NO_AUTO_UPDATE, maxAgeTime, maxAgeTicks); + }; + + /** + * @brief Creates a new data definition variable and adds it to the list of managed variables.

+ * + * The DataDefinitionVariable is a variable which is mapped to a custom data struct and a SimObject + * which can be defined by adding separate data definitions for single sim variables (objects) to + * a container of data definitions (custom SimObject).
+ * It requires a local data struct as a template type which is used to hold the data. + * + * @typename T Type of the data structure to use to store the data + * @param name An arbitrary name for the data definition variable for debugging purposes + * @param dataDefinitions A vector of data definitions for the data definition variable + * @param updateMode optional DataManager update mode of the variable (default=UpdateMode::NO_AUTO_UPDATE) + * @param maxAgeTime optional maximum age of the variable in seconds (default=0) + * @param maxAgeTicks optional maximum age of the variable in ticks (default=0) + * @return A shared pointer to the variable + */ + template + [[nodiscard]] DataDefinitionVariablePtr make_datadefinition_var(const std::string& name, + const std::vector& dataDefinitions, + UpdateMode updateMode = UpdateMode::NO_AUTO_UPDATE, + FLOAT64 maxAgeTime = 0.0, + UINT64 maxAgeTicks = 0) { + DataDefinitionVariablePtr var = DataDefinitionVariablePtr(new DataDefinitionVariable( + hSimConnect, name, dataDefinitions, dataDefIDGen.getNextId(), dataReqIDGen.getNextId(), updateMode, maxAgeTime, maxAgeTicks)); + simObjects.insert({var->getRequestId(), var}); + LOG_DEBUG("DataManager::make_datadefinition_var(): " + name); + return var; + } + + /** + * @brief Creates a new client data area variable and adds it to the list of managed variables.

+ * + * A ClientDataArea allows to define custom SimObjects using memory mapped data to send and + * receive arbitrary data to and from the sim and allows therefore SimConnect clients to exchange + * data with each other.
+ * + * @typename T Type of the data structure to use to store the data + * @param clientDataName String containing the client data area name. This is the name that another + * client will use to specify the data area. The name is not case-sensitive. + * If the name requested is already in use by another addon, a error will be + * printed to the console. + * @param readOnlyForOthers Specify if the data area can only be written to by this client (the + * client creating the data area). By default other clients can write to + * this data area (default=false). + * @param updateMode optional DataManager update mode of the variable (default=UpdateMode::NO_AUTO_UPDATE) + * @param maxAgeTime optional maximum age of the variable in seconds (default=0) + * @param maxAgeTicks optional maximum age of the variable in ticks (default=0) + * @return A shared pointer to the variable + */ + template + [[nodiscard]] ClientDataAreaVariablePtr make_clientdataarea_var(const std::string& clientDataName, + UpdateMode updateMode = UpdateMode::NO_AUTO_UPDATE, + FLOAT64 maxAgeTime = 0.0, + UINT64 maxAgeTicks = 0) { + ClientDataAreaVariablePtr var = ClientDataAreaVariablePtr( + new ClientDataAreaVariable(hSimConnect, clientDataName, clientDataIDGen.getNextId(), dataDefIDGen.getNextId(), + dataReqIDGen.getNextId(), sizeof(T), updateMode, maxAgeTime, maxAgeTicks)); + simObjects.insert({var->getRequestId(), var}); + LOG_DEBUG("DataManager::make_datadefinition_var(): " + clientDataName); + return var; + } + + /** + * @brief Creates a new streaming client data area variable and adds it to the list of managed variables. + * + * A ClientDataBufferedArea is similar to a ClientDataArea but allows to exchange data larger + * than the 8k limit of a ClientDataArea. + * + * This works by letting sender clients split up data into chunks of 8kB and to send these chunks + * serialized one after each other (streaming). These chunks are then read by the receiving client + * and stitched together again. For this the sender usually would use a second variable to announce + * the size of the data to be sent and the receiving client would use this size to know how many + * chunks to expect. For this the reserve(expectedBytes) method of the ClientDataBufferedArea must + * be used before data can be received. + * + * Note: SimConnect seems to support this use case by not overwriting the data area when writing + * multiple chunks quickly in succession. Otherwise this would be a problem if the receiving + * client could not read all chunks in time. + * + * See examples for who this could look like in practice. + * + * @tparam T the type of the data to be sent/received - e.g. char for string data + * @tparam ChunkSize the size in bytes of the chunks to be sent/received (default = 8192 bytes) + * @param clientDataName String containing the client data area name. This is the name that another + * client will use to specify the data area. The name is not case-sensitive. + * If the name requested is already in use by another addon, a error will be + * printed to the console. + * @param updateMode optional DataManager update mode of the variable (default=UpdateMode::NO_AUTO_UPDATE) + * @param maxAgeTime optional maximum age of the variable in seconds (default=0) + * @param maxAgeTicks optional maximum age of the variable in ticks (default=0) + * @return A shared pointer to the variable + */ + template + [[nodiscard]] StreamingClientDataAreaVariablePtr make_streamingclientdataarea_var( + const std::string& clientDataName, + UpdateMode updateMode = UpdateMode::NO_AUTO_UPDATE, + FLOAT64 maxAgeTime = 0.0, + UINT64 maxAgeTicks = 0) { + StreamingClientDataAreaVariablePtr var = + StreamingClientDataAreaVariablePtr(new StreamingClientDataAreaVariable( + hSimConnect, clientDataName, clientDataIDGen.getNextId(), dataDefIDGen.getNextId(), dataReqIDGen.getNextId(), updateMode, + maxAgeTime, maxAgeTicks)); + simObjects.insert({var->getRequestId(), var}); + LOG_DEBUG("DataManager::make_clientdataarea_buffered_var(): " + clientDataName); + return var; + } + + /** + * @brief Creates a new client event with a unique ID and adds it to the list of managed events.
+ * + * The ClientEvent class represents a client event which can be used to:
+ * - create a custom event + * - be mapped to a sim event + * - be mapped to a system event + *

+ * + * The name is used to map the event to a sim event or to create a custom event.
+ * Custom events must have a name that contains a period (e.g. "Custom.Event") to the sim recognizes + * it as a custom event.
+ * To map to sim events the name must be identical to the name of the sim event otherwise there will be + * a SimConnect exception that the event is unknown.
+ * If the ClientEvent is intended to be used as a system event then it must be constructed with the + * registerToSim parameter set to false. This will prevent the event from being registered to the sim. + * The subscribeToSimSystemEvent() method must then be used to subscribe to the system event.

+ * + * Note: for most cases it is easier to use the special + * make_(custom|sim|system)_event methods for the different type of events + * + * @param clientEventName The name of the client event.

+ * If the intention is to map this client event to a sim event the name + * needs to be the same as the sim event name.

+ * If it is a custom event, the name must includes one or more periods + * (e.g. "Custom.Event") so the sim can distinguish it from a sim event. + * Custom events will only be recognized by another client (and not Microsoft + * Flight Simulator) that has been coded to receive such events.

+ * If the intention is to map this client event to a system event, the name + * should also contain a period (e.g. "System.Event"). + * @param registerToSim Flag to indicate if the event should be registered to the sim immediately.
+ * A custom event will be registered and a sim event will be mapped if a + * sim event with this name exists. Otherwise SimConnect will throw an error.
+ * This must be false when it is intended to map the client event + * to a system event with ClientEvent::subscribeToSystemEvent() afterwards. + * @param notificationGroupId Specifies the notification group to which the event is added. If no + * entry is made for this parameter, the event is not added to a + * notification group (default=SIMCONNECT_UNUSED). + * @return A shared pointer to the ClientEvent + */ + [[nodiscard]] ClientEventPtr make_client_event(const std::string& clientEventName, + bool registerToSim, + SIMCONNECT_NOTIFICATION_GROUP_ID notificationGroupId = SIMCONNECT_UNUSED); + + /** + * @brief Creates a new custom client event with a unique ID and adds it to the list of managed events.
+ * Note: Calls make_client_event(clientEventName, true, notificationGroupId) internally after checking if the + * clientEventName contains a period. + * @param clientEventName The name of the custom client event.

+ * The custom client event name must includes one or more periods + * (e.g. "Custom.Event") so the sim can distinguish it from a sim event. + * Custom events will only be recognized by another client (and not Microsoft + * Flight Simulator) that has been coded to receive such events.

+ * @param notificationGroupId Specifies the notification group to which the event is added. If no +* entry is made for this parameter, the event is not added to a +* notification group (default=SIMCONNECT_UNUSED). + * @return A shared pointer to the custom ClientEvent + */ + [[nodiscard]] ClientEventPtr make_custom_event(const std::string& clientEventName, + SIMCONNECT_NOTIFICATION_GROUP_ID notificationGroupId = SIMCONNECT_UNUSED) { + SIMPLE_ASSERT(clientEventName.find('.') != std::string::npos, "Custom event name must contain a period in the name."); + return make_client_event(clientEventName, true, notificationGroupId); + } + + /** + * @brief Creates a new sim client event with a unique ID and adds it to the list of managed events.
+ * Note: Calls make_client_event(clientEventName, true, notificationGroupId) internally after checking if the + * clientEventName does not contain a period. + * @param clientEventName The name of the sim client event.

+ * @param notificationGroupId Specifies the notification group to which the event is added. If no + * entry is made for this parameter, the event is not added to a + * notification group (default=SIMCONNECT_UNUSED). + * @return A shared pointer to the sim ClientEvent + */ + [[nodiscard]] ClientEventPtr make_sim_event(const std::string& clientEventName, + SIMCONNECT_NOTIFICATION_GROUP_ID notificationGroupId = SIMCONNECT_UNUSED) { + SIMPLE_ASSERT(clientEventName.find('.') == std::string::npos, "Sim event name must not contain a period in the name."); + return make_client_event(clientEventName, true, notificationGroupId); + } + + /** + * @brief Creates a new system client event with a unique ID and adds it to the list of managed events.
+ * Note: Calls make_client_event(clientEventName, false, SIMCONNECT_UNUSED) internally after checking if the + * clientEventName contains a period. + * @param clientEventName The name of the system client event.

+ * The system client event name must includes one or more periods (e.g. "System.Event"). + * @param systemEventName The name of the system event to subscribe to. This must be a valid system event name otherwise + * SimConnect will throw an exception error. + * @return A shared pointer to the system ClientEvent + */ + [[nodiscard]] ClientEventPtr make_system_event(const std::string& clientEventName, + const std::string& systemEventName) { + SIMPLE_ASSERT(clientEventName.find('.') != std::string::npos, "Client event name for system events must contain a period in the name."); + auto event = make_client_event(clientEventName, false, SIMCONNECT_UNUSED); + event->subscribeToSimSystemEvent(systemEventName); + SIMPLE_ASSERT(event->isRegisteredToSim(), "Client event not registered to sim. This should not happen."); + return event; + } + + // =============================================================================================== + // Key Event Handling + // =============================================================================================== + + /** + * Adds a callback function to be called when a key event is triggered in the sim.
+ * OBS: The callback will be called even if the sim is paused. + * @param keyEventId The ID of the key event to listen to. + * @param callback + * @return The ID of the callback required for removing a callback. + * @see https://docs.flightsimulator.com/html/Programming_Tools/Event_IDs/Event_IDs.htm + * @see #define KEY_events in gauges.h. + */ + KeyEventCallbackID addKeyEventCallback(KeyEventID keyEventId, const KeyEventCallbackFunction& callback); + + /** + * Removes a callback from a key event. + * @param keyEventId The ID of the key event to remove the callback from. + * @param callbackId The ID receive when adding the callback. + */ + bool removeKeyEventCallback(KeyEventID keyEventId, KeyEventCallbackID callbackId); + + /** + * Sends a key event to the sim. + * Specifies up to 5 additional integer values (param0-4). + * Set this to zero if it is not required. + * @param keyEventId The ID of the key event to send. + * @param param0 + * @param param1 + * @param param2 + * @param param3 + * @param param4 + * @return true if successful, false otherwise + * @see https://docs.flightsimulator.com/html/Programming_Tools/Event_IDs/Event_IDs.htm + * @see #define KEY_events in gauges.h. + */ + bool sendKeyEvent(KeyEventID keyEventId, DWORD param0, DWORD param1, DWORD param2, DWORD param3, DWORD param4); + + /** + * Is called by the MsfsHandler when a key event is received from the sim. + * + * @param event The ID of the key event. + * @param evdata0 + * @param evdata1 + * @param evdata2 + * @param evdata3 + * @param evdata4 + * @param userdata An optional value for use by the gauge developer. This value will be returned + * to the key event handler function, when it has been set when registering the + * key event handler. Currently this is not used by this framework. + */ + void processKeyEvent(KeyEventID event, UINT32 evdata0, UINT32 evdata1, UINT32 evdata2, UINT32 evdata3, UINT32 evdata4); + + // =============================================================================================== + // Getters and setters + // =============================================================================================== + + /** + * @returns the current SimConnect handle + */ + [[nodiscard]] HANDLE getSimConnectHandle() const { return hSimConnect; }; + + private: + // ================================================================================================= + // Private methods + // ================================================================================================= + + /** + * This is called everytime we receive a message from the sim in getRequestedData(). + * @param pRecv + * @param cbData + * + * @see https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Structures_And_Enumerations/SIMCONNECT_RECV.htm + */ + void processDispatchMessage(SIMCONNECT_RECV* pRecv, [[maybe_unused]] DWORD* cbData) const; + + /** + * Callback for SimConnect_GetNextDispatch events in the MsfsHandler used for data definition + * variable batches. + * Must map data request IDs to known IDs of data definition variables and return true if the + * requestId has been handled. + * @param data Pointer to the data structure of gauge pre-draw event + */ + void processSimObjectData(SIMCONNECT_RECV* data) const; + + /** + * Called from processDispatchMessage() for SIMCONNECT_RECV_ID_EVENT messages. + * @param pRecv the SIMCONNECT_RECV_EVENT structure + */ + void processEvent(const SIMCONNECT_RECV_EVENT* pRecv) const; + + /** + * Called from processDispatchMessage() for SIMCONNECT_RECV_EVENT_EX1 messages. + * @param pRecv the SIMCONNECT_RECV_EVENT_EX1 structure + */ + void processEvent(const SIMCONNECT_RECV_EVENT_EX1* pRecv) const; +}; + +#endif // FLYBYWIRE_DATAMANAGER_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/AircraftVariable.cpp b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/AircraftVariable.cpp new file mode 100644 index 00000000000..0b944e244ce --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/AircraftVariable.cpp @@ -0,0 +1,106 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include "AircraftVariable.h" + +FLOAT64 AircraftVariable::rawReadFromSim() const { + if (dataID == -1) { + LOG_ERROR("Aircraft variable " + name + " not found in the Simulator"); + return FLOAT64{}; + } + const FLOAT64 value = aircraft_varget(dataID, unit.id, index); + LOG_TRACE("AircraftVariable::rawReadFromSim() " + this->name + std::to_string(this->index) + " fromSim = " + std::to_string(value) + + " cached = " + std::to_string(cachedValue.value_or(-999999)) + " as " + unit.name); + return value; +} + +// these are overwritten to issue an error message if the variable is read-only +void AircraftVariable::set(FLOAT64 value) { + if (setterEventName.empty() && setterEvent == nullptr) { + LOG_ERROR("AircraftVariable::set() called on [" + name + "] but no setter event name is set"); + return; + } + CacheableVariable::set(value); +} + +// these are overwritten to issue an error message if the variable is read-only +void AircraftVariable::rawWriteToSim() { + if (setterEventName.empty() && setterEvent == nullptr) { + LOG_ERROR("AircraftVariable::setAndWriteToSim() called on [" + name + "] but no setter event name is set"); + return; + } + // use the given event if one is set + if (setterEvent) { + useEventSetter(); + return; + } + // Alternative use calculator code if no event is set + useCalculatorCodeSetter(); +} + +void AircraftVariable::setAutoWrite(bool autoWriting) { + if (setterEventName.empty() && setterEvent == nullptr) { + LOG_ERROR("AircraftVariable::setAutoWrite() called on [" + name + "] but no setter event name is set"); + return; + } + CacheableVariable::setAutoWrite(autoWriting); +} + +// ================================================================================================= +// PRIVATE METHODS +// ================================================================================================= + +void AircraftVariable::useEventSetter() { + const auto data = static_cast(cachedValue.value()); + if (index != 0) { + setterEvent->trigger_ex1(index, data, 0, 0, 0); + return; + } + setterEvent->trigger_ex1(data, 0, 0, 0, 0); +} + +void AircraftVariable::useCalculatorCodeSetter() { + std::string calculator_code{}; + calculator_code += std::to_string(cachedValue.value()); + calculator_code += " "; + if (index != 0) { + calculator_code += std::to_string(index); + calculator_code += " "; + calculator_code += " (>K:2:" + setterEventName + ")"; + } else { + calculator_code += " (>K:" + setterEventName + ")"; + } + if (!execute_calculator_code(calculator_code.c_str(), nullptr, nullptr, nullptr)) { + LOG_ERROR("AircraftVariable::setAndWriteToSim() failed to execute calculator code: [" + calculator_code + "]"); + } +} + +std::string AircraftVariable::str() const { + std::stringstream ss; + ss << "AircraftVariable: [" << name << (index ? ":" + std::to_string(index) : ""); + ss << ", value: " << (cachedValue.has_value() ? std::to_string(cachedValue.value()) : "N/A"); + ss << ", unit: " << unit.name; + ss << ", changed: " << hasChanged(); + ss << ", dirty: " << dirty; + ss << ", timeStamp: " << timeStampSimTime; + ss << ", nextUpdateTimeStamp: " << nextUpdateTimeStamp; + ss << ", tickStamp: " << tickStamp; + ss << ", nextUpdateTickStamp: " << nextUpdateTickStamp; + ss << ", autoRead: " << isAutoRead(); + ss << ", autoWrite: " << isAutoWrite(); + ss << ", maxAgeTime: " << maxAgeTime; + ss << ", maxAgeTicks: " << maxAgeTicks; + ss << "]"; + return ss.str(); +} + +/** + * Overload of the << operator for AircraftVariable. + * @return the string representation of the variable as returned by AircraftVariable::str() + */ +std::ostream& operator<<(std::ostream& os, const AircraftVariable& aircraftVariable) { + os << aircraftVariable.str(); + return os; +} diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/AircraftVariable.h b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/AircraftVariable.h new file mode 100644 index 00000000000..13a2af5afd4 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/AircraftVariable.h @@ -0,0 +1,129 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFTVARIABLE_H +#define FLYBYWIRE_AIRCRAFTVARIABLE_H + +#include +#include + +#include "CacheableVariable.h" +#include "ClientEvent.h" +#include "UpdateMode.h" +#include "logging.h" + +class DataManager; + +/** + * @brief The AircraftVariable class is a specialized class for aircraft cacheable variables (aka simvars or A:VARS). + * + * This class uses events or calculator code to write to a variable as + * AircraftVariables are read-only.

+ * + * If a setter event or event name is provided the variable will be writable.

+ * + * AircraftVariables can't be copy constructed or assigned. They can only be moved. + * Create a new AircraftVariable instance instead. + * + * It is recommended to use the DataManager's make_aircraft_var() to create instances of AircraftVariable + * as it de-duplicates variables and only creates one instance of each name-index-unit combination. + */ +class AircraftVariable : public CacheableVariable { + private: + // The data manager is a friend, so it can access the private constructor. + friend DataManager; + + /** + * Index of an indexed variable. + */ + int index; + + /** + * the event used in the calculator code to write to the variable. + */ + std::string setterEventName; + + /** + * the event used to write to the variable. + */ + std::shared_ptr setterEvent{}; + + /** + * Creates an instance of a writable aircraft variable.

+ * If a setter event or event name is provided the variable will be writable.

+ * It is recommended to use the DataManager's make_aircraft_var() to create instances of AircraftVariable + * as it de-duplicates variables and only creates one instance of each name-index-unit combination. + * @param varName The name of the variable in the sim. + * @param varIndex The index of the variable in the sim. + * @param setterEventName The name of the event used to write to the variable. + * @param unit The unit of the variable as per the sim. See SimUnits.h + * @param updateMode The DataManager update mode of the variable. (default: UpdateMode::NO_AUTO_UPDATE) + * @param maxAgeTime The maximum age of an auto updated the variable in seconds. + * @param maxAgeTicks The maximum age of an auto updated the variable in sim ticks. + * @param setterEventName The calculator code to write to the variable. + */ + explicit AircraftVariable(const std::string& varName, + int varIndex = 0, + std::string setterEventName = "", + SimUnit unit = UNITS.Number, + UpdateMode updateMode = UpdateMode::NO_AUTO_UPDATE, + FLOAT64 maxAgeTime = 0.0, + UINT64 maxAgeTicks = 0) + : CacheableVariable(varName, unit, updateMode, maxAgeTime, maxAgeTicks), + index(varIndex), + setterEventName(std::move(setterEventName)), + setterEvent(nullptr) { + dataID = get_aircraft_var_enum(varName.c_str()); + if (dataID == -1) { // cannot throw an exception in MSFS + LOG_ERROR("Aircraft variable " + varName + " not found in the Simulator"); + } + } + + /** + * Creates an instance of a writable aircraft variable. + * If a setter event object is provided the variable will be writable. + * @param varName The name of the variable in the sim. + * @param varIndex The index of the variable in the sim. + * @param setterEvent The event used to write to the variable. + * @param unit The unit of the variable as per the sim. See SimUnits.h + * @param updateMode The DataManager update mode of the variable . (default: UpdateMode::NO_AUTO_UPDATE) + * @param maxAgeTime The maximum age of an auto updated the variable in seconds. + * @param maxAgeTicks The maximum age of an auto updated the variable in sim ticks. + * @param setterEventName The calculator code to write to the variable. + */ + explicit AircraftVariable(const std::string& varName, + int varIndex = 0, + const std::shared_ptr& setterEvent = nullptr, + SimUnit unit = UNITS.Number, + UpdateMode updateMode = UpdateMode::NO_AUTO_UPDATE, + FLOAT64 maxAgeTime = 0.0, + UINT64 maxAgeTicks = 0) + : CacheableVariable(varName, unit, updateMode, maxAgeTime, maxAgeTicks), index(varIndex), setterEvent(setterEvent) { + dataID = get_aircraft_var_enum(varName.c_str()); + if (dataID == -1) { // cannot throw an exception in MSFS + LOG_ERROR("Aircraft variable " + varName + " not found in the Simulator"); + } + } + + public: + AircraftVariable() = delete; // no default constructor + AircraftVariable(const AircraftVariable&) = delete; // no copy constructor + AircraftVariable& operator=(const AircraftVariable&) = delete; // no copy assignment + AircraftVariable(AircraftVariable&&) = delete; // move constructor + AircraftVariable& operator=(AircraftVariable&&) = delete; // move assignment + + [[nodiscard]] FLOAT64 rawReadFromSim() const override; + void rawWriteToSim() override; + void setAutoWrite(bool autoWriting) override; + void set(FLOAT64 value) override; + + [[nodiscard]] std::string str() const override; + + private: + void useEventSetter(); + void useCalculatorCodeSetter(); + + friend std::ostream& operator<<(std::ostream& os, const AircraftVariable& aircraftVariable); +}; + +#endif // FLYBYWIRE_AIRCRAFTVARIABLE_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/CacheableVariable.cpp b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/CacheableVariable.cpp new file mode 100644 index 00000000000..fb80073d666 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/CacheableVariable.cpp @@ -0,0 +1,72 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include "CacheableVariable.h" +#include "ManagedDataObjectBase.hpp" +#include "logging.h" +#include "math_utils.hpp" + +FLOAT64 CacheableVariable::get() const { + if (cachedValue.has_value()) { + if (dirty) { + LOG_WARN("CacheableVariable::get() called on " + name + " but the value is dirty"); + } + return cachedValue.value(); + } + LOG_ERROR("CacheableVariable::get() called on " + name + " but no value is cached"); + return 0.0; +} + +FLOAT64 CacheableVariable::updateFromSim(FLOAT64 timeStamp, UINT64 tickCounter) { + if (cachedValue.has_value() && !needsUpdateFromSim(timeStamp, tickCounter)) { + setChanged(false); + LOG_TRACE("CacheableVariable::updateFromSim() - from cache " + this->name + " " + str()); + return cachedValue.value(); + } + LOG_TRACE("CacheableVariable::updateFromSim() - read from sim " + this->name + " " + str()); + // update the value from the sim + updateStamps(timeStamp, tickCounter); + return readFromSim(); +} + +FLOAT64 CacheableVariable::readFromSim() { + const FLOAT64 fromSim = rawReadFromSim(); + // compare the value from the sim with the cached value + bool changed = skipChangeCheckFlag || !cachedValue.has_value() || !helper::Math::almostEqual(fromSim, cachedValue.value(), epsilon); + if (changed) + cachedValue = fromSim; + dirty = false; + setChanged(changed); + return cachedValue.value(); +} + +void CacheableVariable::set(FLOAT64 value) { + if (cachedValue.has_value() && helper::Math::almostEqual(value, cachedValue.value(), epsilon)) { + return; + } + cachedValue = value; + dirty = true; + setChanged(true); +} + +void CacheableVariable::updateToSim() { + if (cachedValue.has_value() && dirty) { + writeToSim(); + } +} + +void CacheableVariable::setAndWriteToSim(FLOAT64 value) { + set(value); + writeToSim(); +} + +void CacheableVariable::writeToSim() { + if (cachedValue.has_value()) { + dirty = false; + rawWriteToSim(); + return; + } + LOG_ERROR("CacheableVariable::writeDataToSim() called on [" + name + "] but no value is cached"); +} diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/CacheableVariable.h b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/CacheableVariable.h new file mode 100644 index 00000000000..279c4aa7844 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/CacheableVariable.h @@ -0,0 +1,229 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_CACHEABLEVARIABLE_H +#define FLYBYWIRE_CACHEABLEVARIABLE_H + +#include +#include + +#include + +#include "ManagedDataObjectBase.hpp" +#include "SimUnits.h" +#include "UpdateMode.h" + +/** + * @brief Virtual base class for sim variables like named variables, aircraft variables that support value caching. + * + * Specialized classes must implement the rawReadFromSim and rawWriteToSim methods and can + * overwrite any other method if the default implementation is not sufficient. + */ +class CacheableVariable : public ManagedDataObjectBase { + protected: + /** + * The index of an indexed sim variable + */ + int index = 0; + + /** + * The unit of the variable as per the sim + * @See SimUnits.h + * @See https://docs.flightsimulator.com/html/Programming_Tools/SimVars/Simulation_Variable_Units.htm + */ + SimUnit unit{UNITS.Number}; + + /** + * The value of the variable as it was last read from the sim or updated by the + * set() method. If the variable has not been read from the sim yet and has never been set + * this value will be the FLOAT64 default value. + * Prints an error to std::cerr if the cache is empty. + * (MSFS does not allow exceptions) + */ + std::optional cachedValue{}; + + /** + * Flag to indicate if the variable has been changed by set() since the last read from the sim and + * that it needs to be written back to the sim. + */ + bool dirty = false; + + /** + * The epsilon required to change a variable after a read from the sim. This is used to + * set the changed flag and cache the new value if it is different by >epsilon from the last + * cached value. + */ + FLOAT64 epsilon = std::numeric_limits::epsilon(); + + /** + * The sim's data ID for the variable + */ + ID dataID = -1; + + + /** + * Constructor + * @param name The name of the variable in the sim + * @param unit The unit of the variable as per the sim (see SimUnit.h) + * @param updateMode The update mode for the variable + * @param maxAgeTime The maximum age of the variable in seconds when using requestUpdateFromSim() + * @param maxAgeTicks The maximum age of the variable in ticks when using updateDataToSim() + */ + CacheableVariable(const std::string& varName, const SimUnit& unit, UpdateMode updateMode, FLOAT64 maxAgeTime, UINT64 maxAgeTicks) + : ManagedDataObjectBase(varName, updateMode, maxAgeTime, maxAgeTicks), unit(unit) {} + + public: + CacheableVariable() = delete; // no default constructor + CacheableVariable(const CacheableVariable&) = delete; // no copy constructor + CacheableVariable& operator=(const CacheableVariable&) = delete; // no copy assignment + CacheableVariable(CacheableVariable&&) = delete; // no move constructor + CacheableVariable& operator=(CacheableVariable&&) = delete; // no move assignment + + /** + * Returns the cached value or the default value (FLOAT64{}) if the cache is empty.

+ * + * Prints an error to std::cerr if the cache is empty.

+ * + * If the value has been set by the set() method since the last read from the sim (is dirty) + * but has not been written to the sim yet an error message is printed to std::cerr. + * (MSFS does not allow exceptions) + * @return cached value or default value + */ + [[nodiscard]] FLOAT64 get() const; + + /** + * Reads the value from the sim if the cached value is older than the max age (time and ticks).

+ * + * If a value has already been read during one tick it will therefore not be read again as the + * variable's timeStamp and tickStamp will not be older than the current time and tick.

+ * + * It updates the cache (clears dirty flag), the hasChanged flag, the timeStampSimTime and + * tickStamp.

+ * + * If the value has been set by the set() method since the last read from the sim (is dirty) + * but has not been written to the sim yet an error message is printed to std::cerr. + * (MSFS does not allow exceptions) + * + * @param timeStamp the current sim time (taken from the sim update event) + * @param tickCounter the current tick counter (taken from a custom counter at each update event + * @return the value read from the sim + */ + FLOAT64 updateFromSim(FLOAT64 timeStamp, UINT64 tickCounter); + + /** + * Reads the value from the sim ignoring the cached value. It is recommended to use the + * updateFromSim() method instead as this guarantees that a variable is only read once per tick + * no matter in which module the variable is used.

+ * + * It updates the cache (clears dirty flag) and manages the changed flag and sets it to true + * if the value has changed since last read or sets it to false if the value has not changed.

+ * + * This does not update the timeStampSimTime or tickStamp. + * @return the value read from the sim + */ + FLOAT64 readFromSim(); + + /** + * Raw read call to the sim.

+ * Must be implemented by specialized classes.

+ * This method is called by the readFromSim() method. + * @return the value read from the sim + */ + [[nodiscard]] virtual FLOAT64 rawReadFromSim() const = 0; + + /** + * Sets the cache value and marks the variable as dirty.

+ * Does not write the value to the sim or update the time and tick stamps. + * @param value the value to set + */ + virtual void set(FLOAT64 value); + + /** + * Writes the cached value to the sim if the dirty flag is set.

+ * If the cached value has never been set this method does nothing.

+ * This does not update the timeStampSimTime or tickStamp.

+ */ + void updateToSim(); + + /** + * Writes the current value to the sim.

+ * Clears the dirty flag. + */ + void writeToSim(); + + /** + * Writes the given value to the cache and the sim.

+ * Clears the dirty flag. + * @param value The value to set the variable to. + */ + void setAndWriteToSim(FLOAT64 value); + + /** + * Raw write call to the sim.

+ * Must be implemented by specialized classes.

+ * This method is called by the writeDataToSim()methods. + */ + virtual void rawWriteToSim() = 0; + + // Getters and Setters + + /** + * @return the SimUnit of the variable + * @see SimUnit.h + */ + [[nodiscard]] const SimUnit& getUnit() const { return unit; } + + /** + * @return the index of the variable + */ + [[nodiscard]] int getIndex() const { return index; } + + /** + * @return true if the value has been changed via set() since the last read from the sim. + */ + [[nodiscard]] bool isDirty() const { return dirty; } + + /** + * OBS: This method only does a simple static cast to a bool. Make sure that a cast from + * the double (FLOAT64) is sufficient for your use case. Otherwise use get() and almostEqual(). + * @return the value casted to a boolean + */ + [[nodiscard]] bool getAsBool() const { return static_cast(get()); } + + /** + * Sets the value from a bool and marks the variable as dirty. + * @param b the value to set + */ + void setAsBool(bool b) { set(b ? 1.0 : 0.0); } + + /** + * casted to an INT64 + */ + [[nodiscard]] INT64 getAsInt64() const { return static_cast(get()); } + + /** + * Sets the value from an INT64 and marks the variable as dirty. + * @param i the value to set + */ + void setAsInt64(UINT64 i) { set(static_cast(i)); } + + /** + * @return Epsilon used for comparing floating point values. Variables are considered equal if the + * difference is smaller than this value. + */ + [[nodiscard]] FLOAT64 getEpsilon() const { return epsilon; } + + /** + * Epsilon used for comparing floating point values. Variables are considered equal if the + * difference is smaller or equal than this value. + * @param eps epsilon value to be used + */ + void setEpsilon(FLOAT64 eps) { epsilon = eps; } + + /** + * @return the data id the sim assigned to this variable + */ + [[nodiscard]] ID getDataId() const { return dataID; } +}; + +#endif // FLYBYWIRE_CACHEABLEVARIABLE_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/ClientDataAreaVariable.hpp b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/ClientDataAreaVariable.hpp new file mode 100644 index 00000000000..91ec7d1d7b7 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/ClientDataAreaVariable.hpp @@ -0,0 +1,298 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_CLIENTDATAAREAVARIABLE_H +#define FLYBYWIRE_CLIENTDATAAREAVARIABLE_H + +#include +#include +#include + +#include "SimObjectBase.hpp" +#include "UpdateMode.h" +#include "logging.h" + +#define quote(x) #x + +class DataManager; + +/** + * @brief The ClientDataAreaVariable class is a template class that can be used to create a memory + * area to exchange data with other SimConnect clients.

+ * + * The difference between the ClientDataAreaVariable and the DataDefinitionVariable is that this + * class does not use simulation variables as data but a raw data type from a data struct.

+ * + * The data struct is defined by the template parameter T.

+ * + * The owning client needs to allocate a client data area and register it with the sim by calling + * allocateClientDataArea() (calls SimConnect_CreateClientData()).

+ * + * Usage:
+ * - Create a data struct that will be used to store the data.
+ * - Create a ClientDataAreaVariable object and pass the data struct as template parameter.
+ * - Call allocateClientDataArea() to allocate the client data area in the sim if this variable is + * owning the data and other clients will only use it. Otherwise, if this only uses external data + * then skip this step.

+ * + * The class is based on ManagedDataObjectBase and therefore supports auto reading and writing of + * the data to the sim. It also supports using the SimConnect SIMCONNECT_PERIOD flags to update the + * data by using this method to request the data: requestPeriodicUpdateFromSim(). + * + * Use the DataManager's make_clientdataarea_var() to create instances of ClientDataAreaVariable as + * it ensures unique clientDataId, clientDataDefinitionId and requestId within the SimConnect session. + * + * @tparam T The data struct that will be used to store the data + * @see + * https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_MapClientDataNameToID.htm + * @see + * https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_AddToClientDataDefinition.htm + * @see https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_CreateClientData.htm + */ +template +class ClientDataAreaVariable : public SimObjectBase { + protected: + // The data manager is a friend, so it can access the private constructor. + friend DataManager; + + // The client data area ID + SIMCONNECT_CLIENT_DATA_ID clientDataId; + + // The data struct that will be used to store the data + T dataStruct{}; + + /** + * Creates a new ClientDataAreaVariable object.

+ * + * Use the DataManager's make_clientdataarea_var() to create instances of ClientDataAreaVariable + * as it ensures unique clientDataId, clientDataDefinitionId and requestId within the SimConnect session. + * + * @tparam T The data struct that will be used to store the data + * @param hSimConnect The SimConnect handle + * @param clientDataName String containing the client data area name. This is the name that another + * client will use to specify the data area. The name is not case-sensitive. + * If the name requested is already in use by another addon, an error will + * be returned. + * @param clientDataId A unique ID for the client data area, specified by the client. If the ID + * number is already in use, an error will be returned. + * @param clientDataDefinitionId A unique ID for the client data definition, specified by the client. + * This class only supports one definition per client data area, + * the definition given by the template parameter type + * @param requestId Each request for sim object data requires a unique id so the sim can provide + * the request ID in the response (message SIMCONNECT_RECV_ID_SIMOBJECT_DATA). + * @param dataSize The size of the data struct in bytes. This is used to define the client data area + * in the sim. It defaults to the size of the template parameter type. + * @param updateMode The DataManager update mode of the variable. (default: UpdateMode::NO_AUTO_UPDATE) + * @param maxAgeTime The maximum age of the value in sim time before it is updated from the sim by + * the requestUpdateFromSim() method. + * @param maxAgeTicks The maximum age of the value in ticks before it is updated from the sim by + * the requestUpdateFromSim() method. + */ + ClientDataAreaVariable(HANDLE hSimConnect, + const std::string& clientDataName, + SIMCONNECT_CLIENT_DATA_ID clientDataId, + SIMCONNECT_CLIENT_DATA_DEFINITION_ID clientDataDefinitionId, + SIMCONNECT_DATA_REQUEST_ID requestId, + std::size_t dataSize = sizeof(T), + UpdateMode updateMode = UpdateMode::NO_AUTO_UPDATE, + FLOAT64 maxAgeTime = 0.0, + UINT64 maxAgeTicks = 0) + : SimObjectBase(hSimConnect, + clientDataName, + clientDataDefinitionId, + requestId, + updateMode, + maxAgeTime, + maxAgeTicks), + clientDataId(clientDataId) { + // Map the client data area name to the client data area ID + HRESULT hresult = SimConnect_MapClientDataNameToID(hSimConnect, name.c_str(), clientDataId); + if (hresult != S_OK) { + switch (hresult) { + case SIMCONNECT_EXCEPTION_ALREADY_CREATED: + LOG_ERROR("ClientDataAreaVariable: Client data area name already in use: " + name); + break; + case SIMCONNECT_EXCEPTION_DUPLICATE_ID: + LOG_ERROR("ClientDataAreaVariable: Client data area ID already in use: " + std::to_string(clientDataId)); + break; + default: + LOG_ERROR("ClientDataAreaVariable: Mapping client data area name to ID failed: " + name); + } + } + + // Add the data struct to the client data definition + if (!SUCCEEDED(SimConnect_AddToClientDataDefinition(hSimConnect, this->dataDefId, SIMCONNECT_CLIENTDATAOFFSET_AUTO, dataSize))) { + LOG_ERROR("ClientDataAreaVariable: Adding to client data definition failed: " + name); + } + + LOG_DEBUG("ClientDataAreaVariable: Created client data area variable: " + name + ", clientDataId: " + std::to_string(clientDataId) + + ", clientDataDefinitionId: " + std::to_string(this->dataDefId) + ", requestId: " + std::to_string(this->requestId) + + ", dataSize: " + std::to_string(dataSize)); + } + + public: + ClientDataAreaVariable() = delete; // no default constructor + ClientDataAreaVariable(const ClientDataAreaVariable&) = delete; // no copy constructor + ClientDataAreaVariable& operator=(const ClientDataAreaVariable&) = delete; // no copy assignment + ClientDataAreaVariable(ClientDataAreaVariable&&) = delete; // no move constructor + ClientDataAreaVariable& operator=(ClientDataAreaVariable&&) = delete; // no move assignment + + /** + * Destructor - clears the client data definition but does not free any sim memory. The sim memory + * is freed when the sim is closed. + */ + ~ClientDataAreaVariable() override { + LOG_INFO("ClientDataAreaVariable: Clearing client data definition: " + name); + if (!SUCCEEDED(SimConnect_ClearClientDataDefinition(hSimConnect, dataDefId))) { + LOG_ERROR("ClientDataAreaVariable: Clearing client data definition failed: " + name); + } + } + + /** + * Allocates the client data area in the sim.

+ * This must be done by the client owning the data. Clients only reading the data area do not + * need to allocate it. In fact trying to allocated a data area with the same name twice throws + * a Simconnect exception. + * @param readOnlyForOthers (optional) If true, the data area is read-only for other clients. + * @return true if the allocation was successful, false otherwise + */ + virtual bool allocateClientDataArea(bool readOnlyForOthers = false) { + const DWORD readOnlyFlag = + readOnlyForOthers ? SIMCONNECT_CREATE_CLIENT_DATA_FLAG_READ_ONLY : SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT; + if (!SUCCEEDED(SimConnect_CreateClientData(hSimConnect, clientDataId, sizeof(T), readOnlyFlag))) { + LOG_ERROR("ClientDataAreaVariable: Creating client data area failed: " + name); + return false; + } + return true; + } + + bool requestDataFromSim() const override { + if (!SUCCEEDED(SimConnect_RequestClientData(hSimConnect, clientDataId, requestId, dataDefId, SIMCONNECT_CLIENT_DATA_PERIOD_ONCE, + SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT))) { + LOG_ERROR("ClientDataAreaVariable: Requesting client data failed: " + name); + return false; + } + return true; + } + + /** + * Sends a data request to the sim to have the sim prepare the requested data.

+ * This is an alternative to autoRead which is used by the DataManager to periodically request data from the + * sim.

+ * + * This method can be very efficient as the sim will only send the data when it is required and + * the DataManager will not have to manage the updates.

+ * + * If this is used make sure to have autoRead set to false otherwise this will throw an error.

+ * + * OBS: If a repeating periodic update is requested the data will be updated and callbacks will + * be called even if the sim if paused + * + * @param period the SIMCONNECT_CLIENT_DATA_PERIOD with which the sim should send the data + * @param periodFlags the SIMCONNECT_CLIENT_DATA_REQUEST_FLAG with which the sim should send the data + * @param origin The number of Period events that should elapse before transmission of the data + * begins. The default is zero, which means transmissions will start immediately. + * @param interval The number of Period events that should elapse between transmissions of the + * data. The default is zero, which means the data is transmitted every Period. + * @param limit The number of times the data should be transmitted before this communication is + * ended. The default is zero, which means the data should be transmitted endlessly. + * @return true if the request was successful, false otherwise + * @see https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_RequestClientData.htm + * @see + * https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Structures_And_Enumerations/SIMCONNECT_CLIENT_DATA_PERIOD.htm + */ + bool requestPeriodicDataFromSim( + SIMCONNECT_CLIENT_DATA_PERIOD period, + SIMCONNECT_CLIENT_DATA_REQUEST_FLAG periodFlags = SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT, + DWORD origin = 0, + DWORD interval = 0, + DWORD limit = 0) const { + if (this->isAutoRead() && period >= SIMCONNECT_CLIENT_DATA_PERIOD_ONCE) { + LOG_ERROR("ClientDataAreaVariable: Requested periodic data update from sim is ignored as autoRead is enabled."); + return false; + } + if (!SUCCEEDED( + SimConnect_RequestClientData(hSimConnect, clientDataId, requestId, dataDefId, period, periodFlags, origin, interval, limit))) { + LOG_ERROR("ClientDataAreaVariable: Requesting client data failed: " + name); + return false; + } + return true; + } + + bool requestUpdateFromSim(FLOAT64 timeStamp, UINT64 tickCounter) override { + if (!needsUpdateFromSim(timeStamp, tickCounter)) { + return true; + } + return requestDataFromSim(); + } + + void processSimData(const SIMCONNECT_RECV* pData, FLOAT64 simTime, UINT64 tickCounter) override { + const auto pClientData = reinterpret_cast(pData); + // if not required then skip the rather expensive check for change + if (skipChangeCheckFlag || std::memcmp(&pClientData->dwData, &this->dataStruct, sizeof(T)) != 0) { + std::memcpy(&this->dataStruct, &pClientData->dwData, sizeof(T)); + updateStamps(simTime, tickCounter); + setChanged(true); + return; + } + setChanged(false); + } + + bool writeDataToSim() override { + if (!SUCCEEDED(SimConnect_SetClientData(hSimConnect, clientDataId, dataDefId, SIMCONNECT_CLIENT_DATA_SET_FLAG_DEFAULT, 0, sizeof(T), + &this->dataStruct))) { + LOG_ERROR("ClientDataAreaVariable: Setting data to sim for " + name + " with dataDefId=" + std::to_string(dataDefId) + " failed!"); + return false; + } + return true; + } + + /** + * Returns a modifiable reference to the data container + * @return T& Reference to the data container + */ + T& data() { return dataStruct; } + + /** + * Returns a constant reference to the data container + * @return std::vector& Reference to the data container + */ + const T& data() const { return dataStruct; } + + [[nodiscard]] std::string str() const override { + std::stringstream ss; + ss << "ClientDataAreaVariable[ name=" << getName(); + ss << ", clientDataId=" << clientDataId; + ss << ", dataDefId=" << dataDefId; + ss << ", requestId=" << requestId; + ss << ", structSize=" << sizeof(T); + ss << ", timeStamp: " << timeStampSimTime; + ss << ", nextUpdateTimeStamp: " << nextUpdateTimeStamp; + ss << ", tickStamp: " << tickStamp; + ss << ", nextUpdateTickStamp: " << nextUpdateTickStamp; + ss << ", skipChangeCheckFlag: " << skipChangeCheckFlag; + ss << ", dataChanged: " << hasChanged(); + ss << ", autoRead: " << isAutoRead(); + ss << ", autoWrite: " << isAutoWrite(); + ss << ", maxAgeTime: " << maxAgeTime; + ss << ", maxAgeTicks: " << maxAgeTicks; + ss << ", dataType=" << typeid(dataStruct).name() << "::" << quote(dataStruct); + ss << "]"; + return ss.str(); + } + + friend std::ostream& operator<<(std::ostream& os, const ClientDataAreaVariable& ddv); +}; + +/** + * Overload of the << operator for ClientDataAreaVariable + * @return returns a string representation of the ClientDataAreaVariable as returned by + * ClientDataAreaVariable::str() + */ +template +std::ostream& operator<<(std::ostream& os, const ClientDataAreaVariable& ddv) { + os << ddv.str(); + return os; +} + +#endif // FLYBYWIRE_CLIENTDATAAREAVARIABLE_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/ClientEvent.cpp b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/ClientEvent.cpp new file mode 100644 index 00000000000..c553f19ed80 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/ClientEvent.cpp @@ -0,0 +1,277 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#include +#include + +#include "ClientEvent.h" +#include "logging.h" + +ClientEvent::ClientEvent(HANDLE hSimConnect, SIMCONNECT_CLIENT_EVENT_ID clientEventId, std::string clientEventName) + : hSimConnect(hSimConnect), clientEventId(clientEventId), clientEventName(std::move(clientEventName)) {} + +ClientEvent::~ClientEvent() { + callbacks.clear(); +} + +// ================================================================================================= +// Registration / Mapping of the Client Event to Sim Events +// ================================================================================================= + +void ClientEvent::mapToSimEvent() { + if (!SUCCEEDED(SimConnect_MapClientEventToSimEvent(hSimConnect, clientEventId, clientEventName.c_str()))) { + LOG_ERROR("Failed to map event client ID " + std::to_string(clientEventId) + " to sim event " + clientEventName); + return; + } + LOG_DEBUG("Mapped client event " + clientEventName + " with client ID " + std::to_string(clientEventId)); + registeredToSim = true; +} + +// ================================================================================================= +// Registration / Mapping of the Client Event to Sim System Events +// ================================================================================================= + +void ClientEvent::subscribeToSimSystemEvent(const std::string& eventName) { + if (!SUCCEEDED(SimConnect_SubscribeToSystemEvent(hSimConnect, getClientEventId(), eventName.c_str()))) { + LOG_ERROR("Failed to map client event " + clientEventName + " with client ID " + std::to_string(clientEventId) + + " to sim system event " + eventName); + return; + } + LOG_DEBUG("Mapped client event " + clientEventName + " with client ID " + std::to_string(clientEventId) + " to sim system event " + + eventName); + registeredToSim = true; +} + +void ClientEvent::unsubscribeFromSimSystemEvent() { + if (!SUCCEEDED(SimConnect_UnsubscribeFromSystemEvent(hSimConnect, getClientEventId()))) { + LOG_ERROR("Failed to unsubscribe client event " + clientEventName + " with client ID " + std::to_string(clientEventId) + + " from sim system event: " + clientEventName); + return; + } + LOG_DEBUG("Unsubscribed client event " + clientEventName + " with client ID " + std::to_string(clientEventId) + + " from sim system event: " + clientEventName); + registeredToSim = false; +} + +void ClientEvent::setSystemEventState(SIMCONNECT_STATE state) { + if (!SUCCEEDED(SimConnect_SetSystemEventState(hSimConnect, getClientEventId(), state))) { + LOG_ERROR("Failed to set system event state " + std::to_string(state) + " for client event " + clientEventName + " with client ID " + + std::to_string(clientEventId)); + return; + } + LOG_DEBUG("Set system event state " + std::to_string(state) + " for client event " + clientEventName + " with client ID " + + std::to_string(clientEventId)); +} + +// ================================================================================================= +// Notification group +// ================================================================================================= + +void ClientEvent::addClientEventToNotificationGroup(SIMCONNECT_NOTIFICATION_GROUP_ID notificationGroupId, bool maskEvent) { + if (!SUCCEEDED( + SimConnect_AddClientEventToNotificationGroup(hSimConnect, notificationGroupId, getClientEventId(), maskEvent ? TRUE : FALSE))) { + LOG_ERROR("Failed to add event " + getClientEventName() + " to client notification group " + std::to_string(notificationGroupId)); + return; + } + LOG_DEBUG("Subscribed to event " + getClientEventName() + " with client ID " + std::to_string(getClientEventId())); +} + +void ClientEvent::removeClientEventFromNotificationGroup(SIMCONNECT_NOTIFICATION_GROUP_ID notificationGroupId) { + if (!SUCCEEDED(SimConnect_RemoveClientEvent(hSimConnect, notificationGroupId, getClientEventId()))) { + LOG_ERROR("Failed to remove event " + getClientEventName() + " from the sim"); + return; + } + LOG_DEBUG("Unsubscribed from event " + getClientEventName() + " with client ID " + std::to_string(getClientEventId())); +} + +void ClientEvent::clearNotificationGroup(SIMCONNECT_NOTIFICATION_GROUP_ID notificationGroupId) { + if (!SUCCEEDED(SimConnect_ClearNotificationGroup(hSimConnect, notificationGroupId))) { + LOG_ERROR("Failed to clear notification group " + std::to_string(notificationGroupId)); + } + LOG_DEBUG("Cleared notification group " + std::to_string(notificationGroupId)); +} + +void ClientEvent::setNotificationGroupPriority(SIMCONNECT_NOTIFICATION_GROUP_ID notificationGroupId, + DWORD notificationGroupPriority) const { + if (!SUCCEEDED(SimConnect_SetNotificationGroupPriority(hSimConnect, notificationGroupId, notificationGroupPriority))) { + LOG_ERROR("Failed to set notification group " + std::to_string(notificationGroupId) + " to highest priority"); + } + LOG_DEBUG("Set notification group " + std::to_string(notificationGroupId) + " to priority " + std::to_string(notificationGroupPriority)); +} + +// ================================================================================================= +// Triggering Events +// ================================================================================================= + +void ClientEvent::trigger(DWORD data0) const { + if (!registeredToSim) { + LOG_ERROR("Cannot trigger event " + clientEventName + " as it is not registered to the sim"); + return; + } + if (!SUCCEEDED(SimConnect_TransmitClientEvent(hSimConnect, SIMCONNECT_OBJECT_ID_USER, clientEventId, data0, + SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY))) { + LOG_ERROR("Failed to trigger event " + clientEventName + " with client event " + std::to_string(clientEventId)); + return; + } + LOG_VERBOSE("Triggered event " + clientEventName + " with client event " + std::to_string(clientEventId) + " with data " + + std::to_string(data0)); +} + +void ClientEvent::trigger_ex1(DWORD data0, DWORD data1, DWORD data2, DWORD data3, DWORD data4) const { + if (!registeredToSim) { + LOG_ERROR("Cannot trigger_ex1 event " + clientEventName + " as it is not registered to the sim"); + return; + } + if (!SUCCEEDED(SimConnect_TransmitClientEvent_EX1(hSimConnect, SIMCONNECT_OBJECT_ID_USER, clientEventId, + SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY, data0, + data1, data2, data3, data4))) { + LOG_ERROR("Failed to trigger_ex1 event " + clientEventName + " with client ID " + std::to_string(clientEventId)); + return; + } + LOG_VERBOSE("Triggered_ex1 event " + clientEventName + " with client ID " + std::to_string(clientEventId) + " with data " + + std::to_string(data0) + ", " + std::to_string(data1) + ", " + std::to_string(data2) + ", " + std::to_string(data3) + ", " + + std::to_string(data4)); +} + +// ================================================================================================= +// Callbacks +// ================================================================================================= + +CallbackID ClientEvent::addCallback(const EventCallbackFunction& callback) { + const auto id = callbackIdGen.getNextId(); + callbacks.insert({id, callback}); + LOG_DEBUG("Added callback to event " + clientEventName + " with callback ID " + std::to_string(id)); + return id; +} + +bool ClientEvent::removeCallback(CallbackID callbackId) { + if (auto pair = callbacks.find(callbackId); pair != callbacks.end()) { + callbacks.erase(pair); + LOG_DEBUG("Removed callback from event " + clientEventName + " with callback ID " + std::to_string(callbackId)); + return true; + } + LOG_WARN("Failed to remove callback with ID " + std::to_string(callbackId) + " from event " + str() + " as it does not exist"); + return false; +} + +// ================================================================================================= +// Event Processing +// ================================================================================================= + +void ClientEvent::processEvent(DWORD data) { + for (const auto& [id, callback] : callbacks) { + callback(1, data, 0, 0, 0, 0); + } +} + +void ClientEvent::processEvent(DWORD data0, DWORD data1, DWORD data2, DWORD data3, DWORD data4) { + for (const auto& [id, callback] : callbacks) { + callback(5, data0, data1, data2, data3, data4); + } +} + +// ================================================================================================= +// Input Events +// ================================================================================================= + +void ClientEvent::mapInputDownUpEvent(const std::string& inputDefinition, + SIMCONNECT_INPUT_GROUP_ID inputGroupId, + DWORD downValue, + DWORD upValue, + bool maskable) { + if (!SUCCEEDED(SimConnect_MapInputEventToClientEvent(hSimConnect, inputGroupId, inputDefinition.c_str(), getClientEventId(), downValue, + getClientEventId(), upValue, maskable))) { + LOG_ERROR("Failed to map input down/up event " + inputDefinition + " to client event " + getClientEventName() + " (" + + std::to_string(getClientEventId()) + ")"); + return; + } + LOG_DEBUG("Mapped input event " + inputDefinition + " to client event " + getClientEventName() + " (" + + std::to_string(getClientEventId()) + ")"); +} + +void ClientEvent::mapInputDownEvent(const std::string& inputDefinition, + SIMCONNECT_INPUT_GROUP_ID inputGroupId, + DWORD downValue, + bool maskable) const { + SIMCONNECT_CLIENT_EVENT_ID upId = reinterpret_cast(SIMCONNECT_UNUSED); + if (!SUCCEEDED(SimConnect_MapInputEventToClientEvent(hSimConnect, inputGroupId, inputDefinition.c_str(), getClientEventId(), downValue, + upId, 0, maskable))) { + LOG_ERROR("Failed to map input down event " + inputDefinition + " to client event with" + " downEvent: " + getClientEventName() + " (" + + std::to_string(getClientEventId()) + ")"); + return; + } + LOG_DEBUG("Mapped input event " + inputDefinition + " to client event with" + " downEvent: " + getClientEventName() + " (" + + std::to_string(getClientEventId()) + ")"); +} + +void ClientEvent::mapInputUpEvent(const std::string& inputDefinition, + SIMCONNECT_INPUT_GROUP_ID inputGroupId, + DWORD upValue, + bool maskable) const { + SIMCONNECT_CLIENT_EVENT_ID downId = reinterpret_cast(SIMCONNECT_UNUSED); + if (!SUCCEEDED(SimConnect_MapInputEventToClientEvent(hSimConnect, inputGroupId, inputDefinition.c_str(), downId, 0, getClientEventId(), + upValue, maskable))) { + LOG_ERROR("Failed to map input up event " + inputDefinition + " to client event with" + " upEvent: " + getClientEventName() + " (" + + std::to_string(getClientEventId()) + ")"); + return; + } + LOG_DEBUG("Mapped input event " + inputDefinition + " to client event with" + " upEvent: " + getClientEventName() + " (" + + std::to_string(getClientEventId()) + ")"); +} + +void ClientEvent::unmapInputEvent(const std::string& inputDefinition, SIMCONNECT_INPUT_GROUP_ID inputGroupId) { + if (!SUCCEEDED(SimConnect_RemoveInputEvent(hSimConnect, inputGroupId, inputDefinition.c_str()))) { + LOG_ERROR("Failed to unmap input event " + inputDefinition + " from notification group " + std::to_string(inputGroupId)); + return; + } + registeredToSim = false; + LOG_DEBUG("Unmapped input event " + inputDefinition + " from notification group " + std::to_string(inputGroupId)); +} + +void ClientEvent::clearInputGroup(SIMCONNECT_INPUT_GROUP_ID inputGroupId) const { + if (!SUCCEEDED(SimConnect_ClearInputGroup(hSimConnect, inputGroupId))) { + LOG_ERROR("Failed to unmap all input events from notification group " + std::to_string(inputGroupId)); + return; + } + LOG_DEBUG("Unmapped all input events from notification group " + std::to_string(inputGroupId)); +} + +bool ClientEvent::setInputGroupState(SIMCONNECT_INPUT_GROUP_ID inputGroupId, SIMCONNECT_STATE state) const { + if (!SUCCEEDED(SimConnect_SetInputGroupState(hSimConnect, inputGroupId, state))) { + LOG_ERROR("Failed to set input group state " + std::to_string(state) + " for group " + std::to_string(inputGroupId)); + return false; + } + LOG_DEBUG("Set input group state " + std::to_string(state) + " for group " + std::to_string(inputGroupId)); + return true; +} + +void ClientEvent::setInputGroupPriority(SIMCONNECT_INPUT_GROUP_ID inputGroupId, DWORD inputGroupPriority) const { + if (!SUCCEEDED(SimConnect_SetInputGroupPriority(hSimConnect, inputGroupId, inputGroupPriority))) { + LOG_ERROR("Failed to set input group priority " + std::to_string(inputGroupPriority) + " for group " + std::to_string(inputGroupId)); + return; + } + LOG_DEBUG("Set input group priority " + std::to_string(inputGroupPriority) + " for group " + std::to_string(inputGroupId)); +} + +// ================================================================================================= +// MISC +// ================================================================================================= + +std::string ClientEvent::str() const { + std::stringstream ss; + ss << "Event: [" << clientEventName; + ss << ", ClientID:" << clientEventId; + ss << ", Registered:" << (registeredToSim); + ss << ", Callbacks:" << callbacks.size(); + ss << "]"; + return ss.str(); +} + +/** + * Overload of the << operator for Event. + * @return the a string representation of the Event as returned by Event::str() + */ +std::ostream& operator<<(std::ostream& os, const ClientEvent& clientEvent) { + os << clientEvent.str(); + return os; +} diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/ClientEvent.h b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/ClientEvent.h new file mode 100644 index 00000000000..fc031bc6f48 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/ClientEvent.h @@ -0,0 +1,403 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFT_CLIENTEVENT_H +#define FLYBYWIRE_AIRCRAFT_CLIENTEVENT_H + +#include +#include +#include +#include +#include + +#include +#include + +#include "IDGenerator.h" + +class DataManager; + +// Used for callback registration to allow removal of callbacks +using CallbackID = uint64_t; + +/** + * Defines a callback function for an event + * @param number of parameters to use - TODO: maybe remove this + * @param parameters 0-4 to pass to the callback function + */ +using EventCallbackFunction = std::function; + +/** + * @brief The ClientEvent class represents a client event which can be used to create a custom event, + * or be mapped to a sim event, or be mapped to a system event.

+ * + * A ClientEvent has a unique id and a name. The name can be used to map the event to a sim event or + * to create a custom event.
+ * Custom events must have a name that contains a period (e.g. "Custom.Event") to the sim recognizes + * it as a custom event.
+ * To map to sim events the name must be identical to the name of the sim event otherwise there will be + * a SimConnect exception that the event is unknown.
+ * If the ClientEvent is intended to be used as a system event then it must be constructed with the + * registerToSim parameter set to false. This will prevent the event from being registered to the sim. + * See constructor documentation for more details. + */ +class ClientEvent { + private: + // allow DataManager to access the private constructor + friend DataManager; + + // allow the streaming operator to access the private members + friend std::ostream& operator<<(std::ostream& os, const ClientEvent& event); + + // Simconnect handle + HANDLE hSimConnect; + + // the id of the client event + SIMCONNECT_CLIENT_EVENT_ID clientEventId; + + // the name of the client event - for custom events this should contain a period + const std::string clientEventName; + + // used for client event ID generation + IDGenerator callbackIdGen{}; + + // the callbacks for the event when the sim sends the event + std::map callbacks; + + // flag to indicate if the event is registered to the sim + bool registeredToSim = false; + + private: + /** + * Creates a new ClientEvent instance with the given name and a given id. The id must be unique + * for all client events within a SimConnect session. The name can be used to map the event to a + * sim event or to create a custom event. Custom events must have a name that contains a period + * (e.g. "Custom.Event") so the sim recognizes it as a custom event. Top map to sim events the + * name must be identical to the name of the sim event. + * + * Use mapToSimEvent() to map the client event to a sim event or to register as a custom event. + * + * @param hSimConnect + * @param clientEventName Specifies the Microsoft Flight Simulator event name. Refer to the Event + * IDs document for a list of event names (listed under String Name). If + * the event name includes one or more periods (such as "Custom.Event" in + * the example below) then they are custom events specified by the client, + * and will only be recognized by another client (and not Microsoft Flight + * Simulator) that has been coded to receive such events. No Microsoft + * Flight Simulator events include periods. If no entry is made for this + * parameter, the event is private to the client. + * @see + * https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_MapClientEventToSimEvent.htm + */ + ClientEvent(HANDLE hSimConnect, SIMCONNECT_CLIENT_EVENT_ID clientEventId, std::string clientEventName); + + public: + ClientEvent() = delete; // no default constructor + ClientEvent(const ClientEvent&) = delete; // no copy constructor + ClientEvent& operator=(const ClientEvent&) = delete; // no copy assignment + ClientEvent(ClientEvent&&) = delete; // no move constructor + ClientEvent& operator=(ClientEvent&&) = delete; // no move assignment + ~ClientEvent(); + + // ================================================================================================= + // Registration / Mapping of the Client Event to Sim Events + // ================================================================================================= + + /** + * Maps the client event to a sim event. The name of the client event must be identical to the + * name of the sim event otherwise there will be a SimConnect exception that the event is unknown. + * If the name of the client event contains a period then it will be registered as a custom event. + */ + void mapToSimEvent(); + + // ================================================================================================= + // Registration / Mapping of the Client Event to Sim System Events + // ================================================================================================= + + /** + * Maps the client event to a sim system event. The name of the client event must contain a period + * otherwise there will be a SimConnect exception that the event is unknown. The event must not have + * been mapped to a sim event before. + * @param eventName The name of the system event to map to. See the SimConnect documentation for a list of + * system events. + * @see + * https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_SubscribeToSystemEvent.htm + */ + void subscribeToSimSystemEvent(const std::string& eventName); + + /** + * Unsubscribes from the system event. The event must have been mapped to a sim system event before. + */ + void unsubscribeFromSimSystemEvent(); + + /** + * Use this function to turn system events temporarily on and off, rather than make multiple calls + * to subscribeToSimSystemEvent and unsubscribeFromSimSystemEvent, which is less efficient. + * + * Use the SIMCONNECT_STATE enum to set the state.
+ * SIMCONNECT_STATE_OFF
+ * SIMCONNECT_STATE_ON
+ * + * @param state SIMCONNECT_STATE_ON or SIMCONNECT_STATE_OFF + */ + void setSystemEventState(SIMCONNECT_STATE state); + + // =============================================================================================== + // Notification Group + // =============================================================================================== + + /** + * Adds the ClientEvent to the given notification group of the event.
+ * Prints an error message if the event is already subscribed to a notification group.
+ * + * OBS: If this client event is mapped to a system event this function will fail with a SimConnect exception. + * + * @param notificationGroupId The ID of the notification group to subscribe to. (default: 0) + * @param maskEvent Flag to indicate if the event should be masked. + * From SDK doc: True indicates that the event will be masked by this client and will not be + * transmitted to any more clients, possibly including Microsoft Flight Simulator + * itself (if the priority of the client exceeds that of Flight Simulator). + * False is the default. + */ + void addClientEventToNotificationGroup(SIMCONNECT_NOTIFICATION_GROUP_ID notificationGroupId = 0, bool maskEvent = false); + + /** + * Removes the ClientEvent from the given notification group of the event.
+ * + * @param notificationGroupId The ID of the notification group to subscribe to. (default: 0) + */ + void removeClientEventFromNotificationGroup(SIMCONNECT_NOTIFICATION_GROUP_ID notificationGroupId = 0); + + /** + * Removes the notification group.
+ * This will remove all events from the notification group. + * + * @param notificationGroupId The ID of the notification group to subscribe to. (default: 0) + */ + void clearNotificationGroup(SIMCONNECT_NOTIFICATION_GROUP_ID notificationGroupId = 0); + + /** + * Sets the priority of the notification group.
+ * + * @param notificationGroupId The ID of the notification group to set the priority of. + * @param notificationGroupPriority The priority of the notification group. + * @see https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/SimConnect_API_Reference.htm#simconnect-priorities + */ + void setNotificationGroupPriority(SIMCONNECT_NOTIFICATION_GROUP_ID notificationGroupId, DWORD notificationGroupPriority) const; + + // =============================================================================================== + // Triggering events + // =============================================================================================== + + /** + * Sends the event with the given data to the sim. + * @param data0 Parameter 0 of the event. + * + * Note: This uses the "SimConnect_TransmitClientEvent" function. + */ + [[maybe_unused]] void trigger(DWORD data0) const; + + /** + * Sends the event with the given data to the sim. + * @param data0 Parameter 0 of the event. + * @param data1 Parameter 1 of the event. + * @param data2 Parameter 2 of the event. + * @param data3 Parameter 3 of the event. + * @param data4 Parameter 4 of the event. + * + * Note: This uses the "SimConnect_TransmitClientEvent_EX1" function. + */ + void trigger_ex1(DWORD data0, DWORD data1, DWORD data2, DWORD data3, DWORD data4) const; + + // =============================================================================================== + // Callbacks + // =============================================================================================== + + /** + * Adds a callback function to be called when the event is triggered in the sim.
+ * @param callback + * @return The ID of the callback required for removing a callback. + */ + CallbackID addCallback(const EventCallbackFunction& callback); + + /** + * Removes a callback from the event. + * @param callbackId The ID receive when adding the callback. + */ + bool removeCallback(CallbackID callbackId); + + // =============================================================================================== + // Event processing + // =============================================================================================== + + /** + * Called by the DataManager or another class to process the event. + * @param data event data + */ + void processEvent(DWORD data); + + /** + * Called by the DataManager or another class to process the ex1 event. + * @param data0-4 event data + */ + void processEvent(DWORD data0, DWORD data1, DWORD data2, DWORD data3, DWORD data4); + + // =============================================================================================== + // Input Events + // =============================================================================================== + + /** + * Adds an down input event to this client event and add it to an input group.
+ * The input group can be used to enable/disable the input events with setInputGroupState().

+ * + * @param inputDefinition The input definition to map to the event. See the SDK documentation + * linked below. + * @param groupId The ID of the group to add the event to. This is useful to be able to use + * setInputGroupState() to enable/disable the group without unmapping the events. + * (default = 0) + * @param downValue The value to pass to the event when the input is pressed (default = 0). + * @param upValue The value to pass to the event when the input is released (default = 0). + * @param maskable True if the event should be masked by this client and not be transmitted to any + * more clients, possibly including Microsoft Flight Simulator itself (if the + * priority of the client exceeds that of Flight Simulator) (default = false). + * @see + * https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_MapInputEventToClientEvent.htm + */ + void mapInputDownUpEvent(const std::string& inputDefinition, + SIMCONNECT_INPUT_GROUP_ID inputGroupId = 0, + DWORD downValue = 0, + DWORD upValue = 0, + bool maskable = false); + + /** + * Adds an down input event to this client event and add it to an input group.
+ * The input group can be used to enable/disable the input events with setInputGroupState().

+ * If you want to map down and up events to the same client event, use mapInputDownUpEvent(). + * + * @param inputDefinition The input definition to map to the event. See the SDK documentation + * linked below. + * @param groupId The ID of the group to add the event to. This is useful to be able to use + * setInputGroupState() to enable/disable the group without unmapping the events. + * (default = 0) + * @param downValue The value to pass to the event when the input is pressed (default = 0). + * @param maskable True if the event should be masked by this client and not be transmitted to any + * more clients, possibly including Microsoft Flight Simulator itself (if the + * priority of the client exceeds that of Flight Simulator) (default = false). + * @see + * https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_MapInputEventToClientEvent.htm + */ + void mapInputDownEvent(const std::string& inputDefinition, + SIMCONNECT_INPUT_GROUP_ID inputGroupId = 0, + DWORD downValue = 0, + bool maskable = false) const; + + /** + * Adds an up input event to this client event and add it to an input group.
+ * The input group can be used to enable/disable the input events with setInputGroupState().

+ * If you want to map down and up events to the same client event, use mapInputDownUpEvent(). + * + * @param inputDefinition The input definition to map to the event. See the SDK documentation + * linked below. + * @param groupId The ID of the group to add the event to. This is useful to be able to use + * setInputGroupState() to enable/disable the group without unmapping the events. + * (default = 0) + * @param upValue The value to pass to the event when the input is released (default = 0). + * @param maskable True if the event should be masked by this client and not be transmitted to any + * more clients, possibly including Microsoft Flight Simulator itself (if the + * priority of the client exceeds that of Flight Simulator) (default = false).
+ * @see + * https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_MapInputEventToClientEvent.htm + */ + void mapInputUpEvent(const std::string& inputDefinition, + SIMCONNECT_INPUT_GROUP_ID inputGroupId = 0, + DWORD upValue = 0, + bool maskable = false) const; + + /** + * Removes down and up input events from the event input group and unmaps them from the event. + * + * OBS: Tests have shown that this does not work reliably in MSFS 2020. It seems that the input event + * mapping is not removed from the client event and input group. This is probably a bug in the + * MSFS 2020 SDK. + * TODO: double check + * + * @param groupId The ID of the group to remove the event from. + * @param inputDefinition The input definition to unmap from the event. This must be exactly the same + * as the one used when mapping the event (incl. case). + * + * @see https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_RemoveInputEvent.htm + */ + void unmapInputEvent(const std::string& inputDefinition, SIMCONNECT_INPUT_GROUP_ID inputGroupId); + + /** + * Removes the input group ID and all event ID mapped to it. + * + * @param inputGroupId + * @see https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_ClearInputGroup.htm + * @see https://devsupport.flightsimulator.com/questions/6515/simconnect-clearinputgroup-not-working.html + */ + void clearInputGroup(SIMCONNECT_INPUT_GROUP_ID inputGroupId) const; + + /** + * Allows to enable/disable the input event groups. This method is not specific to the instance + * but uses the instances SimConnect handle. It can be used for any event group on any Event instance.

+ * + * Use the SIMCONNECT_STATE enum to set the state.
+ * SIMCONNECT_STATE_OFF
+ * SIMCONNECT_STATE_ON
+ * + * @param inputGroupId The ID of the group to enable/disable. + * @param state The state to set the group to. + * @return true if the state was successfully set, false otherwise. + */ + bool setInputGroupState(SIMCONNECT_INPUT_GROUP_ID inputGroupId, SIMCONNECT_STATE state) const; + + /** + * Sets the priority of the input group.
+ * + * @param inputGroupId The ID of the input group to set the priority of. + * @param inputGroupPriority The priority of the notification group. + * @see https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/SimConnect_API_Reference.htm#simconnect-priorities + * + */ + void setInputGroupPriority(SIMCONNECT_INPUT_GROUP_ID inputGroupId, DWORD inputGroupPriority) const; + + // =============================================================================================== + // Misc + // =============================================================================================== + + /** + * @return A string representation of the event. + */ + [[nodiscard]] std::string str() const; + + // ==================== Getter and setter ==================================== + + /** + * @return The SimConnect handle of the instance. + */ + [[nodiscard]] HANDLE getHSimConnect() const { return hSimConnect; } + + /** + * @return The ID of the client event. + */ + [[nodiscard]] SIMCONNECT_CLIENT_EVENT_ID getClientEventId() const { return clientEventId; } + + /** + * @return The name of the client event. + */ + [[nodiscard]] const std::string& getClientEventName() const { return clientEventName; } + + /** + * @return True if the client event is registered to the sim either as a custom event, or + * mapped to a sim event or mapped to a system event. + */ + [[nodiscard]] bool isRegisteredToSim() const { return registeredToSim; } + + /** + * @return True if the client event has callbacks registered to it. + */ + [[nodiscard]] bool hasCallbacks() const { return !callbacks.empty(); } +}; + +#endif // FLYBYWIRE_AIRCRAFT_CLIENTEVENT_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/DataDefinitionVariable.hpp b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/DataDefinitionVariable.hpp new file mode 100644 index 00000000000..cd00d756cd0 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/DataDefinitionVariable.hpp @@ -0,0 +1,288 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_DATADEFINITIONVARIABLE_H +#define FLYBYWIRE_DATADEFINITIONVARIABLE_H + +#include + +#include "SimObjectBase.hpp" +#include "UpdateMode.h" +#include "logging.h" + +#define quote(x) #x + +class DataManager; + +/** + * @brief The DataDefinition struct is used to register a data definition with the sim.

+ * + * @field name: the name of the variable + * @field index: the index of the variable (default: 0) + * @field unit: the unit of the variable (default: Number). + * This should be set to UNITS.None if a dataType is used + * @field dataType: the data type of the variable (default: SIMCONNECT_DATATYPE_FLOAT64).
+ * Set unit to UNITS.None if a dataType is used.
+ * OBS: If you use a string type, the string must be null terminated and the size + * of the string must be less than the data type's length. If the string is longer + * the sim will crash. + * @field epsilon: the epsilon value to be used to compare the data value with the current value + * when using the SIMCONNECT_DATA_REQUEST_FLAG_CHANGED flag (default: 0) + */ +struct DataDefinition { + std::string name; + int index{0}; + SimUnit unit{UNITS.Number}; + SIMCONNECT_DATATYPE dataType{SIMCONNECT_DATATYPE_FLOAT64}; + float epsilon{0.0}; +}; + +/** + * @brief A DataDefinitionVariable represents a sim variable defined by one ore more SimConnect + * data definitions of SimVars (SimConnect_AddToDataDefinition). + * + * DataDefinitionVariables are used to define sim data objects that can be used to retrieve and + * write simulation variables from and to the sim.

+ * + * The difference between DataDefinitionVariable and ClientDataAreaVariable is that this class is used + * to read and write simulation variables that are defined by one or more SimConnect data definitions + * whereas the ClientDataAreaVariable is to read and write arbitrary data structs to/from the sim.

+ * + * A local data struct is used to store the sim's data when received from the sim. This data struct + * needs to be passed a template parameter to this class and an instance of the struct is created + * to store the actual data.

+ * + * Usage:
+ * - Create a data struct that will be used to store the data from the sim.
+ * - Create a vector of DataDefinitions that will be used to define the sim data mapped to the data + * struct.
+ * - Create a DataDefinitionVariable instance and pass the data struct type and the vector of + * DataDefinitions as template parameters.

+ * + * The class is based on ManagedDataObjectBase and therefore supports auto reading and writing of + * the data to the sim. It also supports using the SimConnect SIMCONNECT_PERIOD flags to update the + * data by using this method to request the data: requestPeriodicUpdateFromSim().

+ * + * It is recommended to use the DataManager's make_datadefinition_var() to create instances of + * DataDefinitionVariable as it ensures unique ids for the data definition and request. + * + * @tparam T The data struct type that will be used to store the data from the sim. + * @see requestPeriodicUpdateFromSim() + * @see DataDefinition + * @see SimObjectBase + * @see https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_AddToDataDefinition.htm + */ +template +class DataDefinitionVariable : public SimObjectBase { + private: + // The data manager is a friend, so it can access the private constructor. + friend DataManager; + + // List of data definitions to add to the sim object data + // Used for "SimConnect_AddToDataDefinition" + std::vector dataDefinitions; + + // The data struct that will be used to store the data from the sim. + T dataStruct{}; + + /** + * Creates a new instance of a DataDefinitionVariable.

+ * + * It is recommended to use the DataManager's make_datadefinition_var() to create instances of + * DataDefinitionVariable as it ensures unique ids for the data definition and request within the + * SimConnect session. + * + * @typename T: the data struct type that will be used to store the data + * @param hSimConnect Handle to the SimConnect object. + * @param name Arbitrary name for the data definition variable for debugging purposes + * @param dataDefinitions List of data definitions to add to the sim object data + * @param dataDefinitionId Each data definition variable has its own unique id so the sim can map registered data sim objects to data + * definitions. + * @param requestId Each request for sim object data requires a unique id so the sim can provide the request ID in the response (message + * SIMCONNECT_RECV_ID_SIMOBJECT_DATA). + * @param updateMode The DataManager update mode of the variable. (default: UpdateMode::NO_AUTO_UPDATE) + * @param maxAgeTime The maximum age of the value in sim time before it is updated from the sim by the requestUpdateFromSim() method. + * @param maxAgeTicks The maximum age of the value in ticks before it is updated from the sim by the requestUpdateFromSim() method. + */ + DataDefinitionVariable(HANDLE hSimConnect, + const std::string& varName, + const std::vector& dataDefinitions, + SIMCONNECT_DATA_DEFINITION_ID dataDefId, + SIMCONNECT_DATA_REQUEST_ID requestId, + UpdateMode updateMode = UpdateMode::NO_AUTO_UPDATE, + FLOAT64 maxAgeTime = 0.0, + UINT64 maxAgeTicks = 0) + : SimObjectBase(hSimConnect, varName, dataDefId, requestId, updateMode, maxAgeTime, maxAgeTicks), + dataDefinitions(dataDefinitions), + dataStruct{} { + for (const auto& definition : dataDefinitions) { + std::string fullVarName = definition.name; + if (definition.index != 0) + fullVarName += ":" + std::to_string(definition.index); + if (!SUCCEEDED(SimConnect_AddToDataDefinition(hSimConnect, dataDefId, fullVarName.c_str(), definition.unit.name, definition.dataType, + definition.epsilon))) { + LOG_ERROR("Failed to add " + definition.name + " to data definition."); + } + } + } + + public: + DataDefinitionVariable() = delete; // no default constructor + DataDefinitionVariable(const DataDefinitionVariable&) = delete; // no copy constructor + DataDefinitionVariable& operator=(const DataDefinitionVariable&) = delete; // no copy assignment + DataDefinitionVariable(DataDefinitionVariable&&) = delete; // no move constructor + DataDefinitionVariable& operator=(DataDefinitionVariable&&) = delete; // no move assignment + + /** + * Destructor - clears the client data definition but does not free any sim memory. The sim memory + * is freed when the sim is closed. + */ + ~DataDefinitionVariable() override { + // Clear the client data definition + LOG_INFO("DataDefinitionVariable: Clearing client data definition: " + name); + if (!SUCCEEDED(SimConnect_ClearClientDataDefinition(hSimConnect, dataDefId))) { + LOG_ERROR("DataDefinitionVariable: Clearing client data definition failed: " + name); + } + }; + + bool requestDataFromSim() const override { + if (!SUCCEEDED( + SimConnect_RequestDataOnSimObject(hSimConnect, requestId, dataDefId, SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_ONCE))) { + LOG_ERROR("DataDefinitionVariable: Failed to request data from sim: " + name); + return false; + } + return true; + }; + + /** + * Sends a data request to the sim to have the sim prepare the requested data.

+ * This is an alternative to autoRead which is used by the DataManager to request data from the + * sim.

+ * This method can be very efficient as the sim will only send the data when it is required and + * the DataManager will not have to manage the updates.

+ * If this is used make sure to have autoRead set to false otherwise this will throw an error.

+ * OBS: If a repeating periodic update is requested the data will be updated and callbacks will + * be called even if the sim if paused + * + * @param period the SIMCONNECT_PERIOD with which the sim should send the data + * @param periodFlags the SIMCONNECT_DATA_REQUEST_FLAG with which the sim should send the data (default + * SIMCONNECT_DATA_REQUEST_FLAG_CHANGED) + * @param origin The number of Period events that should elapse before transmission of the data + * begins. The default is zero, which means transmissions will start immediately. (default 0) + * @param interval The number of Period events that should elapse between transmissions of the + * data. The default is zero, which means the data is transmitted every Period. (default 0) + * @param limit The number of times the data should be transmitted before this communication is + * ended. The default is zero, which means the data should be transmitted endlessly. (default 0) + * @return true if the request was successful, false otherwise + * @see + * https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_RequestDataOnSimObject.htm + * @see https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Structures_And_Enumerations/SIMCONNECT_PERIOD.htm + */ + bool requestPeriodicDataFromSim(SIMCONNECT_PERIOD period, + DWORD periodFlags = SIMCONNECT_DATA_REQUEST_FLAG_DEFAULT, + DWORD origin = 0, + DWORD interval = 0, + DWORD limit = 0) const { + if (isAutoRead() && period >= SIMCONNECT_PERIOD_ONCE) { + LOG_ERROR("DataDefinitionVariable: Requested periodic data update from sim is ignored as autoRead is enabled."); + return false; + } + if (!SUCCEEDED(SimConnect_RequestDataOnSimObject(hSimConnect, requestId, dataDefId, SIMCONNECT_OBJECT_ID_USER, period, periodFlags, + origin, interval, limit))) { + LOG_ERROR("DataDefinitionVariable: Failed to request data from sim: " + name); + return false; + } + return true; + }; + + bool requestUpdateFromSim(FLOAT64 timeStamp, UINT64 tickCounter) override { + if (!needsUpdateFromSim(timeStamp, tickCounter)) { + LOG_TRACE( + "DataDefinitionVariable::requestUpdateFromSim: Not requesting update from sim as " + "value is not older than max age."); + return true; + } + LOG_TRACE("DataDefinitionVariable::requestUpdateFromSim: Requesting update from sim."); + return requestDataFromSim(); + }; + + void processSimData(const SIMCONNECT_RECV* pData, FLOAT64 simTime, UINT64 tickCounter) override { + LOG_TRACE("DataDefinitionVariable: Received client data: " + name); + const auto pSimobjectData = reinterpret_cast(pData); + + // if not required then skip the rather expensive check for change + if (skipChangeCheckFlag || std::memcmp(&pSimobjectData->dwData, &this->dataStruct, sizeof(T)) != 0) { + LOG_TRACE("DataDefinitionVariable: Data has changed: " + name); + std::memcpy(&this->dataStruct, &pSimobjectData->dwData, sizeof(T)); + updateStamps(simTime, tickCounter); + setChanged(true); + return; + } + setChanged(false); + LOG_TRACE("DataDefinitionVariable: Data has not changed: " + name); + }; + + bool writeDataToSim() override { + if (!SUCCEEDED(SimConnect_SetDataOnSimObject(hSimConnect, dataDefId, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(T), &dataStruct))) { + LOG_ERROR("Setting data to sim for " + name + " with dataDefId=" + std::to_string(dataDefId) + " failed!"); + return false; + } + return true; + }; + + // Getters and setters + + /** + * @return a constant reference to the data definition vector + */ + [[nodiscard]] const std::vector& getDataDefinitions() const { return dataDefinitions; } + + /** + * Returns a modifiable reference to the data container + * @return T& Reference to the data container + */ + [[nodiscard]] T& data() { return dataStruct; } + + /** + * Returns a constant reference to the data container + * @return std::vector& Reference to the data container + */ + [[nodiscard]] const T& data() const { return dataStruct; } + + [[nodiscard]] std::string str() const override { + std::stringstream ss; + ss << "DataDefinition[ name=" << getName(); + ss << ", dataDefId=" << dataDefId; + ss << ", requestId=" << requestId; + ss << " definitions=" << dataDefinitions.size(); + ss << ", structSize=" << sizeof(T); + ss << ", timeStamp: " << timeStampSimTime; + ss << ", nextUpdateTimeStamp: " << nextUpdateTimeStamp; + ss << ", tickStamp: " << tickStamp; + ss << ", nextUpdateTickStamp: " << nextUpdateTickStamp; + ss << ", skipChangeCheckFlag: " << skipChangeCheckFlag; + ss << ", dataChanged: " << hasChanged(); + ss << ", autoRead: " << isAutoRead(); + ss << ", autoWrite: " << isAutoWrite(); + ss << ", maxAgeTime: " << maxAgeTime; + ss << ", maxAgeTicks: " << maxAgeTicks; + ss << ", dataType=" << typeid(dataStruct).name() << "::" << quote(dataStruct); + ss << "]"; + return ss.str(); + } + + friend std::ostream& operator<<(std::ostream& os, const DataDefinitionVariable& ddv); +}; + +/** + * Overload of the << operator for DataDefinitionVariable + * @return returns a string representation of the DataDefinitionVariable as returned by + * DataDefinitionVariable::str() + */ +template +std::ostream& operator<<(std::ostream& os, const DataDefinitionVariable& ddv) { + os << ddv.str(); + return os; +} + +#endif // FLYBYWIRE_DATADEFINITIONVARIABLE_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/DataObjectBase.hpp b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/DataObjectBase.hpp new file mode 100644 index 00000000000..36508566c6c --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/DataObjectBase.hpp @@ -0,0 +1,39 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_A32NX_DATAOBJECTBASE_H +#define FLYBYWIRE_A32NX_DATAOBJECTBASE_H + +#include + +/** + * @brief The DataObjectBase class is the base class for all data objects. + */ +class DataObjectBase { + protected: + /** + * The name of the variable in the sim + */ + const std::string name; + + explicit DataObjectBase(std::string varName) : name(std::move(varName)) {} + + public: + DataObjectBase() = delete; // no default constructor + DataObjectBase(const DataObjectBase&) = delete; // no copy constructor + DataObjectBase& operator=(const DataObjectBase&) = delete; // no copy assignment + DataObjectBase(DataObjectBase&&) = delete; // no move constructor + DataObjectBase& operator=(DataObjectBase&&) = delete; // no move assignment + + /** + * @return the name of the variable + */ + [[nodiscard]] inline const std::string& getName() const { return name; } + + /** + * @return as string representation of the data object for logging and debugging purposes + */ + [[nodiscard]] virtual std::string str() const { return name; } +}; + +#endif // FLYBYWIRE_A32NX_DATAOBJECTBASE_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/ManagedDataObjectBase.hpp b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/ManagedDataObjectBase.hpp new file mode 100644 index 00000000000..b711c3328c7 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/ManagedDataObjectBase.hpp @@ -0,0 +1,264 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_A32NX_MANAGEDDATAOBJECTBASE_H +#define FLYBYWIRE_A32NX_MANAGEDDATAOBJECTBASE_H + +#include +#include +#include + +#include + +#include "DataObjectBase.hpp" +#include "IDGenerator.h" +#include "UpdateMode.h" +#include "logging.h" + +// Used for callback registration to allow removal of callbacks +using CallbackID = uint64_t; + +// Callback function type +using CallbackFunction = std::function; + +/** + * @brief The ManagedDataObjectBase class is the base class for all data objects and provides auto + * read and write functionality. + * + * Adds the ability to autoRead, autoWrite variables considering max age based on + * time- and tick-stamps. + * Also adds a hasChanged flag and the ability to register callbacks for when + * the variable changes. + */ +class ManagedDataObjectBase : public DataObjectBase { + private: + /** + * Used to generate unique IDs for callbacks. + */ + IDGenerator callbackIdGen{}; + + /** + * Map of callbacks to be called when the event is triggered in the sim. + */ + std::map callbacks{}; + + // Flag to indicate if the variable has changed compared to the last read/write from the sim. + // Private because it should only be set by the setChanged() method so callbacks from + // listeners can be triggered. + bool changedFlag = false; + + protected: + /** + * Flag to indicate if the check for data changes should be skipped to save performance when the + * check is not required. + */ + bool skipChangeCheckFlag = false; + + /** + * @brief the update mode for auto read and write + */ + UpdateMode updateMode = UpdateMode::NO_AUTO_UPDATE; + + /** + * The time stamp of the last update from the sim + */ + FLOAT64 timeStampSimTime = 0.0; + + /** + * The time the next update from the sim should be done + */ + FLOAT64 nextUpdateTimeStamp = 0.0; + + /** + * The maximum age of the value in sim time before it is updated from the sim by the + * requestUpdateFromSim() method. + */ + FLOAT64 maxAgeTime = 0.0; + + /** + * The tick counter of the last update from the sim + */ + UINT64 tickStamp = 0; + + /** + * The tick counter the next update from the sim should be done + */ + UINT64 nextUpdateTickStamp = 0; + + /** + * The maximum age of the value in ticks before it is updated from the sim by the + */ + UINT64 maxAgeTicks = 0; + + /** + * Create base super class for all managed data objects. + * @param varName the name of the variable in the sim + * @param updateMode The DataManager update mode of the variable. (default: UpdateMode::NO_AUTO_UPDATE) + * @param maxAgeTime the maximum age of the value in sim time before it is updated from the sim + * @param maxAgeTicks the maximum age of the value in ticks before it is updated from the sim + */ + ManagedDataObjectBase(const std::string& varName, UpdateMode updateMode, FLOAT64 maxAgeTime, UINT64 maxAgeTicks) + : DataObjectBase(varName), updateMode(updateMode), maxAgeTime(maxAgeTime), maxAgeTicks(maxAgeTicks) {} + + /** + * Sets the changedFlag flag to the given value and triggers the registered callbacks + * if the value has changedFlag. + * @param changed the new value for the changedFlag flag + */ + void setChanged(bool changed) { + changedFlag = changed; + if (changedFlag) { + for (const auto& [_, callback] : callbacks) { + callback(); + } + } + } + + public: + ManagedDataObjectBase() = delete; // no default constructor + ManagedDataObjectBase(const ManagedDataObjectBase&) = delete; // no copy constructor + ManagedDataObjectBase& operator=(const ManagedDataObjectBase&) = delete; // no copy assignment + ManagedDataObjectBase(ManagedDataObjectBase&&) = delete; // no move constructor + ManagedDataObjectBase& operator=(ManagedDataObjectBase&&) = delete; // no move assignment + virtual ~ManagedDataObjectBase() = default; // so derived classes can be destroyed with base class pointer + + /** + * Adds a callback function to be called when the data object's data changed.

+ * @param callback + * @return The ID of the callback required for removing a callback. + */ + CallbackID addCallback(const CallbackFunction& callback) { + const auto id = callbackIdGen.getNextId(); + callbacks.insert({id, callback}); + LOG_DEBUG("Added callback to data object " + name + " with callback ID " + std::to_string(id)); + return id; + } + + /** + * Removes a callback from the data object. + * @param callbackId The ID receive when adding the callback. + */ + bool removeCallback(CallbackID callbackId) { + if (auto pair = callbacks.find(callbackId); pair != callbacks.end()) { + callbacks.erase(pair); + LOG_DEBUG("Removed callback from data object " + name + " with callback ID " + std::to_string(callbackId)); + return true; + } + LOG_WARN("Failed to remove callback with ID " + std::to_string(callbackId) + " from data object " + str()); + return false; + } + + /** + * Checks if the variable needs to be updated from the sim based on the given time stamp + * and tickCounter.

+ * Returns true if the value is older than the max age for sim time and ticks.

+ * This includes to make sure is only read from the sim once per tick when max age is 0. + * @param timeStamp - current sim time + * @param tickCounter - current tick counter + * @return true if the variable needs to be updated from the sim, false otherwise + */ + [[nodiscard]] bool needsUpdateFromSim(FLOAT64 timeStamp, UINT64 tickCounter) const { + return (nextUpdateTimeStamp < timeStamp && nextUpdateTickStamp < tickCounter); + } + + /** + * Updates the time stamps and stamps for the next update from the sim. + * @param timeStamp - current sim time + * @param tickCounter - current tick counter + */ + void updateStamps(FLOAT64 timeStamp, UINT64 tickCounter) { + timeStampSimTime = timeStamp; + nextUpdateTimeStamp = timeStamp + maxAgeTime; + tickStamp = tickCounter; + nextUpdateTickStamp = tickCounter + maxAgeTicks; + } + + /** + * @return true if the value has changed since the last read from the sim. + */ + [[nodiscard]] bool hasChanged() const { return changedFlag; } + + /** + * When this is true every read from the sim will set the changed flag to true + * no matter if the value has changed or not. + * When this is false the changed flag will be set to true only if the value + * has actually changed. + * @return true if the check for data changes should be skipped to save performance when the check is not required, false otherwise + */ + [[nodiscard]] bool getSkipChangeCheck() const { return skipChangeCheckFlag; } + + /** + * Sets the flag to skip the check for data changes to save performance when + * the check is not required. When this is set every read from the sim will + * set the changed flag to true no matter if the value has changed or not. + * @param changeCheck + */ + void setSkipChangeCheck(bool skipChangeCheck) { skipChangeCheckFlag = skipChangeCheck; } + + /** + * @brief Sets the auto read update mode for the variable. + * @param autoRead if true the variable will be updated from the sim every time the DataManager::preUpdate() method is called, false otherwise + */ + virtual void setAutoRead(bool autoRead) { + updateMode = static_cast(autoRead ? updateMode | UpdateMode::AUTO_READ : updateMode & ~UpdateMode::AUTO_READ); + } + + /** + * @return true if the variable should be automatically updated from the sim n the DataManagers + * postUpdate() method. + */ + [[nodiscard]] bool isAutoRead() const { return updateMode & UpdateMode::AUTO_READ; } + + /** + * @brief Sets the auto write update mode for the variable. + * @param autoWrite if true the variable will be written to the sim every time the DataManager::postUpdate() method is called, false otherwise + */ + virtual void setAutoWrite(bool autoWrite) { + updateMode = static_cast(autoWrite ? updateMode | UpdateMode::AUTO_WRITE : updateMode & ~UpdateMode::AUTO_WRITE); + } + + /** + * @return true if the variable will be written to the sim in the DataManagers postUpdate() method. + */ + [[nodiscard]] bool isAutoWrite() const { return updateMode & UpdateMode::AUTO_WRITE; } + + /** + * @brief Sets the update mode. + * @param updateMode the new update mode + */ + void setUpdateMode(UpdateMode updateMode) { this->updateMode = updateMode; } + + /** + * @return the time stamp of the last read from the sim + */ + [[nodiscard]] FLOAT64 getTimeStamp() const { return timeStampSimTime; } + + /** + * @return the maximum age of the variable in seconds + */ + [[nodiscard]] FLOAT64 getMaxAgeTime() const { return maxAgeTime; } + + /** + * Sets the maximum age of the variable in seconds + * @param maxAgeTimeInMilliseconds + */ + void setMaxAgeTime(FLOAT64 maxAgeTimeInMilliseconds) { maxAgeTime = maxAgeTimeInMilliseconds; } + + /** + * @return the tick count when variable was last read from the sim + */ + [[nodiscard]] UINT64 getTickStamp() const { return tickStamp; } + + /** + * @return the maximum age of the variable in ticks + */ + [[nodiscard]] UINT64 getMaxAgeTicks() const { return maxAgeTicks; } + + /** + * Sets the maximum age of the variable in ticks + * @param maxAgeTicksInTicks the maximum age of the variable in ticks + */ + void setMaxAgeTicks(UINT64 maxAgeTicksInTicks) { maxAgeTicks = maxAgeTicksInTicks; } +}; + +#endif // FLYBYWIRE_A32NX_MANAGEDDATAOBJECTBASE_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/NamedVariable.cpp b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/NamedVariable.cpp new file mode 100644 index 00000000000..7d60425e962 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/NamedVariable.cpp @@ -0,0 +1,44 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include + +#include "NamedVariable.h" + +FLOAT64 NamedVariable::rawReadFromSim() const { + return get_named_variable_typed_value(dataID, unit.id); +} + +void NamedVariable::rawWriteToSim() { + set_named_variable_typed_value(dataID, cachedValue.value(), unit.id); +} + +std::string NamedVariable::str() const { + std::stringstream ss; + ss << "NamedVariable: [" << name; + ss << ", value: " << (cachedValue.has_value() ? std::to_string(cachedValue.value()) : "N/A"); + ss << ", unit: " << unit.name; + ss << ", changed: " << hasChanged(); + ss << ", dirty: " << dirty; + ss << ", timeStamp: " << timeStampSimTime; + ss << ", nextUpdateTimeStamp: " << nextUpdateTimeStamp; + ss << ", tickStamp: " << tickStamp; + ss << ", nextUpdateTickStamp: " << nextUpdateTickStamp; + ss << ", autoRead: " << isAutoRead(); + ss << ", autoWrite: " << isAutoWrite(); + ss << ", maxAgeTime: " << std::to_string(maxAgeTime); + ss << ", maxAgeTicks: " << maxAgeTicks; + ss << "]"; + return ss.str(); +} + +/** + * Overload of the << operator for NamedVariable. + * @return the a string representation of the NamedVariable as returned by NamedVariable::str() + */ +std::ostream& operator<<(std::ostream& os, const NamedVariable& namedVariable) { + os << namedVariable.str(); + return os; +} diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/NamedVariable.h b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/NamedVariable.h new file mode 100644 index 00000000000..4e669b6ed89 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/NamedVariable.h @@ -0,0 +1,91 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_NAMEDVARIABLE_H +#define FLYBYWIRE_NAMEDVARIABLE_H + +#include + +#include "CacheableVariable.h" +#include "UpdateMode.h" +#include "simple_assert.h" + +class DataManager; + +/** + * @brief The NamedVariable class is a specialization of CacheableVariable for named variables (LVARS). + * + * It is recommended to use the DataManager's make_named_var() to create instances of NamedVariable + * as it de-duplicates variables and only creates one instance of each name-unit combination.

+ * + * NamedVariables can't be copy constructed or assigned. They can only be moved. + * Create a new NamedVariable instance instead. + * + * @see CacheableVariable + */ +class NamedVariable : public CacheableVariable { + // The data manager is a friend, so it can access the private constructor. + friend DataManager; + + static std::string AIRCRAFT_PREFIX; + + /** + * Creates an instance of a named variable.

+ * If the variable is not found in the sim it will be created.

+ * + * It is recommended to use the DataManager's make_named_var() to create instances of NamedVariable + * as it de-duplicates variables and only creates one instance of each name-unit combination. + * + * @param varName The varName of the variable in the sim. An aircraft prefix (e.g. A32NX_) will be added automatically. + * @param unit The unit of the variable as per the sim. See SimUnits.h + * @param updateMode The DataManager update mode of the variable. (default: UpdateMode::NO_AUTO_UPDATE) + * @param maxAgeTime The maximum age of an auto updated variable in seconds. + * @param maxAgeTicks The maximum age of an auto updated variable in sim ticks. + */ + explicit NamedVariable(const std::string& varName, + SimUnit unit = UNITS.Number, + UpdateMode updateMode = UpdateMode::NO_AUTO_UPDATE, + FLOAT64 maxAgeTime = 0.0, + UINT64 maxAgeTicks = 0) + : CacheableVariable(NamedVariable::AIRCRAFT_PREFIX + varName, unit, updateMode, maxAgeTime, maxAgeTicks) { + // this makes sure to quickly spot an issue with the prefix + SIMPLE_ASSERT(NamedVariable::AIRCRAFT_PREFIX == "A32NX_" || NamedVariable::AIRCRAFT_PREFIX == "A380X_", + "Aircraft prefix is not set correctly!"); + dataID = register_named_variable(name.c_str()); + }; + + public: + NamedVariable() = delete; // no default constructor + NamedVariable(const NamedVariable&) = delete; // no copy constructor + NamedVariable& operator=(const NamedVariable&) = delete; // no copy assignment + NamedVariable(NamedVariable&&) = delete; // move constructor + NamedVariable& operator=(NamedVariable&&) = delete; // move assignment + + [[nodiscard]] FLOAT64 rawReadFromSim() const override; + void rawWriteToSim() override; + + [[nodiscard]] std::string str() const override; + + /** + * Sets the aircraft prefix for all NamedVariables. + * This will usually be set by the MsfsHandler constructor. + * @param aircraftPrefix The aircraft prefix to use. + */ + static void setAircraftPrefix(const std::string& aircraftPrefix) { + NamedVariable::AIRCRAFT_PREFIX = aircraftPrefix; + } + + /** + * Returns the aircraft prefix for all NamedVariables. + * @return The aircraft prefix. + */ + static const std::string& getAircraftPrefix() { return AIRCRAFT_PREFIX; } + + friend std::ostream& operator<<(std::ostream& os, const NamedVariable& namedVariable); +}; + +// This is a default value for the prefix, it will be overwritten +// by the MsfsHandler constructor. +inline std::string NamedVariable::AIRCRAFT_PREFIX = "FBW_"; + +#endif // FLYBYWIRE_NAMEDVARIABLE_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/SimObjectBase.hpp b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/SimObjectBase.hpp new file mode 100644 index 00000000000..d81aa9dc17f --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/SimObjectBase.hpp @@ -0,0 +1,118 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_A32NX_SIMOBJECTBASE_H +#define FLYBYWIRE_A32NX_SIMOBJECTBASE_H + +#include +#include + +#include +#include + +#include "IDGenerator.h" +#include "ManagedDataObjectBase.hpp" +#include "UpdateMode.h" +#include "simple_assert.h" + +/** + * @brief The SimObjectBase class is the base class for all sim objects. + */ +class SimObjectBase : public ManagedDataObjectBase { + protected: + /** + * SimConnect handle is required for data definitions. + */ + HANDLE hSimConnect; + + /** + * Each data definition variable has its own unique id so the sim can map registered data sim + * objects to data definitions. + */ + SIMCONNECT_DATA_DEFINITION_ID dataDefId = 0; + + /** + * Each request for sim object data requires a unique id so the sim can provide the request ID + * in the response (message SIMCONNECT_RECV_ID_SIMOBJECT_DATA). + */ + SIMCONNECT_DATA_REQUEST_ID requestId = 0; + + /** + * Creates a new instance of a DataDefinitionVariable. + * @param hSimConnect Handle to the SimConnect object. + * @param name Arbitrary name for the data definition variable for debugging purposes + * @param dataDefinitionId Each data definition variable has its own unique id so the sim can map registered data sim objects to data + * definitions. + * @param requestId Each request for sim object data requires a unique id so the sim can provide the request ID in the response (message + * SIMCONNECT_RECV_ID_SIMOBJECT_DATA). + * @param updateMode The DataManager update mode of the variable. (default: UpdateMode::NO_AUTO_UPDATE) + * @param maxAgeTime The maximum age of the value in sim time before it is updated from the sim by the requestUpdateFromSim() method. + * @param maxAgeTicks The maximum age of the value in ticks before it is updated from the sim by the requestUpdateFromSim() method. + */ + SimObjectBase(HANDLE hSimConnect, + const std::string& varName, + DWORD dataDefId, + DWORD requestId, + UpdateMode updateMode = UpdateMode::NO_AUTO_UPDATE, + FLOAT64 maxAgeTime = 0.0, + UINT64 maxAgeTicks = 0) + : ManagedDataObjectBase(varName, updateMode, maxAgeTime, maxAgeTicks), + hSimConnect(hSimConnect), + dataDefId(dataDefId), + requestId(requestId) {} + + public: + SimObjectBase() = delete; // no default constructor + SimObjectBase(const SimObjectBase&) = delete; // no copy constructor + SimObjectBase& operator=(const SimObjectBase&) = delete; // no copy assignment + SimObjectBase(SimObjectBase&&) = delete; // no move constructor + SimObjectBase& operator=(SimObjectBase&&) = delete; // no move assignment + + /** + * Sends a data request to the sim to have the sim prepare the requested data. + * The sim will send the data exactly once via the SIMCONNECT_RECV_ID_SIMOBJECT_DATA message. + * This calls processSimData() to process the data. + * @return true if the request was successful, false otherwise + * @See SimConnect_RequestDataOnSimObject + */ + [[nodiscard]] virtual bool requestDataFromSim() const = 0; + + /** + * Checks the age (time/ticks) of the data and requests an update from the sim if the data is too old. + * @param timeStamp the current sim time (taken from the sim update event) + * @param tickCounter the current tick counter (taken from a custom counter at each update event + * @return false if the request was not successful, true otherwise + * (also true when max age is not exceeded - no request will be sent to the sim in this case + */ + [[nodiscard]] virtual bool requestUpdateFromSim(FLOAT64 timeStamp, UINT64 tickCounter) = 0; + + /** + * Called by the DataManager when a SIMCONNECT_RECV_ID_SIMOBJECT_DATA + * or SIMCONNECT_RECV_ID_CLIENT_DATA message for this variables request ID is received. + * It processes the data and sets the dataChanged flag if the data has changed. + * The dataChanged flag can be checked by the external class to determine if the data has changed. + * @param pointer to the SIMCONNECT_RECV_SIMOBJECT_DATA of SIMCONNECT_RECV_CLIENT_DATA structure + * @param simTime the current sim time (taken from the sim update event) + * @param tickCounter the current tick counter (taken from a custom counter at each update event) + * @See SIMCONNECT_RECV + */ + virtual void processSimData(const SIMCONNECT_RECV* pData, FLOAT64 simTime, UINT64 tickCounter) = 0; + + /** + * Writes the data object to the sim. + * @return true if the write was successful, false otherwise + */ + virtual bool writeDataToSim() = 0; + + /** + * @return the data definition id + */ + [[nodiscard]] DWORD getDataDefID() const { return dataDefId; } + + /** + * @return the request id + */ + [[nodiscard]] DWORD getRequestId() const { return requestId; } +}; + +#endif // FLYBYWIRE_A32NX_SIMOBJECTBASE_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/StreamingClientDataAreaVariable.hpp b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/StreamingClientDataAreaVariable.hpp new file mode 100644 index 00000000000..8905a3a5930 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/StreamingClientDataAreaVariable.hpp @@ -0,0 +1,242 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFT_STREAMINGCLIENTDATAAREAVARIABLE_HPP +#define FLYBYWIRE_AIRCRAFT_STREAMINGCLIENTDATAAREAVARIABLE_HPP + +#include "ClientDataAreaVariable.hpp" +#include "UpdateMode.h" + +class DataManager; + +/** + * @brief The StreamingClientDataAreaVariable class is a special variant of the ClientDataAreaVariable class which allows to + * send and receive data larger than the maximum size of a single SimConnect client data area chunk of 8192 bytes.

+ * + * The data is split into chunks of a fixed size (8192 bytes) and sent/received in chunks.
+ * The data is stored in a vector of T, which is resized to the number of bytes expected to be received.

+ * + * Before receiving data the reserve() method must be called to reset the data and set the number of bytes to be + * received.
+ * + * @tparam T the type of the data to be sent/received - e.g. char for string data + * @tparam ChunkSize the size of the chunks to be sent/received - must be <= 8192. Default is 8192. + */ +template +class StreamingClientDataAreaVariable : public ClientDataAreaVariable { + private: + // The data manager is a friend, so it can access the private constructor. + friend DataManager; + + // the content of the client data area as a vector of T + std::vector content; + + // the number of bytes expected to be received - set in reserve() + std::size_t expectedByteCount{}; + + // the number of bytes received so far - re-set in reserve() + std::size_t receivedBytes{}; + + // the number of chunks received so far - re-set in reserve() + std::size_t receivedChunks{}; + + // hide incompatible methods - alternative would be to make this class independent of ClientDataAreaVariable + using ClientDataAreaVariable::data; + + /** + * Creates an instance of a streaming client data area variable.

+ * + * Use the DataManager's make_clientdatabufferedarea_var() to create instances of StreamingClientDataAreaVariable + * as it ensures unique clientDataId, clientDataDefinitionId and requestId within the SimConnect session. + * + * @param hSimConnect the SimConnect handle + * @param clientDataName the name of the client data area + * @param clientDataId the ID of the client data area + * @param clientDataDefinitionId the definition ID of the client data area + * @param requestId the request ID of the client data area + * @param updateMode optional DataManager update mode of the variable (default=UpdateMode::NO_AUTO_UPDATE) + * @param maxAgeTime The maximum age of the value in sim time before it is updated from the sim by + * the requestUpdateFromSim() method. + * @param maxAgeTicks The maximum age of the value in ticks before it is updated from the sim by + * the requestUpdateFromSim() method. + */ + StreamingClientDataAreaVariable(HANDLE hSimConnect, + const std::string& clientDataName, + SIMCONNECT_CLIENT_DATA_ID clientDataId, + SIMCONNECT_CLIENT_DATA_DEFINITION_ID clientDataDefinitionId, + SIMCONNECT_DATA_REQUEST_ID requestId, + UpdateMode updateMode = UpdateMode::NO_AUTO_UPDATE, + FLOAT64 maxAgeTime = 0.0, + UINT64 maxAgeTicks = 0) + : ClientDataAreaVariable(hSimConnect, + clientDataName, + clientDataId, + clientDataDefinitionId, + requestId, + ChunkSize, + updateMode, + maxAgeTime, + maxAgeTicks), + content() {} + + public: + StreamingClientDataAreaVariable() = delete; // no default constructor + StreamingClientDataAreaVariable(const StreamingClientDataAreaVariable&) = delete; // no copy constructor + // no copy assignment + StreamingClientDataAreaVariable& operator=(const StreamingClientDataAreaVariable&) = delete; + StreamingClientDataAreaVariable(StreamingClientDataAreaVariable&&) = delete; // no move constructor + StreamingClientDataAreaVariable& operator=(StreamingClientDataAreaVariable&&) = delete; // no move assignment + + bool allocateClientDataArea(bool readOnlyForOthers = false) override { + const DWORD readOnlyFlag = + readOnlyForOthers ? SIMCONNECT_CREATE_CLIENT_DATA_FLAG_READ_ONLY : SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT; + if (!SUCCEEDED(SimConnect_CreateClientData(this->hSimConnect, this->clientDataId, ChunkSize, readOnlyFlag))) { + LOG_ERROR("ClientDataAreaVariable: Creating client data area failed: " + this->getName()); + return false; + } + return true; + } + + /** + * This tells this instance the expected number of bytes to be received and prepares the internal + * data structure.
+ * It needs to be called before the first data chunk is received so that the internal data structure + * is reset and prepared to receive new data. + * @param expectedByteCnt Number of expected bytes in streaming cases + */ + void reserve(std::size_t expectedByteCnt) { + this->setChanged(false); + this->content.clear(); + this->receivedBytes = 0; + this->receivedChunks = 0; + this->expectedByteCount = expectedByteCnt; + this->content.reserve(expectedByteCnt); + } + + void processSimData(const SIMCONNECT_RECV* pData, FLOAT64 simTime, UINT64 tickCounter) override { + const auto pClientData = reinterpret_cast(pData); + + std::size_t remainingBytes = this->expectedByteCount - this->receivedBytes; + if (remainingBytes > ChunkSize) { + remainingBytes = ChunkSize; + } + + // insert the data into the vector - very fast as well but results in a valid vector instance (memcpy doesn't) + this->content.insert(this->content.end(), (T*)&pClientData->dwData, (T*)&pClientData->dwData + remainingBytes); + + this->receivedChunks++; + this->receivedBytes += remainingBytes; + + // received all data? + if (this->receivedBytes >= this->expectedByteCount) { + this->updateStamps(simTime, tickCounter); + this->setChanged(true); + return; + } + } + + /** + * Writes the data to the sim by splitting it into chunks of a fixed size (ChunkSize) and sending + * each chunk separately.
+ * @return true if successful, false otherwise + */ + bool writeDataToSim() override { + [[maybe_unused]] int chunkCount = 0; // for debugging output only + std::size_t sentBytes = 0; + std::size_t remainingBytes = this->content.size(); + + while (sentBytes < this->content.size()) { + if (remainingBytes >= ChunkSize) { + chunkCount++; + if (!SUCCEEDED(SimConnect_SetClientData(this->hSimConnect, this->clientDataId, this->dataDefId, + SIMCONNECT_CLIENT_DATA_SET_FLAG_DEFAULT, 0, ChunkSize, &this->content.data()[sentBytes]))) { + LOG_ERROR("Setting data to sim for " + this->getName() + " with dataDefId=" + std::to_string(this->dataDefId) + " failed!"); + return false; + } + sentBytes += ChunkSize; + } else { // last chunk + // use a tmp array buffer to send the remaining bytes + std::array buffer{}; + std::memcpy(buffer.data(), &this->content.data()[sentBytes], remainingBytes); + chunkCount++; + if (!SUCCEEDED(SimConnect_SetClientData(this->hSimConnect, this->clientDataId, this->dataDefId, + SIMCONNECT_CLIENT_DATA_SET_FLAG_DEFAULT, 0, ChunkSize, buffer.data()))) { + LOG_ERROR("Setting data to sim for " + this->getName() + " with dataDefId=" + std::to_string(this->dataDefId) + " failed!"); + return false; + } + sentBytes += remainingBytes; + } + remainingBytes = this->content.size() - sentBytes; + LOG_VERBOSE("Sending chunk: " + std::to_string(chunkCount) + " Sent bytes: " + std::to_string(sentBytes) + + " Remaining bytes: " + std::to_string(remainingBytes)); + } + + LOG_DEBUG("Finished sending data in " + std::to_string(chunkCount) + " chunks" + " Sent bytes: " + std::to_string(sentBytes) + + " Remaining bytes: " + std::to_string(remainingBytes) + " DataSize: " + std::to_string(this->content.size())); + return true; + } + + /** + * Returns a modifiable reference to the data container + * @return T& Reference to the data container + */ + [[nodiscard]] std::vector& getData() { return content; } + + /** + * Returns a constant reference to the data container + * @return std::vector& Reference to the data container + */ + [[nodiscard]] const std::vector& getData() const { return content; } + + /** + * Returns the number of bytes received so far + * @return std::size_t Number of bytes received so far + */ + [[nodiscard]] std::size_t getReceivedBytes() const { return receivedBytes; } + + /** + * Returns the number of chunks received so far + * @return std::size_t Number of chunks received so far + */ + [[nodiscard]] std::size_t getReceivedChunks() const { return receivedChunks; } + + [[nodiscard]] std::string str() const override { + std::stringstream ss; + ss << "StreamingClientDataAreaVariable[ name=" << this->getName(); + ss << ", clientDataId=" << this->clientDataId; + ss << ", dataDefId=" << this->dataDefId; + ss << ", requestId=" << this->requestId; + ss << ", expectedByteCount=" << this->expectedByteCount; + ss << ", receivedBytes=" << this->receivedBytes; + ss << ", receivedChunks=" << this->receivedChunks; + ss << ", structSize=" << content.size() * sizeof(T); + ss << ", timeStamp: " << this->timeStampSimTime; + ss << ", nextUpdateTimeStamp: " << this->nextUpdateTimeStamp; + ss << ", tickStamp: " << this->tickStamp; + ss << ", nextUpdateTickStamp: " << this->nextUpdateTickStamp; + ss << ", skipChangeCheckFlag: " << this->skipChangeCheckFlag; + ss << ", dataChanged: " << this->hasChanged(); + ss << ", autoRead: " << this->isAutoRead(); + ss << ", autoWrite: " << this->isAutoWrite(); + ss << ", maxAgeTime: " << this->maxAgeTime; + ss << ", maxAgeTicks: " << this->maxAgeTicks; + ss << ", dataType=" << typeid(T).name() << "::" << quote(content); + ss << "]"; + return ss.str(); + } + + friend std::ostream& operator<<(std::ostream& os, const StreamingClientDataAreaVariable& ddv); +}; + +/** + * Overload of the << operator for StreamingClientDataAreaVariable + * @return returns a string representation of the StreamingClientDataAreaVariable as returned by + * StreamingClientDataAreaVariable::str() + */ +template +std::ostream& operator<<(std::ostream& os, const StreamingClientDataAreaVariable& ddv) { + os << ddv.str(); + return os; +} + +#endif // FLYBYWIRE_AIRCRAFT_STREAMINGCLIENTDATAAREAVARIABLE_HPP diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/UpdateMode.h b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/UpdateMode.h new file mode 100644 index 00000000000..ab505c93b16 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/DataTypes/UpdateMode.h @@ -0,0 +1,32 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFT_UPDATEMODE_H +#define FLYBYWIRE_AIRCRAFT_UPDATEMODE_H + +/** + * @brief The UpdateMode enum defines the different update modes for auto read and write.

+ * NO_AUTO_UPDATE: No automatic updates (default)
+ * AUTO_READ: Automatic read every tick
+ * AUTO_WRITE: Automatic write every tick
+ * AUTO_READ_WRITE: Automatic read and write every tick
+ * + * @note The values are bit flags and can be combined using the bitwise OR operator. + */ +enum UpdateMode { + // clang-format off + + // No automatic updates (default) + NO_AUTO_UPDATE = 0, // 0x00 + // Automatic read every tick + AUTO_READ = 1, // 0x01 + // Automatic write every tick + AUTO_WRITE = 2, // 0x10 + // Automatic read and write every tick + AUTO_READ_WRITE = 3 // 0x11 + + // clang-format on +}; +namespace ManagedDataObject {} // namespace ManagedDataObject + +#endif // FLYBYWIRE_AIRCRAFT_UPDATEMODE_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/Module.h b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/Module.h new file mode 100644 index 00000000000..732b6784e37 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/Module.h @@ -0,0 +1,94 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_MODULE_H +#define FLYBYWIRE_MODULE_H + +#include + +#include "MsfsHandler.h" + +/** + * @brief The Module class is the base class and interface for all modules to + * ensure that they are compatible with the MsfsHandler. + * + * Make sure to add an error (std::cerr) message if anything goes wrong and especially if + * initialize(), preUpdate(), update() or postUpdate() return false.

+ * + * MSFS does not support Exception so good logging and error messages is the only way to inform the + * user/developer if somethings went wrong and where and what happened.

+ * + * Non-excessive positive logging about what is happening is also a good idea and helps + * tremendously with finding any issues as it will be easier to locate the cause of the issue. + */ +class Module { + protected: + /** + * The MsfsHandler instance that is used to communicate with the simulator. + */ + MsfsHandler& msfsHandler; + + /** + * Flag to indicate if the module has been initialized. + */ + bool _isInitialized = false; + + public: + Module() = delete; // no default constructor + Module(const Module&) = delete; // no copy constructor + Module& operator=(const Module&) = delete; // no copy assignment + virtual ~Module() = default; + + /** + * Creates a new module and takes a reference to the MsfsHandler instance. + * @param msfsHandler The MsfsHandler instance that is used to communicate with the simulator. + */ + explicit Module(MsfsHandler& backRef) : msfsHandler(backRef) { msfsHandler.registerModule(this); } + + /** + * Called by the MsfsHandler instance once to initialize the module. + * Happens during the PANEL_SERVICE_PRE_INSTALL message from the sim. + * @return true if the module was successfully initialized, false otherwise. + */ + virtual bool initialize() = 0; + + /** + * Called first by the MsfsHandler instance during the PANEL_SERVICE_PRE_DRAW phase from the sim. + * This is called directly after the DataManager::preUpdate() method and can be used for additional + * pre-processing before the update() method is called. + * @param pData sGaugeDrawData structure containing the data for the current frame. + * @return false if an error occurred, true otherwise. + */ + virtual bool preUpdate(sGaugeDrawData* pData) = 0; + + /** + * Called by the MsfsHandler instance during the PANEL_SERVICE_PRE_DRAW phase from the sim. + * This is called directly after the DataManager::update() method. + * This is the main update method and should be used to implement the main logic of the module. + * @param pData sGaugeDrawData structure containing the data for the current frame. + * @return false if an error occurred, true otherwise. + */ + virtual bool update(sGaugeDrawData* pData) = 0; + + /** + * Called by the MsfsHandler instance during the PANEL_SERVICE_PRE_DRAW phase from the sim. + * This is called directly after the DataManager::postUpdate() method and can be used for additional + * post-processing after the update() method is called. + * @param pData sGaugeDrawData structure containing the data for the current frame. + * @return false if an error occurred, true otherwise. + */ + virtual bool postUpdate(sGaugeDrawData* pData) = 0; + + /** + * Called by the MsfsHandler instance during the PANEL_SERVICE_PRE_KILL phase from the sim. + * @return false if an error occurred, true otherwise. + */ + virtual bool shutdown() = 0; + + /** + * @return true if the module has been initialized, false otherwise. + */ + [[nodiscard]] bool isInitialized() const { return _isInitialized; } +}; + +#endif // FLYBYWIRE_MODULE_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/MsfsHandler.cpp b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/MsfsHandler.cpp new file mode 100644 index 00000000000..4ba44e3752a --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/MsfsHandler.cpp @@ -0,0 +1,176 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#include +#include + +#include "Callback.h" +#include "ClientEvent.h" +#include "Module.h" +#include "MsfsHandler.h" +#include "NamedVariable.h" +#include "UpdateMode.h" + +// ================================================================================================= +// PUBLIC METHODS +// ================================================================================================= + +void MsfsHandler::registerModule(Module* pModule) { + modules.push_back(pModule); +} + +bool MsfsHandler::initialize() { + // Initialize SimConnect + bool result; + result = SUCCEEDED(SimConnect_Open(&hSimConnect, simConnectName.c_str(), nullptr, 0, 0, 0)); + if (!result) { + LOG_ERROR(simConnectName + ": Failed to initialize SimConnect"); + return false; + } + LOG_INFO(simConnectName + ": Initialized SimConnect"); + + // Initialize data manager + result = dataManager.initialize(hSimConnect); + if (!result) { + LOG_ERROR(simConnectName + ": Failed to initialize data manager"); + return false; + } + LOG_INFO(simConnectName + ": Initialized data manager"); + + // This is a workaround to be able to use a member function as callback as the API callback + // function must be static. + // See https://blog.mbedded.ninja/programming/languages/c-plus-plus/callbacks/#static-variables-with-templating + Callback::func = + [ObjectPtr = &dataManager](auto&& PH1, auto&& PH2, auto&& PH3, auto&& PH4, auto&& PH5, auto&& PH6, [[maybe_unused]] auto&& PH7) { + ObjectPtr->processKeyEvent(std::forward(PH1), std::forward(PH2), std::forward(PH3), + std::forward(PH4), std::forward(PH5), std::forward(PH6)); + }; + keyEventHandlerEx1 = + static_cast(Callback::callback); + + // Register as key event handler + register_key_event_handler_EX1(keyEventHandlerEx1, nullptr); + + // base sim data mainly for pause detection + std::vector baseDataDef = {{"SIMULATION TIME", 0, UNITS.Number}, + {"L:" + NamedVariable::getAircraftPrefix() + "IS_READY", 0, UNITS.Number}, + {"L:" + NamedVariable::getAircraftPrefix() + "DEVELOPER_STATE", 0, UNITS.Number}}; + baseSimData = dataManager.make_datadefinition_var("BASE DATA", baseDataDef); + if (!SUCCEEDED(baseSimData->requestPeriodicDataFromSim(SIMCONNECT_PERIOD_VISUAL_FRAME))) { + LOG_ERROR(simConnectName + ": Failed to request periodic data for base sim data"); + return false; + } + + // Pause detection via System Events + // PAUSE_STATE_FLAG_OFF 0 // No Pause + // PAUSE_STATE_FLAG_PAUSE 1 // "full" Pause (sim + traffic + etc...) + // PAUSE_STATE_FLAG_PAUSE_WITH_SOUND 2 // FSX Legacy Pause (not used anymore) + // PAUSE_STATE_FLAG_ACTIVE_PAUSE 4 // Pause was activated using the "Active Pause" Button + // PAUSE_STATE_FLAG_SIM_PAUSE 8 // Pause the player sim but traffic, multi, etc... will still run + a32nxPauseDetected = dataManager.make_named_var("PAUSE_DETECTED", UNITS.Number, UpdateMode::AUTO_READ_WRITE); + pauseDetectedEvent = dataManager.make_client_event("A32NX.PAUSE_DETECTED_EVENT", false); + pauseDetectedEvent->addCallback([&](const int, const DWORD param0, const DWORD, const DWORD, const DWORD, const DWORD) { + LOG_INFO(simConnectName + ": Pause detected: " + std::to_string(param0)); + a32nxPauseDetected->setAndWriteToSim(param0); + }); + if (!SUCCEEDED(SimConnect_SubscribeToSystemEvent(hSimConnect, pauseDetectedEvent->getClientEventId(), "Pause_EX1"))) { + LOG_ERROR(simConnectName + ": Failed to subscribe to PAUSE_EX1 event"); + return false; + } + LOG_INFO(simConnectName + ": Subscribed to PAUSE_EX1 event"); + + // Initialize modules + result = std::all_of(modules.begin(), modules.end(), [](Module* pModule) { return pModule->initialize(); }); + if (!result) { + LOG_ERROR(simConnectName + ": Failed to initialize modules"); + return false; + } + LOG_INFO(simConnectName + ": Initialized modules"); + + LOG_INFO(simConnectName + ": Initialized"); + isInitialized = result; + return result; +} + +bool MsfsHandler::update(sGaugeDrawData* pData) { + if (!isInitialized) { + LOG_ERROR(simConnectName + ": MsfsHandler::update() - not initialized"); + return false; + } + +#ifdef PROFILING + profiler.start(); +#endif + + // initial request of data from sim to retrieve all requests which have + // periodic updates enabled. This includes the base sim data for pause detection. + // Other data without periodic updates are requested either in the data manager or + // in the modules. + dataManager.getRequestedData(); + + // Pause detection + // In all pause states except active pause return immediately. + // Active pause can be handled by the modules but usually simulation should run normally in + // active pause with just the aircraft not moving. + // See comments above for the different pause states. + if (a32nxPauseDetected->readFromSim()) { + if (a32nxPauseDetected->getAsInt64() > 0) { + if (a32nxPauseDetected->getAsInt64() != 4) { + // LOG_DEBUG(simConnectName + ": Pause detected = " + std::to_string(a32nxPauseDetected->getAsInt64())); + return true; + } + // LOG_DEBUG(simConnectName + ": Active Pause detected = " + std::to_string(a32nxPauseDetected->getAsInt64())); + } + } + + // read and update base data from sim + timeStamp = baseSimData->data().simulationTime; + tickCounter++; + + // Call preUpdate(), update() and postUpdate() for all modules + // Datamanager is always called first to ensure that all variables are updated before the modules + // are called. + + // PRE UPDATE + bool result = true; + result &= dataManager.preUpdate(pData); + result &= std::all_of(modules.begin(), modules.end(), [&pData](Module* pModule) { return pModule->preUpdate(pData); }); + + // UPDATE + result &= dataManager.update(pData); + result &= std::all_of(modules.begin(), modules.end(), [&pData](Module* pModule) { return pModule->update(pData); }); + + // POST UPDATE + result &= dataManager.postUpdate(pData); + result &= std::all_of(modules.begin(), modules.end(), [&pData](Module* pModule) { return pModule->postUpdate(pData); }); + + if (!result) { + LOG_ERROR(simConnectName + ": MsfsHandler::update() - failed"); + } + +#ifdef PROFILING + profiler.stop(); + if (tickCounter % 120 == 0) { + profiler.print(); + } +#endif + + return result; +} + +bool MsfsHandler::shutdown() { + bool result = std::all_of(modules.begin(), modules.end(), [](Module* pModule) { return pModule->shutdown(); }); + result &= dataManager.shutdown(); + modules.clear(); + unregister_key_event_handler_EX1(reinterpret_cast(keyEventHandlerEx1), nullptr); + unregister_all_named_vars(); + return result; +} + +bool MsfsHandler::getAircraftIsReadyVar() const { + return static_cast(baseSimData->data().aircraftIsReady); +} + +FLOAT64 MsfsHandler::getAircraftDevelopmentStateVar() const { + return baseSimData->data().aircraftDevelopmentState; +} diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/MsfsHandler.h b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/MsfsHandler.h new file mode 100644 index 00000000000..a0026321a52 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/MsfsHandler.h @@ -0,0 +1,177 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_MSFSHANDLER_H +#define FLYBYWIRE_MSFSHANDLER_H + +#include +#include +#include + +#include +#include + +#include "DataManager.h" +#include "SimpleProfiler.hpp" + +class Module; + +/** + * @brief The MsfsHandler class is a lightweight abstraction layer for the MSFS SDK and Simconnect. + * + * It handles the communication with the simulator mainly for standard variables and events.

+ * + * It is not meant to fully replace the SDK but to provide a simple C++ style interface for the + * most common tasks.

+ * + * It does not limit the direct usage of the SDK or Simconnect in any way! + */ +class MsfsHandler { + /** + * A list of all modules that are currently loaded. + * This list is used to call the preUpdate, update and postUpdate methods of each module. + * Each module is responsible for registering itself in this list - this is done in the + * constructor of the module. + * The order of the modules in this list is important as the update methods are called in + * the order of the list. The order is determined by the order of creation of the modules. + */ + std::vector modules{}; + + /** + * The data manager is responsible for managing all variables and events. + * It is used to register variables and events and to update them. + * It de-duplicates variables and events and only creates one instance of each if multiple modules + * use the same variable or event. + */ + DataManager dataManager; + + /** + * Each simconnect instance has a name to identify it. + */ + std::string simConnectName; + + /** + * The handle of the simconnect instance. + */ + HANDLE hSimConnect{}; + + /** + * Flag to indicate if the MsfsHandler instance is initialized. + */ + bool isInitialized = false; + + /** + * This struct is used to define the data definition for the base sim data. + */ + struct BaseSimData { + FLOAT64 simulationTime; + FLOAT64 aircraftIsReady; + FLOAT64 aircraftDevelopmentState; + }; + std::shared_ptr> baseSimData; + + /** + * @brief Pause detection via System Events + * Uses the value from the system event "PAUSE_STATE" to detect if the sim is paused.
+ * PAUSE_STATE_FLAG_OFF 0 // No Pause
+ * PAUSE_STATE_FLAG_PAUSE 1 // "full" Pause (sim + traffic + etc...)
+ * PAUSE_STATE_FLAG_PAUSE_WITH_SOUND 2 // FSX Legacy Pause (not used anymore)
+ * PAUSE_STATE_FLAG_ACTIVE_PAUSE 4 // Pause was activated using the "Active Pause" Button
+ * PAUSE_STATE_FLAG_SIM_PAUSE 8 // Pause the player sim but traffic, multi, etc... will still run
+ */ + // Pause detection + NamedVariablePtr a32nxPauseDetected; + ClientEventPtr pauseDetectedEvent; + + /** + * Current simulation time used for pause detection and time stamping variable updates + */ + FLOAT64 timeStamp{}; + + /** + * Counts the number of ticks since start instance creation (calls to update). Used to + * tick stamping the variable updates. + */ + UINT64 tickCounter{}; + + // Callback function for register_key_event_handler_EX1 + GAUGE_KEY_EVENT_HANDLER_EX1 keyEventHandlerEx1 = nullptr; + + // Allows immediate view on runtime performance issue. Add additional instances into + // Modules while developing and profiling a module's performance. +#ifdef PROFILING + SimpleProfiler profiler{"MsfsHandler::update()", 120}; +#endif + + public: + /** + * Creates a new MsfsHandler instance. + * @param name string containing an appropriate simconnect name for the client program. + * @param aircraftPrefix string containing the prefix for all named variables (LVARs). + * E.g. "A32NX_" for the A32NX aircraft or "A380X_" for the A380X aircraft. + */ + explicit MsfsHandler(std::string&& name, const std::string& aircraftPrefix) : dataManager(this), simConnectName(std::move(name)) { + LOG_INFO("Creating MsfsHandler instance with Simconnect name " + simConnectName + " and aircraft prefix " + aircraftPrefix); + NamedVariable::setAircraftPrefix(aircraftPrefix); + } + + /** + * Initializes the MsfsHandler instance. This method must be called before any other method. + * Opens a simconnect instance, initializes the data manager and calls initialize on all modules. + * Is called by the gauge handler when the PANEL_SERVICE_PRE_INSTALL event is received. + * @return true if the initialization was successful, false otherwise. + */ + bool initialize(); + + /** + * Calls the preUpdate, update, postUpdate method of the DataManager and all modules. + * Is called by the gauge handler when the PANEL_SERVICE_PRE_DRAW event is received. + * @param pData pointer to the sGaugeDrawData struct. + * @return true if the update was successful, false otherwise. + */ + bool update(sGaugeDrawData* pData); + + /** + * Calls the shutdown method of the DataManager and all modules and closes the simconnect instance. + * Is called by the gauge handler when the PANEL_SERVICE_PRE_KILL event is received. + * @return true if the shutdown was successful, false otherwise. + */ + bool shutdown(); + + /** + * Callback method for modules to register themselves. This is done in the constructor of the + * module base class. + * @param pModule pointer to the module that should be registered. + */ + void registerModule(Module* pModule); + + // Getters and setters + public: + /** + * @return a modifiable reference to the data manager. + */ + DataManager& getDataManager() { return dataManager; } + + /** + * @return value of LVAR A32NX_IS_READY + */ + [[nodiscard]] bool getAircraftIsReadyVar() const; + + /** + * + * @return value of LVAR A32NX_DEVELOPMENT_STATE + */ + [[nodiscard]] FLOAT64 getAircraftDevelopmentStateVar() const; + + /** + * @return the current simulation time + */ + [[nodiscard]] FLOAT64 getTimeStamp() const { return timeStamp; } + + /** + * @return the current tick counter + */ + [[nodiscard]] UINT64 getTickCounter() const { return tickCounter; } +}; + +#endif // FLYBYWIRE_MSFSHANDLER_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/SimUnits.h b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/SimUnits.h new file mode 100644 index 00000000000..2d939f5367b --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/SimUnits.h @@ -0,0 +1,415 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_UNITS_H +#define FLYBYWIRE_UNITS_H + +#include + +#include + +#include "logging.h" + +/** + * @brief The SimUnit struct is a helper struct representing one unit + */ +struct SimUnit { + public: + const char* name; + const ENUM id; + [[maybe_unused]] explicit SimUnit(const char* nameInSim) : name(nameInSim), id(get_units_enum(name)) { + // LOG_INFO("SimUnit::SimUnit() " + std::string(name) + " = " + std::to_string(id)); + } +}; + +/** + * @brief The SimUnits class is a helper class to make handling of MSFS SDK units easier. + */ +class SimUnits { + public: + // Selected commonly used units + + const SimUnit None{""}; // special case for no unit (e.g. in conjunction with a data type) + const SimUnit Bool{"Bool"}; + const SimUnit Celsius{"Celsius"}; + const SimUnit Feet{"Feet"}; + const SimUnit FeetMin{"feet/minute"}; + const SimUnit FeetSec{"feet/second"}; + const SimUnit FeetSecSquared{"feet per second squared"}; + const SimUnit FootPounds{"Foot pounds"}; + const SimUnit Gallons{"Gallons"}; + const SimUnit Hours{"Hours"}; + const SimUnit Mach{"Mach"}; + const SimUnit Millibars{"Millibars"}; + const SimUnit Minutes{"Minutes"}; + const SimUnit Number{"Number"}; + const SimUnit Percent{"Percent"}; + const SimUnit PercentOver100{"Percent over 100"}; + const SimUnit Pounds{"Pounds"}; + const SimUnit Pph{"Pounds per hour"}; + const SimUnit Psi{"Psi"}; + const SimUnit Rad{"radians"}; + const SimUnit RadSec{"radians per second"}; + const SimUnit RadSecSquared{"radians per second squared"}; + const SimUnit Seconds{"Seconds"}; + + // All units taken from the MSFS SDK Examples SimWatcher + const SimUnit Bco16{"Bco16"}; + const SimUnit Bool_{"Bool"}; + const SimUnit Boolean{"Boolean"}; + const SimUnit Enum{"Enum"}; + const SimUnit Frequency_ADF_BCD32{"Frequency ADF BCD32"}; + const SimUnit Frequency_BCD16{"Frequency BCD16"}; + const SimUnit Frequency_BCD32{"Frequency BCD32"}; + const SimUnit GForce{"GForce"}; + const SimUnit GLOBALP_delta_heading_rate{"GLOBALP->delta_heading_rate"}; + const SimUnit GLOBALP_eng1_manifold_pressure{"GLOBALP->eng1.manifold_pressure"}; + const SimUnit GLOBALP_eng1_oil_prs{"GLOBALP->eng1.oil_prs"}; + const SimUnit GLOBALP_eng1_oil_tmp{"GLOBALP->eng1.oil_tmp"}; + const SimUnit GLOBALP_vertical_speed{"GLOBALP->vertical_speed"}; + const SimUnit G_Force_624_scaled{"G Force 624 scaled"}; + const SimUnit G_Force{"G Force"}; + const SimUnit Hertz{"Hertz"}; + const SimUnit Hz{"Hz"}; + const SimUnit KHz{"KHz"}; + const SimUnit KgFSqCm{"KgFSqCm"}; + const SimUnit Kilohertz{"Kilohertz"}; + const SimUnit MHz{"MHz"}; + const SimUnit Megahertz{"Megahertz"}; + const SimUnit Nm{"Nm"}; + const SimUnit Pa{"Pa"}; + const SimUnit Slug_ft3{"Slug/ft3"}; + const SimUnit Slug_per_cubic_feet{"Slug per cubic feet"}; + const SimUnit Slug_per_cubic_foot{"Slug per cubic foot"}; + const SimUnit Slugs_per_cubic_feet{"Slugs per cubic feet"}; + const SimUnit Slugs_per_cubic_foot{"Slugs per cubic foot"}; + const SimUnit Watts{"Watts"}; + const SimUnit Watt{"Watt"}; + const SimUnit amperes{"amperes"}; + const SimUnit ampere{"ampere"}; + const SimUnit amps{"amps"}; + const SimUnit amp{"amp"}; + const SimUnit angl16{"angl16"}; + const SimUnit angl32{"angl32"}; + const SimUnit atmospheres{"atmospheres"}; + const SimUnit atmosphere{"atmosphere"}; + const SimUnit atm{"atm"}; + const SimUnit bars{"bars"}; + const SimUnit bar{"bar"}; + const SimUnit bels{"bels"}; + const SimUnit bel{"bel"}; + const SimUnit boost_cmHg{"boost cmHg"}; + const SimUnit boost_inHg{"boost inHg"}; + const SimUnit boost_psi{"boost psi"}; + const SimUnit celsius_fs7_egt{"celsius fs7 egt"}; + const SimUnit celsius_fs7_oil_temp{"celsius fs7 oil temp"}; + const SimUnit celsius_scaler_16k{"celsius scaler 16k"}; + const SimUnit celsius_scaler_1_256{"celsius scaler 1/256"}; + const SimUnit celsius_scaler_256{"celsius scaler 256"}; + const SimUnit celsius{"celsius"}; + const SimUnit centimeter_of_mercury{"centimeter of mercury"}; + const SimUnit centimeters_of_mercury{"centimeters of mercury"}; + const SimUnit centimeters{"centimeters"}; + const SimUnit centimeter{"centimeter"}; + const SimUnit cm2{"cm2"}; + const SimUnit cm3{"cm3"}; + const SimUnit cmHg{"cmHg"}; + const SimUnit cm{"cm"}; + const SimUnit cu_cm{"cu cm"}; + const SimUnit cu_ft{"cu ft"}; + const SimUnit cu_in{"cu in"}; + const SimUnit cu_km{"cu km"}; + const SimUnit cu_mm{"cu mm"}; + const SimUnit cu_m{"cu m"}; + const SimUnit cu_yd{"cu yd"}; + const SimUnit cubic_centimeters{"cubic centimeters"}; + const SimUnit cubic_centimeter{"cubic centimeter"}; + const SimUnit cubic_feet{"cubic feet"}; + const SimUnit cubic_foot{"cubic foot"}; + const SimUnit cubic_inches{"cubic inches"}; + const SimUnit cubic_inch{"cubic inch"}; + const SimUnit cubic_kilometers{"cubic kilometers"}; + const SimUnit cubic_kilometer{"cubic kilometer"}; + const SimUnit cubic_meters{"cubic meters"}; + const SimUnit cubic_meter{"cubic meter"}; + const SimUnit cubic_miles{"cubic miles"}; + const SimUnit cubic_mile{"cubic mile"}; + const SimUnit cubic_millimeters{"cubic millimeters"}; + const SimUnit cubic_millimeter{"cubic millimeter"}; + const SimUnit cubic_yards{"cubic yards"}; + const SimUnit cubic_yard{"cubic yard"}; + const SimUnit days{"days"}; + const SimUnit day{"day"}; + const SimUnit decibels{"decibels"}; + const SimUnit decibel{"decibel"}; + const SimUnit decimiles{"decimiles"}; + const SimUnit decimile{"decimile"}; + const SimUnit decinmiles{"decinmiles"}; + const SimUnit decinmile{"decinmile"}; + const SimUnit degree_angl16{"degree angl16"}; + const SimUnit degree_angl32{"degree angl32"}; + const SimUnit degree_latitude{"degree latitude"}; + const SimUnit degree_longitude{"degree longitude"}; + const SimUnit degree_per_second_ang16{"degree per second ang16"}; + const SimUnit degree_per_second_squared{"degree per second squared"}; + const SimUnit degree_per_second{"degree per second"}; + const SimUnit degrees_angl16{"degrees angl16"}; + const SimUnit degrees_angl32{"degrees angl32"}; + const SimUnit degrees_latitude{"degrees latitude"}; + const SimUnit degrees_longitude{"degrees longitude"}; + const SimUnit degrees_per_second_ang16{"degrees per second ang16"}; + const SimUnit degrees_per_second_squared{"degrees per second squared"}; + const SimUnit degrees_per_second{"degrees per second"}; + const SimUnit degrees{"degrees"}; + const SimUnit degree{"degree"}; + const SimUnit fahrenheit{"fahrenheit"}; + const SimUnit farenheit{"farenheit"}; + const SimUnit feet_minute{"feet/minute"}; + const SimUnit feet_per_minute{"feet per minute"}; + const SimUnit feet_per_second_squared{"feet per second squared"}; + const SimUnit feet_per_second{"feet per second"}; + const SimUnit feet_second{"feet/second"}; + const SimUnit feet{"feet"}; + const SimUnit flags{"flags"}; + const SimUnit foot_per_second_squared{"foot per second squared"}; + const SimUnit foot_pound_{"foot-pound"}; + const SimUnit foot_pounds_{"foot pounds"}; + const SimUnit foot_pounds{"foot-pounds"}; + const SimUnit foot_pound{"foot pound"}; + const SimUnit foot{"foot"}; + const SimUnit fs7_charging_amps{"fs7 charging amps"}; + const SimUnit fs7_oil_quantity{"fs7 oil quantity"}; + const SimUnit ft2{"ft2"}; + const SimUnit ft3{"ft3"}; + const SimUnit ft_lb_per_second{"ft lb per second"}; + const SimUnit ft_lbs{"ft-lbs"}; + const SimUnit ft_min{"ft/min"}; + const SimUnit ft{"ft"}; + const SimUnit gallon_per_hour{"gallon per hour"}; + const SimUnit gallons_per_hour{"gallons per hour"}; + const SimUnit gallons{"gallons"}; + const SimUnit gallon{"gallon"}; + const SimUnit geepounds{"geepounds"}; + const SimUnit geepound{"geepound"}; + const SimUnit gph{"gph"}; + const SimUnit grads{"grads"}; + const SimUnit grad{"grad"}; + const SimUnit halfs{"halfs"}; + const SimUnit half{"half"}; + const SimUnit hectopascals{"hectopascals"}; + const SimUnit hectopascal{"hectopascal"}; + const SimUnit hour_over_10{"hour over 10"}; + const SimUnit hours_over_10{"hours over 10"}; + const SimUnit hours{"hours"}; + const SimUnit hour{"hour"}; + const SimUnit in2{"in2"}; + const SimUnit in3{"in3"}; + const SimUnit inHg_64_over_64k{"inHg 64 over 64k"}; + const SimUnit inHg{"inHg"}; + const SimUnit inch_of_mercury{"inch of mercury"}; + const SimUnit inches_of_mercury{"inches of mercury"}; + const SimUnit inches{"inches"}; + const SimUnit inch{"inch"}; + const SimUnit in{"in"}; + const SimUnit kPa{"kPa"}; + const SimUnit kelvin{"kelvin"}; + const SimUnit keyframes{"keyframes"}; + const SimUnit keyframe{"keyframe"}; + const SimUnit kgf_meters{"kgf meters"}; + const SimUnit kgf_meter{"kgf meter"}; + const SimUnit kg{"kg"}; + const SimUnit kilogram_force_per_square_centime{"kilogram force per square centimeter"}; + const SimUnit kilogram_meter_squared{"kilogram meter squared"}; + const SimUnit kilogram_meters{"kilogram meters"}; + const SimUnit kilogram_meter{"kilogram meter"}; + const SimUnit kilogram_per_cubic_meter{"kilogram per cubic meter"}; + const SimUnit kilogram_per_second{"kilogram per second"}; + const SimUnit kilograms_meter_squared{"kilograms meter squared"}; + const SimUnit kilograms_per_cubic_meter{"kilograms per cubic meter"}; + const SimUnit kilograms_per_second{"kilograms per second"}; + const SimUnit kilograms{"kilograms"}; + const SimUnit kilogram{"kilogram"}; + const SimUnit kilometer_hour{"kilometer/hour"}; + const SimUnit kilometer_per_hour{"kilometer per hour"}; + const SimUnit kilometers_hour{"kilometers/hour"}; + const SimUnit kilometers_per_hour{"kilometers per hour"}; + const SimUnit kilometers{"kilometers"}; + const SimUnit kilometer{"kilometer"}; + const SimUnit kilopascal{"kilopascal"}; + const SimUnit km2{"km2"}; + const SimUnit km3{"km3"}; + const SimUnit km{"km"}; + const SimUnit knot_scaler_128{"knot scaler 128"}; + const SimUnit knots_scaler_128{"knots scaler 128"}; + const SimUnit knots{"knots"}; + const SimUnit knot{"knot"}; + const SimUnit kph{"kph"}; + const SimUnit lbf_feet{"lbf-feet"}; + const SimUnit lbs{"lbs"}; + const SimUnit liter_per_hour{"liter per hour"}; + const SimUnit liters_per_hour{"liters per hour"}; + const SimUnit liters{"liters"}; + const SimUnit liter{"liter"}; + const SimUnit m2{"m2"}; + const SimUnit m3{"m3"}; + const SimUnit m_s{"m/s"}; + const SimUnit mach_3d2_over_64k{"mach 3d2 over 64k"}; + const SimUnit machs{"machs"}; + const SimUnit mach{"mach"}; + const SimUnit mask{"mask"}; + const SimUnit mbars{"mbars"}; + const SimUnit mbar{"mbar"}; + const SimUnit meter_cubed_per_second{"meter cubed per second"}; + const SimUnit meter_cubed{"meter cubed"}; + const SimUnit meter_latitude{"meter latitude"}; + const SimUnit meter_per_minute{"meter per minute"}; + const SimUnit meter_per_second_scaler_256{"meter per second scaler 256"}; + const SimUnit meter_per_second_squared{"meter per second squared"}; + const SimUnit meter_per_second{"meter per second"}; + const SimUnit meter_scaler_256{"meter scaler 256"}; + const SimUnit meter_second{"meter/second"}; + const SimUnit meters_cubed_per_second{"meters cubed per second"}; + const SimUnit meters_cubed{"meters cubed"}; + const SimUnit meters_latitude{"meters latitude"}; + const SimUnit meters_per_minute{"meters per minute"}; + const SimUnit meters_per_second_scaler_256{"meters per second scaler 256"}; + const SimUnit meters_per_second_squared{"meters per second squared"}; + const SimUnit meters_per_second{"meters per second"}; + const SimUnit meters_scaler_256{"meters scaler 256"}; + const SimUnit meters_second{"meters/second"}; + const SimUnit meters{"meters"}; + const SimUnit meter{"meter"}; + const SimUnit mile_per_hour{"mile per hour"}; + const SimUnit miles_per_hour{"miles per hour"}; + const SimUnit miles{"miles"}; + const SimUnit mile{"mile"}; + const SimUnit millibar_scaler_16{"millibar scaler 16"}; + const SimUnit millibars_scaler_16{"millibars scaler 16"}; + const SimUnit millibars{"millibars"}; + const SimUnit millibar{"millibar"}; + const SimUnit millimeter_of_mercury{"millimeter of mercury"}; + const SimUnit millimeter_of_water{"millimeter of water"}; + const SimUnit millimeters_of_mercury{"millimeters of mercury"}; + const SimUnit millimeters_of_water{"millimeters of water"}; + const SimUnit millimeters{"millimeters"}; + const SimUnit millimeter{"millimeter"}; + const SimUnit minute_per_round{"minute per round"}; + const SimUnit minutes_per_round{"minutes per round"}; + const SimUnit minutes{"minutes"}; + const SimUnit minute{"minute"}; + const SimUnit mm2{"mm2"}; + const SimUnit mm3{"mm3"}; + const SimUnit mmHg{"mmHg"}; + const SimUnit more_than_a_half{"more_than_a_half"}; + const SimUnit mph{"mph"}; + const SimUnit m{"m"}; + const SimUnit nautical_miles{"nautical miles"}; + const SimUnit nautical_mile{"nautical mile"}; + const SimUnit newton_meters{"newton meters"}; + const SimUnit newton_meter{"newton meter"}; + const SimUnit newton_per_square_meter{"newton per square meter"}; + const SimUnit newtons_per_square_meter{"newtons per square meter"}; + const SimUnit nice_minute_per_round{"nice minute per round"}; + const SimUnit nice_minutes_per_round{"nice minutes per round"}; + const SimUnit nmiles{"nmiles"}; + const SimUnit nmile{"nmile"}; + const SimUnit numbers_{"numbers"}; + const SimUnit number{"number"}; + const SimUnit part{"part"}; + const SimUnit pascals{"pascals"}; + const SimUnit pascal{"pascal"}; + const SimUnit per_degree{"per degree"}; + const SimUnit per_hour{"per hour"}; + const SimUnit per_minute{"per minute"}; + const SimUnit per_radian{"per radian"}; + const SimUnit per_second{"per second"}; + const SimUnit percent_over_100{"percent over 100"}; + const SimUnit percent_scaler_16k{"percent scaler 16k"}; + const SimUnit percent_scaler_2pow23{"percent scaler 2pow23"}; + const SimUnit percent_scaler_32k{"percent scaler 32k"}; + const SimUnit percentage{"percentage"}; + const SimUnit percent{"percent"}; + const SimUnit position_128{"position 128"}; + const SimUnit position_16k{"position 16k"}; + const SimUnit position_32k{"position 32k"}; + const SimUnit position{"position"}; + const SimUnit pound_force_per_square_foot{"pound-force per square foot"}; + const SimUnit pound_force_per_square_inch{"pound-force per square inch"}; + const SimUnit pound_per_hour{"pound per hour"}; + const SimUnit pound_scaler_256{"pound scaler 256"}; + const SimUnit poundal_feet{"poundal feet"}; + const SimUnit pounds_per_hour{"pounds per hour"}; + const SimUnit pounds_scaler_256{"pounds scaler 256"}; + const SimUnit pounds{"pounds"}; + const SimUnit pound{"pound"}; + const SimUnit pph{"pph"}; + const SimUnit psf_scaler_16k{"psf scaler 16k"}; + const SimUnit psf{"psf"}; + const SimUnit psi_4_over_16k{"psi 4 over 16k"}; + const SimUnit psi_fs7_oil_pressure{"psi fs7 oil pressure"}; + const SimUnit psi_scaler_16k{"psi scaler 16k"}; + const SimUnit psi{"psi"}; + const SimUnit quarts{"quarts"}; + const SimUnit quart{"quart"}; + const SimUnit radian_per_second_squared{"radian per second squared"}; + const SimUnit radian_per_second{"radian per second"}; + const SimUnit radians_per_second_squared{"radians per second squared"}; + const SimUnit radians_per_second{"radians per second"}; + const SimUnit radians{"radians"}; + const SimUnit radian{"radian"}; + const SimUnit rankine{"rankine"}; + const SimUnit ratio{"ratio"}; + const SimUnit revolution_per_minute{"revolution per minute"}; + const SimUnit revolutions_per_minute{"revolutions per minute"}; + const SimUnit rounds{"rounds"}; + const SimUnit round{"round"}; + const SimUnit rpm_1_over_16k{"rpm 1 over 16k"}; + const SimUnit rpms{"rpms"}; + const SimUnit rpm{"rpm"}; + const SimUnit scaler{"scaler"}; + const SimUnit seconds{"seconds"}; + const SimUnit second{"second"}; + const SimUnit slug_feet_squared{"slug feet squared"}; + const SimUnit slugs_feet_squared{"slugs feet squared"}; + const SimUnit slugs{"slugs"}; + const SimUnit slug{"slug"}; + const SimUnit sq_cm{"sq cm"}; + const SimUnit sq_ft{"sq ft"}; + const SimUnit sq_in{"sq in"}; + const SimUnit sq_km{"sq km"}; + const SimUnit sq_mm{"sq mm"}; + const SimUnit sq_m{"sq m"}; + const SimUnit sq_yd{"sq yd"}; + const SimUnit square_centimeters{"square centimeters"}; + const SimUnit square_centimeter{"square centimeter"}; + const SimUnit square_feet{"square feet"}; + const SimUnit square_foot{"square foot"}; + const SimUnit square_inches{"square inches"}; + const SimUnit square_inch{"square inch"}; + const SimUnit square_kilometers{"square kilometers"}; + const SimUnit square_kilometer{"square kilometer"}; + const SimUnit square_meters{"square meters"}; + const SimUnit square_meter{"square meter"}; + const SimUnit square_miles{"square miles"}; + const SimUnit square_mile{"square mile"}; + const SimUnit square_millimeters{"square millimeters"}; + const SimUnit square_millimeter{"square millimeter"}; + const SimUnit square_yards{"square yards"}; + const SimUnit square_yard{"square yard"}; + const SimUnit thirds{"thirds"}; + const SimUnit third{"third"}; + const SimUnit times{"times"}; + const SimUnit volts{"volts"}; + const SimUnit volt{"volt"}; + const SimUnit yards{"yards"}; + const SimUnit yard{"yard"}; + const SimUnit yd2{"yd2"}; + const SimUnit yd3{"yd3"}; + const SimUnit years{"years"}; + const SimUnit year{"year"}; +}; + +// Global instance of the SimUnits class +inline SimUnits UNITS{}; + +#endif // FLYBYWIRE_UNITS_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/SimconnectExceptionStrings.h b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/SimconnectExceptionStrings.h new file mode 100644 index 00000000000..6a2c5f5c6e9 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/SimconnectExceptionStrings.h @@ -0,0 +1,99 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_SIMCONNECTEXCEPTIONSTRINGS_H +#define FLYBYWIRE_SIMCONNECTEXCEPTIONSTRINGS_H + +#include +#include + +/** + * @brief This class is used to get a string representation of a SimConnect exception. + */ +class SimconnectExceptionStrings { + public: + static std::string 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"; + } + }; +}; + +#endif // FLYBYWIRE_SIMCONNECTEXCEPTIONSTRINGS_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/README.md b/fbw-common/src/wasm/cpp-msfs-framework/README.md new file mode 100644 index 00000000000..96bf7fb001e --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/README.md @@ -0,0 +1,453 @@ +# FlybyWire Simulations - C++ WASM framework + +A lightweight framework to abstract the most common aspects when developing +C++ WASM modules using the MSFS SDK and SimConnect. + +See [GUIDELINES.md](https://github.com/flybywiresim/a32nx/blob/cpp-msfs-framework/fbw-common/src/wasm/extra-backend/GUIDELINES.md) +for more information on how to write good C++ code for FlyByWire Simulations. + +## Purpose + +The purpose of this framework is to provide a lightweight abstraction layer +to the MSFS SDK and SimConnect for FlyByWire which encapsulates the most common +aspects of the SDK and SimConnect in C++ objects. This allows developers to focus +on the implementation of the actual module without having to worry about the +boilerplate code required to get the module up and running. + +It also helps to avoid doubling of code, variables and multiple calls to retrieve +the same data from the simulator. + +On of the main purposes of this framework is to avoid multiple WASM files which +have to be compiled by MSFS at the start of the flight when files have been +updated. Every file adds a significant overhead to the startup time of a flight +in MSFS. Having fewer wasm files is more efficient and allows for faster startup +times. + +## Goals + +This framework will not cover all aspects of MSFS SDK or SimConnect, but it will +also not limit the developer to use the full SDK or SimConnect directly. +The goal is to make easy things easy and hard things possible. + +It is not aimed at any specific use case or systems - it does not abstract the +aircraft or its systems. This will be done in the actual modules. + +It helps new developers to get started with C++ WASM development in the FlyByWire +Code base without an overwhelming incomprehensible framework. + +The framework should be continuously improved to make it easier to use and more +powerful for additional use cases without making it overly complex. + +## Overview and Features + +The framework is split into two parts: + +### Gauge and Modules +These components simplify setting up a C++ WASM module and provide a simple API +to implement a module with all necessary boilerplate code. + +It allows to avoid having multiple WASM files which all add to the startup time +of the flight in MSFS. + +With this framework it is easy to have multiple gauges and WASM modules in one +WASM file. See details below. + +This part does not take care of any data or logic from or to the simulator. If +a developer chooses to only use this part of the framework, MSFS SDK and +SimConnect have to be used directly. + +These components live in the aircraft src folders. + +Details see below. + +### MsfsHandler and DataManager / Data Objects +MsfsHandler and DataManager are the central components which provide a simple +API to retrieve and send data from and to the simulator. + +The MsfsHandler is the central component acting as a dispatcher for the custom +modules. It manages the SimConnect connection, all module updates, owns the +DataManager and provides some imported core data variables to the modules. + +The DataManager is a central data store which allows to store and retrieve data +from the simulator. It provides different kind of data objects / variables which abstract the +sim's data types and allows to easily retrieve and send data from the simulator. +One of its main features is de-duplication of variables over all modules and +to automatically update (read/write) the data to and from the simulator. + +These components live in the common src folder. + +Details see below. + +## Components + +### Gauge +A gauge is the central entry point for the simulator into the WASM module. +It basically provides a callback function the sim calls with different messages +(service_ids) which will be handled accordingly. + +In this framework the gauge code can be found in the Gauge_Extra_Backend.cpp file:
+/fbw-a32nx/src/wasm/extra-backend-a32nx/src/Gauge_Extra_Backend.cpp
+or +/fbw-a380x/src/wasm/extra-backend-a380x/src/Gauge_Extra_Backend.cpp + +Gauges need to be configured into the panel.cfg file:
+flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/panel.cfg +or the corresponding panel.cfg file of the A380X. + +The Gauge_Extra_Backend.cpp also instantiates the MsfsHandler and the custom +modules - this is the only place a new module has to be added. + +```cpp +... +MsfsHandler msfsHandler("Gauge_Extra_Backend_A32NX", "A32NX_"); + +// ADD ADDITIONAL MODULES HERE +// This is the only place these have to be added - everything else is handled automatically +LightingPresets_A32NX lightingPresets(msfsHandler); +Pushback pushback(msfsHandler); +AircraftPresets aircraftPresets(msfsHandler, AircraftPresetProcedures_A32NX::aircraftProcedureDefinition); +... +``` + +It is not expected that a Module-developer will have to modify the gauge other +than adding new Modules. + +Also see: +- [MSFS SDK Documentation: C/C++ GAUGES](https://docs.flightsimulator.com/html/Content_Configuration/SimObjects/Aircraft_SimO/Instruments/C_C++_Gauges.htm?rhhlterm=_gauge_callback&rhsearch=_gauge_callback) + +### MsfsHandler +fbw-common/src/wasm/cpp-msfs-framework/MsfsHandler/MsfsHandler.h + +The MsfsHandler is the central component acts as a dispatcher for the custom +modules. It manages the SimConnect connection, all module updates, owns the +DataManager and provides some imported core data variables to the modules. + +Each module has to be registered with the MsfsHandler (done automatically in the +Module's constructor). The MsfsHandler will then call the update functions of the +module and provides access to the DataManager and the raw sim-data if required. + +It provides the following calls to the module at the appropriate time: +- initialize() - called once at the start of the flight session +- preUpdate() - called before the update() call +- update() - called every frame +- postUpdate() - called after the update() call +- shutdown() - called once at the end of the flight session + +It is not expected that a Module-developer will have to modify the MsfsHandler. + +### DataManager + +The DataManager is a central data store which allows to store and retrieve data +from the simulator. It provides different kinds of variables which abstract the +various sim SDK API elements and data types into C++ objects. + +It currently provides the following data types: + +- **AircraftVariable:** a variable which is directly mapped to a simvar. +- **NamedVariable**: a variable which is mapped to a LVAR. +- **DataDefinitionVariable**: Custom defined SimObjects base on simvars and custom + C++ structs (with SU12 also LVARs can be part of the data definition). +- **ClientDataAreaVariable**: Custom defined SimObjects base on memory mapped data to + exchange arbitrary data between SimConnect clients. +- **StreamingClientDataAreaVariable**: Custom defined SimObjects base on memory mapped + which can be larger than the limit of 8k bytes per ClientDataArea by using a + streaming buffer approach to send and retrieve data. +- **ClientEvent**: These events are used to either create a custom event or to be mapped + to a sim event or sim system event. The main feature of a ClientEvent is that it + has a unique ID which can be used to map and recognize the event. Callbacks can + then be registered to be called when the event is triggered. + +Also, it allows to register callback functions for KeyEvents. + +The below described data types can be created via the DataManager's make_... functions. + +#### DataObjectBase (abstract base class) + +The base class for all data objects providing the variable's name. + +#### ManagedDataObjectBase (abstract base class) +MSFS SDK and SimConnect provide different kinds of variables each with different +APIs on how to read and write them to the sim. + +The idea of variables in this framework is to provide a relatively consistent +interface to the data from the sim. + +Each variable has various ways to be updated and written back to the sim: + +- Manual read/write: The developer can manually read and write the variable from and to the sim at any time +- Auto read: The variable can be configured to be automatically read from the sim (preUpdate) via the DataManager +- Auto write: The variable can be configured to be automatically written to the sim (postUpdate) via the DataManager +- Max Age in Ticks: The variable can be configured to be automatically read from the sim + if it is older than a certain number of ticks (preUpdate) +- Max Age in Seconds: The variable can be configured to be automatically read from the sim if + it is older than a certain number of seconds (preUpdate) + +This base class also provides the means to register and remove callbacks for updates +to the variable. Any time the variable is read and has changed the callbacks will be fired. + +See the documentation of ManagedDataObjectBase for more details. + +##### CacheableVariable (abstract base class) +The CacheableVariable is the base class for AircraftVariable and NamedVariable +which can be cached to avoid multiple calls to the sim for the same variable. + +It is still possible to explicitly read and write the variable from and to the +sim if required. + +**Reading** + +| method | description | +|:-----------------|:---------------------------------------------------------------------------------------------------------| +| get() | Returns cached value - never reads directly from sim | +| updateFromSim() | Returns updated cached value - reads once per tick from sim if update criteria are met (maxAge) | +| readFromSim() | Reads the value from the sim, updates cache, clears dirty flag - does not check update criteria (maxAge) | +| rawReadFromSim() | The raw MSFS SDK call to read the different variable types from the sim. | + +See the documentation of CacheableVariable for more details. + +**Writing** + +| method | description | +|--------------------|----------------------------------------------------------------------------------------------------------------------| +| set() | Sets cached value - never writes directly to sim - sets dirty flag if set with a different value as the cached value | +| updateToSim() | Updates a value to the sim if it is dirty. | +| writeToSim() | Writes the current cached value to the sim. Clears the dirty flag. | +| setAndWriteToSim() | Sets the current value and writes it to the sim. Clears the dirty flag. | +| rawWriteToSim() | The raw MSFS SDK call to write the sim. | + +See the documentation of CacheableVariable for more details. + +##### NamedVariable +The NamedVariable is a variable which is mapped to a LVAR (Local Variable). It is the simplest +variable type and can be used to store and retrieve custom numeric data from the +sim. + +It is based on the CacheableVariable - see above. + +OBS: A prefix is added to the variable name to distinguish different aircraft +(e.g. A32NX_ or A380X_). + +The prefix is set via the static variable `AIRCRAFT_PREFIX` in the NamedVariable class. +It is set by the MsfsHandler class' constructor. + +_Author note: this is done because of a team decision for this convention. The author does not think +such a preset should be part of the framework API._ + +##### AircraftVariable +The AircraftVariable is a variable which is mapped to an aircraft simvar. As simvars +are read-only it is required to use an event to write the variable back to the sim. + +It allows to specify either an event-name or an instance of an ClientEvent object to +write data back to the sim. + +It is based on the CacheableVariable - see above. + +No prefix is added to the variable name. + +#### SimObjectBase (abstract base class) + +The SimObjectBase is the base class for all custom SimObjects. + +One major difference of SimObjects to Named or Aircraft Variables is that SimObjects +are read asynchronously from the sim. This means that the data is not available at the +time of the call to the read function. Instead, the data is received via SimConnect Callback +in the next tick. Although this is all handled automatically by the DataManager it is +important to understand this difference. + +**Reading** + +| method | description | +|:-----------------------|:-----------------------------------------------------------------------------------------| +| requestDataFromSim() | Sends a data request to the sim | +| requestUpdateFromSim() | Sends a data request to the sim if update criteria are met (maxAge) | +| processSimData() | The callback the DataManager used when the requested data has been received from the sim | + +See the documentation of CacheableVariable for more details. + +**Writing** + +| method | description | +|-------------------|---------------------------------------------------| +| writeDataToSim() | Write the current data struct contents to the sim | + +##### DataDefinitionVariable (Custom SimObjects) +The DataDefinitionVariable is a variable (in fact a data structure) which is mapped +to a custom data struct and a SimObject which can be defined by adding separate +data definitions for single sim variables to a container of data definitions. + +It requires a local data structure as a template type which is then used to hold the data. + +The class is based on ManagedDataObjectBase and therefore supports auto reading and writing of +the data to the sim. It also supports using the SIMCONNECT_PERIOD flags to update the +data by using this method to request the data: requestPeriodicUpdateFromSim().

+ +As data definition sim objects use memory mapped data between clients they are +very efficient but a bit harder to set up and use. + +A data definition variable consisting of only writable simvars can be used to +write data back to the sim without the need to define an event. + +Writing back a read only simvar will produce a SimConnect exception (visible in the console) +in the next update tick. + +See the DataDefinitionVariable class documentation for more details. + +A DataDefinitionVariable requires unique IDs for the data definition and the +request. These IDs are used to identify the data definition and the data received +from the sim. The DataManager will create these variables, and it will automatically +assign unique IDs. + +Also see: +- Example and Pushback modules have examples of custom writable sim objects +- [MSFS SDK Documentation: SimConnect Data Definition](https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_AddToClientDataDefinition.htm) + +#### ClientDataAreaVariable (Custom Data Area) +SimConnect also allows to define custom SimObjects using memory mapped data +between clients to send and receive arbitrary data to and from the sim. + +It requires a local data struct as a template type which is used to hold the data. + +The client owning the data area is responsible for creating and managing the +data area whereas the other clients can only read and write to the data area. + +As client data areas use memory mapped data between clients they are +very efficient but a bit harder to set up and use. + +See the ClientDataAreaVariable class documentation for more details. + +A ClientDataAreaVariable requires unique IDs for the data area, the data definition +and the request. These IDs are used to identify the data definition and the data +received from the sim. The DataManager will create these variables, and it will automatically +assign unique IDs. + +Also see: +- [MSFS SDK Documentation: SimConnect_MapClientDataNameToID](https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_MapClientDataNameToID.htm) + +#### StreamingClientDataAreaVariable + +The StreamingClientDataAreaVariable class is a special variant of the ClientDataAreaVariable +class which allows to send and receive data larger than the maximum size of a single +SimConnect client data area chunk of 8192 bytes. + +The data is split into chunks of a fixed size (default 8192 bytes) and sent and +received in chunks. + +The data is stored in a vector of the given type T, which is resized to the number of +bytes expected to be received. + +Before receiving data the reserve() method must be called to reset the data and set +the number of bytes to be received. + +See the StreamingClientDataAreaVariable class documentation for more details. + +#### ClientEvent +The ClientEvent class represents a client event which can be used to:
+- create a custom event (between simconnect clients) +- be mapped to a sim event +- be mapped to a system event + + A ClientEvent has a unique id and a name. The name can be used to map the event to a sim event or + to create a custom event. + + Custom events must have a name that contains a period (e.g. "Custom.Event") to the sim recognizes + it as a custom event. + + To map to sim events the name must be identical to the name of the sim event otherwise there will be + a SimConnect exception that the event is unknown. + + If the ClientEvent is intended to be used as a system event then it must be constructed with the + registerToSim parameter set to false. This will prevent the event from being registered to the sim. + +See the DataManager::make_xxx_event() functions for more details. + +#### Input Event +Input events can be added and mapped to an Event instance to be triggered by the +defined input events. + +See [MSFS SDK Documentation: SimConnect_MapInputEventToClientEvent](https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_MapInputEventToClientEvent.htm) +for how to define input events. + +These input events can be clustered in groups to be able to enable and disable a set of multiple +input events at once. Use Event::setInputGroupState() to enable or disable a group of input events. + +OBS: There are still some inconsistencies in the MSFS SDK regarding input events, esp. when removing +and re-adding input events. It is recommended to only add input events once and not remove them. + +For details see the ClientEvent class documentation. + +#### Key Event +A Key Event is not a data type which can be created. Use the DataManager to register a callback +to handle key events (addKeyEventCallback). The callback will be called with the key event data. + +For details see the DataManager class documentation. + +#### Mouse Events +Not yet supported. + +## Example Code +Good examples of how to use the framework can be found in the modules: + +- ExampleModule + - Is used to demonstrate various features of the framework and also to debug + and test it. It is not meant to be used as a real module but rather as a + playground to test and learn how to use the framework. + +- LightingPresets + - Uses NamedVariable and writeable AircraftVariables + - Uses the one update tick set read a request as set the light levels accordingly. + +- Pushback + - Uses NamedVariable, writeable AircraftVariables and DataDefinitionVariable + - The NamesVariables and AircraftVariables are used to control the pushback process. + - Events and DataDefinitionVariables are used to actually control the pushback + movement + +- AircraftPresets + - Uses NamedVariable and writeable AircraftVariables to control the loading of + aircraft presets + - Uses several update ticks to load the preset and set the various variables + accordingly + - Uses the MSFS SDK API call to execute calculator code directly for reading + and setting the state of aircraft systems. + +## Building +Assuming you are able to build the aircraft as a whole this describes how to add +a new module (classes/headers) to the project. + +The framework code is split into three parts: +- the common c++ framework code which lives in /fbw-common/src/wasm/cpp-msfs-framework +- the instance of a coomon backend using the c++ framework is in /fbw-common/src/wasm/extra-backend +- the aircraft specific gauge and modules which live in /fbw-a32nx/src/wasm/extra-backend-a32nx + and /fbw-a380x/src/wasm/extra-backend-a380x + +When adding new modules please place them in a new folder in the aircraft's extra-backend folder:
+E.g. /fbw-a32nx/src/wasm/extra-backend-a32nx + +Add it to the following files: +- /fbw-a32nx/src/wasm/extra-backend-a32nx/CMakeLists.txt +- /fbw-a32nx/src/wasm/extra-backend-a32nx/src/Gauge_Extra_Backend.cpp +(or the A380X equivalents) + +To build it separately you can use the following commands: + +```pwsh +.\scripts\dev-env\run.cmd npm run build:cpp-wasm-cmake +``` + +or + +```pwsh +.\scripts\dev-env\run.cmd npm run build:cpp-wasm-cmake-clean +``` + +If you want debug information in the build use:
+ +```pwsh +.\scripts\dev-env\run.cmd npm run build:cpp-wasm-cmake-debug +``` + +or + +```pwsh +.\scripts\dev-env\run.cmd npm run build:cpp-wasm-cmake-debug-clean +``` diff --git a/fbw-common/src/wasm/cpp-msfs-framework/cmake/TargetDefinition.cmake b/fbw-common/src/wasm/cpp-msfs-framework/cmake/TargetDefinition.cmake new file mode 100644 index 00000000000..d3179eff86a --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/cmake/TargetDefinition.cmake @@ -0,0 +1,55 @@ +# @brief Creates an optimized WASM library +# @param TARGET the library name +# @param DEPENDENCIES the library dependencies +macro(add_wasm_library) + set(target NAME) + set(dependencies DEPENDENCIES) + cmake_parse_arguments(ADD_WASM_LIBRARY "" "${target}" "${dependencies}" ${ARGN}) + + # create the list of the created object files + set(OBJECT_FILES) + foreach (arg IN ITEMS ${ADD_WASM_LIBRARY_DEPENDENCIES}) + list(APPEND OBJECT_FILES $) + endforeach() + + # wasm-ld general flags + set(CMAKE_WASM_LINKER_FLAGS --no-entry --allow-undefined --export __wasm_call_ctors --export-dynamic --export malloc --export free --export-table --gc-sections -lc++ -lc++abi -L${MSFS_SDK}/WASM/wasi-sysroot/lib/wasm32-wasi -lc ${MSFS_SDK}/WASM/wasi-sysroot/lib/wasm32-wasi/libclang_rt.builtins-wasm32.a) + + # wasm build options for debug and release + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(WASM_LD_ARGS -O0) + set(WASM_OPT_FLAGS -O0) + # to enable debugging wasm-opt must not run at all + # we use `cp` to copy the unoptimized wasm file to the output directory + set(WASM_OPT_FULL_CMD cp ${CMAKE_CURRENT_BINARY_DIR}/${ADD_WASM_LIBRARY_NAME}.wasm ${OUTPUT_DIRECTORY}/) + else() + # FIXME: 20231111 setting optimization to other than -o0 causes the wasm to crash + set(WASM_LD_ARGS -O2 --lto-O2 --strip-debug) + set(WASM_OPT_FLAGS -O1 --signext-lowering) + set(WASM_OPT_FULL_CMD ${CMAKE_WASM_OPTIMIZER} ${WASM_OPT_FLAGS} -o ${OUTPUT_DIRECTORY}/${ADD_WASM_LIBRARY_NAME}.wasm ${CMAKE_CURRENT_BINARY_DIR}/${ADD_WASM_LIBRARY_NAME}.wasm) + endif() + + # create the custom command to create the wasm library + add_custom_command( + OUTPUT always_rebuild + # OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${ADD_WASM_LIBRARY_NAME}.wasm + COMMAND ${CMAKE_WASM_LINKER} ${WASM_LD_ARGS} ${CMAKE_WASM_LINKER_FLAGS} ${OBJECT_FILES} -o ${CMAKE_CURRENT_BINARY_DIR}/${ADD_WASM_LIBRARY_NAME}.wasm + DEPENDS ${ADD_WASM_LIBRARY_DEPENDENCIES} + COMMENT "Compiling WASM library ${ADD_WASM_LIBRARY_NAME}" + COMMAND_EXPAND_LISTS + ) + + # create the optimized wasm library + add_custom_command( + OUTPUT ${OUTPUT_DIRECTORY}/${ADD_WASM_LIBRARY_NAME}.wasm + COMMAND ${WASM_OPT_FULL_CMD} + DEPENDS always_rebuild + COMMENT "Compiling optimized WASM library ${ADD_WASM_LIBRARY_NAME}" + ) + + # define the target + add_custom_target(${ADD_WASM_LIBRARY_NAME}_wasm ALL + SOURCES ${OUTPUT_DIRECTORY}/${ADD_WASM_LIBRARY_NAME}.wasm + DEPENDS always_rebuild + ) +endmacro() diff --git a/fbw-common/src/wasm/cpp-msfs-framework/lib/Callback.h b/fbw-common/src/wasm/cpp-msfs-framework/lib/Callback.h new file mode 100644 index 00000000000..ffc59802add --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/lib/Callback.h @@ -0,0 +1,40 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFT_CALLBACK_H +#define FLYBYWIRE_AIRCRAFT_CALLBACK_H + +#include + +/** + * @brief Callback container to define callbacks with use of template arguments. + * @tparam T The callback's signature + */ +template +struct Callback; + +/** + * @brief Callback container to define callbacks with use of template arguments. + * + * This allows for static callbacks to be used with member functions. + * + * @tparam Ret The callback's return type + * @tparam Params The callback's parameters as a variadic list + * @See https://blog.mbedded.ninja/programming/languages/c-plus-plus/callbacks/#static-variables-with-templating + */ +template +struct Callback { + template + static Ret callback(Args... args) { + return func(args...); + } + + static std::function func; + + std::function function; +}; + +template +std::function Callback::func; + +#endif // FLYBYWIRE_AIRCRAFT_CALLBACK_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/lib/IDGenerator.h b/fbw-common/src/wasm/cpp-msfs-framework/lib/IDGenerator.h new file mode 100644 index 00000000000..87dfee9f8f4 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/lib/IDGenerator.h @@ -0,0 +1,24 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_IDGENERATOR_H +#define FLYBYWIRE_IDGENERATOR_H + +#include + +/** + * @brief The IDGenerator class is used to generate unique IDs for the modules. + * + * Largest possible value is 2^64 - 1 (uint_64_t) then it wraps around. + * Uniqueness is only guaranteed within the same instance of this class. + * It is used to identify the modules in the MSFS gauges system. + */ +class IDGenerator { + private: + uint64_t nextId = 0; + + public: + inline uint64_t getNextId() { return nextId++; }; +}; + +#endif // FLYBYWIRE_IDGENERATOR_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/lib/ProfileBuffer.hpp b/fbw-common/src/wasm/cpp-msfs-framework/lib/ProfileBuffer.hpp new file mode 100644 index 00000000000..18798c8e6ff --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/lib/ProfileBuffer.hpp @@ -0,0 +1,129 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFT_PROFILEBUFFER_HPP +#define FLYBYWIRE_AIRCRAFT_PROFILEBUFFER_HPP + +#include +#include +#include +#include +#include + +// required to check valid template parameters - numeric types and std::chrono::duration +template +struct is_duration : std::false_type {}; +template +struct is_duration> : std::true_type {}; +template +struct is_numeric : std::integral_constant::value> {}; +template +struct is_numeric> : std::true_type {}; + +/** + * @brief A buffer to collect a fixed number of values for profiling purposes. + * @details The buffer will collect the last n values and provide methods to + * calculate the sum, average and trimmed average of the collected values. + * @tparam T the type of the values to collect - must be numeric or a std::chrono::duration + */ +template +class ProfileBuffer { + private: + std::size_t _capacity; + std::deque _buffer; + + public: + /** + * @brief Construct a new Profile Buffer object + * @param capacity the maximum number of values to collect in the buffer + */ + explicit ProfileBuffer(std::size_t capacity) : _capacity{capacity}, _buffer{std::deque(capacity)} { + static_assert(is_numeric::value || is_duration::value, "T must be numeric or duration type"); + } + + /** + * @brief Push a new value into the buffer. + * @details If the buffer is full, the oldest value will be removed. + * @param value the value to push + */ + void push(T value) { + if (_buffer.size() == _capacity) { + _buffer.pop_front(); + } + _buffer.push_back(value); + } + + /** + * @brief Calculate the sum of all values in the buffer at the time of the call. + * @return sum of all values + */ + T sum() { return std::reduce(_buffer.begin(), _buffer.end(), T(0)); } + + /** + * @brief Calculate the average of all values in the buffer at the time of the call. + * @return average of all values with type T + */ + [[nodiscard]] inline T avg() { return sum() / _buffer.size(); } + + /** + * @brief Calculate the trimmed average of all values in the buffer at the time of the call. + * The trimmed average is calculated by creating a sorted copy of the buffer and removing the + * lowest and highest values before calculating the average. + * @param trimPercent the percentage of values to trim from the buffer before calculating the average (default: 5%) + * @return trimmed average of all values + */ + [[nodiscard]] T trimmedAverage(float trimPercent = 0.05f) { + auto sorted = _buffer; + std::sort(sorted.begin(), sorted.end()); + const std::size_t trimSize = sorted.size() * trimPercent; + return std::reduce(sorted.begin() + trimSize, sorted.end() - trimSize, T(0)) / (sorted.size() - trimSize * 2); + } + + /** + * @brief Get the minimum value in the buffer. If percentile is set, the minimum value will be calculated by + * averaging the lowest percentile values in the buffer. + * @param percentile Percentile to return, e.g. 0.05 for the 5% minimum. If no percentile is given, the minimum of all values is returned. + * @return Average value of the minimum percentile of the collected samples at the time of calling this method or the minimum of + * all samples if no percentile is given + */ + [[nodiscard]] T minimum(float percentile = 0.0f) { + if (percentile > 0.0) { + auto sorted = _buffer; + std::sort(sorted.begin(), sorted.end()); + const std::size_t trimSize = sorted.size() * percentile; + return std::reduce(sorted.begin(), sorted.begin() + trimSize, T(0)) / trimSize; + } + return *std::min_element(_buffer.begin(), _buffer.end()); + } + + /** + * @brief Get the maximum value in the buffer. If percentile is set, the maximum value will be calculated by + * averaging the highest percentile values in the buffer. + * @param percentile Percentile to return, e.g. 0.05 for the 5% maximum. If no percentile is given, the maximum of all values is returned. + * @return Average value of the maximum percentile of the collected samples at the time of calling this method or the maximum of + * all samples if no percentile is given + */ + [[nodiscard]] T maximum(float percentile = 0.0f) { + if (percentile > 0.0) { + auto sorted = _buffer; + std::sort(sorted.begin(), sorted.end()); + const std::size_t trimSize = sorted.size() * percentile; + return std::accumulate(sorted.end() - trimSize, sorted.end(), T(0)) / trimSize; + } + return *std::max_element(_buffer.begin(), _buffer.end()); + } + + /** + * @brief Get the current number of values in the buffer. + * @return Current number of values in the buffer. + */ + [[nodiscard]] std::size_t size() const { return _buffer.size(); } + + /** + * @brief Get the capacity of the buffer. + * @return Capacity of the buffer. + */ + [[nodiscard]] std::size_t capacity() const { return _capacity; } +}; + +#endif // FLYBYWIRE_AIRCRAFT_PROFILEBUFFER_HPP diff --git a/fbw-common/src/wasm/cpp-msfs-framework/lib/ScopedTimer.hpp b/fbw-common/src/wasm/cpp-msfs-framework/lib/ScopedTimer.hpp new file mode 100644 index 00000000000..1fa3a24b8ee --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/lib/ScopedTimer.hpp @@ -0,0 +1,64 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFT_SCOPEDTIMER_HPP +#define FLYBYWIRE_AIRCRAFT_SCOPEDTIMER_HPP + +#include +#include +#include +#include + +/** + * @brief Macro to create a scoped timer. Will only create the timer if profiling is enabled. + */ +#if PROFILING +#define SCOPED_TIMER(name) ScopedTimer timer{name}; +#else +#define SCOPED_TIMER(name) void(0); +#endif + +/** + * Simple scoped timer to measure the execution time of a function. + * Use in the following way: + * + * void foo() { + * ScopedTimer timer{"foo"}; + * // do something + * } + * + * Output will be printed to std::cout as soon as the timer goes out of scope. + */ +class ScopedTimer { + using ClockType = std::chrono::high_resolution_clock; + + private: + const std::string_view _timerName{}; + const ClockType::time_point _start{}; + + public: + /** + * @brief Construct a new Scoped Timer object. Will immediately start the timer and will print the + * execution time when the object goes out of scope. + * @param timerName Name of the timer for the output + */ + ScopedTimer(const std::string_view& timerName) : _timerName{timerName}, _start{ClockType::now()} {} + + /** + * @brief Destroy the Scoped Timer object. Will print the execution time to std::cout. + */ + ~ScopedTimer() { + using namespace std::chrono; + const auto stop = ClockType::now(); + const auto duration = (stop - _start); + const auto duration_micro = duration_cast(duration).count(); + std::cout << "Timer: " << std::setw(10) << std::right << duration_micro << " microseconds" << " for " << _timerName << std::endl; + } + + ScopedTimer(const ScopedTimer&) = delete; + ScopedTimer(ScopedTimer&&) = delete; + auto operator=(const ScopedTimer&) -> ScopedTimer& = delete; + auto operator=(const ScopedTimer&&) -> ScopedTimer& = delete; +}; + +#endif // FLYBYWIRE_AIRCRAFT_SCOPEDTIMER_HPP diff --git a/fbw-common/src/wasm/cpp-msfs-framework/lib/SimpleProfiler.hpp b/fbw-common/src/wasm/cpp-msfs-framework/lib/SimpleProfiler.hpp new file mode 100644 index 00000000000..ce4d5810b1b --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/lib/SimpleProfiler.hpp @@ -0,0 +1,131 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFT_SIMPLEPROFILER_HPP +#define FLYBYWIRE_AIRCRAFT_SIMPLEPROFILER_HPP + +#include +#include +#include +#include +#include +#include + +#include "ProfileBuffer.hpp" +#include "string_utils.hpp" + +/** + * @brief Simple profiler to measure the execution time of a section of code by collecting a certain + * number of samples and averaging them to provide the average execution time. + * + * @usage + * Use in the following way:
+ * - Create a SimpleProfiler instance with a name and the number of samples to collect. (e.g. SimpleProfiler profiler{"foo", 1000};)
+ * - Start the profiler. (e.g. profiler.start();)
+ * - Execute the code to be profiled. (e.g. doSomething(); doSomethingElse();)
+ * - Stop the profiler. (e.g. profiler.stop();)
+ * - Print the average execution time. (e.g. profiler.print();)
+ * - the average will always be re-calculated from the available samples everytime this method is called.
+ * - Output will be printed to std::cout: "Profiler: 33,052 (32,898) nanoseconds for MsfsHandler::update() (avg of 120 + * samples)"
+ * - The first number is the average execution time of the collected samples at the time of calling this method.
+ * - The second number is the avg of the 5-95% samples at the time of calling this method.
+ */ +class SimpleProfiler { + using Clock = std::chrono::high_resolution_clock; + + private: + const std::string _name; + ProfileBuffer _samples; + Clock::time_point _start{}; + bool _started = false; + + public: + /** + * @brief Construct a new Simple Profiler object + * @param name Name of the profiler for the output + * @param sampleCount Maximum number of samples to collect + */ + SimpleProfiler(const std::string& name, size_t sampleCount) : _name{name}, _samples{sampleCount} {} + + /** + * @brief Start the profiler - will reset the start time if it was already started + */ + void start() { + _started = true; + _start = Clock::now(); + } + + /** + * @brief Stop the profiler and add the sample to the buffer + */ + void stop() { + if (!_started) + return; + _samples.push((Clock::now() - _start)); + _started = false; + } + + /** + * @brief Return the average execution time of the collected samples at the time of calling this method + * @return Average execution time of the collected samples at the time of calling this method + */ + [[nodiscard]] std::uint64_t getAverage() { return _samples.avg().count(); } + + /** + * @brief Return the average execution time of the collected samples at the time of calling this method + * @return Average execution time of the collected samples at the time of calling this method + */ + [[nodiscard]] std::uint64_t getTrimmedAverage(double trim) { return _samples.trimmedAverage(trim).count(); } + + /** + * @brief Return the avg minimum execution time of a percentile of the collected samples at the time of calling this method. + * If no percentile is given, the minimum of all samples is returned. + * @param percentile Percentile to return, e.g. 0.05 for the 5% minimum. If no percentile is given, the minimum of all samples is returned. + * @return Average execution time of the minimum percentile of the collected samples at the time of calling this method or the minimum of + * all samples if no percentile is given + */ + std::uint64_t getMinimum(float percentile = 0.0f) { return _samples.minimum(percentile).count(); } + + /** + * @brief Return the avg maximum execution time of a percentile of the collected samples at the time of calling this method. + * @param percentile Percentile to return, e.g. 0.95 for the 95% maximum. If no percentile is given, the maximum of all samples is returned. + * @return Average execution time of the maximum percentile of the collected samples at the time of calling this method or the maximum of + * all samples if no percentile is given + */ + std::uint64_t getMaximum(float percentile = 1.0f) { return _samples.maximum(percentile).count(); } + + /** + * @brief Return the sum of all collected samples at the time of calling this method + * @return Sum of all collected samples at the time of calling this method + */ + [[nodiscard]] std::uint64_t getSum() { return _samples.sum().count(); } + + /** + * @brief Return the number of collected samples at the time of calling this method + * @return Number of collected samples at the time of calling this method + */ + [[nodiscard]] std::size_t getSampleCount() { return _samples.size(); } + + /** + * @brief Return a string with the average execution time of the collected samples at the time of calling this method + * @return String with the average execution time of the collected samples at the time of calling this method + */ + [[nodiscard]] std::string str() { + auto avg = _samples.avg(); + std::stringstream os{}; + os << "Profiler: " << std::setw(10) << std::right << helper::StringUtils::insertThousandsSeparator(avg.count()) << " (" << std::setw(10) + << std::right << helper::StringUtils::insertThousandsSeparator(_samples.minimum().count()) << " / " << std::setw(10) << std::right + << helper::StringUtils::insertThousandsSeparator(_samples.trimmedAverage().count()) << " / " << std::setw(10) << std::right + << helper::StringUtils::insertThousandsSeparator(_samples.maximum().count()) << ")" + << " nanoseconds for " << _name << " (avg of " << _samples.size() << " samples) " << std::endl; + return os.str(); + } + + /** + * @brief Print the average execution time of the collected samples at the time of calling this method to std::cout + */ + void print() { std::cout << str() << std::endl; } +}; + +#endif // FLYBYWIRE_AIRCRAFT_SIMPLEPROFILER_HPP diff --git a/fbw-common/src/wasm/cpp-msfs-framework/lib/fingerprint.hpp b/fbw-common/src/wasm/cpp-msfs-framework/lib/fingerprint.hpp new file mode 100644 index 00000000000..c5c653030ee --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/lib/fingerprint.hpp @@ -0,0 +1,47 @@ + +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFT_FINGERPRINT_HPP +#define FLYBYWIRE_AIRCRAFT_FINGERPRINT_HPP + +#include +#include + +/** + * This class provides a fingerprint function for vectors of values. + */ +class Fingerprint { + +public: + + /** + * Fowler-Noll-Vo hash function + * @tparam T the type of the values in the provided vector + * @param vec the vector of values to hash + * @return the hash value + */ + template + static uint64_t fingerPrintFVN(const std::vector& vec) { + const uint64_t FNV_OFFSET_BASIS = 0xcbf29ce484222325ULL; + const uint64_t FNV_PRIME = 0x100000001b3ULL; + uint64_t fp = 0; + for (const auto& elem : vec) { + const T& value = elem; + uint64_t hash = FNV_OFFSET_BASIS; + const unsigned char* bytes = reinterpret_cast(&value); + for (size_t i = 0; i < sizeof(T); i++) { + hash ^= static_cast(bytes[i]); + hash *= FNV_PRIME; + } + uint64_t h = hash; + fp ^= h; + fp *= FNV_PRIME; + } + return fp; + } + +}; + + +#endif //FLYBYWIRE_AIRCRAFT_FINGERPRINT_HPP diff --git a/fbw-common/src/wasm/cpp-msfs-framework/lib/inih/ini.h b/fbw-common/src/wasm/cpp-msfs-framework/lib/inih/ini.h new file mode 100644 index 00000000000..feada18b53b --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/lib/inih/ini.h @@ -0,0 +1,612 @@ +/* +* 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 +}; // namespace INIStringUtil + +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; +} +}; // namespace INIParser + +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; + } +}; +} // namespace mINI + +#endif // MINI_INI_H_ diff --git a/fbw-common/src/wasm/cpp-msfs-framework/lib/inih/ini_type_conversion.h b/fbw-common/src/wasm/cpp-msfs-framework/lib/inih/ini_type_conversion.h new file mode 100644 index 00000000000..54b628cf278 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/lib/inih/ini_type_conversion.h @@ -0,0 +1,68 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#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/fbw-common/src/wasm/cpp-msfs-framework/lib/logging.h b/fbw-common/src/wasm/cpp-msfs-framework/lib/logging.h new file mode 100644 index 00000000000..e174b52b947 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/lib/logging.h @@ -0,0 +1,120 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_LOGGING_H +#define FLYBYWIRE_LOGGING_H + +#include + +/** + * Simple logging facility for the FlyByWire Simulations C++ WASM framework. + * No performance when turned off and only the minimum overhead when turned on to a specific level. + * + * Use in the following way: + * + * LOG_INFO("PANEL_SERVICE_PRE_INSTALL"); + * LOG_INFO("PANEL_SERVICE_PRE_INSTALL: " + panelService->getPanelServiceName()); + * + * This will be improved and extended in the future. + * E.g. it could be extended to log to a file. + */ + +#define ZERO_LVL 0 +#define CRITICAL_LVL 1 +#define ERROR_LVL 2 +#define WARN_LVL 3 +#define INFO_LVL 4 +#define DEBUG_LVL 5 +#define VERBOSE_LVL 6 +#define TRACE_LVL 7 + +#if LOG_LEVEL > ZERO_LVL +#define LOG_CRITICAL(msg) logger->critical(msg) +#define LOG_CRITICAL_BLOCK(block) block +#else +#define LOG_CRITICAL(msg) void(0) +#define LOG_CRITICAL_BLOCK(block) void(0); +#endif + +#if LOG_LEVEL > CRITICAL_LVL +#define LOG_ERROR(msg) logger->error(msg) +#define LOG_ERROR_BLOCK(block) block +#else +#define LOG_ERROR(msg) void(0) +#define LOG_ERROR_BLOCK(block) void(0); +#endif + +#if LOG_LEVEL > ERROR_LVL +#define LOG_WARN(msg) logger->warn(msg) +#define LOG_WARN_BLOCK(block) block +#else +#define LOG_WARN(msg) void(0) +#define LOG_WARN_BLOCK(block) void(0); +#endif + +#if LOG_LEVEL > WARN_LVL +#define LOG_INFO(msg) logger->info(msg) +#define LOG_INFO_BLOCK(block) block +#else +#define LOG_INFO(msg) void(0) +#define LOG_INFO_BLOCK(block) void(0); +#endif + +#if LOG_LEVEL > INFO_LVL +#define LOG_DEBUG(msg) logger->debug(msg) +#define LOG_DEBUG_BLOCK(block) block +#else +#define LOG_DEBUG(msg) void(0) +#define LOG_DEBUG_BLOCK(block) void(0); +#endif + +#if LOG_LEVEL > DEBUG_LVL +#define LOG_VERBOSE(msg) logger->verbose(msg) +#define LOG_VERBOSE_BLOCK(block) block +#else +#define LOG_VERBOSE(msg) void(0) +#define LOG_VERBOSE_BLOCK(block) void(0); +#endif + +#if LOG_LEVEL > VERBOSE_LVL +#define LOG_TRACE(msg) logger->trace(msg) +#define LOG_TRACE_BLOCK(block) block +#else +#define LOG_TRACE(msg) void(0) +#define LOG_TRACE_BLOCK(block) void(0); +#endif + +/** + * @brief The Logger class is a very simple logging facility. + * + * Singleton class for Logger + * Very simple implementation for now. + */ +class Logger { + public: + Logger() = default; + /** get the singleton instance of Logger */ + static Logger* instance() { + static Logger instance; + return &instance; + } + + public: + // disallow copies + Logger(Logger const&) = delete; // copy + Logger& operator=(const Logger&) = delete; // copy assignment + Logger(Logger const&&) = delete; // move + Logger& operator=(const Logger&&) = delete; // move assignment + + void critical(const std::string& msg) { std::cerr << "critical: " + msg; } + void error(const std::string& msg) { std::cerr << "error: " + msg; } + void warn(const std::string& msg) { std::cerr << "warn: " + msg; } + void info(const std::string& msg) { std::cout << "info: " << msg << std::endl; } + void debug(const std::string& msg) { std::cout << "debug: " << msg << std::endl; } + void verbose(const std::string& msg) { std::cout << "verbose: " << msg << std::endl; } + void trace(const std::string& msg) { std::cout << "trace: " << msg << std::endl; } +}; + +inline Logger* logger = Logger::instance(); + +#endif // FLYBYWIRE_LOGGING_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/lib/math_utils.hpp b/fbw-common/src/wasm/cpp-msfs-framework/lib/math_utils.hpp new file mode 100644 index 00000000000..98b9ab20d43 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/lib/math_utils.hpp @@ -0,0 +1,61 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_MATH_UTILS_H +#define FLYBYWIRE_MATH_UTILS_H + +#include +#include +#include + +namespace helper { + +class Math { + public: + /** + * Compares if two floating numbers are equal + * @tparam T The type of the values. Must be a floating point type. + * @param value0 The first comparable value + * @param value1 The second comparable value + * @param epsilon The epsilon which defines both values as equal + * @return true if the difference between both values is smaller or equal the epsilon + * @return false if the difference between both values is greater the epsilon + */ + template + static bool almostEqual(T value0, T value1, T epsilon = std::numeric_limits::epsilon()) { + static_assert(std::is_floating_point_v, "T must be a floating point type"); + return std::abs(value0 - value1) <= epsilon; + } + + /** + * Adds two angles with wrap around to result in 0-360° + * @param a - positive or negative angle + * @param b - positive or negative angle + */ + static double angleAdd(double a, double b) { + double r = a + b; + while (r > 360.0) { + r -= 360.0; + } + while (r < 0.0) { + r += 360.0; + } + return r; + }; + + /** + * Returns the signum (sign) of the given value. + * @tparam T + * @param val + * @return sign of value or 0 when value==0 + */ + template + static inline int sign(T x) { + return (x > 0) ? 1 : ((x < 0) ? -1 : 0); + } + +}; + +} // namespace helper + +#endif // FLYBYWIRE_MATH_UTILS_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/lib/quantity.hpp b/fbw-common/src/wasm/cpp-msfs-framework/lib/quantity.hpp new file mode 100644 index 00000000000..a4dd6660315 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/lib/quantity.hpp @@ -0,0 +1,350 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include +#include + +#include "math_utils.hpp" + +/** + * @brief Defines SI unit based quantities to validate SI units during compile time + * @tparam M Power of mass elements + * @tparam L Power of length elements + * @tparam T Power of time elements + * @tparam A Power of angular elements + */ +template +class Quantity { + private: + float m_value; + + public: + constexpr Quantity() noexcept : m_value(0.0f) {} + explicit constexpr Quantity(float value) : m_value(value) {} + + constexpr Quantity const& operator+=(const Quantity& rhs) { + this->m_value += rhs.m_value; + return *this; + } + constexpr Quantity const& operator-=(const Quantity& rhs) { + this->m_value -= rhs.m_value; + return *this; + } + + [[nodiscard]] constexpr float value() const { return this->m_value; } + constexpr void setValue(float value) { this->m_value = value; } + [[nodiscard]] constexpr float convert(const Quantity& rhs) const { return this->m_value / rhs.m_value; } + [[nodiscard]] constexpr Quantity>, + std::ratio_divide>, + std::ratio_divide>, + std::ratio_divide>> + sqrt() const { + return Quantity>, std::ratio_divide>, std::ratio_divide>, + std::ratio_divide>>(std::sqrtf(this->m_value)); + } + [[nodiscard]] constexpr Quantity abs() const { return Quantity(std::abs(this->m_value)); } +}; + +/** The mass specialization [kg] */ +typedef Quantity, std::ratio<0>, std::ratio<0>, std::ratio<0>> Mass; +/** The length specialization [m] */ +typedef Quantity, std::ratio<1>, std::ratio<0>, std::ratio<0>> Length; +/** The time specialization [s] */ +typedef Quantity, std::ratio<0>, std::ratio<1>, std::ratio<0>> Time; +/** The angle specialization [rad] */ +typedef Quantity, std::ratio<0>, std::ratio<0>, std::ratio<1>> Angle; +/** The velocity specialization [m/s] */ +typedef Quantity, std::ratio<1>, std::ratio<-1>, std::ratio<0>> Velocity; +/** The acceleration specialization [m/(s*s)] */ +typedef Quantity, std::ratio<1>, std::ratio<-2>, std::ratio<0>> Acceleration; +/** The angular velocity specialization [1/s] */ +typedef Quantity, std::ratio<0>, std::ratio<-1>, std::ratio<1>> AngularVelocity; +/** The angular acceleration specialization [1/(s*s)] */ +typedef Quantity, std::ratio<0>, std::ratio<-2>, std::ratio<1>> AngularAcceleration; + +/** + * @brief Adds rhs to lhs and returns a new instance + * @param[in] lhs The left-hand-side component + * @param[in] rhs The right-hand-side component + * @return The resulting quantity with the updated value + */ +template +constexpr Quantity operator+(const Quantity& lhs, const Quantity& rhs) { + return Quantity(lhs.value() + rhs.value()); +} +/** + * @brief Subtracts rhs to lhs and returns a new instance + * @param[in] lhs The left-hand-side component + * @param[in] rhs The right-hand-side component + * @return The resulting quantity with the updated value + */ +template +constexpr Quantity operator-(const Quantity& lhs, const Quantity& rhs) { + return Quantity(lhs.value() - rhs.value()); +} +/** + * @brief Multiplies rhs to lhs and returns a new instance + * @param[in] lhs The left-hand-side component + * @param[in] rhs The right-hand-side component + * @return The resulting quantity with the updated value + */ +template +constexpr Quantity, std::ratio_add, std::ratio_add, std::ratio_add> operator*( + const Quantity& lhs, + const Quantity& rhs) { + return Quantity, std::ratio_add, std::ratio_add, std::ratio_add>(lhs.value() * + rhs.value()); +} +/** + * @brief Multiplies lhs to rhs and returns a new instance + * @param[in] lhs The SI-unit free factor + * @param[in] rhs The right-hand-side component + * @return The resulting quantity with the updated value + */ +template +constexpr Quantity operator*(const float& lhs, const Quantity& rhs) { + return Quantity(lhs * rhs.value()); +} +/** + * @brief Multiplies rhs to lhs and returns a new instance + * @param[in] lhs The left-hand-side component + * @param[in] rhs The SI-unit free factor + * @return The resulting quantity with the updated value + */ +template +constexpr Quantity operator*(const Quantity& lhs, const float& rhs) { + return Quantity(lhs.value() * rhs); +} +/** + * @brief Divides rhs from lhs and returns a new instance + * @param[in] lhs The left-hand-side component + * @param[in] rhs The right-hand-side component + * @return The resulting quantity with the updated value + */ +template +constexpr Quantity, std::ratio_subtract, std::ratio_subtract, std::ratio_subtract> +operator/(const Quantity& lhs, const Quantity& rhs) { + return Quantity, std::ratio_subtract, std::ratio_subtract, std::ratio_subtract>( + lhs.value() / rhs.value()); +} +/** + * @brief Divides rhs from lhs and returns a new instance + * @param[in] lhs The SI-unit free factor + * @param[in] rhs The right-hand-side component + * @return The resulting quantity with the updated value + */ +template +constexpr Quantity, M>, + std::ratio_subtract, L>, + std::ratio_subtract, T>, + std::ratio_subtract, A>> +operator/(const float& lhs, const Quantity& rhs) { + return Quantity, M>, std::ratio_subtract, L>, std::ratio_subtract, T>, + std::ratio_subtract, A>>(lhs / rhs.value()); +} +/** + * @brief Multiplies lhs from rhs and returns a new instance + * @param[in] lhs The left-hand-side component + * @param[in] rhs The SI-unit free factor + * @return The resulting quantity with the updated value + */ +template +constexpr Quantity operator/(const Quantity& lhs, const float& rhs) { + return Quantity(lhs.value() / rhs); +} + +template +constexpr bool operator==(const Quantity& lhs, const Quantity& rhs) { + return true == helper::Math::almostEqual(lhs.value(), rhs.value(), 1e-8f); +} + +template +constexpr bool operator!=(const Quantity& lhs, const Quantity& rhs) { + return false == helper::Math::almostEqual(lhs.value(), rhs.value(), 1e-8f); +} + +template +constexpr bool operator<=(const Quantity& lhs, const Quantity& rhs) { + return lhs.value() <= rhs.value(); +} + +template +constexpr bool operator<(const Quantity& lhs, const Quantity& rhs) { + return lhs.value() < rhs.value(); +} + +template +constexpr bool operator>=(const Quantity& lhs, const Quantity& rhs) { + return lhs.value() >= rhs.value(); +} + +template +constexpr bool operator>(const Quantity& lhs, const Quantity& rhs) { + return lhs.value() > rhs.value(); +} + +constexpr Mass kilogram(1.0f); +constexpr Mass pound = 0.453592f * kilogram; +constexpr Mass operator"" _kg(long double value) { + return Mass(static_cast(value)); +} +constexpr Mass operator"" _kg(unsigned long long int value) { + return Mass(static_cast(value)); +} +constexpr Mass operator"" _lbs(long double value) { + return static_cast(value) * pound; +} +constexpr Mass operator"" _lbs(unsigned long long int value) { + return static_cast(value) * pound; +} + +constexpr Length metre(1.0f); +constexpr Length feet = 0.3048f * metre; +constexpr Length kilometre = 1000.0f * metre; +constexpr Length nauticmile = 1852.0f * metre; +constexpr Length operator"" _m(long double value) { + return Length(static_cast(value)); +} +constexpr Length operator"" _m(unsigned long long int value) { + return Length(static_cast(value)); +} +constexpr Length operator"" _ft(long double value) { + return static_cast(value) * feet; +} +constexpr Length operator"" _ft(unsigned long long int value) { + return static_cast(value) * feet; +} +constexpr Length operator"" _km(long double value) { + return static_cast(value) * kilometre; +} +constexpr Length operator"" _km(unsigned long long int value) { + return static_cast(value) * kilometre; +} +constexpr Length operator"" _nm(long double value) { + return static_cast(value) * nauticmile; +} +constexpr Length operator"" _nm(unsigned long long int value) { + return static_cast(value) * nauticmile; +} + +constexpr Time second(1.0f); +constexpr Time millisecond = second / 1000.0f; +constexpr Time minute = 60.0f * second; +constexpr Time hour = 60.0f * minute; +constexpr Time operator"" _ms(long double value) { + return static_cast(value) * millisecond; +} +constexpr Time operator"" _ms(unsigned long long int value) { + return static_cast(value) * millisecond; +} +constexpr Time operator"" _s(long double value) { + return static_cast(value) * second; +} +constexpr Time operator"" _s(unsigned long long int value) { + return static_cast(value) * second; +} +constexpr Time operator"" _min(long double value) { + return static_cast(value) * minute; +} +constexpr Time operator"" _min(unsigned long long int value) { + return static_cast(value) * minute; +} +constexpr Time operator"" _h(long double value) { + return static_cast(value) * hour; +} +constexpr Time operator"" _h(unsigned long long int value) { + return static_cast(value) * hour; +} + +constexpr float operator"" _pi(long double value) { + return static_cast(value) * 3.1415926535897932384626433832795f; +} +constexpr float operator"" _pi(unsigned long long int value) { + return static_cast(value) * 3.1415926535897932384626433832795f; +} + +constexpr Angle degree = Angle(1.0f); +constexpr Angle radian = 180.0f / 1_pi * degree; +constexpr Angle operator"" _rad(long double value) { + return static_cast(value) * radian; +} +constexpr Angle operator"" _rad(unsigned long long int value) { + return static_cast(value) * radian; +} +constexpr Angle operator"" _deg(long double value) { + return static_cast(value) * degree; +} +constexpr Angle operator"" _deg(unsigned long long int value) { + return static_cast(value) * degree; +} + +constexpr Velocity knot = 0.51444f * metre / second; +constexpr Velocity ftpmin = feet / minute; +constexpr Velocity operator"" _mps(long double value) { + return Velocity(static_cast(value)); +} +constexpr Velocity operator"" _mps(unsigned long long int value) { + return Velocity(static_cast(value)); +} +constexpr Velocity operator"" _ftpmin(long double value) { + return static_cast(value) * feet / minute; +} +constexpr Velocity operator"" _ftpmin(unsigned long long int value) { + return static_cast(value) * feet / minute; +} +constexpr Velocity operator"" _kmph(long double value) { + return static_cast(value) * kilometre / hour; +} +constexpr Velocity operator"" _kmph(unsigned long long int value) { + return static_cast(value) * kilometre / hour; +} +constexpr Velocity operator"" _kn(long double value) { + return static_cast(value) * knot; +} +constexpr Velocity operator"" _kn(unsigned long long int value) { + return static_cast(value) * knot; +} + +constexpr Acceleration G = 9.80665f * metre / (second * second); +constexpr Acceleration operator"" _mps2(long double value) { + return Acceleration(static_cast(value)); +} +constexpr Acceleration operator"" _mps2(unsigned long long int value) { + return Acceleration(static_cast(value)); +} +constexpr Acceleration operator"" _g(long double value) { + return static_cast(value) * G; +} +constexpr Acceleration operator"" _g(unsigned long long int value) { + return static_cast(value) * G; +} + +constexpr AngularVelocity operator"" _radps(long double value) { + return AngularVelocity(static_cast(value)); +} +constexpr AngularVelocity operator"" _radps(unsigned long long int value) { + return AngularVelocity(static_cast(value)); +} +constexpr AngularVelocity operator"" _degps(long double value) { + return static_cast(value) * degree / second; +} +constexpr AngularVelocity operator"" _degps(unsigned long long int value) { + return static_cast(value) * degree / second; +} + +constexpr AngularAcceleration operator"" _radps2(long double value) { + return AngularAcceleration(static_cast(value)); +} +constexpr AngularAcceleration operator"" _radps2(unsigned long long int value) { + return AngularAcceleration(static_cast(value)); +} +constexpr AngularAcceleration operator"" _degps2(long double value) { + return static_cast(value) * degree / (second * second); +} +constexpr AngularAcceleration operator"" _degps2(unsigned long long int value) { + return static_cast(value) * degree / (second * second); +} + +#pragma clang diagnostic pop diff --git a/fbw-common/src/wasm/cpp-msfs-framework/lib/simple_assert.h b/fbw-common/src/wasm/cpp-msfs-framework/lib/simple_assert.h new file mode 100644 index 00000000000..385b09f2d9b --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/lib/simple_assert.h @@ -0,0 +1,22 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_SIMPLE_ASSERT_H +#define FLYBYWIRE_SIMPLE_ASSERT_H + +#include + +/** + * This macro is used to assert a condition. As Microsoft Flight Simulator does + * not support exceptions, this macro will only print an error message to the console. + */ +#ifdef NDEBUG +#define SIMPLE_ASSERT(condition, message) +#else +#define SIMPLE_ASSERT(condition, message) \ + if (!(condition)) { \ + std::cerr << "Assertion failed: " << message << std::endl; \ + } +#endif + +#endif // FLYBYWIRE_SIMPLE_ASSERT_H diff --git a/fbw-common/src/wasm/cpp-msfs-framework/lib/string_utils.hpp b/fbw-common/src/wasm/cpp-msfs-framework/lib/string_utils.hpp new file mode 100644 index 00000000000..dea8d0954f2 --- /dev/null +++ b/fbw-common/src/wasm/cpp-msfs-framework/lib/string_utils.hpp @@ -0,0 +1,39 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFT_STRING_UTILS_HPP +#define FLYBYWIRE_AIRCRAFT_STRING_UTILS_HPP + +#include + +namespace helper { + +/** + * @brief Helper class for string operations. + */ +class StringUtils { +public: + /** + * @brief Inserts a thousands separator into a number. + * @tparam T type of the number + * @param n the number + * @param separator the separator to use + * @return new string with the separator + */ + template + static std::string insertThousandsSeparator(T n, const std::string_view separator = ",") { + static_assert(std::is_integral::value, "T must be an integral type"); + std::string s = std::to_string(n); + const int len = s.length(); + const int numCommas = (len - 1) / 3; + s.reserve(len + numCommas); + for (int i = len - 4; i >= 0; i -= 3) { + s.insert(i + 1, separator); + } + return s; + } +}; + +} // namespace helper + +#endif // FLYBYWIRE_AIRCRAFT_STRING_UTILS_HPP diff --git a/fbw-common/src/wasm/extra-backend/AircraftPresets/AircraftPresets.cpp b/fbw-common/src/wasm/extra-backend/AircraftPresets/AircraftPresets.cpp new file mode 100644 index 00000000000..ce353259abc --- /dev/null +++ b/fbw-common/src/wasm/extra-backend/AircraftPresets/AircraftPresets.cpp @@ -0,0 +1,199 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include "AircraftPresets.h" +#include "SimUnits.h" +#include "UpdateMode.h" +#include "logging.h" +#include "math_utils.hpp" + +/// +// DataManager Howto Note: +// ======================= +// +// The AircraftPresets module uses the DataManager to get and set variables. +// Looking at the make_xxx_var functions, you can see that they are updated +// with different update cycles. +// +// Some variables are read from the sim at every tick: +// - A32NX_LOAD_AIRCRAFT_PRESET +// - SIM ON GROUND +// +// The rest are read on demand after the state of the above variables have been checked. +// No variable is written automatically. +// +// This makes sure variables are only read or written to when really needed. And as +// AircraftPresets will be dormant most of the time, this is saving a lot of +// unnecessary reads/writes. +// +// In addition, the AircraftPresets module is a very specific use case amd uses +// SimConnect execute_calculator_code extensively for the procedures to work. +// This is a good demonstration that the Cpp WASM framework does not limit +// applications to a specific pattern. +/// + +bool AircraftPresets::initialize() { + dataManager = &msfsHandler.getDataManager(); + + // LVARs + aircraftPresetVerbose = dataManager->make_named_var("AIRCRAFT_PRESET_VERBOSE", UNITS.Bool, UpdateMode::AUTO_READ); + loadAircraftPresetRequest = dataManager->make_named_var("AIRCRAFT_PRESET_LOAD", UNITS.Number, UpdateMode::AUTO_READ_WRITE); + progressAircraftPreset = dataManager->make_named_var("AIRCRAFT_PRESET_LOAD_PROGRESS"); + progressAircraftPresetId = dataManager->make_named_var("AIRCRAFT_PRESET_LOAD_CURRENT_ID"); + loadAircraftPresetRequest->setAndWriteToSim(0); // reset to 0 on startup + + // Simvars + simOnGround = dataManager->make_simple_aircraft_var("SIM ON GROUND", UNITS.Number, true); + + _isInitialized = true; + LOG_INFO("AircraftPresets initialized"); + return true; +} + +bool AircraftPresets::update(sGaugeDrawData* pData) { + if (!_isInitialized) { + LOG_ERROR("AircraftPresets::update() - not initialized"); + return false; + } + + if (!msfsHandler.getAircraftIsReadyVar()) + return true; + + // has request to load a preset been received? + if (loadAircraftPresetRequest->getAsInt64() > 0) { + // we do not allow loading of presets in the air to prevent users from + // accidentally changing the aircraft configuration + if (!simOnGround->getAsBool()) { + LOG_WARN("AircraftPresets: Aircraft must be on the ground to load a preset!"); + loadAircraftPresetRequest->setAsInt64(0); + loadingIsActive = false; + return true; + } + + // read the progress vars once to get the current state + progressAircraftPreset->updateFromSim(msfsHandler.getTimeStamp(), msfsHandler.getTickCounter()); + progressAircraftPresetId->updateFromSim(msfsHandler.getTimeStamp(), msfsHandler.getTickCounter()); + + // check if we already have an active loading process or if this is a new request which + // needs to be initialized + if (!loadingIsActive) { + // get the requested procedure + const std::optional requestedProcedure = presetProcedures.getProcedure(loadAircraftPresetRequest->getAsInt64()); + + // check if procedure ID exists + if (!requestedProcedure.has_value()) { + LOG_WARN("AircraftPresets: Preset " + std::to_string(loadAircraftPresetRequest->getAsInt64()) + " not found!"); + loadAircraftPresetRequest->set(0); + loadingIsActive = false; + return true; + } + + // initialize new loading process + currentProcedureID = loadAircraftPresetRequest->getAsInt64(); + currentProcedure = requestedProcedure.value(); + currentLoadingTime = 0; + currentDelay = 0; + currentStep = 0; + loadingIsActive = true; + progressAircraftPreset->setAndWriteToSim(0); + progressAircraftPresetId->setAndWriteToSim(0); + LOG_INFO("AircraftPresets: Aircraft Preset " + std::to_string(currentProcedureID) + " starting procedure!"); + return true; + } + + // 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 + loadAircraftPresetRequest->setAsInt64(currentProcedureID); + + // check if all procedure steps are done and the procedure is finished + if (currentStep >= currentProcedure->size()) { + LOG_INFO("AircraftPresets: Aircraft Preset " + std::to_string(currentProcedureID) + " done!"); + progressAircraftPreset->setAndWriteToSim(0); + progressAircraftPresetId->setAndWriteToSim(0); + loadAircraftPresetRequest->set(0); + loadingIsActive = false; + return true; + } + + // update run timer + currentLoadingTime += pData->dt * 1000; + + // check if we are in a delay and return if we have to wait + if (currentLoadingTime <= currentDelay) + return true; + + // convenience tmp + const ProcedureStep* currentStepPtr = (*currentProcedure)[currentStep]; + + // calculate next delay + currentDelay = currentLoadingTime + currentStepPtr->delayAfter; + + // prepare return values for execute_calculator_code + FLOAT64 fvalue = 0.0; + SINT32 ivalue = 0; + PCSTRINGZ svalue = nullptr; + + // check if the current step is a condition step and check the condition + if (currentStepPtr->isConditional) { + // update progress var + progressAircraftPreset->setAndWriteToSim(static_cast(currentStep) / currentProcedure->size()); + progressAircraftPresetId->setAndWriteToSim(currentStepPtr->id); + execute_calculator_code(currentStepPtr->actionCode.c_str(), &fvalue, &ivalue, &svalue); + LOG_INFO("AircraftPresets: Aircraft Preset Step " + std::to_string(currentStep) + " Condition: " + currentStepPtr->description + + " (delay between tests: " + std::to_string(currentStepPtr->delayAfter) + ")"); + if (!helper::Math::almostEqual(0.0, fvalue)) { + currentDelay = 0; + currentStep++; + } + return true; + } + + // test if the next step is required or if the state is already set in + // which case the action can be skipped and delay can be ignored. + fvalue = 0; + ivalue = 0; + svalue = nullptr; + if (!currentStepPtr->expectedStateCheckCode.empty()) { + if (aircraftPresetVerbose->getAsBool()) { + std::cout << "AircraftPresets: Aircraft Preset Step " << currentStep << " Test: " << currentStepPtr->description << " TEST: \"" + << currentStepPtr->expectedStateCheckCode << "\"" << std::endl; + } + execute_calculator_code(currentStepPtr->expectedStateCheckCode.c_str(), &fvalue, &ivalue, &svalue); + if (!helper::Math::almostEqual(0.0, fvalue)) { + if (aircraftPresetVerbose->getAsBool()) { + std::cout << "AircraftPresets: Aircraft Preset Step " << currentStep << " Skipping: " << currentStepPtr->description + << " TEST: \"" << currentStepPtr->expectedStateCheckCode << "\"" << std::endl; + } + currentDelay = 0; + currentStep++; + return true; + } + } + + // update progress var + progressAircraftPreset->setAndWriteToSim(static_cast(currentStep) / currentProcedure->size()); + progressAircraftPresetId->setAndWriteToSim(currentStepPtr->id); + + // execute code to set expected state + LOG_INFO("AircraftPresets: Aircraft Preset Step " + std::to_string(currentStep) + " Execute: " + currentStepPtr->description + + " (delay after: " + std::to_string(currentStepPtr->delayAfter) + ")"); + 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 + LOG_INFO("AircraftPresets:update() Aircraft Preset " + std::to_string(currentProcedureID) + " loading cancelled!"); + loadingIsActive = false; + } + + return true; +} + +bool AircraftPresets::shutdown() { + _isInitialized = false; + std::cout << "AircraftPresets::shutdown()" << std::endl; + return true; +} diff --git a/fbw-common/src/wasm/extra-backend/AircraftPresets/AircraftPresets.h b/fbw-common/src/wasm/extra-backend/AircraftPresets/AircraftPresets.h new file mode 100644 index 00000000000..6ddf8ee4591 --- /dev/null +++ b/fbw-common/src/wasm/extra-backend/AircraftPresets/AircraftPresets.h @@ -0,0 +1,69 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFTPRESETS_H +#define FLYBYWIRE_AIRCRAFTPRESETS_H + +#include + +#include "DataManager.h" +#include "Module.h" +#include "PresetProcedures.h" +#include "PresetProceduresDefinition.h" +#include "ProcedureStep.h" + +class MsfsHandler; + +/** + * This module is responsible for loading aircraft presets. + * It uses the AircraftProcedures.h definition of procedures to load the presets. + */ +class AircraftPresets : public Module { + private: + // Convenience pointer to the data manager + DataManager* dataManager = nullptr; + + // LVARs + NamedVariablePtr aircraftPresetVerbose{}; + NamedVariablePtr loadAircraftPresetRequest{}; + NamedVariablePtr progressAircraftPreset{}; + NamedVariablePtr progressAircraftPresetId{}; + + // Sim-vars + AircraftVariablePtr simOnGround{}; + + // Procedures + const PresetProcedures presetProcedures; + + // current procedure ID + int currentProcedureID = 0; + // current procedure + const std::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 + std::size_t currentStep = 0; + + public: + AircraftPresets() = delete; + + /** + * Creates a new AircraftPresets instance and takes a reference to the MsfsHandler instance. + * @param msfsHandler The MsfsHandler instance that is used to communicate with the simulator. + * @param aircraftProceduresDefinitions The AircraftProceduresDefinition instance that is used to define the procedures. + */ + explicit AircraftPresets(MsfsHandler& msfsHandler, const PresetProceduresDefinition& aircraftProceduresDefinitions) + : Module(msfsHandler), presetProcedures(PresetProcedures(aircraftProceduresDefinitions)) {} + + bool initialize() override; + bool preUpdate(sGaugeDrawData*) override { return true; }; // not required for this module + bool update(sGaugeDrawData* pData) override; + bool postUpdate(sGaugeDrawData*) override { return true; }; // not required for this module + bool shutdown() override; +}; + +#endif // FLYBYWIRE_AIRCRAFTPRESETS_H diff --git a/fbw-common/src/wasm/extra-backend/AircraftPresets/PresetProcedures.h b/fbw-common/src/wasm/extra-backend/AircraftPresets/PresetProcedures.h new file mode 100644 index 00000000000..a84886b03bf --- /dev/null +++ b/fbw-common/src/wasm/extra-backend/AircraftPresets/PresetProcedures.h @@ -0,0 +1,102 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include +#include +#include +#include +#include "PresetProceduresDefinition.h" +#include "ProcedureStep.h" + +#ifdef DEBUG +#include +#endif + +typedef const std::vector ProcedureDefinition; +typedef std::vector Procedure; + +/** + * The PresetProcedures class loads the procedure definitions from the PresetProceduresDefinition + * and provides the procedures for the different configurations of the aircraft.

+ */ +class PresetProcedures { + + Procedure coldAndDark; + Procedure powered; + Procedure readyForPushback; + Procedure readyForTaxi; + Procedure readyForTakeoff; + + static void insert(Procedure& dest, ProcedureDefinition& src) { + std::transform(begin(src), end(src), back_inserter(dest), [](const auto& procedure) { return &procedure; }); + } + +#ifdef DEBUG + static inline void printProcedure(const ProcedureDefinition& procedures) { + for (const auto& p : procedures) { + std::cout << p.id << " = " << p.description << std::endl; + } + } +#endif + + public: + PresetProcedures(const PresetProceduresDefinition& proceduresDefinition) { + +#ifdef DEBUG + // Map the procedure groups + // Print to console to add them to the EFB code to display the current step. + printProcedure(proceduresDefinition.POWERED_CONFIG_ON); + printProcedure(proceduresDefinition.PUSHBACK_CONFIG_ON); + printProcedure(proceduresDefinition.TAXI_CONFIG_ON); + printProcedure(proceduresDefinition.TAKEOFF_CONFIG_ON); + printProcedure(proceduresDefinition.TAKEOFF_CONFIG_OFF); + printProcedure(proceduresDefinition.TAXI_CONFIG_OFF); + printProcedure(proceduresDefinition.PUSHBACK_CONFIG_OFF); + printProcedure(proceduresDefinition.POWERED_CONFIG_OFF); +#endif + + insert(coldAndDark, proceduresDefinition.TAKEOFF_CONFIG_OFF); + insert(coldAndDark, proceduresDefinition.TAXI_CONFIG_OFF); + insert(coldAndDark, proceduresDefinition.PUSHBACK_CONFIG_OFF); + insert(coldAndDark, proceduresDefinition.POWERED_CONFIG_OFF); + + insert(powered, proceduresDefinition.TAKEOFF_CONFIG_OFF); + insert(powered, proceduresDefinition.TAXI_CONFIG_OFF); + insert(powered, proceduresDefinition.PUSHBACK_CONFIG_OFF); + insert(powered, proceduresDefinition.POWERED_CONFIG_ON); + + insert(readyForPushback, proceduresDefinition.TAKEOFF_CONFIG_OFF); + insert(readyForPushback, proceduresDefinition.TAXI_CONFIG_OFF); + insert(readyForPushback, proceduresDefinition.POWERED_CONFIG_ON); + insert(readyForPushback, proceduresDefinition.PUSHBACK_CONFIG_ON); + + insert(readyForTaxi, proceduresDefinition.TAKEOFF_CONFIG_OFF); + insert(readyForTaxi, proceduresDefinition.POWERED_CONFIG_ON); + insert(readyForTaxi, proceduresDefinition.PUSHBACK_CONFIG_ON); + insert(readyForTaxi, proceduresDefinition.TAXI_CONFIG_ON); + + insert(readyForTakeoff, proceduresDefinition.POWERED_CONFIG_ON); + insert(readyForTakeoff, proceduresDefinition.PUSHBACK_CONFIG_ON); + insert(readyForTakeoff, proceduresDefinition.TAXI_CONFIG_ON); + insert(readyForTakeoff, proceduresDefinition.TAKEOFF_CONFIG_ON); + } + + [[nodiscard]] std::optional getProcedure(int64_t pID) const { + switch (pID) { + case 1: + return &coldAndDark; + case 2: + return &powered; + case 3: + return &readyForPushback; + case 4: + return &readyForTaxi; + case 5: + return &readyForTakeoff; + default: + return std::nullopt; + } + } +}; diff --git a/fbw-common/src/wasm/extra-backend/AircraftPresets/PresetProceduresDefinition.h b/fbw-common/src/wasm/extra-backend/AircraftPresets/PresetProceduresDefinition.h new file mode 100644 index 00000000000..e925b9857cb --- /dev/null +++ b/fbw-common/src/wasm/extra-backend/AircraftPresets/PresetProceduresDefinition.h @@ -0,0 +1,28 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFT_PRESETPROCEDURESDEFINITION_H +#define FLYBYWIRE_AIRCRAFT_PRESETPROCEDURESDEFINITION_H + +#include + +#include "ProcedureStep.h" + +/** + * The PresetProceduresDefinition struct contains the procedure definitions for + * the different configurations of the aircraft.

+ * + * It defines a series of procedure steps for each configuration.

+ */ +struct PresetProceduresDefinition { + std::vector POWERED_CONFIG_ON{}; + std::vector POWERED_CONFIG_OFF{}; + std::vector PUSHBACK_CONFIG_ON{}; + std::vector PUSHBACK_CONFIG_OFF{}; + std::vector TAXI_CONFIG_ON{}; + std::vector TAXI_CONFIG_OFF{}; + std::vector TAKEOFF_CONFIG_ON{}; + std::vector TAKEOFF_CONFIG_OFF{}; +}; + +#endif // FLYBYWIRE_AIRCRAFT_PRESETPROCEDURESDEFINITION_H diff --git a/fbw-common/src/wasm/extra-backend/AircraftPresets/ProcedureStep.h b/fbw-common/src/wasm/extra-backend/AircraftPresets/ProcedureStep.h new file mode 100644 index 00000000000..379f6223685 --- /dev/null +++ b/fbw-common/src/wasm/extra-backend/AircraftPresets/ProcedureStep.h @@ -0,0 +1,28 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFT_PROCEDURESTEP_H +#define FLYBYWIRE_AIRCRAFT_PROCEDURESTEP_H + +#include + +/** + * A procedure step is a single step in a procedure. + * + * @field description A description of the step + * @field id A unique id for each step + * @field isConditional True if the procedure step is a pure condition check to wait for a certain state + * @field delayAfter Time to delay next step of execution of action - will be skipped if expected state is already set + * @field expectedStateCheckCode Check if desired state is already set so the action can be skipped + * @field actionCode Calculator code to achieve the desired state. If it is a conditional this calculator code needs to eval to true or false + */ +struct ProcedureStep { + std::string description; + int id; + bool isConditional; + double delayAfter; + std::string expectedStateCheckCode; + std::string actionCode; +}; + +#endif // FLYBYWIRE_AIRCRAFT_PROCEDURESTEP_H diff --git a/fbw-common/src/wasm/extra-backend/LightingPresets/LightingPresets.cpp b/fbw-common/src/wasm/extra-backend/LightingPresets/LightingPresets.cpp new file mode 100644 index 00000000000..146ebe39607 --- /dev/null +++ b/fbw-common/src/wasm/extra-backend/LightingPresets/LightingPresets.cpp @@ -0,0 +1,110 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include "LightingPresets.h" +#include "ScopedTimer.hpp" + +bool LightingPresets::update([[maybe_unused]] sGaugeDrawData* pData) { + if (!_isInitialized) { + LOG_ERROR("LightingPresets_A32NX::update() - not initialized"); + return false; + } + + // only run when aircraft is powered + if (!msfsHandler.getAircraftIsReadyVar() || !elecAC1Powered->getAsBool()) { + return true; + } + + // load becomes priority in case both vars are set. + if (const INT64 presetRequest = loadLightingPresetRequest->getAsInt64()) { + if (loadLightingPreset(presetRequest)) { + readIniFile = true; + loadLightingPresetRequest->setAsInt64(0); + LOG_INFO("LightingPresets_A32NX: Lighting Preset: " + std::to_string(presetRequest) + " successfully loaded."); + } + } else if (saveLightingPresetRequest->getAsBool()) { + saveLightingPreset(saveLightingPresetRequest->getAsInt64()); + saveLightingPresetRequest->setAsInt64(0); + } + + return true; +} + +bool LightingPresets::shutdown() { + _isInitialized = false; + LOG_INFO("LightingPresets::shutdown()"); + return true; +} + +bool LightingPresets::loadLightingPreset(INT64 loadPresetRequest) { + // throttle the load process so animation can keep up + if (msfsHandler.getTickCounter() % 2 != 0) + return false; + + // Read current values to be able to calculate intermediate values which are then applied to the aircraft + // Once the intermediate values are identical to the target values then the load is finished + readFromAircraft(); + if (readFromStore(loadPresetRequest)) { + bool finished = calculateIntermediateValues(); + applyToAircraft(); + return finished; + } + LOG_WARN("LightingPresets_A32NX: Loading Lighting Preset: " + std::to_string(loadPresetRequest) + " failed."); + return true; +} + +void LightingPresets::saveLightingPreset(INT64 savePresetRequest) { + std::cout << "LightingPresets_A32NX: Save to Lighting Preset: " << savePresetRequest << std::endl; + readFromAircraft(); + if (saveToStore(savePresetRequest)) { + LOG_INFO("LightingPresets_A32NX: Lighting Preset: " + std::to_string(savePresetRequest) + " successfully saved."); + return; + } + LOG_WARN("LightingPresets_A32NX: Saving Lighting Preset: " + std::to_string(savePresetRequest) + " failed."); +} + +bool LightingPresets::readFromStore(INT64 presetNr) { + // only read ini file from disk if we load a new preset + if (readIniFile) { + if (!iniFile.read(ini)) { + LOG_ERROR("LightingPresets_A32NX: Could not read ini file"); + return false; + }; + readIniFile = false; + } + loadFromIni(ini, "preset " + std::to_string(presetNr)); + return true; +} + +bool LightingPresets::saveToStore(INT64 presetNr) { + // add/update iniSectionName + const std::string iniSectionName = "preset " + std::to_string(presetNr); + saveToIni(ini, iniSectionName); + return iniFile.write(ini, true); +} + +AircraftVariablePtr LightingPresets::getLightPotentiometerVar(int index) const { + return dataManager->make_aircraft_var("LIGHT POTENTIOMETER", index, "", lightPotentiometerSetEvent, UNITS.Percent); +} + +FLOAT64 LightingPresets::iniGetOrDefault(const mINI::INIStructure& ini, + const std::string& section, + const std::string& key, + const double defaultValue) { + if (auto value = ini.get(section).get(key); !value.empty()) { + // As MSFS wasm does not support exceptions (try/catch) we can't use + // std::stof here. Workaround with std::stringstreams. + std::stringstream input(value); + if (double result; input >> result) { + return result; + } + LOG_WARN("LightingPresets: reading ini value for [" + section + "] " + key + " = " + ini.get(section).get(key) + " failed."); + } + return defaultValue; +} + +FLOAT64 LightingPresets::convergeValue(FLOAT64 momentary, const FLOAT64 target) { + return momentary < target ? (std::min)(momentary + STEP_SIZE, target) : (std::max)(momentary - STEP_SIZE, target); +} diff --git a/fbw-common/src/wasm/extra-backend/LightingPresets/LightingPresets.h b/fbw-common/src/wasm/extra-backend/LightingPresets/LightingPresets.h new file mode 100644 index 00000000000..d0679e12ea3 --- /dev/null +++ b/fbw-common/src/wasm/extra-backend/LightingPresets/LightingPresets.h @@ -0,0 +1,134 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_AIRCRAFT_LIGHTINGPRESETS_H +#define FLYBYWIRE_AIRCRAFT_LIGHTINGPRESETS_H + +#include + +#include "DataManager.h" +#include "Module.h" +#include "inih/ini.h" + +class LightingPresets : public Module { + public: + LightingPresets() = delete; + + protected: + const std::string CONFIGURATION_FILEPATH = "\\work\\InteriorLightingPresets.ini"; + static constexpr SIMCONNECT_NOTIFICATION_GROUP_ID NOTIFICATION_GROUP_1 = 1; + static constexpr FLOAT64 STEP_SIZE = 2.0; + + // Convenience pointer to the data manager + DataManager* dataManager = nullptr; + + // Setter events + ClientEventPtr lightPotentiometerSetEvent; + + // Control LVARs + NamedVariablePtr elecAC1Powered; + NamedVariablePtr loadLightingPresetRequest; + NamedVariablePtr saveLightingPresetRequest; + + // create ini file and data structure + mINI::INIStructure ini; + mINI::INIFile iniFile; + // Flag to indicate if the ini file should be read - only read it when loading a new preset + // but don't read it again during the loading process of one preset + // This way we avoid reading the ini file multiple times when loading a single preset but still read it + // when loading a new preset which allows to manually change the ini file while the sim is running + // for testing manually configured presets. + bool readIniFile = true; + + LightingPresets(MsfsHandler& handler) : Module(handler), iniFile(CONFIGURATION_FILEPATH) {} + + virtual bool initialize() override = 0; // this needs to be implemented by the derived class + bool preUpdate([[maybe_unused]] sGaugeDrawData* pData) override { return true; }; // not required for this module + bool update(sGaugeDrawData* pData) override; + bool postUpdate([[maybe_unused]] sGaugeDrawData* pData) override { return true; }; // not required for this module + bool shutdown() override; + + /** + * Loads a specified preset + * @param loadPresetRequest the number of the preset to be loaded + * @return true if loading is finished or an error occurred - this should end the loading process + * @return false if loading is not finished + */ + bool loadLightingPreset(INT64 loadPresetRequest); + + /** + * Save a specified preset + * @param savePresetRequest the number of the preset to be saved + */ + void saveLightingPreset(INT64 savePresetRequest); + + /** + * Read the current lighting level from the aircraft. + */ + virtual void readFromAircraft() = 0; + + /** + * Applies the currently loaded preset to the aircraft + */ + virtual void applyToAircraft() = 0; + + /** + * Reads a stored preset from the persistence store. + * @return true if successful, false otherwise. + */ + bool readFromStore(INT64 presetNr); + + /** + * Loads the current values from the provided ini structure. + * @return true if successful, false otherwise. + */ + virtual void loadFromIni(const mINI::INIStructure& ini, const std::string& iniSectionName) = 0; + + /** + * Stores the current values into the persistent store. + * @return true if successful, false otherwise. + */ + virtual bool saveToStore(INT64 presetNr); + + /** + * Saves the current values to the provided ini structure. + * @param ini + * @param iniSectionName + */ + virtual void saveToIni(mINI::INIStructure& ini, const std::string& iniSectionName) const = 0; + + /** + * Calculates the intermediate values between the current values and the preset values. + * @return true if the intermediate values are equal to the current values. + */ + virtual bool calculateIntermediateValues() = 0; + + /** + * Get the variable for the potentiometer of a specific light + * @param index + * @return a shared pointer to the variable + */ + [[nodiscard]] AircraftVariablePtr getLightPotentiometerVar(int index) const; + + /** + * 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 FLOAT64 iniGetOrDefault(const mINI::INIStructure& ini, const std::string& section, const std::string& key, FLOAT64 defaultValue); + + /** + * Converges a momentary value towards a target value. + * @param momentary the momentary value + * @param target the target value + * @return the converged value + */ + FLOAT64 convergeValue(FLOAT64 momentary, FLOAT64 target); +}; + +#endif // FLYBYWIRE_AIRCRAFT_LIGHTINGPRESETS_H diff --git a/fbw-a32nx/src/wasm/flypad-backend/src/Pushback/InertialDampener.h b/fbw-common/src/wasm/extra-backend/Pushback/InertialDampener.hpp similarity index 56% rename from fbw-a32nx/src/wasm/flypad-backend/src/Pushback/InertialDampener.h rename to fbw-common/src/wasm/extra-backend/Pushback/InertialDampener.hpp index f9b866114a3..57bd82051fa 100644 --- a/fbw-a32nx/src/wasm/flypad-backend/src/Pushback/InertialDampener.h +++ b/fbw-common/src/wasm/extra-backend/Pushback/InertialDampener.hpp @@ -1,33 +1,36 @@ -// Copyright (c) 2022 FlyByWire Simulations +// Copyright (c) 2023 FlyByWire Simulations // SPDX-License-Identifier: GPL-3.0 #pragma once +#include "math_utils.hpp" + /** * The InertialDampener provides a dampened output based on the current input * and an internal state value. The output value increases or decreases from the * internal state value towards the input value with the given acceleration value. */ class InertialDampener { - -private: + private: double lastValue{}; double accelStepSize{}; + double epsilon{}; -public: - + public: /** * Creates a new instance of the InertialDampener * @param startValue initial value to avoid a too large delta for the first usage * @param accelStepSize value which will be added/subtracted to/from the internal * state towards the input value. + * @param epsilon the epsilon value used to compare the input value with the internal + * state value. If the difference is smaller than epsilon the input value + * is returned. */ - InertialDampener(double startValue, double accelStepSize); - - /** - * Destructor - */ - ~InertialDampener() = default; + InertialDampener(double startValue, double accelStepSize, double epsilon) { + this->lastValue = startValue; + this->accelStepSize = accelStepSize; + this->epsilon = epsilon; + }; /** * Given a target value this returns a value increased or decreased from the last @@ -36,8 +39,11 @@ class InertialDampener { * @param newTargetValue * @return new value loser to newTarget value by accelStepSize */ - double updateSpeed(double newTargetValue); - -private: - static double round(double value, int decimalPrecision); + double updateSpeed(double newTargetValue) { + if (helper::Math::almostEqual(lastValue, newTargetValue, epsilon)) { + return newTargetValue; + } + lastValue += (newTargetValue > lastValue ? accelStepSize : -accelStepSize); + return lastValue; + } }; diff --git a/fbw-common/src/wasm/extra-backend/Pushback/Pushback.cpp b/fbw-common/src/wasm/extra-backend/Pushback/Pushback.cpp new file mode 100644 index 00000000000..1f77db11f1b --- /dev/null +++ b/fbw-common/src/wasm/extra-backend/Pushback/Pushback.cpp @@ -0,0 +1,162 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include "AircraftVariable.h" +#include "MsfsHandler.h" +#include "NamedVariable.h" +#include "Pushback.h" +#include "SimUnits.h" +#include "UpdateMode.h" +#include "logging.h" +#include "math_utils.hpp" + +/// +// DataManager Howto Note: +// ======================= + +// The Pushback module uses the DataManager to get and set variables. +// Looking at the make_xxx_var functions, you can see that they are updated +// with different update cycles. +// +// Some variables are read from the sim at every tick: +// - A32NX_PUSHBACK_SYSTEM_ENABLED +// - Pushback Attached +// - SIM ON GROUND +// +// The rest are read on demand after the state of the above variables have been checked. +// No variable is written automatically. +// +// This makes sure variables are only read or written to when really needed. And as pushback will +// be dormant most of the time, this is saving a lot of unnecessary reads/writes. +/// + +static constexpr FLOAT64 SPEED_FACTOR = 18.0; // ft/sec for "VELOCITY BODY Z" +static constexpr FLOAT64 TURN_SPEED_FACTOR = 0.16; // ft/sec for "ROTATION VELOCITY BODY Y" + +bool Pushback::initialize() { + dataManager = &msfsHandler.getDataManager(); + + // LVARs + pushbackSystemEnabled = dataManager->make_named_var("PUSHBACK_SYSTEM_ENABLED", UNITS.Bool, UpdateMode::AUTO_READ); + parkingBrakeEngaged = dataManager->make_named_var("PARK_BRAKE_LEVER_POS"); + tugCommandedSpeedFactor = dataManager->make_named_var("PUSHBACK_SPD_FACTOR"); + tugCommandedHeadingFactor = dataManager->make_named_var("PUSHBACK_HDG_FACTOR"); + // debug purposes + tugCommandedSpeed = dataManager->make_named_var("PUSHBACK_SPD"); + tugCommandedHeading = dataManager->make_named_var("PUSHBACK_HDG"); + tugInertiaSpeed = dataManager->make_named_var("PUSHBACK_INERTIA_SPD"); + updateDelta = dataManager->make_named_var("PUSHBACK_UPDT_DELTA"); + rotXOut = dataManager->make_named_var("PUSHBACK_R_X_OUT"); + + // Simvars + pushbackAttached = dataManager->make_simple_aircraft_var("Pushback Attached", UNITS.Bool, true); + simOnGround = dataManager->make_simple_aircraft_var("SIM ON GROUND", UNITS.Number, true); + aircraftHeading = dataManager->make_simple_aircraft_var("PLANE HEADING DEGREES TRUE", UNITS.Rad); + windVelBodyZ = dataManager->make_simple_aircraft_var("RELATIVE WIND VELOCITY BODY Z"); + + // Data definitions for PushbackDataID + std::vector pushBackDataDef = {{"Pushback Wait", 0, UNITS.Bool}, + {"VELOCITY BODY Z", 0, UNITS.FeetSec}, + {"ROTATION VELOCITY BODY Y", 0, UNITS.FeetSec}, + {"ROTATION ACCELERATION BODY X", 0, UNITS.RadSecSquared}}; + pushbackData = dataManager->make_datadefinition_var("PUSHBACK DATA", pushBackDataDef); + + // Events + // Normally "KEY_..." events can't be treated like normal events, but in this case there is no + // normal event (e.g. TUG_HEADING) that we can use. So we use the "KEY_..." events instead and + // surprisingly the sim accepts them. This seems to be an inconsistency in the sim. + tugHeadingEvent = dataManager->make_sim_event("KEY_TUG_HEADING", NOTIFICATION_GROUP_1); + tugSpeedEvent = dataManager->make_sim_event("KEY_TUG_SPEED", NOTIFICATION_GROUP_1); + + _isInitialized = true; + LOG_INFO("Pushback initialized"); + return true; +} + +bool Pushback::preUpdate([[maybe_unused]] sGaugeDrawData* pData) { + // empty + return true; +} + +bool Pushback::update(sGaugeDrawData* pData) { + if (!_isInitialized) { + std::cerr << "Pushback::update() - not initialized" << std::endl; + return false; + } + + // Check if the pushback system is enabled and conditions are met + if (!msfsHandler.getAircraftIsReadyVar() || !pushbackSystemEnabled->getAsBool() || !pushbackAttached->getAsBool() || + !simOnGround->getAsBool()) { + return true; + } + + const FLOAT64 timeStamp = msfsHandler.getTimeStamp(); + const UINT64 tickCounter = msfsHandler.getTickCounter(); + + // read all data from sim - could be done inline but better readability this way + tugCommandedSpeedFactor->updateFromSim(timeStamp, tickCounter); + tugCommandedHeadingFactor->updateFromSim(timeStamp, tickCounter); + parkingBrakeEngaged->updateFromSim(timeStamp, tickCounter); + aircraftHeading->updateFromSim(timeStamp, tickCounter); + windVelBodyZ->updateFromSim(timeStamp, tickCounter); + + const double speedFactor = parkingBrakeEngaged->getAsBool() ? (SPEED_FACTOR / 10) : SPEED_FACTOR; + const FLOAT64 tugCmdSpd = tugCommandedSpeedFactor->get() * speedFactor; + const FLOAT64 inertiaSpeed = inertialDampener.updateSpeed(tugCmdSpd); + + const double turnSpeedHdgFactor = parkingBrakeEngaged->getAsBool() ? (TURN_SPEED_FACTOR / 10) : TURN_SPEED_FACTOR; + const FLOAT64 computedRotationVelocity = helper::Math::sign(tugCmdSpd) * tugCommandedHeadingFactor->get() * turnSpeedHdgFactor; + + // As we might use the elevator for taxiing we compensate for wind to avoid + // the aircraft lifting any gears. The hard coded values are based on testing + // in the sim. + FLOAT64 movementCounterRotAccel = 0.0; + if (inertiaSpeed > 0) { + movementCounterRotAccel = (windVelBodyZ->get() / 2000.0) - 0.5; + } else if (inertiaSpeed < 0) { + movementCounterRotAccel = (windVelBodyZ->get() / 2000.0) + 1.0; + } + + FLOAT64 aircraftHeadingDeg = aircraftHeading->get() * (180.0 / PI); + const FLOAT64 computedHdg = helper::Math::angleAdd(aircraftHeadingDeg, -90 * tugCommandedHeadingFactor->get()); + + // K:KEY_TUG_HEADING expects an unsigned integer scaling 360° to 0 to 2^32-1 (0xffffffff / 360) + // https://docs.flightsimulator.com/html/Programming_Tools/Event_IDs/Aircraft_Misc_Events.htm#TUG_HEADING + constexpr DWORD headingToInt32 = 0xffffffff / 360; + const DWORD convertedComputedHeading = static_cast(computedHdg) * headingToInt32; + + // send as LVARs for debugging in the flyPad + updateDelta->setAndWriteToSim(pData->dt); // debug value + tugInertiaSpeed->setAndWriteToSim(inertiaSpeed); // debug value + tugCommandedSpeed->setAndWriteToSim(tugCmdSpd); // debug value + rotXOut->setAndWriteToSim(movementCounterRotAccel); // debug value + tugCommandedHeading->setAndWriteToSim(computedHdg); // debug value + + // send K:KEY_TUG_HEADING event + tugHeadingEvent->trigger_ex1(convertedComputedHeading, 0, 0, 0, 0); + + // K:KEY_TUG_SPEED - seems to actually do nothing + // tugSpeedEvent->trigger_ex1(static_cast(inertiaSpeed)); + + // Update sim data + pushbackData->data().pushbackWait = helper::Math::almostEqual(inertiaSpeed, 0.0) ? 1 : 0; + pushbackData->data().velBodyZ = inertiaSpeed; + pushbackData->data().rotVelBodyY = computedRotationVelocity; + pushbackData->data().rotAccelBodyX = movementCounterRotAccel; + pushbackData->writeDataToSim(); + + return true; +} + +bool Pushback::postUpdate([[maybe_unused]] sGaugeDrawData* pData) { + // empty + return true; +} + +bool Pushback::shutdown() { + _isInitialized = false; + std::cout << "Pushback::shutdown()" << std::endl; + return true; +} diff --git a/fbw-common/src/wasm/extra-backend/Pushback/Pushback.h b/fbw-common/src/wasm/extra-backend/Pushback/Pushback.h new file mode 100644 index 00000000000..4bb78204f67 --- /dev/null +++ b/fbw-common/src/wasm/extra-backend/Pushback/Pushback.h @@ -0,0 +1,89 @@ +// Copyright (c) 2023 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +#ifndef FLYBYWIRE_PUSHBACK_H +#define FLYBYWIRE_PUSHBACK_H + +#include "DataManager.h" +#include "Module.h" +#include "Pushback/InertialDampener.hpp" + +#ifdef __cpp_lib_math_constants +#include +constexpr double PI = std::numbers::pi; +#else +constexpr double PI = 3.14159265358979323846; +#endif + +class MsfsHandler; + +/** + * This module is responsible for the pushback process. + * + * It is controlled by two LVARs: + * - A32NX_PUSHBACK_SYSTEM_ENABLED + * - A32NX_PUSHBACK_SPD_FACTOR + * - A32NX_PUSHBACK_HDG_FACTOR + * + * - Pushback Attached (simvar) + * - SIM ON GROUND (simvar) + */ +class Pushback : public Module { + private: + static const SIMCONNECT_NOTIFICATION_GROUP_ID NOTIFICATION_GROUP_1 = 1; + + // Convenience pointer to the data manager + DataManager* dataManager = nullptr; + + // Used to smoothen acceleration and deceleration + InertialDampener inertialDampener{0.0, 0.15, 0.1}; + + // LVARs + NamedVariablePtr pushbackSystemEnabled; + NamedVariablePtr parkingBrakeEngaged; + NamedVariablePtr tugCommandedHeadingFactor; + NamedVariablePtr tugCommandedSpeedFactor; + // debug purposes - send as LVARs for debugging to the flyPad + NamedVariablePtr tugCommandedHeading; + NamedVariablePtr tugCommandedSpeed; + NamedVariablePtr tugInertiaSpeed; + NamedVariablePtr updateDelta; + NamedVariablePtr rotXOut; + + // Sim-vars + AircraftVariablePtr simOnGround; + AircraftVariablePtr pushbackAttached; + AircraftVariablePtr aircraftHeading; + AircraftVariablePtr windVelBodyZ; + + // Data structure for PushbackDataID + struct PushbackData { + [[maybe_unused]] FLOAT64 pushbackWait; + [[maybe_unused]] FLOAT64 velBodyZ; + [[maybe_unused]] FLOAT64 rotVelBodyY; + [[maybe_unused]] FLOAT64 rotAccelBodyX; + }; + std::shared_ptr> pushbackData; + + // Events + ClientEventPtr tugHeadingEvent; + ClientEventPtr tugSpeedEvent; + + public: + Pushback() = delete; + + /** + * Creates a new Pushback instance and takes a reference to the MsfsHandler instance. + * @param msfsHandler The MsfsHandler instance that is used to communicate with the simulator. + */ + explicit Pushback(MsfsHandler& msfsHandler) : Module(msfsHandler) {} + + bool initialize() override; + bool preUpdate(sGaugeDrawData* pData) override; + bool update(sGaugeDrawData* pData) override; + bool postUpdate(sGaugeDrawData* pData) override; + bool shutdown() override; + +}; + +#endif // FLYBYWIRE_PUSHBACK_H diff --git a/igniter.config.mjs b/igniter.config.mjs index 2dd6de6bb4b..f811a7ca441 100644 --- a/igniter.config.mjs +++ b/igniter.config.mjs @@ -1,4 +1,4 @@ -import { ExecTask, TaskOfTasks } from "@flybywiresim/igniter"; +import {ExecTask, TaskOfTasks} from "@flybywiresim/igniter"; import { getInstrumentsIgniterTasks as getA320InstrumentsIgniterTasks } from "./fbw-a32nx/src/systems/instruments/buildSrc/igniter/tasks.mjs"; import { getInstrumentsIgniterTasks as getA380InstrumentsIgniterTasks } from './fbw-a380x/src/systems/instruments/buildSrc/igniter/tasks.mjs'; @@ -127,20 +127,20 @@ export default new TaskOfTasks("all", [ "fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/fbw.wasm" ]), new ExecTask("systems-terronnd", [ - "fbw-common/src/wasm/terronnd/build.sh", - "wasm-opt -O1 --signext-lowering -o fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/terronnd.wasm fbw-common/src/wasm/terronnd/out/terronnd.wasm" + "npm run build-a32nx:terronnd", ], [ "fbw-common/src/wasm/terronnd", "fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/terronnd.wasm", "fbw-common/src/wasm/terronnd/out/terronnd.wasm", ]), - new ExecTask("flypad-backend", - "npm run build-a32nx:flypad-backend", + new ExecTask('extra-backend-a32nx', + "npm run build:cpp-wasm-cmake", [ - "fbw-a32nx/src/wasm/flypad-backend", - "fbw-common/src/wasm/fbw_common", - "fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/flypad-backend.wasm" - ]) + 'fbw-common/src/wasm/cpp-msfs-framework', + 'fbw-common/src/wasm/extra-backend', + 'fbw-a32nx/src/wasm/extra-backend-a32nx', + 'fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/extra-backend-a32nx.wasm' + ]), ], true), // Create final package meta files. @@ -155,7 +155,9 @@ export default new TaskOfTasks("all", [ new TaskOfTasks("preparation", [ new ExecTask("copy-base-files", [ - "npm run build-a380x:copy-base-files" + "npm run build-a380x:copy-base-files", + // temporary until folder exists + "mkdir -p fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/" ]) ], true), @@ -168,42 +170,42 @@ export default new TaskOfTasks("all", [ new ExecTask("systems", "npm run build-a380x:systems", [ - "fbw-a380x/src/wasm/systems", "fbw-common/src/wasm/systems", "Cargo.lock", "Cargo.toml", + "fbw-a380x/src/wasm/systems", "fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/systems.wasm" ]), new ExecTask("systems-fadec", "npm run build-a380x:fadec", [ - "fbw-a380x/src/wasm/fadec_a380", "fbw-common/src/wasm/fbw_common", "fbw-common/src/wasm/fadec_common", + "fbw-a380x/src/wasm/fadec_a380", "fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/fadec.wasm" ]), new ExecTask("systems-fbw", "npm run build-a380x:fbw", [ - "fbw-a380x/src/wasm/fbw_a380", "fbw-common/src/wasm/fbw_common", + "fbw-a380x/src/wasm/fbw_a380", "fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/fbw.wasm" ]), new ExecTask("systems-terronnd", [ - "fbw-common/src/wasm/terronnd/build.sh", - "wasm-opt -O1 --signext-lowering -o fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/terronnd.wasm fbw-common/src/wasm/terronnd/out/terronnd.wasm" + "npm run build-a380x:terronnd", ], [ "fbw-common/src/wasm/terronnd", - "fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/terronnd.wasm", "fbw-common/src/wasm/terronnd/out/terronnd.wasm", + "fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/terronnd.wasm", ]), - new ExecTask("flypad-backend", - "npm run build-a380x:flypad-backend", + new ExecTask('extra-backend', + "npm run build:cpp-wasm-cmake", [ - "fbw-a380x/src/wasm/flypad-backend", - "fbw-common/src/wasm/fbw_common", - "fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/flypad-backend.wasm" - ]) + 'fbw-common/src/wasm/cpp-msfs-framework', + 'fbw-common/src/wasm/extra-backend', + 'fbw-a380x/src/wasm/extra-backend-a380x', + 'fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/extra-backend-a380x.wasm' + ]), ], true), // Create final package meta files. diff --git a/package.json b/package.json index bececb9f790..5348ce6c609 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,13 @@ "#build-utils": "./build-utils.js" }, "scripts": { + "====== Generic===============": "==========================================", + + "build:cpp-wasm-cmake": "scripts/build-cmake.sh", + "build:cpp-wasm-cmake-clean": "scripts/build-cmake.sh --clean", + "build:cpp-wasm-cmake-debug": "scripts/build-cmake.sh --debug", + "build:cpp-wasm-cmake-debug-clean": "scripts/build-cmake.sh --debug --clean", + "====== A320 =================": "==========================================", "build-a32nx:copy-base-package": "mkdir -p fbw-a32nx/out/flybywire-aircraft-a320-neo && (rsync -a fbw-a32nx/src/base/flybywire-aircraft-a320-neo fbw-a32nx/out/ || cp -a -u fbw-a32nx/src/base/flybywire-aircraft-a320-neo fbw-a32nx/out/)", @@ -28,50 +35,49 @@ "build-a32nx:systems-host": "node fbw-a32nx/src/systems/systems-host/build.js", "build-a32nx:tcas": "node fbw-a32nx/src/systems/tcas/build.js", - "build-a32nx:fadec": "cd fbw-a32nx/src/wasm/fadec_a320 && ./build.sh && wasm-opt -O1 --signext-lowering -o /external/fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/fadec.wasm /external/fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/fadec.wasm", - "build-a32nx:fbw": "cd fbw-a32nx/src/wasm/fbw_a320 && ./build.sh && wasm-opt -O1 --signext-lowering -o /external/fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/fbw.wasm /external/fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/fbw.wasm", - "build-a32nx:flypad-backend": "cd fbw-a32nx/src/wasm/flypad-backend && ./build.sh && wasm-opt -O1 --signext-lowering -o /external/fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/flypad-backend.wasm /external/fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/flypad-backend.wasm", + "build-a32nx:pfd": "cd fbw-a32nx/src/systems/instruments/src/PFD && rollup -c", + "build-a32nx:clock": "cd fbw-a32nx/src/systems/instruments/src/Clock && rollup -c", + "build-a32nx:ewd": "cd fbw-a32nx/src/systems/instruments/src/EWD && rollup -c", + "build-a32nx:systems": "cargo build -p a320_systems_wasm --target wasm32-wasi --release && wasm-opt -O1 --signext-lowering --enable-bulk-memory -o /external/fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/systems.wasm /external/target/wasm32-wasi/release/a320_systems_wasm.wasm", + "build-a32nx:fadec": "cd fbw-a32nx/src/wasm/fadec_a320 && ./build.sh && wasm-opt -O1 --signext-lowering -o /external/fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/fadec.wasm /external/fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/fadec.wasm", + "build-a32nx:fbw": "cd fbw-a32nx/src/wasm/fbw_a320 && ./build.sh && wasm-opt -O1 --signext-lowering -o /external/fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/fbw.wasm /external/fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/fbw.wasm", + "build-a32nx:terronnd": "cd fbw-common/src/wasm/terronnd && ./build.sh && wasm-opt -O1 --signext-lowering -o /external/fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel/terronnd.wasm /external/fbw-common/src/wasm/terronnd/out/terronnd.wasm", "build-a32nx:metadata": "node scripts/metadata.js fbw-a32nx/out/flybywire-aircraft-a320-neo a32nx", "build-a32nx:manifest": "node scripts/build_a32nx.js", + "prettier": "prettier --write **/*.json **/*.yml fbw-a32nx/src/systems/instruments/**/*.css", "build-a32nx:ace": "node --max-old-space-size=8192 node_modules/rollup/dist/bin/rollup -c .\\fbw-a32nx\\src\\systems\\instruments\\buildSrc\\aceBuild.mjs", "watch-a32nx:ace": "node --max-old-space-size=8192 node_modules/rollup/dist/bin/rollup -wc .\\fbw-a32nx\\src\\systems\\instruments\\buildSrc\\aceBuild.mjs", - "build-a32nx:all": "npm run build-a32nx:copy-base-files && npm run build-a32nx:efb-translation && npm run build-a32nx:model && npm run build-a32nx:behavior && npm run build-a32nx:atsu-common && npm run build-a32nx:atsu-fms-client && npm run build-a32nx:systems-host && npm run build-a32nx:failures && npm run build-a32nx:fmgc && npm run build-a32nx:sentry-client && npm run build-a32nx:simbridge-client && npm run build-a32nx:tcas && npm run build-a32nx:pfd && npm run build-a32nx:systems && npm run build-a32nx:fadec && npm run build-a32nx:fbw && npm run build-a32nx:flypad-backend && npm run build-a32nx:metadata && npm run build-a32nx:manifest", - "=======deprecated=========": "===========================", - "build-a32nx:pfd": "cd fbw-a32nx/src/systems/instruments/src/PFD && rollup -c", - "build-a32nx:clock": "cd fbw-a32nx/src/systems/instruments/src/Clock && rollup -c", - "build-a32nx:ewd": "cd fbw-a32nx/src/systems/instruments/src/EWD && rollup -c", - - "prettier": "prettier --write **/*.json **/*.yml fbw-a32nx/src/systems/instruments/**/*.css", "serve:efb": "cd fbw-a32nx/src/systems/instruments/src/EFB/ && vite --port 9696", "build:instruments": "rollup --max-old-space-size=8192 -c src/systems/instruments/buildSrc/simulatorBuild.mjs", "watch:instruments": "rollup --max-old-space-size=8192 -wc src/systems/instruments/buildSrc/simulatorBuild.mjs", "====== A380 =================": "==========================================", + "build-a380x:copy-base-files": "mkdir -p fbw-a380x/out/flybywire-aircraft-a380-842 && (rsync -a fbw-a380x/src/base/flybywire-aircraft-a380-842 fbw-a380x/out/ || cp -a -u fbw-a380x/src/base/flybywire-aircraft-a380-842 fbw-a380x/out/)", "build-a380x:instruments": "mach build --config fbw-a380x/mach.config.js --work-in-config-dir", "build-a380x:systems": "cargo build -p a380_systems_wasm --target wasm32-wasi --release && wasm-opt -O1 --signext-lowering --enable-bulk-memory -o /external/fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/systems.wasm /external/target/wasm32-wasi/release/a380_systems_wasm.wasm", - "build-a380x:fadec": "cd fbw-a380x/src/wasm/fadec_a380 && ./build.sh && wasm-opt -O1 --signext-lowering -o /external/fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/fadec.wasm /external/fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/fadec.wasm", - "build-a380x:fbw": "cd fbw-a380x/src/wasm/fbw_a380 && ./build.sh && wasm-opt -O1 --signext-lowering -o /external/fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/fbw.wasm /external/fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/fbw.wasm", - "build-a380x:flypad-backend": "cd fbw-a380x/src/wasm/flypad-backend && ./build.sh && wasm-opt -O1 --signext-lowering -o /external/fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/flypad-backend.wasm /external/fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/flypad-backend.wasm", + "build-a380x:fadec": "cd fbw-a380x/src/wasm/fadec_a380 && ./build.sh && wasm-opt -O1 --signext-lowering -o /external/fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/fadec.wasm /external/fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/fadec.wasm", + "build-a380x:fbw": "cd fbw-a380x/src/wasm/fbw_a380 && ./build.sh && wasm-opt -O1 --signext-lowering -o /external/fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/fbw.wasm /external/fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/fbw.wasm", + "build-a380x:terronnd": "cd fbw-common/src/wasm/terronnd && ./build.sh && wasm-opt -O1 --signext-lowering -o /external/fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel/terronnd.wasm /external/fbw-common/src/wasm/terronnd/out/terronnd.wasm", "build-a380x:metadata": "node scripts/metadata.js fbw-a380x/out/flybywire-aircraft-a380-842 a380x", "build-a380x:manifest": "node scripts/build_a380x.js flybywire-aircraft-a380-842", - "build-a380x:all": "npm run build-a380x:copy-base-files && npm run build-a380x:instruments && npm run build-a380x:systems && npm run build-a380x:fadec && npm run build-a380x:fbw && npm run build-a380x:flypad-backend && npm run build-a380x:metadata && build-a380x:manifest", - "====== INGAMEPANELS CHECKLIST FIX ====": "==========================================", + "build-ingamepanels-checklist-fix:copy-base-package": "mkdir -p fbw-ingamepanels-checklist-fix/out/flybywire-ingamepanels-checklist-fix && (rsync -a fbw-ingamepanels-checklist-fix/src/base/flybywire-ingamepanels-checklist-fix fbw-ingamepanels-checklist-fix/out/ || cp -a -u fbw-ingamepanels-checklist-fix/src/base/flybywire-ingamepanels-checklist-fix fbw-ingamepanels-checklist-fix/out/)", "build-ingamepanels-checklist-fix:copy-base-files": "npm run build-ingamepanels-checklist-fix:copy-base-package", "build-ingamepanels-checklist-fix:manifest": "node scripts/build_ingamepanels_checklist_fix.js", "====== COMMON ================": "==========================================", + "lint": "eslint --cache **/*.{js,mjs,jsx,ts,tsx}", "lint-fix": "npm run lint -- --fix", "test": "jest" diff --git a/scripts/build-cmake.sh b/scripts/build-cmake.sh new file mode 100755 index 00000000000..c97c4935e2d --- /dev/null +++ b/scripts/build-cmake.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# This script builds the WASM module for the extra backend with CMake. + +# set -x or set -o xtrace expands variables and prints a little + sign before the line. +# set -v or set -o verbose does not expand the variables before printing. +# Use set +x and set +v to turn off the above settings +#set -x + +# get directory of this script relative to root +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +pushd "${DIR}" || exit + +PARALLEL=8 +OUTPUT_DIR="../build/cmake-build-devenv-release/" +CONFIG="Release" +CLEAN="" + +# Parse command line arguments +while [ $# -gt 0 ]; do + case "$1" in + --debug| -d) + OUTPUT_DIR="../build/cmake-build-devenv-debug/" + CONFIG="Debug" + ;; + --clean| -c) + CLEAN="--clean-first" + ;; + *) + echo "Unknown option: $1" + ;; + esac +shift +done + +A32NX_WASM_OUT_DIR="../fbw-a32nx/out/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/panel" +if [ ! -d "$A32NX_WASM_OUT_DIR" ]; then + echo "$A32NX_WASM_OUT_DIR directory does not exist." + mkdir -p $A32NX_WASM_OUT_DIR + echo "$A32NX_WASM_OUT_DIR directory created." +fi +A380X_WASM_OUT_DIR="../fbw-a380x/out/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/panel" +if [ ! -d "$A380X_WASM_OUT_DIR" ]; then + echo "$A380X_WASM_OUT_DIR directory does not exist." + mkdir -p $A380X_WASM_OUT_DIR + echo "$A380X_WASM_OUT_DIR directory created." +fi + +echo "Toolchain versions:" +cmake --version +clang++ --version +wasm-ld --version +echo "" + +echo "Building extra-backend with CMAKE..." +cmake -DCMAKE_TOOLCHAIN_FILE=scripts/cmake/DockerToolchain.cmake -B${OUTPUT_DIR} -DCMAKE_BUILD_TYPE=${CONFIG} ../ || (echo "CMake config failed" && exit 1) +cmake --build ${OUTPUT_DIR} --config ${CONFIG} ${CLEAN} -j ${PARALLEL} || (echo "CMake build failed" && exit 1) +echo "" + +echo "WASM module built successfully!" +echo "" +exit 0 diff --git a/scripts/build_a380x.sh b/scripts/build_a380x.sh index eea2de2135a..dbd57769c4a 100755 --- a/scripts/build_a380x.sh +++ b/scripts/build_a380x.sh @@ -18,6 +18,7 @@ for arg in "$@"; do if [ "$arg" = "-clean" ]; then echo "Removing out directories..." rm -rf /external/fbw-a380x/out + rm -rf /external/fbw-a380x/bundles else # Otherwise, add the arg it to the new array args+=("$arg") diff --git a/scripts/cmake/DockerToolchain.cmake b/scripts/cmake/DockerToolchain.cmake new file mode 100644 index 00000000000..a095f6459f4 --- /dev/null +++ b/scripts/cmake/DockerToolchain.cmake @@ -0,0 +1,8 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR AMD64) + +set(MSFS_SDK "/workdir/MSFS_SDK") +set(CMAKE_C_COMPILER "clang") +set(CMAKE_CXX_COMPILER "clang++") +set(CMAKE_WASM_LINKER "wasm-ld") +set(CMAKE_WASM_OPTIMIZER "wasm-opt") diff --git a/scripts/dev-env/run-local.cmd b/scripts/dev-env/run-local.cmd index 9f2d92fbaa2..701b6c73f89 100644 --- a/scripts/dev-env/run-local.cmd +++ b/scripts/dev-env/run-local.cmd @@ -1,6 +1,6 @@ @echo off -rem This is a script to use a locally build docker image to run the tests +rem This is a script to use a locally built docker image to run the tests set image="sha256:d1fa5a6ced00ca075f1e54aacdea086c52f321387245126bcb0cd7f84fbfa34b" diff --git a/scripts/dev-env/run-local.sh b/scripts/dev-env/run-local.sh index 6b6c08e13a6..d4037b95149 100644 --- a/scripts/dev-env/run-local.sh +++ b/scripts/dev-env/run-local.sh @@ -1,7 +1,7 @@ #!/bin/bash -# This is a script to use a locally build docker image to run the tests +# This is a script to use a locally built docker image to run the tests IMAGE="sha256:2cf75753022112a79d4a512caecfbdf601e12b01b382263cf78f90635cdea6bf"