diff --git a/.vscode/settings.json b/.vscode/settings.json index 9417e0e0d..d55e82b24 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -144,7 +144,9 @@ "__functional_03": "cpp", "memory_resource": "cpp", "numbers": "cpp", - "xmemory0": "cpp" + "xmemory0": "cpp", + "span": "cpp", + "ranges": "cpp" }, "typescript.preferences.quoteStyle": "single", "typescript.preferences.importModuleSpecifier": "non-relative" diff --git a/hdw-a339x-acj/src/base/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_ACJ330_900/panel/panel.cfg b/hdw-a339x-acj/src/base/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_ACJ330_900/panel/panel.cfg index db76f707f..c114cebcf 100644 --- a/hdw-a339x-acj/src/base/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_ACJ330_900/panel/panel.cfg +++ b/hdw-a339x-acj/src/base/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_ACJ330_900/panel/panel.cfg @@ -122,7 +122,7 @@ size_mm = 1430,1000 pixel_size = 1430,1000 texture = $EFB -htmlgauge00 = ACJ339X/EFB/efb.html, 0,0,1430,1000 +htmlgauge00 = ACJ339X/EFB/efb.html?Airframe=ACJ330_941, 0,0,1430,1000 [VCockpit17] size_mm = 0,0 diff --git a/hdw-a339x-acj/src/systems/instruments/src/EFB/Ground/Pages/Payload/Payload.tsx b/hdw-a339x-acj/src/systems/instruments/src/EFB/Ground/Pages/Payload/Payload.tsx deleted file mode 100644 index 86eba578f..000000000 --- a/hdw-a339x-acj/src/systems/instruments/src/EFB/Ground/Pages/Payload/Payload.tsx +++ /dev/null @@ -1,975 +0,0 @@ -/* eslint-disable max-len */ -import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { - Shuffle, - ArrowLeftRight, - BoxArrowRight, - BriefcaseFill, - CloudArrowDown, - PersonFill, - StopCircleFill, -} from 'react-bootstrap-icons'; -import { useSimVar, Units, SeatFlags, useSeatFlags, usePersistentNumberProperty, usePersistentProperty } from '@flybywiresim/fbw-sdk'; -import { round } from 'lodash'; -import { CargoWidget } from './Seating/CargoWidget'; -import { ChartWidget } from './Chart/ChartWidget'; -import { CargoStationInfo, PaxStationInfo } from './Seating/Constants'; -import { t } from '../../../translation'; -import { TooltipWrapper } from '../../../UtilComponents/TooltipWrapper'; -import { SimpleInput } from '../../../UtilComponents/Form/SimpleInput/SimpleInput'; -import Loadsheet from './Loadsheet/acj339x.json'; -import Card from '../../../UtilComponents/Card/Card'; -import { SelectGroup, SelectItem } from '../../../UtilComponents/Form/Select'; -import { SeatMapWidget } from './Seating/SeatMapWidget'; -import { isSimbriefDataLoaded } from '../../../Store/features/simBrief'; -import { PromptModal, useModals } from '../../../UtilComponents/Modals/Modals'; -import { useAppSelector } from '../../../Store/store'; - -export const Payload = () => { - const { usingMetric } = Units; - const { showModal } = useModals(); - - const [aFlags] = useSeatFlags(`L:${Loadsheet.seatMap[0].simVar}`, Loadsheet.seatMap[0].capacity); - const [bFlags] = useSeatFlags(`L:${Loadsheet.seatMap[1].simVar}`, Loadsheet.seatMap[1].capacity); - const [cFlags] = useSeatFlags(`L:${Loadsheet.seatMap[2].simVar}`, Loadsheet.seatMap[2].capacity); - - const [aFlagsDesired, setAFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[0].simVar}_DESIRED`, Loadsheet.seatMap[0].capacity); - const [bFlagsDesired, setBFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[1].simVar}_DESIRED`, Loadsheet.seatMap[1].capacity); - const [cFlagsDesired, setCFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[2].simVar}_DESIRED`, Loadsheet.seatMap[2].capacity); - - const activeFlags = useMemo(() => [aFlags, bFlags, cFlags], [aFlags, bFlags, cFlags]); - const desiredFlags = useMemo(() => [aFlagsDesired, bFlagsDesired, cFlagsDesired], [aFlagsDesired, bFlagsDesired, cFlagsDesired]); - const setDesiredFlags = useMemo(() => [setAFlagsDesired, setBFlagsDesired, setCFlagsDesired], []); - - const [fwdBag] = useSimVar(`L:${Loadsheet.cargoMap[0].simVar}`, 'Number', 200); - const [aftCont] = useSimVar(`L:${Loadsheet.cargoMap[1].simVar}`, 'Number', 200); - const [aftBag] = useSimVar(`L:${Loadsheet.cargoMap[2].simVar}`, 'Number', 200); - const [aftBulk] = useSimVar(`L:${Loadsheet.cargoMap[3].simVar}`, 'Number', 200); - - const [fwdBagDesired, setFwdBagDesired] = useSimVar(`L:${Loadsheet.cargoMap[0].simVar}_DESIRED`, 'Number', 200); - const [aftContDesired, setAftContDesired] = useSimVar(`L:${Loadsheet.cargoMap[1].simVar}_DESIRED`, 'Number', 200); - const [aftBagDesired, setAftBagDesired] = useSimVar(`L:${Loadsheet.cargoMap[2].simVar}_DESIRED`, 'Number', 200); - const [aftBulkDesired, setAftBulkDesired] = useSimVar(`L:${Loadsheet.cargoMap[3].simVar}_DESIRED`, 'Number', 200); - - const cargo = useMemo(() => [fwdBag, aftCont, aftBag, aftBulk], [fwdBag, aftCont, aftBag, aftBulk]); - const cargoDesired = useMemo(() => [fwdBagDesired, aftContDesired, aftBagDesired, aftBulkDesired], [fwdBagDesired, aftContDesired, aftBagDesired, aftBulkDesired]); - const setCargoDesired = useMemo(() => [setFwdBagDesired, setAftContDesired, setAftBagDesired, setAftBulkDesired], []); - - const massUnitForDisplay = usingMetric ? 'KGS' : 'LBS'; - - const simbriefDataLoaded = isSimbriefDataLoaded(); - const [boardingStarted, setBoardingStarted] = useSimVar('L:A32NX_BOARDING_STARTED_BY_USR', 'Bool', 200); - const [boardingRate, setBoardingRate] = usePersistentProperty('CONFIG_BOARDING_RATE', 'REAL'); - const [paxWeight, setPaxWeight] = useSimVar('L:A32NX_WB_PER_PAX_WEIGHT', 'Kilograms', 200); - const [paxBagWeight, setPaxBagWeight] = useSimVar('L:A32NX_WB_PER_BAG_WEIGHT', 'Kilograms', 200); - const [galToKg] = useSimVar('FUEL WEIGHT PER GALLON', 'Kilograms', 2_000); - // const [destEfob] = useSimVar('L:A32NX_DESTINATION_FUEL_ON_BOARD', 'Kilograms', 5_000); - - const [emptyWeight] = useSimVar('A:EMPTY WEIGHT', 'Kilograms', 2_000); - - const [stationSize, setStationLen] = useState([]); - const maxPax = useMemo(() => ((stationSize && stationSize.length > 0) ? stationSize.reduce((a, b) => a + b) : -1), [stationSize]); - - // Calculate Total Pax from Pax Flags - const totalPax = useMemo(() => { - let p = 0; - activeFlags.forEach((flag) => { - p += flag.getTotalFilledSeats(); - }); - return p; - }, [...activeFlags]); - - const totalPaxDesired = useMemo(() => { - let p = 0; - desiredFlags.forEach((flag) => { - p += flag.getTotalFilledSeats(); - }); - return p; - }, [...desiredFlags]); - - const totalCargoDesired = useMemo(() => ((cargoDesired && cargoDesired.length > 0) ? cargoDesired.reduce((a, b) => a + b) : -1), [...cargoDesired]); - - const [cargoStationWeights, setCargoStationWeight] = useState([]); - - const totalCargo = useMemo(() => ((cargo && cargo.length > 0) ? cargo.reduce((a, b) => a + b) : -1), [...cargo]); - const maxCargo = useMemo(() => ((cargoStationWeights && cargoStationWeights.length > 0) ? cargoStationWeights.reduce((a, b) => a + b) : -1), [cargoStationWeights]); - - const [centerCurrent] = useSimVar('FUEL TANK CENTER QUANTITY', 'Gallons', 7_000); - const [LInnCurrent] = useSimVar('FUEL TANK LEFT MAIN QUANTITY', 'Gallons', 7_000); - const [LOutCurrent] = useSimVar('FUEL TANK LEFT AUX QUANTITY', 'Gallons', 7_000); - const [RInnCurrent] = useSimVar('FUEL TANK RIGHT MAIN QUANTITY', 'Gallons', 7_000); - const [ROutCurrent] = useSimVar('FUEL TANK RIGHT AUX QUANTITY', 'Gallons', 7_000); - - const [LInnCapacity] = useSimVar('FUEL TANK LEFT MAIN CAPACITY', 'Gallons', 7_000); - const [LOutCapacity] = useSimVar('FUEL TANK LEFT AUX CAPACITY', 'Gallons', 7_000); - - const fuel = [centerCurrent, LInnCurrent, LOutCurrent, RInnCurrent, ROutCurrent]; - - // Units - // Weight/CG - // TODO FIXME: Move all ZFW and GW calculations to rust - Will be refactored in phase 2 - const [zfw, setZfw] = useState(0); - const [zfwCg, setZfwCg] = useState(0); - const [zfwDesired, setZfwDesired] = useState(0); - const [zfwDesiredCg, setZfwDesiredCg] = useState(0); - const [gw, setGw] = useState(emptyWeight); - const [gwDesired, setGwDesired] = useState(emptyWeight); - const [cg, setCg] = useState(25); - const [totalDesiredWeight, setTotalDesiredWeight] = useState(0); - const [desiredCg, setDesiredCg] = useState(0); - const [mlw, setMlw] = useState(0); - const [mlwCg, setMlwCg] = useState(0); - const [mlwDesired, setMlwDesired] = useState(0); - const [mlwDesiredCg, setMlwDesiredCg] = useState(0); - - const [seatMap] = useState(Loadsheet.seatMap); - const [cargoMap] = useState(Loadsheet.cargoMap); - - const totalCurrentGallon = useMemo(() => round(Math.max(LInnCurrent + LOutCurrent + RInnCurrent + ROutCurrent + centerCurrent, 0)), [fuel]); - - const [showSimbriefButton, setShowSimbriefButton] = useState(false); - const simbriefUnits = useAppSelector((state) => state.simbrief.data.units); - const simbriefBagWeight = parseInt(useAppSelector((state) => state.simbrief.data.weights.bagWeight)); - const simbriefPaxWeight = parseInt(useAppSelector((state) => state.simbrief.data.weights.passengerWeight)); - const simbriefPax = parseInt(useAppSelector((state) => state.simbrief.data.weights.passengerCount)); - const simbriefBag = parseInt(useAppSelector((state) => state.simbrief.data.weights.bagCount)); - const simbriefFreight = parseInt(useAppSelector((state) => state.simbrief.data.weights.freight)); - - const [displayZfw, setDisplayZfw] = useState(true); - - // GSX - const [gsxPayloadSyncEnabled] = usePersistentNumberProperty('GSX_PAYLOAD_SYNC', 0); - const [_, setGsxNumPassengers] = useSimVar('L:FSDT_GSX_NUMPASSENGERS', 'Number'); - const [gsxBoardingState] = useSimVar('L:FSDT_GSX_BOARDING_STATE', 'Number'); - const [gsxDeBoardingState] = useSimVar('L:FSDT_GSX_DEBOARDING_STATE', 'Number'); - const gsxStates = { - AVAILABLE: 1, - NOT_AVAILABLE: 2, - BYPASSED: 3, - REQUESTED: 4, - PERFORMING: 5, - COMPLETED: 6, - }; - - const setSimBriefValues = () => { - if (simbriefUnits === 'kgs') { - setPaxBagWeight(simbriefBagWeight); - setPaxWeight(simbriefPaxWeight); - setTargetPax(simbriefPax > maxPax ? maxPax : simbriefPax); - setTargetCargo(simbriefBag, simbriefFreight, simbriefBagWeight); - } else { - setPaxBagWeight(Units.poundToKilogram(simbriefBagWeight)); - setPaxWeight(Units.poundToKilogram(simbriefPaxWeight)); - setTargetPax(simbriefPax); - setTargetCargo(simbriefBag, Units.poundToKilogram(simbriefFreight), Units.poundToKilogram(simbriefBagWeight)); - } - }; - - const [busDC2] = useSimVar('L:A32NX_ELEC_DC_2_BUS_IS_POWERED', 'Bool', 2_000); - const [busDCHot1] = useSimVar('L:A32NX_ELEC_DC_HOT_1_BUS_IS_POWERED', 'Bool', 2_000); - const [simGroundSpeed] = useSimVar('GPS GROUND SPEED', 'knots', 2_000); - const [isOnGround] = useSimVar('SIM ON GROUND', 'Bool', 2_000); - const [eng1Running] = useSimVar('ENG COMBUSTION:1', 'Bool', 2_000); - const [eng2Running] = useSimVar('ENG COMBUSTION:2', 'Bool', 2_000); - const [coldAndDark, setColdAndDark] = useState(true); - - const chooseDesiredSeats = useCallback((stationIndex: number, fillSeats: boolean = true, numChoose: number) => { - const seatFlags: SeatFlags = desiredFlags[stationIndex]; - if (fillSeats) { - seatFlags.fillEmptySeats(numChoose); - } else { - seatFlags.emptyFilledSeats(numChoose); - } - - setDesiredFlags[stationIndex](seatFlags); - }, [...desiredFlags]); - - const setTargetPax = useCallback((numOfPax: number) => { - setGsxNumPassengers(numOfPax); - - if (!stationSize || numOfPax === totalPaxDesired || numOfPax > maxPax || numOfPax < 0) return; - - let paxRemaining = numOfPax; - - const fillStation = (stationIndex: number, percent: number, paxToFill: number) => { - const sFlags: SeatFlags = desiredFlags[stationIndex]; - const toBeFilled = Math.min(Math.trunc(percent * paxToFill), stationSize[stationIndex]); - - paxRemaining -= toBeFilled; - - const planSeatedPax = sFlags.getTotalFilledSeats(); - chooseDesiredSeats( - stationIndex, - (toBeFilled > planSeatedPax), - Math.abs(toBeFilled - planSeatedPax), - ); - }; - - for (let i = seatMap.length - 1; i > 0; i--) { - fillStation(i, seatMap[i].fill, numOfPax); - } - fillStation(0, 1, paxRemaining); - }, [maxPax, ...stationSize, ...seatMap, totalPaxDesired]); - - const setTargetCargo = useCallback((numberOfPax: number, freight: number, perBagWeight: number = paxBagWeight) => { - const bagWeight = numberOfPax * perBagWeight; - const loadableCargoWeight = Math.min(bagWeight + Math.round(freight), maxCargo); - - let remainingWeight = loadableCargoWeight; - - async function fillCargo(station: number, percent: number, loadableCargoWeight: number) { - const c = Math.round(percent * loadableCargoWeight); - remainingWeight -= c; - setCargoDesired[station](c); - } - - for (let i = cargoDesired.length - 1; i > 0; i--) { - fillCargo(i, cargoStationWeights[i] / maxCargo, loadableCargoWeight); - } - fillCargo(0, 1, remainingWeight); - }, [maxCargo, ...cargoStationWeights, ...cargoMap, ...cargoDesired, paxBagWeight]); - - const calculatePaxMoment = useCallback(() => { - let paxMoment = 0; - activeFlags.forEach((stationFlag, i) => { - paxMoment += stationFlag.getTotalFilledSeats() * paxWeight * seatMap[i].position; - }); - return paxMoment; - }, [paxWeight, seatMap, ...activeFlags]); - - const calculatePaxDesiredMoment = useCallback(() => { - let paxMoment = 0; - desiredFlags.forEach((stationFlag, i) => { - paxMoment += stationFlag.getTotalFilledSeats() * paxWeight * seatMap[i].position; - }); - - return paxMoment; - }, [paxWeight, seatMap, ...desiredFlags]); - - const calculateCargoMoment = useCallback(() => { - let cargoMoment = 0; - cargo.forEach((station, i) => { - cargoMoment += station * cargoMap[i].position; - }); - return cargoMoment; - }, [...cargo, cargoMap]); - - const calculateCargoDesiredMoment = useCallback(() => { - let cargoMoment = 0; - cargoDesired.forEach((station, i) => { - cargoMoment += station * cargoMap[i].position; - }); - return cargoMoment; - }, [...cargoDesired, cargoMap]); - - const calculateCg = useCallback((mass: number, moment: number) => -100 * ((moment / mass - Loadsheet.specs.leMacZ) / Loadsheet.specs.macSize), []); - - const processZfw = useCallback((newZfw) => { - let paxCargoWeight = newZfw - emptyWeight; - - // Load pax first - const pWeight = paxWeight + paxBagWeight; - const newPax = Math.min(Math.round(paxCargoWeight / pWeight), maxPax); - - paxCargoWeight -= newPax * pWeight; - const newCargo = Math.min(paxCargoWeight, maxCargo); - - setTargetPax(newPax); - setTargetCargo(newPax, newCargo); - }, [emptyWeight, paxWeight, paxBagWeight, maxPax, maxCargo]); - - const processGw = useCallback((newGw) => { - const totalFuel = round(totalCurrentGallon * galToKg); - let paxCargoWeight = newGw - emptyWeight - totalFuel; - - // Load pax first - const pWeight = paxWeight + paxBagWeight; - const newPax = Math.min(Math.round(paxCargoWeight / pWeight), maxPax); - - paxCargoWeight -= newPax * pWeight; - const newCargo = Math.min(paxCargoWeight, maxCargo); - - setTargetPax(newPax); - setTargetCargo(newPax, newCargo); - }, [emptyWeight, paxWeight, paxBagWeight, maxPax, maxCargo, totalCurrentGallon]); - - const onClickCargo = useCallback((cargoStation, e) => { - if (gsxPayloadSyncEnabled === 1 && boardingStarted) { - return; - } - const cargoPercent = Math.min(Math.max(0, e.nativeEvent.offsetX / cargoMap[cargoStation].progressBarWidth), 1); - setCargoDesired[cargoStation](Math.round(cargoMap[cargoStation].weight * cargoPercent)); - }, [cargoMap]); - - const onClickSeat = useCallback((stationIndex: number, seatId: number) => { - if (gsxPayloadSyncEnabled === 1 && boardingStarted) { - return; - } - - // TODO FIXME: This calculation does not work correctly if user clicks on many seats in rapid succession - const oldPaxBag = totalPaxDesired * paxBagWeight; - const freight = Math.max(totalCargoDesired - oldPaxBag, 0); - - const seatFlags: SeatFlags = desiredFlags[stationIndex]; - seatFlags.toggleSeatId(seatId); - setDesiredFlags[stationIndex](seatFlags); - - let newPaxDesired = 0; - desiredFlags.forEach((flag) => { - newPaxDesired += flag.getTotalFilledSeats(); - }); - - setTargetCargo(newPaxDesired, freight); - }, [ - paxBagWeight, - totalCargoDesired, - ...cargoDesired, - ...desiredFlags, ...stationSize, - totalPaxDesired, - ]); - - const handleDeboarding = useCallback(() => { - if (!boardingStarted) { - showModal( - { - setTargetPax(0); - setTargetCargo(0, 0); - setBoardingStarted(true); - }} - />, - ); - return; - } - setBoardingStarted(false); - }, [totalPaxDesired, totalPax, totalCargo, boardingStarted, totalCargoDesired]); - - const calculateBoardingTime = useMemo(() => { - // factors taken from payload.rs TODO: Simvar - let boardingRateMultiplier = 0; - if (boardingRate === 'REAL') { - boardingRateMultiplier = 5; - } else if (boardingRate === 'FAST') { - boardingRateMultiplier = 1; - } - - // factors taken from payload.rs TODO: Simvar - const cargoWeightPerWeightStep = 60; - - const differentialPax = Math.abs(totalPaxDesired - totalPax); - const differentialCargo = Math.abs(totalCargoDesired - totalCargo); - - const estimatedPaxBoardingSeconds = differentialPax * boardingRateMultiplier; - const estimatedCargoLoadingSeconds = (differentialCargo / cargoWeightPerWeightStep) * boardingRateMultiplier; - - return Math.max(estimatedPaxBoardingSeconds, estimatedCargoLoadingSeconds); - }, [totalPaxDesired, totalPax, totalCargoDesired, totalCargo, boardingRate]); - - const boardingStatusClass = useMemo(() => { - if (!boardingStarted) { - return 'text-theme-highlight'; - } - return (totalPaxDesired * paxWeight + totalCargoDesired) >= (totalPax * paxWeight + totalCargo) ? 'text-green-500' : 'text-yellow-500'; - }, [boardingStarted, paxWeight, totalCargoDesired, totalCargo, totalPaxDesired, totalPax]); - - // Init - useEffect(() => { - if (paxWeight === 0) { - setPaxWeight(Math.round(Loadsheet.specs.pax.defaultPaxWeight)); - } - if (paxBagWeight === 0) { - setPaxBagWeight(Math.round(Loadsheet.specs.pax.defaultBagWeight)); - } - }, []); - - // Set Cold and Dark State - useEffect(() => { - if (simGroundSpeed > 0.1 || eng1Running || eng2Running || !isOnGround || (!busDC2 && !busDCHot1)) { - setColdAndDark(false); - } else { - setColdAndDark(true); - } - }, [simGroundSpeed, eng1Running, eng2Running, isOnGround, busDC2, busDCHot1]); - - useEffect(() => { - if (boardingRate !== 'INSTANT') { - if (!coldAndDark) { - setBoardingRate('INSTANT'); - } - } - }, [coldAndDark, boardingRate]); - - // Init the seating map - useEffect(() => { - const stationSize: number[] = []; - for (let i = 0; i < seatMap.length; i++) { - stationSize.push(0); - } - seatMap.forEach((station, i) => { - stationSize[i] = station.capacity; - }); - setStationLen(stationSize); - }, [seatMap]); - - // Init the cargo map - useEffect(() => { - const cargoSize: number[] = []; - for (let i = 0; i < cargoMap.length; i++) { - cargoSize.push(0); - } - cargoMap.forEach((station, index) => { - cargoSize[index] = station.weight; - }); - setCargoStationWeight(cargoSize); - }, [cargoMap]); - - useEffect(() => { - if (gsxPayloadSyncEnabled === 1) { - switch (gsxBoardingState) { - // If boarding has been requested, performed or completed - case gsxStates.REQUESTED: - case gsxStates.PERFORMING: - case gsxStates.COMPLETED: - setBoardingStarted(true); - break; - default: - break; - } - } - }, [gsxBoardingState]); - - useEffect(() => { - if (gsxPayloadSyncEnabled === 1) { - switch (gsxDeBoardingState) { - case gsxStates.REQUESTED: - // If Deboarding has been requested, set target pax to 0 for boarding backend - setTargetPax(0); - setTargetCargo(0, 0); - setBoardingStarted(true); - break; - case gsxStates.PERFORMING: - // If deboarding is being performed - setBoardingStarted(true); - break; - case gsxStates.COMPLETED: - // If deboarding is completed - setBoardingStarted(false); - break; - default: - break; - } - } - }, [gsxDeBoardingState]); - - useEffect(() => { - let simbriefStatus = false; - if (simbriefUnits === 'kgs') { - simbriefStatus = (simbriefDataLoaded - && ( - simbriefPax !== totalPaxDesired - || Math.abs(simbriefFreight + simbriefBag * simbriefBagWeight - totalCargoDesired) > 3.0 - || Math.abs(simbriefPaxWeight - paxWeight) > 3.0 - || Math.abs(simbriefBagWeight - paxBagWeight) > 3.0 - ) - ); - } else { - simbriefStatus = (simbriefDataLoaded - && ( - simbriefPax !== totalPaxDesired - || Math.abs(Units.poundToKilogram(simbriefFreight + simbriefBag * simbriefBagWeight) - totalCargoDesired) > 3.0 - || Math.abs(Units.poundToKilogram(simbriefPaxWeight) - paxWeight) > 3.0 - || Math.abs(Units.poundToKilogram(simbriefBagWeight) - paxBagWeight) > 3.0 - ) - ); - } - - if (gsxPayloadSyncEnabled === 1) { - if (boardingStarted) { - setShowSimbriefButton(false); - return; - } - - setShowSimbriefButton(simbriefStatus); - return; - } - setShowSimbriefButton(simbriefStatus); - }, [ - simbriefUnits, - simbriefFreight, - simbriefBag, - simbriefBagWeight, - paxWeight, paxBagWeight, - totalPaxDesired, totalCargoDesired, - simbriefDataLoaded, - boardingStarted, - gsxPayloadSyncEnabled, - ]); - - useEffect(() => { - // TODO FIXME: Move all this logic into rust - // Note: Looks messy after phase 1 refactor, will be fixed by deprecating this and moving all calculations into rust - const centerTankMoment = -20.3; - const innerTankMoment = -25.5 - const outerTankMoment = -41.085 - // Adjust ZFW CG Values based on payload - const newZfw = emptyWeight + totalPax * paxWeight + totalCargo; - const newZfwDesired = emptyWeight + totalPaxDesired * paxWeight + totalCargoDesired; - const newZfwMoment = Loadsheet.specs.emptyPosition * emptyWeight + calculatePaxMoment() + calculateCargoMoment(); - const newZfwDesiredMoment = Loadsheet.specs.emptyPosition * emptyWeight + calculatePaxDesiredMoment() + calculateCargoDesiredMoment(); - const newZfwCg = calculateCg(newZfw, newZfwMoment); - const newZfwDesiredCg = calculateCg(newZfwDesired, newZfwDesiredMoment); - const totalFuel = round(totalCurrentGallon * galToKg); - - const totalFuelMoment = centerCurrent * galToKg * centerTankMoment + (LOutCurrent + ROutCurrent) * galToKg * outerTankMoment + (LInnCurrent + RInnCurrent) * galToKg * innerTankMoment; - const newGw = newZfw + totalFuel; - const newGwDesired = newZfwDesired + totalFuel; - const newTotalMoment = newZfwMoment + totalFuelMoment; - const newCg = calculateCg(newGw, newTotalMoment); - - const newTotalWeightDesired = newZfwDesired + totalFuel; - const newTotalDesiredMoment = newZfwDesiredMoment + totalFuelMoment; - const newDesiredCg = calculateCg(newTotalWeightDesired, newTotalDesiredMoment); - - setZfw(newZfw); - setZfwCg(newZfwCg); - setZfwDesired(newZfwDesired); - setZfwDesiredCg(newZfwDesiredCg); - setGw(newGw); - setGwDesired(newGwDesired); - setCg(newCg); - setTotalDesiredWeight(newTotalWeightDesired); - setDesiredCg(newDesiredCg); - // TODO: Predicted MLDW - setMlw(newGw); - setMlwCg(newCg); - setMlwDesired(newTotalWeightDesired); - setMlwDesiredCg(newDesiredCg); - }, [ - ...desiredFlags, ...activeFlags, - ...cargo, ...cargoDesired, - ...fuel, - paxWeight, paxBagWeight, - emptyWeight, - ]); - - const remainingTimeString = () => { - const minutes = Math.round(calculateBoardingTime / 60); - const seconds = calculateBoardingTime % 60; - const padding = seconds < 10 ? '0' : ''; - return `${minutes}:${padding}${seconds.toFixed(0)} ${t('Ground.Payload.EstimatedDurationUnit')}`; - }; - - return ( -
-
-
- -
- - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {' '} - - {t('Ground.Payload.Planned')} - - {t('Ground.Payload.Current')} -
- {t('Ground.Payload.Passengers')} - - -
- 0 ? maxPax : 999} - value={totalPaxDesired} - onBlur={(x) => { - if (!Number.isNaN(parseInt(x) || parseInt(x) === 0)) { - setTargetPax(parseInt(x)); - setTargetCargo(parseInt(x), 0); - } - }} - unit="PAX" - disabled={gsxPayloadSyncEnabled === 1 && boardingStarted} - /> -
-
-
- -
- {t('Ground.Payload.Cargo')} - - -
- 0 ? Math.round(Units.kilogramToUser(maxCargo)) : 99999} - value={Units.kilogramToUser(totalCargoDesired)} - onBlur={(x) => { - if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) { - setTargetCargo(0, Units.userToKilogram(parseInt(x))); - } - }} - unit={massUnitForDisplay} - disabled={gsxPayloadSyncEnabled === 1 && boardingStarted} - /> -
-
-
- -
- {displayZfw ? t('Ground.Payload.ZFW') : t('Ground.Payload.GW')} - - {(displayZfw - ? ( - -
- { - if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) processZfw(Units.userToKilogram(parseInt(x))); - }} - unit={massUnitForDisplay} - disabled={gsxPayloadSyncEnabled === 1 && boardingStarted} - /> -
-
- ) - : ( - -
- { - if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) processGw(Units.userToKilogram(parseInt(x))); - }} - unit={massUnitForDisplay} - disabled={gsxPayloadSyncEnabled === 1 && boardingStarted} - /> -
-
- ) - )} -
- -
-
-
- {t(displayZfw ? 'Ground.Payload.ZFWCG' : 'Ground.Payload.GWCG')} -
-
- -
-
-
- -
- {/* TODO FIXME: Setting pax/cargo given desired ZFWCG, ZFW, total pax, total cargo */} -
- -
- {/* - 0 ? maxPax : 999} - value={zfwCg.toFixed(2)} - onBlur={{(x) => processZfwCg(x)} - /> - */} -
-
-
- -
- -
- -
- -
- - { - if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) setPaxWeight(Units.userToKilogram(parseInt(x))); - }} - /> -
{massUnitForDisplay}
-
-
- - -
- - { - if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) setPaxBagWeight(Units.userToKilogram(parseInt(x))); - }} - /> -
{massUnitForDisplay}
-
-
- {gsxPayloadSyncEnabled !== 1 && ( - <> - - - - - - - - - )} -
-
- - {showSimbriefButton - && ( - -
- -
-
- )} -
- {(gsxPayloadSyncEnabled !== 1) && ( -
- -
-
- {t('Ground.Payload.BoardingTime')} - - ( - {remainingTimeString()} - ) - -
- - - setBoardingRate('INSTANT')} - > - {t('Settings.Instant')} - - - -
- setBoardingRate('FAST')} - > - {t('Settings.Fast')} - -
-
- -
- setBoardingRate('REAL')} - > - {t('Settings.Real')} - -
-
-
-
- - {/* */} - {/* */} - {/* */} -
- )} -
-
- -
-
-
-
- ); -}; - -interface PayloadValueInputProps { - min: number, - max: number, - value: number - onBlur: (v: string) => void, - unit: string, - disabled?: boolean -} - -const PayloadValueInput: FC = ({ min, max, value, onBlur, unit, disabled }) => ( -
- -
{unit}
-
-); - -interface NumberUnitDisplayProps { - /** - * The value to show - */ - value: number, - - /** - * The amount of leading zeroes to pad with - */ - padTo: number, - - /** - * The unit to show at the end - */ - unit: string, -} - -const PayloadValueUnitDisplay: FC = ({ value, padTo, unit }) => { - const fixedValue = value.toFixed(0); - const leadingZeroCount = Math.max(0, padTo - fixedValue.length); - - return ( - - - {'0'.repeat(leadingZeroCount)} - {fixedValue} - - {' '} - {unit} - - ); -}; - -const PayloadPercentUnitDisplay: FC<{value: number}> = ({ value }) => { - const fixedValue = value.toFixed(2); - - return ( - - - {fixedValue} - - {' '} - % - - ); -}; diff --git a/hdw-a339x-acj/src/wasm/fadec_a320/CMakeList.txt b/hdw-a339x-acj/src/wasm/fadec_a330/CMakeList.txt similarity index 100% rename from hdw-a339x-acj/src/wasm/fadec_a320/CMakeList.txt rename to hdw-a339x-acj/src/wasm/fadec_a330/CMakeList.txt diff --git a/hdw-a339x-acj/src/wasm/fadec_a320/build.sh b/hdw-a339x-acj/src/wasm/fadec_a330/build.sh similarity index 100% rename from hdw-a339x-acj/src/wasm/fadec_a320/build.sh rename to hdw-a339x-acj/src/wasm/fadec_a330/build.sh diff --git a/hdw-a339x-acj/src/wasm/fadec_a320/src/EngineControl.h b/hdw-a339x-acj/src/wasm/fadec_a330/src/EngineControl.h similarity index 82% rename from hdw-a339x-acj/src/wasm/fadec_a320/src/EngineControl.h rename to hdw-a339x-acj/src/wasm/fadec_a330/src/EngineControl.h index add567ed1..b34bfb8bc 100644 --- a/hdw-a339x-acj/src/wasm/fadec_a320/src/EngineControl.h +++ b/hdw-a339x-acj/src/wasm/fadec_a330/src/EngineControl.h @@ -17,14 +17,16 @@ #define CONFIGURATION_SECTION_FUEL_RIGHT_QUANTITY "FUEL_RIGHT_QUANTITY" #define CONFIGURATION_SECTION_FUEL_LEFT_AUX_QUANTITY "FUEL_LEFT_AUX_QUANTITY" #define CONFIGURATION_SECTION_FUEL_RIGHT_AUX_QUANTITY "FUEL_RIGHT_AUX_QUANTITY" +// #define CONFIGURATION_SECTION_FUEL_TRIM_QTY "FUEL_TRIM_QTY" /* Values in gallons */ struct Configuration { double fuelCenter = 0; - double fuelLeft = 1645; + double fuelLeft = 1645.0; double fuelRight = fuelLeft; - double fuelLeftAux = 228; + double fuelLeftAux = 228.0; double fuelRightAux = fuelLeftAux; + // double fuelTrim = 1617.0; }; class EngineControl { @@ -32,8 +34,8 @@ class EngineControl { SimVars* simVars; EngineRatios* ratios; Polynomial* poly; - Timer timerLeft; - Timer timerRight; + Timer timerEngine1; + Timer timerEngine2; Timer timerFuel; std::string confFilename = FILENAME_FADEC_CONF_DIRECTORY; @@ -50,7 +52,7 @@ class EngineControl { int engine; int egtImbalance; int ffImbalance; - int n2Imbalance; + int n3Imbalance; double engineState; double engineStarter; double engineIgniter; @@ -61,19 +63,19 @@ class EngineControl { double simCN1; double simN1; - double simN2; + double simN3; double thrust; - double simN2LeftPre; - double simN2RightPre; - double deltaN2; + double simN3Engine1Pre; + double simN3Engine2Pre; + double deltaN3; double thermalEnergy1; double thermalEnergy2; double oilTemperature; - double oilTemperatureLeftPre; - double oilTemperatureRightPre; + double oilTemperatureEngine1Pre; + double oilTemperatureEngine2Pre; double oilTemperatureMax; double idleN1; - double idleN2; + double idleN3; double idleFF; double idleEGT; double idleOil; @@ -110,13 +112,13 @@ class EngineControl { idleCN1 = iCN1(pressAltitude, mach, ambientTemp); idleN1 = idleCN1 * sqrt(ratios->theta2(0, ambientTemp)); - idleN2 = iCN2(pressAltitude, mach) * sqrt(ratios->theta(ambientTemp)); + idleN3 = iCN3(pressAltitude, mach) * sqrt(ratios->theta(ambientTemp)); idleCFF = poly->correctedFuelFlow(idleCN1, 0, pressAltitude); // lbs/hr idleFF = idleCFF * LBS_TO_KGS * ratios->delta2(0, ambientPressure) * sqrt(ratios->theta2(0, ambientTemp)); // Kg/hr idleEGT = poly->correctedEGT(idleCN1, idleCFF, 0, pressAltitude) * ratios->theta2(0, ambientTemp); simVars->setEngineIdleN1(idleN1); - simVars->setEngineIdleN2(idleN2); + simVars->setEngineIdleN3(idleN3); simVars->setEngineIdleFF(idleFF); simVars->setEngineIdleEGT(idleEGT); } @@ -151,8 +153,8 @@ class EngineControl { // Obtain FF imbalance (Max 36 Kg/h) ffImbalance = (rand() % 36) + 1; - // Obtain N2 imbalance (Max 0.3%) - n2Imbalance = (rand() % 30) + 1; + // Obtain N3 imbalance (Max 0.3%) + n3Imbalance = (rand() % 30) + 1; // Obtain Oil Qty imbalance (Max 2.0 qt) oilQtyImbalance = (rand() % 20) + 1; @@ -168,7 +170,7 @@ class EngineControl { // Zero Padding and Merging imbalanceCode = to_string_with_zero_padding(engine, 2) + to_string_with_zero_padding(egtImbalance, 2) + - to_string_with_zero_padding(ffImbalance, 2) + to_string_with_zero_padding(n2Imbalance, 2) + + to_string_with_zero_padding(ffImbalance, 2) + to_string_with_zero_padding(n3Imbalance, 2) + to_string_with_zero_padding(oilQtyImbalance, 2) + to_string_with_zero_padding(oilPressureImbalance, 2) + to_string_with_zero_padding(oilPressureIdle, 2) + to_string_with_zero_padding(oilTemperatureMax, 2); @@ -183,8 +185,8 @@ class EngineControl { void engineStateMachine(int engine, double engineIgniter, double engineStarter, - double simN2, - double idleN2, + double simN3, + double idleN3, double pressAltitude, double ambientTemp, double deltaTimeDiff) { @@ -212,7 +214,7 @@ class EngineControl { // Present State OFF if (engineState == 0 || engineState == 10) { - if (engineIgniter == 1 && engineStarter == 1 && simN2 > 20) { + if (engineIgniter == 1 && engineStarter == 1 && simN3 > 20) { engineState = 1; } else if (engineIgniter == 2 && engineStarter == 1) { engineState = 2; @@ -232,7 +234,7 @@ class EngineControl { // Present State Starting. if (engineState == 2 || engineState == 12) { - if (engineStarter == 1 && simN2 >= (idleN2 - 0.1)) { + if (engineStarter == 1 && simN3 >= (idleN3 - 0.1)) { engineState = 1; resetTimer = 1; } else if (engineStarter == 0) { @@ -245,7 +247,7 @@ class EngineControl { // Present State Re-Starting. if (engineState == 3 || engineState == 13) { - if (engineStarter == 1 && simN2 >= (idleN2 - 0.1)) { + if (engineStarter == 1 && simN3 >= (idleN3 - 0.1)) { engineState = 1; resetTimer = 1; } else if (engineStarter == 0) { @@ -261,10 +263,10 @@ class EngineControl { if (engineIgniter == 2 && engineStarter == 1) { engineState = 3; resetTimer = 1; - } else if (engineStarter == 0 && simN2 < 0.05 && egtFbw <= ambientTemp) { + } else if (engineStarter == 0 && simN3 < 0.05 && egtFbw <= ambientTemp) { engineState = 0; resetTimer = 1; - } else if (engineStarter == 1 && simN2 > 50) { + } else if (engineStarter == 1 && simN3 > 50) { engineState = 3; resetTimer = 1; } else { @@ -297,21 +299,21 @@ class EngineControl { double imbalance, double deltaTime, double timer, - double simN2, + double simN3, double pressAltitude, double ambientTemp) { - double startCN2Left; - double startCN2Right; - double preN2Fbw; - double newN2Fbw; + double startCN3Engine1; + double startCN3Engine2; + double preN3Fbw; + double newN3Fbw; double preEgtFbw; double startEgtFbw; double shutdownEgtFbw; - n2Imbalance = 0; + n3Imbalance = 0; ffImbalance = 0; egtImbalance = 0; - idleN2 = simVars->getEngineIdleN2(); + idleN3 = simVars->getEngineIdleN3(); idleN1 = simVars->getEngineIdleN1(); idleFF = simVars->getEngineIdleFF(); idleEGT = simVars->getEngineIdleEGT(); @@ -323,36 +325,37 @@ class EngineControl { if (engineImbalanced == engine) { ffImbalance = imbalanceExtractor(imbalance, 3); egtImbalance = imbalanceExtractor(imbalance, 2); - n2Imbalance = imbalanceExtractor(imbalance, 4) / 100; + n3Imbalance = imbalanceExtractor(imbalance, 4) / 100; } if (engine == 1) { // Delay between Engine Master ON and Start Valve Open if (timer < 1.7) { if (simOnGround == 1) { - simVars->setFuelUsedLeft(0); + simVars->setFuelUsedEngine1(0); } simVars->setEngine1Timer(timer + deltaTime); - startCN2Left = 0; - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::StartCN2Left, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), - &startCN2Left); + startCN3Engine1 = 0; + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::StartCN3Engine1, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + &startCN3Engine1); } else { - preN2Fbw = simVars->getEngine1N2(); + preN3Fbw = simVars->getEngine1N3(); preEgtFbw = simVars->getEngine1EGT(); - newN2Fbw = poly->startN2(simN2, preN2Fbw, idleN2 - n2Imbalance); - startEgtFbw = poly->startEGT(newN2Fbw, idleN2 - n2Imbalance, ambientTemp, idleEGT - egtImbalance); + newN3Fbw = poly->startN3(simN3, preN3Fbw, idleN3); + startEgtFbw = poly->startEGT(newN3Fbw, idleN3, ambientTemp, idleEGT); shutdownEgtFbw = poly->shutdownEGT(preEgtFbw, ambientTemp, deltaTime); - simVars->setEngine1N2(newN2Fbw); - simVars->setEngine1N1(poly->startN1(newN2Fbw, idleN2 - n2Imbalance, idleN1)); - simVars->setEngine1FF(poly->startFF(newN2Fbw, idleN2 - n2Imbalance, idleFF - ffImbalance)); + simVars->setEngine1N3(newN3Fbw); + simVars->setEngine1N2(newN3Fbw + 0.7); + simVars->setEngine1N1(poly->startN1(newN3Fbw, idleN3, idleN1)); + simVars->setEngine1FF(poly->startFF(newN3Fbw, idleN3, idleFF)); if (engineState == 3) { if (abs(startEgtFbw - preEgtFbw) <= 1.5) { simVars->setEngine1EGT(startEgtFbw); simVars->setEngine1State(2); } else if (startEgtFbw > preEgtFbw) { - simVars->setEngine1EGT(preEgtFbw + (0.75 * deltaTime * (idleN2 - newN2Fbw))); + simVars->setEngine1EGT(preEgtFbw + (0.75 * deltaTime * (idleN3 - newN3Fbw))); } else { simVars->setEngine1EGT(shutdownEgtFbw); } @@ -360,37 +363,38 @@ class EngineControl { simVars->setEngine1EGT(startEgtFbw); } - oilTemperature = poly->startOilTemp(newN2Fbw, idleN2, ambientTemp); - oilTemperatureLeftPre = oilTemperature; - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempLeft, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + oilTemperature = poly->startOilTemp(newN3Fbw, idleN3, ambientTemp); + oilTemperatureEngine1Pre = oilTemperature; + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempEngine1, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilTemperature); } } else { if (timer < 1.7) { if (simOnGround == 1) { - simVars->setFuelUsedRight(0); + simVars->setFuelUsedEngine2(0); } simVars->setEngine2Timer(timer + deltaTime); - startCN2Right = 0; - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::StartCN2Right, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), - &startCN2Right); + startCN3Engine2 = 0; + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::StartCN3Engine2, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + &startCN3Engine2); } else { - preN2Fbw = simVars->getEngine2N2(); + preN3Fbw = simVars->getEngine2N3(); preEgtFbw = simVars->getEngine2EGT(); - newN2Fbw = poly->startN2(simN2, preN2Fbw, idleN2 - n2Imbalance); - startEgtFbw = poly->startEGT(newN2Fbw, idleN2 - n2Imbalance, ambientTemp, idleEGT - egtImbalance); + newN3Fbw = poly->startN3(simN3, preN3Fbw, idleN3); + startEgtFbw = poly->startEGT(newN3Fbw, idleN3, ambientTemp, idleEGT); shutdownEgtFbw = poly->shutdownEGT(preEgtFbw, ambientTemp, deltaTime); - simVars->setEngine2N2(newN2Fbw); - simVars->setEngine2N1(poly->startN1(newN2Fbw, idleN2 - n2Imbalance, idleN1)); - simVars->setEngine2FF(poly->startFF(newN2Fbw, idleN2 - n2Imbalance, idleFF - ffImbalance)); + simVars->setEngine2N3(newN3Fbw); + simVars->setEngine2N2(newN3Fbw + 0.7); + simVars->setEngine2N1(poly->startN1(newN3Fbw, idleN3, idleN1)); + simVars->setEngine2FF(poly->startFF(newN3Fbw, idleN3, idleFF)); if (engineState == 3) { if (abs(startEgtFbw - preEgtFbw) <= 1.5) { simVars->setEngine2EGT(startEgtFbw); simVars->setEngine2State(2); } else if (startEgtFbw > preEgtFbw) { - simVars->setEngine2EGT(preEgtFbw + (0.75 * deltaTime * (idleN2 - newN2Fbw))); + simVars->setEngine2EGT(preEgtFbw + (0.75 * deltaTime * (idleN3 - newN3Fbw))); } else { simVars->setEngine2EGT(shutdownEgtFbw); } @@ -398,9 +402,9 @@ class EngineControl { simVars->setEngine2EGT(startEgtFbw); } - oilTemperature = poly->startOilTemp(newN2Fbw, idleN2, ambientTemp); - oilTemperatureRightPre = oilTemperature; - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempRight, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + oilTemperature = poly->startOilTemp(newN3Fbw, idleN3, ambientTemp); + oilTemperatureEngine2Pre = oilTemperature; + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempEngine2, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilTemperature); } } @@ -411,10 +415,10 @@ class EngineControl { /// void engineShutdownProcedure(int engine, double ambientTemp, double simN1, double deltaTime, double timer) { double preN1Fbw; - double preN2Fbw; + double preN3Fbw; double preEgtFbw; double newN1Fbw; - double newN2Fbw; + double newN3Fbw; double newEgtFbw; if (engine == 1) { @@ -422,41 +426,42 @@ class EngineControl { simVars->setEngine1Timer(timer + deltaTime); } else { preN1Fbw = simVars->getEngine1N1(); - preN2Fbw = simVars->getEngine1N2(); + preN3Fbw = simVars->getEngine1N3(); preEgtFbw = simVars->getEngine1EGT(); newN1Fbw = poly->shutdownN1(preN1Fbw, deltaTime); if (simN1 < 5 && simN1 > newN1Fbw) { // Takes care of windmilling newN1Fbw = simN1; } - newN2Fbw = poly->shutdownN2(preN2Fbw, deltaTime); + newN3Fbw = poly->shutdownN3(preN3Fbw, deltaTime); newEgtFbw = poly->shutdownEGT(preEgtFbw, ambientTemp, deltaTime); simVars->setEngine1N1(newN1Fbw); - simVars->setEngine1N2(newN2Fbw); + simVars->setEngine1N2(newN3Fbw + 0.7); + simVars->setEngine1N3(newN3Fbw); simVars->setEngine1EGT(newEgtFbw); } - - } else { + } else if (engine == 2) { if (timer < 1.8) { simVars->setEngine2Timer(timer + deltaTime); } else { preN1Fbw = simVars->getEngine2N1(); - preN2Fbw = simVars->getEngine2N2(); + preN3Fbw = simVars->getEngine2N3(); preEgtFbw = simVars->getEngine2EGT(); newN1Fbw = poly->shutdownN1(preN1Fbw, deltaTime); if (simN1 < 5 && simN1 > newN1Fbw) { // Takes care of windmilling newN1Fbw = simN1; } - newN2Fbw = poly->shutdownN2(preN2Fbw, deltaTime); + newN3Fbw = poly->shutdownN3(preN3Fbw, deltaTime); newEgtFbw = poly->shutdownEGT(preEgtFbw, ambientTemp, deltaTime); simVars->setEngine2N1(newN1Fbw); - simVars->setEngine2N2(newN2Fbw); + simVars->setEngine2N2(newN3Fbw + 0.7); + simVars->setEngine2N3(newN3Fbw); simVars->setEngine2EGT(newEgtFbw); } } } /// - /// FBW Engine RPM (N1 and N2) - /// Updates Engine N1 and N2 with our own algorithm for start-up and shutdown + /// FBW Engine RPM (N1, N2 and N3) + /// Updates Engine N1, N2 and N3 with our own algorithm for start-up and shutdown /// void updatePrimaryParameters(int engine, double imbalance, double simN1, double simN2) { // Engine imbalance @@ -471,9 +476,11 @@ class EngineControl { if (engine == 1) { simVars->setEngine1N1(simN1); simVars->setEngine1N2(max(0, simN2 - paramImbalance)); + simVars->setEngine1N3(simN3); } else { simVars->setEngine2N1(simN1); simVars->setEngine2N2(max(0, simN2 - paramImbalance)); + simVars->setEngine2N3(simN3); } } @@ -568,7 +575,7 @@ class EngineControl { /// FBW Oil Qty, Pressure and Temperature (in Quarts, PSI and degree Celsius) /// Updates Oil with realistic values visualized in the SD /// - void updateOil(int engine, double imbalance, double thrust, double simN2, double deltaN2, double deltaTime, double ambientTemp) { + void updateOil(int engine, double thrust, double simN3, double deltaN3, double deltaTime, double ambientTemp) { double steadyTemperature; double thermalEnergy; double oilTemperaturePre; @@ -585,15 +592,15 @@ class EngineControl { if (engine == 1) { steadyTemperature = simVars->getEngine1EGT(); thermalEnergy = thermalEnergy1; - oilTemperaturePre = oilTemperatureLeftPre; + oilTemperaturePre = oilTemperatureEngine1Pre; oilQtyActual = simVars->getEngine1Oil(); - oilTotalActual = simVars->getEngine1OilTotal(); + oilTotalActual = simVars->getEngine1TotalOil(); } else { steadyTemperature = simVars->getEngine2EGT(); thermalEnergy = thermalEnergy2; - oilTemperaturePre = oilTemperatureRightPre; + oilTemperaturePre = oilTemperatureEngine2Pre; oilQtyActual = simVars->getEngine2Oil(); - oilTotalActual = simVars->getEngine2OilTotal(); + oilTotalActual = simVars->getEngine2TotalOil(); } //-------------------------------------------- @@ -605,7 +612,7 @@ class EngineControl { if (steadyTemperature > oilTemperatureMax) { steadyTemperature = oilTemperatureMax; } - thermalEnergy = (0.995 * thermalEnergy) + (deltaN2 / deltaTime); + thermalEnergy = (0.995 * thermalEnergy) + (deltaN3 / deltaTime); oilTemperature = poly->oilTemperature(thermalEnergy, oilTemperaturePre, steadyTemperature, deltaTime); } @@ -634,27 +641,27 @@ class EngineControl { paramImbalance = 0; } - oilPressure = poly->oilPressure(simN2) - paramImbalance + oilIdleRandom; + oilPressure = poly->oilPressure(simN3) - paramImbalance + oilIdleRandom; //-------------------------------------------- // Engine Writing //-------------------------------------------- if (engine == 1) { thermalEnergy1 = thermalEnergy; - oilTemperatureLeftPre = oilTemperature; + oilTemperatureEngine1Pre = oilTemperature; simVars->setEngine1Oil(oilQtyActual); - simVars->setEngine1OilTotal(oilTotalActual); - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempLeft, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + simVars->setEngine1TotalOil(oilTotalActual); + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempEngine1, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilTemperature); - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilPsiLeft, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilPressure); + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilPsiEngine1, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilPressure); } else { thermalEnergy2 = thermalEnergy; - oilTemperatureRightPre = oilTemperature; + oilTemperatureEngine2Pre = oilTemperature; simVars->setEngine2Oil(oilQtyActual); - simVars->setEngine2OilTotal(oilTotalActual); - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempRight, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + simVars->setEngine2TotalOil(oilTotalActual); + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempEngine2, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilTemperature); - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilPsiRight, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilPressure); + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilPsiEngine2, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilPressure); } } @@ -670,8 +677,9 @@ class EngineControl { double refuelRate = simVars->getRefuelRate(); double refuelStartedByUser = simVars->getRefuelStartedByUser(); bool uiFuelTamper = false; - double pumpStateLeft = simVars->getPumpStateLeft(); - double pumpStateRight = simVars->getPumpStateRight(); + + double pumpStateEngine1 = simVars->getPumpStateEngine1(); + double pumpStateEngine2 = simVars->getPumpStateEngine2(); bool xfrCenterLeftManual = simVars->getJunctionSetting(4) > 1.5; bool xfrCenterRightManual = simVars->getJunctionSetting(5) > 1.5; bool xfrCenterLeftAuto = simVars->getValve(11) > 0.0 && !xfrCenterLeftManual; @@ -693,34 +701,35 @@ class EngineControl { /// weight of one gallon of fuel in pounds double fuelWeightGallon = simVars->getFuelWeightGallon(); - double fuelUsedLeft = simVars->getFuelUsedLeft(); // Kg - double fuelUsedRight = simVars->getFuelUsedRight(); // Kg - - double fuelLeftPre = simVars->getFuelLeftPre(); // LBS - double fuelRightPre = simVars->getFuelRightPre(); // LBS - double fuelAuxLeftPre = simVars->getFuelAuxLeftPre(); // LBS - double fuelAuxRightPre = simVars->getFuelAuxRightPre(); // LBS - double fuelCenterPre = simVars->getFuelCenterPre(); // LBS + double fuelUsedEngine1 = simVars->getFuelUsedEngine1(); // Kg + double fuelUsedEngine2 = simVars->getFuelUsedEngine2(); // Kg + + double fuelLeftPre = simVars->getFuelLeftPre(); // LBS + double fuelRightPre = simVars->getFuelRightPre(); // LBS + double fuelAuxLeftPre = simVars->getFuelAuxLeftPre(); // LBS + double fuelAuxRightPre = simVars->getFuelAuxRightPre(); // LBS + double fuelCenterPre = simVars->getFuelCenterPre(); // LBS + // double fuelTrimPre = simVars->getFuelTrimPre(); // LBS double leftQuantity = simVars->getFuelTankQuantity(2) * fuelWeightGallon; // LBS double rightQuantity = simVars->getFuelTankQuantity(3) * fuelWeightGallon; // LBS double leftAuxQuantity = simVars->getFuelTankQuantity(4) * fuelWeightGallon; // LBS double rightAuxQuantity = simVars->getFuelTankQuantity(5) * fuelWeightGallon; // LBS double centerQuantity = simVars->getFuelTankQuantity(1) * fuelWeightGallon; // LBS - /// Left inner tank fuel quantity in pounds + // double trimQuantity = simVars->getTankFuelQuantity(6) * fuelWeightGallon; // LBS + double fuelLeft = 0; - /// Right inner tank fuel quantity in pounds double fuelRight = 0; double fuelLeftAux = 0; double fuelRightAux = 0; double fuelCenter = 0; + // double fuelTrim = 0; double xfrCenterToLeft = 0; double xfrCenterToRight = 0; double xfrAuxLeft = 0; double xfrAuxRight = 0; - double fuelTotalActual = leftQuantity + rightQuantity + leftAuxQuantity + rightAuxQuantity + centerQuantity; // LBS - double fuelTotalPre = fuelLeftPre + fuelRightPre + fuelAuxLeftPre + fuelAuxRightPre + fuelCenterPre; // LBS - double deltaFuelRate = abs(fuelTotalActual - fuelTotalPre) / (fuelWeightGallon * deltaTimeSeconds); // LBS/ sec - + double fuelTotalActual = leftQuantity + rightQuantity + leftAuxQuantity + rightAuxQuantity + centerQuantity; // + trimQuantity; // LBS + double fuelTotalPre = fuelLeftPre + fuelRightPre + fuelAuxLeftPre + fuelAuxRightPre + fuelCenterPre; // + fuelTrimPre; // LBS + double deltaFuelRate = abs(fuelTotalActual - fuelTotalPre) / (fuelWeightGallon * deltaTimeSeconds); // LBS/ sec double engine1State = simVars->getEngine1State(); double engine2State = simVars->getEngine2State(); @@ -740,41 +749,41 @@ class EngineControl { double deltaTime = deltaTimeSeconds / 3600; // Pump State Logic for Left Wing - if (pumpStateLeft == 0 && (timerLeft.elapsed() == 0 || timerLeft.elapsed() >= 1000)) { + if (pumpStateEngine1 == 0 && (timerEngine1.elapsed() == 0 || timerEngine1.elapsed() >= 1000)) { if (fuelLeftPre - leftQuantity > 0 && leftQuantity == 0) { - timerLeft.reset(); - simVars->setPumpStateLeft(1); + timerEngine1.reset(); + simVars->setPumpStateEngine1(1); } else if (fuelLeftPre == 0 && leftQuantity - fuelLeftPre > 0) { - timerLeft.reset(); - simVars->setPumpStateLeft(2); + timerEngine1.reset(); + simVars->setPumpStateEngine1(2); } else { - simVars->setPumpStateLeft(0); + simVars->setPumpStateEngine1(0); } - } else if (pumpStateLeft == 1 && timerLeft.elapsed() >= 2100) { - simVars->setPumpStateLeft(0); - timerLeft.reset(); - } else if (pumpStateLeft == 2 && timerLeft.elapsed() >= 2700) { - simVars->setPumpStateLeft(0); - timerLeft.reset(); + } else if (pumpStateEngine1 == 1 && timerEngine1.elapsed() >= 2100) { + simVars->setPumpStateEngine1(0); + timerEngine1.reset(); + } else if (pumpStateEngine1 == 2 && timerEngine1.elapsed() >= 2700) { + simVars->setPumpStateEngine1(0); + timerEngine1.reset(); } // Pump State Logic for Right Wing - if (pumpStateRight == 0 && (timerRight.elapsed() == 0 || timerRight.elapsed() >= 1000)) { + if (pumpStateEngine2 == 0 && (timerEngine2.elapsed() == 0 || timerEngine2.elapsed() >= 1000)) { if (fuelRightPre - rightQuantity > 0 && rightQuantity == 0) { - timerRight.reset(); - simVars->setPumpStateRight(1); + timerEngine2.reset(); + simVars->setPumpStateEngine2(1); } else if (fuelRightPre == 0 && rightQuantity - fuelRightPre > 0) { - timerRight.reset(); - simVars->setPumpStateRight(2); + timerEngine2.reset(); + simVars->setPumpStateEngine2(2); } else { - simVars->setPumpStateRight(0); + simVars->setPumpStateEngine2(0); } - } else if (pumpStateRight == 1 && timerRight.elapsed() >= 2100) { - simVars->setPumpStateRight(0); - timerRight.reset(); - } else if (pumpStateRight == 2 && timerRight.elapsed() >= 2700) { - simVars->setPumpStateRight(0); - timerRight.reset(); + } else if (pumpStateEngine2 == 1 && timerEngine2.elapsed() >= 2100) { + simVars->setPumpStateEngine2(0); + timerEngine2.reset(); + } else if (pumpStateEngine2 == 2 && timerEngine2.elapsed() >= 2700) { + simVars->setPumpStateEngine2(0); + timerEngine2.reset(); } // Checking for in-game UI Fuel tampering @@ -789,24 +798,29 @@ class EngineControl { simVars->setFuelAuxLeftPre(fuelAuxLeftPre); // in LBS simVars->setFuelAuxRightPre(fuelAuxRightPre); // in LBS simVars->setFuelCenterPre(fuelCenterPre); // in LBS + // simVars->setFuelTrimPre(fuelTrimPre); // in LBS fuelLeft = (fuelLeftPre / fuelWeightGallon); // USG fuelRight = (fuelRightPre / fuelWeightGallon); // USG fuelCenter = (fuelCenterPre / fuelWeightGallon); // USG fuelLeftAux = (fuelAuxLeftPre / fuelWeightGallon); // USG fuelRightAux = (fuelAuxRightPre / fuelWeightGallon); // USG + // fuelTrim = (fuelTrimPre / fuelWeightGallon); // USG SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::FuelCenterMain, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &fuelCenter); SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::FuelLeftMain, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &fuelLeft); SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::FuelRightMain, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &fuelRight); SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::FuelLeftAux, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &fuelLeftAux); SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::FuelRightAux, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &fuelRightAux); + // SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::FuelSystemTrim, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + // &fuelTrim); } else if (!uiFuelTamper && refuelStartedByUser == 1) { // Detects refueling from the EFB simVars->setFuelLeftPre(leftQuantity); // in LBS simVars->setFuelRightPre(rightQuantity); // in LBS simVars->setFuelAuxLeftPre(leftAuxQuantity); // in LBS simVars->setFuelAuxRightPre(rightAuxQuantity); // in LBS simVars->setFuelCenterPre(centerQuantity); // in LBS + // simVars->setFuelTrimPre(trimQty); // in LBS } else { if (uiFuelTamper == 1) { fuelLeftPre = leftQuantity; // LBS @@ -814,6 +828,7 @@ class EngineControl { fuelAuxLeftPre = leftAuxQuantity; // LBS fuelAuxRightPre = rightAuxQuantity; // LBS fuelCenterPre = centerQuantity; // LBS + // fuelTrimPre = trimQuantity; // LBS } //----------------------------------------------------------- // Cross-feed Logic @@ -867,8 +882,8 @@ class EngineControl { //-------------------------------------------- // Fuel used accumulators - fuelUsedLeft += fuelBurn1; - fuelUsedRight += fuelBurn2; + fuelUsedEngine1 += fuelBurn1; + fuelUsedEngine2 += fuelBurn2; //-------------------------------------------- // Cross-feed fuel burn routine @@ -918,8 +933,8 @@ class EngineControl { // Setting new pre-cycle conditions simVars->setEngine1PreFF(engine1FF); simVars->setEngine2PreFF(engine2FF); - simVars->setFuelUsedLeft(fuelUsedLeft); // in KG - simVars->setFuelUsedRight(fuelUsedRight); // in KG + simVars->setFuelUsedEngine1(fuelUsedEngine1); // in KG + simVars->setFuelUsedEngine2(fuelUsedEngine2); // in KG simVars->setFuelAuxLeftPre(leftAuxQuantity); // in LBS simVars->setFuelAuxRightPre(rightAuxQuantity); // in LBS simVars->setFuelCenterPre(centerQuantity); // in LBS @@ -947,6 +962,7 @@ class EngineControl { configuration.fuelCenter = simVars->getFuelCenterPre() / simVars->getFuelWeightGallon(); configuration.fuelLeftAux = simVars->getFuelAuxLeftPre() / simVars->getFuelWeightGallon(); configuration.fuelRightAux = simVars->getFuelAuxRightPre() / simVars->getFuelWeightGallon(); + // configuration.fuelTrim = simVars->getFuelTrimPre() / simVars->getFuelWeightGallon(); saveFuelInConfiguration(configuration); timerFuel.reset(); @@ -1070,8 +1086,8 @@ class EngineControl { simVars = new SimVars(); double engTime = 0; ambientTemp = simVars->getAmbientTemperature(); - simN2LeftPre = simVars->getN2(1); - simN2RightPre = simVars->getN2(2); + simN3Engine1Pre = simVars->getN2(1); + simN3Engine2Pre = simVars->getN2(2); confFilename += acftRegistration; confFilename += FILENAME_FADEC_CONF_FILE_EXTENSION; @@ -1104,9 +1120,9 @@ class EngineControl { // Setting initial Oil if (engine == 1) { - simVars->setEngine1OilTotal(idleOil - paramImbalance); + simVars->setEngine1TotalOil(idleOil - paramImbalance); } else { - simVars->setEngine2OilTotal(idleOil - paramImbalance); + simVars->setEngine2TotalOil(idleOil - paramImbalance); } } @@ -1119,21 +1135,21 @@ class EngineControl { double engine2Combustion = simVars->getEngineCombustion(2); if (simOnGround == 1 && engine1Combustion == 1 && engine2Combustion == 1) { - oilTemperatureLeftPre = 75; - oilTemperatureRightPre = 75; + oilTemperatureEngine1Pre = 75; + oilTemperatureEngine2Pre = 75; } else if (simOnGround == 0 && engine1Combustion == 1 && engine2Combustion == 1) { - oilTemperatureLeftPre = 85; - oilTemperatureRightPre = 85; + oilTemperatureEngine1Pre = 85; + oilTemperatureEngine2Pre = 85; } else { - oilTemperatureLeftPre = ambientTemp; - oilTemperatureRightPre = ambientTemp; + oilTemperatureEngine1Pre = ambientTemp; + oilTemperatureEngine2Pre = ambientTemp; } - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempLeft, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), - &oilTemperatureLeftPre); - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempRight, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), - &oilTemperatureRightPre); + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempEngine1, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + &oilTemperatureEngine1Pre); + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempEngine2, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + &oilTemperatureEngine2Pre); // Initialize Engine State simVars->setEngine1State(10); @@ -1149,10 +1165,11 @@ class EngineControl { simVars->setFuelAuxLeftPre(configuration.fuelLeftAux * simVars->getFuelWeightGallon()); // in LBS simVars->setFuelAuxRightPre(configuration.fuelRightAux * simVars->getFuelWeightGallon()); // in LBS simVars->setFuelCenterPre(configuration.fuelCenter * simVars->getFuelWeightGallon()); // in LBS + // simVars->setFuelTrimPre(configuration.fuelTrim * simVars->getFuelWeightGallon()); // Initialize Pump State - simVars->setPumpStateLeft(0); - simVars->setPumpStateRight(0); + simVars->setPumpStateEngine1(0); + simVars->setPumpStateEngine2(0); // Initialize Thrust Limits simVars->setThrustLimitIdle(0); @@ -1200,38 +1217,38 @@ class EngineControl { engineIgniter = simVars->getEngineIgniter(engine); simCN1 = simVars->getCN1(engine); simN1 = simVars->getN1(engine); - simN2 = simVars->getN2(engine); + simN3 = simVars->getN2(engine); thrust = simVars->getThrust(engine); // Set & Check Engine Status for this Cycle - engineStateMachine(engine, engineIgniter, engineStarter, simN2, idleN2, pressAltitude, ambientTemp, + engineStateMachine(engine, engineIgniter, engineStarter, simN3, idleN3, pressAltitude, ambientTemp, animationDeltaTime - prevAnimationDeltaTime); if (engine == 1) { engineState = simVars->getEngine1State(); - deltaN2 = simN2 - simN2LeftPre; - simN2LeftPre = simN2; + deltaN3 = simN3 - simN3Engine1Pre; + simN3Engine1Pre = simN3; timer = simVars->getEngine1Timer(); } else { engineState = simVars->getEngine2State(); - deltaN2 = simN2 - simN2RightPre; - simN2RightPre = simN2; + deltaN3 = simN3 - simN3Engine2Pre; + simN3Engine2Pre = simN3; timer = simVars->getEngine2Timer(); } switch (int(engineState)) { case 2: case 3: - engineStartProcedure(engine, engineState, imbalance, deltaTime, timer, simN2, pressAltitude, ambientTemp); + engineStartProcedure(engine, engineState, imbalance, deltaTime, timer, simN3, pressAltitude, ambientTemp); break; case 4: engineShutdownProcedure(engine, ambientTemp, simN1, deltaTime, timer); cFbwFF = updateFF(engine, imbalance, simCN1, mach, pressAltitude, ambientTemp, ambientPressure); break; default: - updatePrimaryParameters(engine, imbalance, simN1, simN2); + updatePrimaryParameters(engine, imbalance, simN1, simN3); cFbwFF = updateFF(engine, imbalance, simCN1, mach, pressAltitude, ambientTemp, ambientPressure); updateEGT(engine, imbalance, deltaTime, simOnGround, engineState, simCN1, cFbwFF, mach, pressAltitude, ambientTemp); - // updateOil(engine, imbalance, thrust, simN2, deltaN2, deltaTime, ambientTemp); + // updateOil(engine, imbalance, thrust, simN3, deltaN3, deltaTime, ambientTemp); } // set highest N1 from either engine @@ -1270,6 +1287,7 @@ class EngineControl { mINI::INITypeConversion::getDouble(structure, CONFIGURATION_SECTION_FUEL, CONFIGURATION_SECTION_FUEL_RIGHT_QUANTITY, 1645.0), mINI::INITypeConversion::getDouble(structure, CONFIGURATION_SECTION_FUEL, CONFIGURATION_SECTION_FUEL_LEFT_AUX_QUANTITY, 228.0), mINI::INITypeConversion::getDouble(structure, CONFIGURATION_SECTION_FUEL, CONFIGURATION_SECTION_FUEL_RIGHT_AUX_QUANTITY, 228.0), + // mINI::INITypeConversion::getDouble(structure, CONFIGURATION_SECTION_FUEL, CONFIGURATION_SECTION_FUEL_TRIM_QTY, 1617.0), }; } @@ -1285,6 +1303,7 @@ class EngineControl { stInitStructure[CONFIGURATION_SECTION_FUEL][CONFIGURATION_SECTION_FUEL_RIGHT_QUANTITY] = std::to_string(configuration.fuelRight); stInitStructure[CONFIGURATION_SECTION_FUEL][CONFIGURATION_SECTION_FUEL_LEFT_AUX_QUANTITY] = std::to_string(configuration.fuelLeftAux); stInitStructure[CONFIGURATION_SECTION_FUEL][CONFIGURATION_SECTION_FUEL_RIGHT_AUX_QUANTITY] = std::to_string(configuration.fuelRightAux); + // stInitStructure[CONFIGURATION_SECTION_FUEL][CONFIGURATION_SECTION_FUEL_TRIM_QTY] = std::to_string(configuration.fuelTrim); if (!iniFile.write(stInitStructure, true)) { std::cout << "EngineControl: failed to write engine conf " << confFilename << " due to error \"" << strerror(errno) << "\"" diff --git a/hdw-a339x-acj/src/wasm/fadec_a330/src/FadecGauge.cpp b/hdw-a339x-acj/src/wasm/fadec_a330/src/FadecGauge.cpp new file mode 100644 index 000000000..f502997ce --- /dev/null +++ b/hdw-a339x-acj/src/wasm/fadec_a330/src/FadecGauge.cpp @@ -0,0 +1,32 @@ +#include "FadecGauge.h" + +FadecGauge FADEC_GAUGE; + +__attribute__((export_name("FadecGauge_gauge_callback"))) extern "C" bool FadecGauge_gauge_callback(FsContext ctx, + int service_id, + void* pData) { + switch (service_id) { + case PANEL_SERVICE_PRE_INSTALL: { + return true; + } break; + case PANEL_SERVICE_POST_INSTALL: { + return FADEC_GAUGE.initializeFADEC(); + } break; + case PANEL_SERVICE_PRE_DRAW: { + /* Start updating the engines only if all needed data was fetched */ + /* Due to the data fetching feature from the sim being available at this stage only (from what I have observed) */ + /* Event SIMCONNECT_RECV_ID_OPEN received after the first PANEL_SERVICE_POST_INSTALL */ + if (FADEC_GAUGE.isReady()) { + sGaugeDrawData* drawData = static_cast(pData); + return FADEC_GAUGE.onUpdate(drawData->dt); + } else { + return FADEC_GAUGE.fetchNeededData(); + } + } break; + case PANEL_SERVICE_PRE_KILL: { + FADEC_GAUGE.killFADEC(); + return true; + } break; + } + return false; +} diff --git a/hdw-a339x-acj/src/wasm/fadec_a330/src/FadecGauge.h b/hdw-a339x-acj/src/wasm/fadec_a330/src/FadecGauge.h new file mode 100644 index 000000000..55f7c1968 --- /dev/null +++ b/hdw-a339x-acj/src/wasm/fadec_a330/src/FadecGauge.h @@ -0,0 +1,387 @@ +#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 "EngineControl.h" +// #include "ThrustLimits.h" +#include "RegPolynomials.h" +#include "SimVars.h" +#include "Tables.h" +#include "common.h" + +#define DEFAULT_AIRCRAFT_REGISTRATION "ASX339" + +class FadecGauge { + private: + bool isConnected = false; + bool _isReady = false; + double previousSimulationTime = 0; + SimulationData simulationData = {}; + SimulationDataLivery simulationDataLivery = {}; + + /// + /// Initializes the connection to SimConnect + /// + /// True if successful, false otherwise. + bool initializeSimConnect() { + std::cout << "FADEC: Connecting to SimConnect..." << std::endl; + if (SUCCEEDED(SimConnect_Open(&hSimConnect, "FadecGauge", nullptr, 0, 0, 0))) { + std::cout << "FADEC: SimConnect connected." << std::endl; + + // SimConnect Tanker Definitions + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::FuelLeftMain, "FUEL TANK LEFT MAIN QUANTITY", "Gallons"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::FuelRightMain, "FUEL TANK RIGHT MAIN QUANTITY", "Gallons"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::FuelCenterMain, "FUEL TANK CENTER QUANTITY", "Gallons"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::FuelLeftAux, "FUEL TANK LEFT AUX QUANTITY", "Gallons"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::FuelRightAux, "FUEL TANK RIGHT AUX QUANTITY", "Gallons"); + + // SimConnect Oil Temperature Definitions + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::OilTempEngine1, "GENERAL ENG OIL TEMPERATURE:1", "Celsius"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::OilTempEngine2, "GENERAL ENG OIL TEMPERATURE:2", "Celsius"); + + // SimConnect Oil Pressure Definitions + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::OilPsiEngine1, "GENERAL ENG OIL PRESSURE:1", "Psi"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::OilPsiEngine2, "GENERAL ENG OIL PRESSURE:2", "Psi"); + + // SimConnect Engine Start Definitions + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::StartCN3Engine1, "TURB ENG CORRECTED N2:1", "Percent"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::StartCN3Engine2, "TURB ENG CORRECTED N2:2", "Percent"); + // Simulation Data + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::SimulationDataTypeId, "SIMULATION TIME", "NUMBER"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::SimulationDataTypeId, "SIMULATION RATE", "NUMBER"); + + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::AcftInfo, "ATC ID", NULL, SIMCONNECT_DATATYPE_STRING32); + + std::cout << "FADEC: SimConnect registrations complete." << std::endl; + return true; + } + + std::cout << "FADEC: SimConnect failed." << std::endl; + + return false; + } + + bool isRegistrationFound() { return simulationDataLivery.atc_id[0] != 0; } + + public: + /// + /// Initializes the FADEC control + /// + /// True if successful, false otherwise. + bool initializeFADEC() { + if (!this->initializeSimConnect()) { + std::cout << "FADEC: Init SimConnect failed." << std::endl; + return false; + } + + isConnected = true; + simConnectRequestData(); + + return true; + } + + /// + /// Callback used to update the FADEC at each tick (dt) + /// + /// True if successful, false otherwise. + bool onUpdate(double deltaTime) { + if (isConnected == true) { + // read simulation data from simconnect + simConnectReadData(); + // detect pause + if ((simulationData.simulationTime == previousSimulationTime) || (simulationData.simulationTime < 0.2)) { + // pause detected -> return + return true; + } + // calculate delta time + double calculatedSampleTime = max(0.002, simulationData.simulationTime - previousSimulationTime); + // store previous simulation time + previousSimulationTime = simulationData.simulationTime; + // update engines + EngineControlInstance.update(calculatedSampleTime, simulationData.simulationTime); + } + + return true; + } + + bool simConnectRequestDataAcftInfo() { + // check if we are connected + if (!isConnected) { + return false; + } + + // request data + return S_OK == + SimConnect_RequestDataOnSimObject(hSimConnect, 8, DataTypesID::AcftInfo, SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_ONCE); + } + + bool simConnectRequestData() { + // check if we are connected + if (!isConnected) { + return false; + } + + // request data + HRESULT result = SimConnect_RequestDataOnSimObject(hSimConnect, 0, DataTypesID::SimulationDataTypeId, SIMCONNECT_OBJECT_ID_USER, + SIMCONNECT_PERIOD_VISUAL_FRAME); + + // check result of data request + if (result != S_OK) { + // request failed + return false; + } + + // success + return true; + } + + bool simConnectReadData() { + // check if we are connected + if (!isConnected) { + return false; + } + + // get next dispatch message(s) and process them + DWORD cbData; + SIMCONNECT_RECV* pData; + while (SUCCEEDED(SimConnect_GetNextDispatch(hSimConnect, &pData, &cbData))) { + simConnectProcessDispatchMessage(pData, &cbData); + } + + // success + return true; + } + + void simConnectProcessDispatchMessage(SIMCONNECT_RECV* pData, DWORD* cbData) { + switch (pData->dwID) { + case SIMCONNECT_RECV_ID_OPEN: + // connection established + std::cout << "FADEC: SimConnect connection established" << std::endl; + break; + + case SIMCONNECT_RECV_ID_QUIT: + // connection lost + std::cout << "FADEC: Received SimConnect connection quit message" << std::endl; + break; + + case SIMCONNECT_RECV_ID_SIMOBJECT_DATA: + // process data + simConnectProcessSimObjectData(static_cast(pData)); + break; + + case SIMCONNECT_RECV_ID_EXCEPTION: + // exception + std::cout << "FADEC: Exception in SimConnect connection: "; + std::cout << getSimConnectExceptionString( + static_cast(static_cast(pData)->dwException)); + std::cout << std::endl; + break; + + default: + break; + } + } + + void simConnectProcessSimObjectData(const SIMCONNECT_RECV_SIMOBJECT_DATA* data) { + // process depending on request id + switch (data->dwRequestID) { + case 0: + // store aircraft data + simulationData = *((SimulationData*)&data->dwData); + return; + case 8: + simulationDataLivery = *((SimulationDataLivery*)&data->dwData); + if (simulationDataLivery.atc_id[0] == '\0') { + std::cout << "FADEC: Use default aircraft registration " << DEFAULT_AIRCRAFT_REGISTRATION << std::endl; + strncpy(simulationDataLivery.atc_id, DEFAULT_AIRCRAFT_REGISTRATION, sizeof(simulationDataLivery.atc_id)); + } + return; + + default: + // print unknown request id + std::cout << "FADEC: Unknown request id in SimConnect connection: "; + std::cout << data->dwRequestID << std::endl; + return; + } + } + + /// + /// Kills the FADEC and unregisters all LVars + /// + /// True if successful, false otherwise. + bool killFADEC() { + std::cout << "FADEC: Disconnecting ..." << std::endl; + EngineControlInstance.terminate(); + + isConnected = false; + unregister_all_named_vars(); + + std::cout << "FADEC: Disconnected." << std::endl; + return SUCCEEDED(SimConnect_Close(hSimConnect)); + } + + 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"; + } + } + + /* + * This function is to call if some data are needed before initialization of the engine (such as aircraft registration). + * This has to be done here due to data fetch feature being available after the first PANEL_SERVICE_PRE_DRAW (from what I have observed) + * This problem was observed when engine configuration file was under development + * Reach Julian Sebline on Discord if information needed + * + * Modify this function as needed + * + * @return true if all the requirements to initialize the engine are fulfilled + */ + bool fetchNeededData() { + // This two lines request aircraft registration + simConnectRequestDataAcftInfo(); + simConnectReadData(); + + _isReady = isRegistrationFound(); + + if (_isReady) { + EngineControlInstance.initialize(simulationDataLivery.atc_id); + } + + return _isReady; + } + + bool isReady() { return _isReady; } +}; diff --git a/hdw-a339x/src/wasm/fadec_a320/src/RegPolynomials.h b/hdw-a339x-acj/src/wasm/fadec_a330/src/RegPolynomials.h similarity index 56% rename from hdw-a339x/src/wasm/fadec_a320/src/RegPolynomials.h rename to hdw-a339x-acj/src/wasm/fadec_a330/src/RegPolynomials.h index 530dd1ea4..64fc4cff2 100644 --- a/hdw-a339x/src/wasm/fadec_a320/src/RegPolynomials.h +++ b/hdw-a339x-acj/src/wasm/fadec_a330/src/RegPolynomials.h @@ -9,18 +9,18 @@ class Polynomial { public: /// - /// Shutdown polynomials - N2 (%) + /// Shutdown polynomials - N3 (%) /// - double shutdownN2(double preN2, double deltaTime) { - double outN2 = 0; + double shutdownN3(double preN3, double deltaTime) { + double outN3 = 0; double k = -0.08183; - if (preN2 < 30) + if (preN3 < 30) k = -0.0515; - outN2 = preN2 * expFBW(k * deltaTime); + outN3 = preN3 * expFBW(k * deltaTime); - return outN2; + return outN3; } /// @@ -61,51 +61,51 @@ class Polynomial { } /// - /// Start-up polynomials - N2 (%) + /// Start-up polynomials - N3 (%) /// - double startN2(double n2, double preN2, double idleN2) { - double outN2 = 0; - double normalN2 = 0; + double startN3(double n3, double preN3, double idleN3) { + double outN3 = 0; + double normalN3 = 0; - normalN2 = n2 * 68.2 / idleN2; + normalN3 = n3 * 60.0 / idleN3; - double c_N2[16] = {4.03649879e+00, -9.41981960e-01, 1.98426614e-01, -2.11907840e-02, 1.00777507e-03, -1.57319166e-06, + double c_N3[16] = {4.03649879e+00, -9.41981960e-01, 1.98426614e-01, -2.11907840e-02, 1.00777507e-03, -1.57319166e-06, -2.15034888e-06, 1.08288379e-07, -2.48504632e-09, 2.52307089e-11, -2.06869243e-14, 8.99045761e-16, -9.94853959e-17, 1.85366499e-18, -1.44869928e-20, 4.31033031e-23}; - outN2 = c_N2[0] + (c_N2[1] * normalN2) + (c_N2[2] * powFBW(normalN2, 2)) + (c_N2[3] * powFBW(normalN2, 3)) + - (c_N2[4] * powFBW(normalN2, 4)) + (c_N2[5] * powFBW(normalN2, 5)) + (c_N2[6] * powFBW(normalN2, 6)) + - (c_N2[7] * powFBW(normalN2, 7)) + (c_N2[8] * powFBW(normalN2, 8)) + (c_N2[9] * powFBW(normalN2, 9)) + - (c_N2[10] * powFBW(normalN2, 10)) + (c_N2[11] * powFBW(normalN2, 11)) + (c_N2[12] * powFBW(normalN2, 12)) + - (c_N2[13] * powFBW(normalN2, 13)) + (c_N2[14] * powFBW(normalN2, 14)) + (c_N2[15] * powFBW(normalN2, 15)); + outN3 = c_N3[0] + (c_N3[1] * normalN3) + (c_N3[2] * powFBW(normalN3, 2)) + (c_N3[3] * powFBW(normalN3, 3)) + + (c_N3[4] * powFBW(normalN3, 4)) + (c_N3[5] * powFBW(normalN3, 5)) + (c_N3[6] * powFBW(normalN3, 6)) + + (c_N3[7] * powFBW(normalN3, 7)) + (c_N3[8] * powFBW(normalN3, 8)) + (c_N3[9] * powFBW(normalN3, 9)) + + (c_N3[10] * powFBW(normalN3, 10)) + (c_N3[11] * powFBW(normalN3, 11)) + (c_N3[12] * powFBW(normalN3, 12)) + + (c_N3[13] * powFBW(normalN3, 13)) + (c_N3[14] * powFBW(normalN3, 14)) + (c_N3[15] * powFBW(normalN3, 15)); - outN2 = outN2 * n2; + outN3 = outN3 * n3; - if (outN2 < preN2) { - outN2 = preN2 + 0.002; + if (outN3 < preN3) { + outN3 = preN3 + 0.002; } - if (outN2 >= idleN2 + 0.1) { - outN2 = idleN2 + 0.05; + if (outN3 >= idleN3 + 0.1) { + outN3 = idleN3 + 0.05; } - return outN2; + return outN3; } /// /// Start-up polynomials - N1 (%) /// - double startN1(double fbwN2, double idleN2, double idleN1) { + double startN1(double fbwN3, double idleN3, double idleN1) { double normalN1pre = 0; double normalN1post = 0; - double normalN2 = fbwN2 / idleN2; + double normalN3 = fbwN3 / idleN3; double c_N1[9] = {-2.2812156e-12, -5.9830374e+01, 7.0629094e+02, -3.4580361e+03, 9.1428923e+03, -1.4097740e+04, 1.2704110e+04, -6.2099935e+03, 1.2733071e+03}; - normalN1pre = (-2.4698087 * powFBW(normalN2, 3)) + (0.9662026 * powFBW(normalN2, 2)) + (0.0701367 * normalN2); + normalN1pre = (-2.4698087 * powFBW(normalN3, 3)) + (0.9662026 * powFBW(normalN3, 2)) + (0.0701367 * normalN3); - normalN1post = c_N1[0] + (c_N1[1] * normalN2) + (c_N1[2] * powFBW(normalN2, 2)) + (c_N1[3] * powFBW(normalN2, 3)) + - (c_N1[4] * powFBW(normalN2, 4)) + (c_N1[5] * powFBW(normalN2, 5)) + (c_N1[6] * powFBW(normalN2, 6)) + - (c_N1[7] * powFBW(normalN2, 7)) + (c_N1[8] * powFBW(normalN2, 8)); + normalN1post = c_N1[0] + (c_N1[1] * normalN3) + (c_N1[2] * powFBW(normalN3, 2)) + (c_N1[3] * powFBW(normalN3, 3)) + + (c_N1[4] * powFBW(normalN3, 4)) + (c_N1[5] * powFBW(normalN3, 5)) + (c_N1[6] * powFBW(normalN3, 6)) + + (c_N1[7] * powFBW(normalN3, 7)) + (c_N1[8] * powFBW(normalN3, 8)); if (normalN1post >= normalN1pre) return normalN1post * idleN1; @@ -116,20 +116,20 @@ class Polynomial { /// /// Start-up polynomials - Fuel Flow (Kg/hr) /// - double startFF(double fbwN2, double idleN2, double idleFF) { + double startFF(double fbwN3, double idleN3, double idleFF) { double normalFF = 0; double outFF = 0; - double normalN2 = fbwN2 / idleN2; + double normalN3 = fbwN3 / idleN3; - if (normalN2 <= 0.37) { + if (normalN3 <= 0.37) { normalFF = 0; } else { double c_FF[9] = {3.1110282e-12, 1.0804331e+02, -1.3972629e+03, 7.4874131e+03, -2.1511983e+04, 3.5957757e+04, -3.5093994e+04, 1.8573033e+04, -4.1220062e+03}; - normalFF = c_FF[0] + (c_FF[1] * normalN2) + (c_FF[2] * powFBW(normalN2, 2)) + (c_FF[3] * powFBW(normalN2, 3)) + - (c_FF[4] * powFBW(normalN2, 4)) + (c_FF[5] * powFBW(normalN2, 5)) + (c_FF[6] * powFBW(normalN2, 6)) + - (c_FF[7] * powFBW(normalN2, 7)) + (c_FF[8] * powFBW(normalN2, 8)); + normalFF = c_FF[0] + (c_FF[1] * normalN3) + (c_FF[2] * powFBW(normalN3, 2)) + (c_FF[3] * powFBW(normalN3, 3)) + + (c_FF[4] * powFBW(normalN3, 4)) + (c_FF[5] * powFBW(normalN3, 5)) + (c_FF[6] * powFBW(normalN3, 6)) + + (c_FF[7] * powFBW(normalN3, 7)) + (c_FF[8] * powFBW(normalN3, 8)); } if (normalFF < 0) { @@ -142,22 +142,22 @@ class Polynomial { /// /// Start-up polynomials - EGT (Celsius) /// - double startEGT(double fbwN2, double idleN2, double ambientTemp, double idleEGT) { + double startEGT(double fbwN3, double idleN3, double ambientTemp, double idleEGT) { double normalEGT = 0; double outEGT = 0; - double normalN2 = fbwN2 / idleN2; + double normalN3 = fbwN3 / idleN3; - if (normalN2 < 0.17) { + if (normalN3 < 0.17) { normalEGT = 0; - } else if (normalN2 <= 0.4) { - normalEGT = (0.04783 * normalN2) - 0.00813; + } else if (normalN3 <= 0.4) { + normalEGT = (0.04783 * normalN3) - 0.00813; } else { double c_EGT[9] = {-6.8725167e+02, 7.7548864e+03, -3.7507098e+04, 1.0147016e+05, -1.6779273e+05, 1.7357157e+05, -1.0960924e+05, 3.8591956e+04, -5.7912600e+03}; - normalEGT = c_EGT[0] + (c_EGT[1] * normalN2) + (c_EGT[2] * powFBW(normalN2, 2)) + (c_EGT[3] * powFBW(normalN2, 3)) + - (c_EGT[4] * powFBW(normalN2, 4)) + (c_EGT[5] * powFBW(normalN2, 5)) + (c_EGT[6] * powFBW(normalN2, 6)) + - (c_EGT[7] * powFBW(normalN2, 7)) + (c_EGT[8] * powFBW(normalN2, 8)); + normalEGT = c_EGT[0] + (c_EGT[1] * normalN3) + (c_EGT[2] * powFBW(normalN3, 2)) + (c_EGT[3] * powFBW(normalN3, 3)) + + (c_EGT[4] * powFBW(normalN3, 4)) + (c_EGT[5] * powFBW(normalN3, 5)) + (c_EGT[6] * powFBW(normalN3, 6)) + + (c_EGT[7] * powFBW(normalN3, 7)) + (c_EGT[8] * powFBW(normalN3, 8)); } outEGT = (normalEGT * (idleEGT - (ambientTemp))) + (ambientTemp); @@ -168,12 +168,12 @@ class Polynomial { /// /// Start-up polynomials - Oil Temperature (Celsius) /// - double startOilTemp(double fbwN2, double idleN2, double ambientTemp) { + double startOilTemp(double fbwN3, double idleN3, double ambientTemp) { double outOilTemp = 0; - if (fbwN2 < 0.79 * idleN2) { + if (fbwN3 < 0.79 * idleN3) { outOilTemp = ambientTemp; - } else if (fbwN2 < 0.98 * idleN2) { + } else if (fbwN3 < 0.98 * idleN3) { outOilTemp = ambientTemp + 5; } else { outOilTemp = ambientTemp + 10; @@ -187,15 +187,14 @@ class Polynomial { /// double correctedEGT(double cn1, double cff, double mach, double alt) { double outCEGT = 0; - double cff_a330 = 2.7; + cff = cff / 2; // to account for the A380 double fuel flow. Will have to be taken care of - double c_EGT[16] = {443.3145034, 0.0000000e+00, 3.0141710e+00, 3.9132758e-02, -4.8488279e+02, -1.2890964e-03, - -2.2332050e-02, 8.3849683e-05, 6.0478647e+00, 6.9171710e-05, -6.5369271e-07, -8.1438322e-03, - -5.1229403e-07, 7.4657497e+01, -4.6016728e-03, 2.8637860e-08}; + double c_EGT[16] = {3.2636e+02, 0.0000e+00, 9.2893e-01, 3.9505e-02, 3.9070e+02, -4.7911e-04, 7.7679e-03, 5.8361e-05, + -2.5566e+00, 5.1227e-06, 1.0178e-07, -7.4602e-03, 1.2106e-07, -5.1639e+01, -2.7356e-03, 1.9312e-08}; - outCEGT = c_EGT[0] + c_EGT[1] + (c_EGT[2] * cn1) + (c_EGT[3] * cff_a330) + (c_EGT[4] * mach) + (c_EGT[5] * alt) + - (c_EGT[6] * powFBW(cn1, 2)) + (c_EGT[7] * cn1 * cff_a330) + (c_EGT[8] * cn1 * mach) + (c_EGT[9] * cn1 * alt) + - (c_EGT[10] * powFBW(cff_a330, 2)) + (c_EGT[11] * mach * cff_a330) + (c_EGT[12] * cff_a330 * alt) + (c_EGT[13] * powFBW(mach, 2)) + + outCEGT = c_EGT[0] + c_EGT[1] + (c_EGT[2] * cn1) + (c_EGT[3] * cff) + (c_EGT[4] * mach) + (c_EGT[5] * alt) + + (c_EGT[6] * powFBW(cn1, 2)) + (c_EGT[7] * cn1 * cff) + (c_EGT[8] * cn1 * mach) + (c_EGT[9] * cn1 * alt) + + (c_EGT[10] * powFBW(cff, 2)) + (c_EGT[11] * mach * cff) + (c_EGT[12] * cff * alt) + (c_EGT[13] * powFBW(mach, 2)) + (c_EGT[14] * mach * alt) + (c_EGT[15] * powFBW(alt, 2)); return outCEGT; @@ -206,11 +205,10 @@ class Polynomial { /// double correctedFuelFlow(double cn1, double mach, double alt) { double outCFF = 0; - double a330_f = 2.7; - double c_Flow[21] = {-639.6602981, 0.00000e+00, 1.03705e+02, -2.23264e+03, 5.70316e-03, -2.29404e+00, 1.08230e+02, - 2.77667e-04, -6.17180e+02, -7.20713e-02, 2.19013e-07, 2.49418e-02, -7.31662e-01, -1.00003e-05, - -3.79466e+01, 1.34552e-03, 5.72612e-09, -2.71950e+02, 8.58469e-02, -2.72912e-06, 2.02928e-11}; + double c_Flow[21] = {-1.7630e+02, -2.1542e-01, 4.7119e+01, 6.1519e+02, 1.8047e-03, -4.4554e-01, -4.3940e+01, + 4.0459e-05, -3.2912e+01, -6.2894e-03, -1.2544e-07, 1.0938e-02, 4.0936e-01, -5.5841e-06, + -2.3829e+01, 9.3269e-04, 2.0273e-11, -2.4100e+02, 1.4171e-02, -9.5581e-07, 1.2728e-11}; outCFF = c_Flow[0] + c_Flow[1] + (c_Flow[2] * cn1) + (c_Flow[3] * mach) + (c_Flow[4] * alt) + (c_Flow[5] * powFBW(cn1, 2)) + (c_Flow[6] * cn1 * mach) + (c_Flow[7] * cn1 * alt) + (c_Flow[8] * powFBW(mach, 2)) + (c_Flow[9] * mach * alt) + @@ -219,7 +217,7 @@ class Polynomial { (c_Flow[16] * cn1 * powFBW(alt, 2)) + (c_Flow[17] * powFBW(mach, 3)) + (c_Flow[18] * powFBW(mach, 2) * alt) + (c_Flow[19] * mach * powFBW(alt, 2)) + (c_Flow[20] * powFBW(alt, 3)); - return outCFF * a330_f; + return 2 * outCFF; } double oilTemperature(double energy, double preOilTemp, double maxOilTemp, double deltaTime) { @@ -240,6 +238,9 @@ class Polynomial { oilTemp_out = (t_steady - dt); } + // std::cout << "FADEC: Max= " << maxOilTemp << " Energy = " << energy << " dt = " << dt << " preT= " << preOilTemp + // << " Tss = " << t_steady << " To = " << oilTemp_out << std::flush; + return oilTemp_out; } @@ -248,12 +249,10 @@ class Polynomial { /// double oilGulpPct(double thrust) { double outOilGulpPct = 0; - double thrust_a330 = thrust/2.53; - double c_OilGulp[3] = {20.1968848, -1.2270302e-4, 1.78442e-8}; - outOilGulpPct = c_OilGulp[0] + (c_OilGulp[1] * thrust) + (c_OilGulp[2] * powFBW(thrust_a330, 2)); + outOilGulpPct = c_OilGulp[0] + (c_OilGulp[1] * thrust) + (c_OilGulp[2] * powFBW(thrust, 2)); return outOilGulpPct / 100; } @@ -261,12 +260,12 @@ class Polynomial { /// /// Real-life modeled polynomials - Oil Pressure (PSI) /// - double oilPressure(double simN2) { + double oilPressure(double simN3) { double outOilPressure = 0; double c_OilPress[3] = {-0.88921, 0.23711, 0.00682}; - outOilPressure = c_OilPress[0] + (c_OilPress[1] * simN2) + (c_OilPress[2] * powFBW(simN2, 2)); + outOilPressure = c_OilPress[0] + (c_OilPress[1] * simN3) + (c_OilPress[2] * powFBW(simN3, 2)); return outOilPressure; } diff --git a/hdw-a339x-acj/src/wasm/fadec_a330/src/SimVars.h b/hdw-a339x-acj/src/wasm/fadec_a330/src/SimVars.h new file mode 100644 index 000000000..32767533b --- /dev/null +++ b/hdw-a339x-acj/src/wasm/fadec_a330/src/SimVars.h @@ -0,0 +1,388 @@ +#pragma once + +/// +/// SimConnect data types to send to Sim Updated +/// +enum DataTypesID { + FuelLeftMain, + FuelRightMain, + FuelCenterMain, + FuelLeftAux, + FuelRightAux, + // FuelTrim, + OilTempEngine1, + OilTempEngine2, + OilPsiEngine1, + OilPsiEngine2, + StartCN3Engine1, + StartCN3Engine2, + SimulationDataTypeId, + AcftInfo, +}; + +struct SimulationData { + double simulationTime; + double simulationRate; +}; + +struct SimulationDataLivery { + char atc_id[32] = ""; +}; + +/// +/// A collection of SimVar unit enums. +/// +class Units { + public: + ENUM Percent = get_units_enum("Percent"); + 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 Gph = get_units_enum("Gallons per hour"); + ENUM Feet = get_units_enum("Feet"); + ENUM FootPounds = get_units_enum("Foot pounds"); + ENUM FeetMin = get_units_enum("Feet per minute"); + ENUM Number = get_units_enum("Number"); + ENUM Mach = get_units_enum("Mach"); + ENUM Millibars = get_units_enum("Millibars"); + ENUM SluggerSlugs = get_units_enum("Slug per cubic feet"); + ENUM Celsius = get_units_enum("Celsius"); + ENUM Bool = get_units_enum("Bool"); + ENUM Hours = get_units_enum("Hours"); + ENUM Seconds = get_units_enum("Seconds"); +}; + +/// +/// A collection of SimVars and LVars for the A32NX +/// +class SimVars { + public: + Units* m_Units; + + /// + /// Collection of SimVars for the A32NX + /// + ENUM CorrectedN1 = get_aircraft_var_enum("TURB ENG CORRECTED N1"); + ENUM CorrectedN2 = get_aircraft_var_enum("TURB ENG CORRECTED N2"); + ENUM N1 = get_aircraft_var_enum("TURB ENG N1"); + ENUM N2 = get_aircraft_var_enum("TURB ENG N2"); + ENUM OilPSI = get_aircraft_var_enum("GENERAL ENG OIL PRESSURE"); + ENUM OilTemp = get_aircraft_var_enum("GENERAL ENG OIL TEMPERATURE"); + ENUM Thrust = get_aircraft_var_enum("TURB ENG JET THRUST"); + ENUM correctedFF = get_aircraft_var_enum("TURB ENG CORRECTED FF"); + ENUM PlaneAltitude = get_aircraft_var_enum("PLANE ALTITUDE"); + ENUM PlaneAltitudeAGL = get_aircraft_var_enum("PLANE ALT ABOVE GROUND"); + ENUM PressureAltitude = get_aircraft_var_enum("PRESSURE ALTITUDE"); + ENUM AirSpeedMach = get_aircraft_var_enum("AIRSPEED MACH"); + ENUM AmbientTemp = get_aircraft_var_enum("AMBIENT TEMPERATURE"); + ENUM AmbientPressure = get_aircraft_var_enum("AMBIENT PRESSURE"); + ENUM VerticalSpeed = get_aircraft_var_enum("VERTICAL SPEED"); + ENUM StdTemp = get_aircraft_var_enum("STANDARD ATM TEMPERATURE"); + ENUM SimOnGround = get_aircraft_var_enum("SIM ON GROUND"); + ENUM EngineTime = get_aircraft_var_enum("GENERAL ENG ELAPSED TIME"); + ENUM EngineStarter = get_aircraft_var_enum("GENERAL ENG STARTER"); + ENUM EngineIgniter = get_aircraft_var_enum("TURB ENG IGNITION SWITCH EX1"); + ENUM EngineCombustion = get_aircraft_var_enum("GENERAL ENG COMBUSTION"); + ENUM animDeltaTime = get_aircraft_var_enum("ANIMATION DELTA TIME"); + + ENUM FuelTankQuantity = get_aircraft_var_enum("FUELSYSTEM TANK QUANTITY"); + ENUM EmptyWeight = get_aircraft_var_enum("EMPTY WEIGHT"); + ENUM TotalWeight = get_aircraft_var_enum("TOTAL WEIGHT"); + ENUM FuelTotalQuantity = get_aircraft_var_enum("FUEL TOTAL QUANTITY"); + ENUM FuelWeightGallon = get_aircraft_var_enum("FUEL WEIGHT PER GALLON"); + + ENUM FuelPump = get_aircraft_var_enum("FUELSYSTEM PUMP ACTIVE"); + ENUM FuelValve = get_aircraft_var_enum("FUELSYSTEM VALVE OPEN"); + ENUM FuelLineFlow = get_aircraft_var_enum("FUELSYSTEM LINE FUEL FLOW"); + ENUM FuelJunctionSetting = get_aircraft_var_enum("FUELSYSTEM JUNCTION SETTING"); + + ENUM NacelleAntiIce = get_aircraft_var_enum("ENG ANTI ICE"); + + /// + /// Collection of LVars for the A32NX + /// + ID DevVar; + ID IsReady; + ID FlexTemp; + ID Engine1N3; + ID Engine2N3; + ID Engine1N2; + ID Engine2N2; + ID Engine1N1; + ID Engine2N1; + ID EngineIdleN1; + ID EngineIdleN3; + ID EngineIdleFF; + ID EngineIdleEGT; + ID Engine1EGT; + ID Engine2EGT; + ID Engine1Oil; + ID Engine2Oil; + ID Engine1TotalOil; + ID Engine2TotalOil; + ID Engine1VibN1; + ID Engine2VibN1; + ID Engine1VibN2; + ID Engine2VibN2; + ID Engine1FF; + ID Engine2FF; + ID Engine1PreFF; + ID Engine2PreFF; + ID EngineCycleTime; + ID EngineImbalance; + ID WingAntiIce; + ID FuelUsedEngine1; + ID FuelUsedEngine2; + ID FuelLeftPre; + ID FuelRightPre; + ID FuelAuxLeftPre; + ID FuelAuxRightPre; + ID FuelCenterPre; + // ID FuelTrimPre; + ID RefuelRate; + ID RefuelStartedByUser; + ID FuelOverflowLeft; + ID FuelOverflowRight; + ID Engine1State; + ID Engine2State; + ID Engine1Timer; + ID Engine2Timer; + ID PumpStateEngine1; + ID PumpStateEngine2; + ID ThrustLimitType; + ID ThrustLimitIdle; + ID ThrustLimitToga; + ID ThrustLimitFlex; + ID ThrustLimitClimb; + ID ThrustLimitMct; + ID PacksState1; + ID PacksState2; + + SimVars() { this->initializeVars(); } + + void initializeVars() { + DevVar = register_named_variable("A32NX_DEVELOPER_STATE"); + IsReady = register_named_variable("A32NX_IS_READY"); + FlexTemp = register_named_variable("AIRLINER_TO_FLEX_TEMP"); + Engine1N3 = register_named_variable("A32NX_ENGINE_N3:1"); + Engine2N3 = register_named_variable("A32NX_ENGINE_N3:2"); + Engine1N2 = register_named_variable("A32NX_ENGINE_N2:1"); + Engine2N2 = register_named_variable("A32NX_ENGINE_N2:2"); + Engine1N1 = register_named_variable("A32NX_ENGINE_N1:1"); + Engine2N1 = register_named_variable("A32NX_ENGINE_N1:2"); + EngineIdleN1 = register_named_variable("A32NX_ENGINE_IDLE_N1"); + EngineIdleN3 = register_named_variable("A32NX_ENGINE_IDLE_N3"); + EngineIdleFF = register_named_variable("A32NX_ENGINE_IDLE_FF"); + EngineIdleEGT = register_named_variable("A32NX_ENGINE_IDLE_EGT"); + Engine1EGT = register_named_variable("A32NX_ENGINE_EGT:1"); + Engine2EGT = register_named_variable("A32NX_ENGINE_EGT:2"); + Engine1Oil = register_named_variable("A32NX_ENGINE_OIL_QTY:1"); + Engine2Oil = register_named_variable("A32NX_ENGINE_OIL_QTY:2"); + Engine1TotalOil = register_named_variable("A32NX_ENGINE_OIL_TOTAL:1"); + Engine2TotalOil = register_named_variable("A32NX_ENGINE_OIL_TOTAL:2"); + Engine1VibN1 = register_named_variable("A32NX_ENGINE_VIB_N1:1"); + Engine2VibN1 = register_named_variable("A32NX_ENGINE_VIB_N1:2"); + Engine1FF = register_named_variable("A32NX_ENGINE_FF:1"); + Engine2FF = register_named_variable("A32NX_ENGINE_FF:2"); + Engine1PreFF = register_named_variable("A32NX_ENGINE_PRE_FF:1"); + Engine2PreFF = register_named_variable("A32NX_ENGINE_PRE_FF:2"); + EngineImbalance = register_named_variable("A32NX_ENGINE_IMBALANCE"); + WingAntiIce = register_named_variable("A32NX_PNEU_WING_ANTI_ICE_SYSTEM_ON"); + FuelUsedEngine1 = register_named_variable("A32NX_FUEL_USED:1"); + FuelUsedEngine2 = register_named_variable("A32NX_FUEL_USED:2"); + FuelLeftPre = register_named_variable("A32NX_FUEL_LEFT_PRE"); + FuelRightPre = register_named_variable("A32NX_FUEL_RIGHT_PRE"); + FuelAuxLeftPre = register_named_variable("A32NX_FUEL_AUX_LEFT_PRE"); + FuelAuxRightPre = register_named_variable("A32NX_FUEL_AUX_RIGHT_PRE"); + FuelCenterPre = register_named_variable("A32NX_FUEL_CENTER_PRE"); + // FuelTrimPre = register_named_variable("A32NX_FUEL_TRIM_PRE"); + RefuelRate = register_named_variable("A32NX_EFB_REFUEL_RATE_SETTING"); + RefuelStartedByUser = register_named_variable("A32NX_REFUEL_STARTED_BY_USR"); + Engine1State = register_named_variable("A32NX_ENGINE_STATE:1"); + Engine2State = register_named_variable("A32NX_ENGINE_STATE:2"); + Engine1Timer = register_named_variable("A32NX_ENGINE_TIMER:1"); + Engine2Timer = register_named_variable("A32NX_ENGINE_TIMER:2"); + PumpStateEngine1 = register_named_variable("A32NX_PUMP_STATE:1"); + PumpStateEngine2 = register_named_variable("A32NX_PUMP_STATE:2"); + + ThrustLimitType = register_named_variable("A32NX_AUTOTHRUST_THRUST_LIMIT_TYPE"); + ThrustLimitIdle = register_named_variable("A32NX_AUTOTHRUST_THRUST_LIMIT_IDLE"); + ThrustLimitToga = register_named_variable("A32NX_AUTOTHRUST_THRUST_LIMIT_TOGA"); + ThrustLimitFlex = register_named_variable("A32NX_AUTOTHRUST_THRUST_LIMIT_FLX"); + ThrustLimitClimb = register_named_variable("A32NX_AUTOTHRUST_THRUST_LIMIT_CLB"); + ThrustLimitMct = register_named_variable("A32NX_AUTOTHRUST_THRUST_LIMIT_MCT"); + + PacksState1 = register_named_variable("A32NX_COND_PACK_FLOW_VALVE_1_IS_OPEN"); + PacksState2 = register_named_variable("A32NX_COND_PACK_FLOW_VALVE_2_IS_OPEN"); + + this->setDeveloperState(0); + this->setEngine1N3(0); + this->setEngine2N3(0); + this->setEngine1N2(0); + this->setEngine2N2(0); + this->setEngine1N1(0); + this->setEngine2N1(0); + this->setEngineIdleN1(0); + this->setEngineIdleN3(0); + this->setEngineIdleFF(0); + this->setEngineIdleEGT(0); + this->setEngine1EGT(0); + this->setEngine2EGT(0); + this->setEngine1Oil(0); + this->setEngine2Oil(0); + this->setEngine1TotalOil(0); + this->setEngine2TotalOil(0); + this->setEngine1FF(0); + this->setEngine2FF(0); + this->setEngine1PreFF(0); + this->setEngine2PreFF(0); + this->setEngineImbalance(0); + this->setFuelUsedEngine1(0); + this->setFuelUsedEngine2(0); + this->setFuelLeftPre(0); + this->setFuelRightPre(0); + this->setFuelAuxLeftPre(0); + this->setFuelAuxRightPre(0); + this->setFuelCenterPre(0); + // this->setFuelTrimPre(0); + this->setEngine1State(0); + this->setEngine2State(0); + this->setEngine1Timer(0); + this->setEngine2Timer(0); + this->setPumpStateEngine1(0); + this->setPumpStateEngine2(0); + this->setThrustLimitIdle(0); + this->setThrustLimitToga(0); + this->setThrustLimitFlex(0); + this->setThrustLimitClimb(0); + this->setThrustLimitMct(0); + + m_Units = new Units(); + } + + // Collection of LVar 'set' Functions + void setDeveloperState(FLOAT64 value) { set_named_variable_value(DevVar, value); } + void setEngine1N3(FLOAT64 value) { set_named_variable_value(Engine1N3, value); } + void setEngine2N3(FLOAT64 value) { set_named_variable_value(Engine2N3, value); } + void setEngine1N2(FLOAT64 value) { set_named_variable_value(Engine1N2, value); } + void setEngine2N2(FLOAT64 value) { set_named_variable_value(Engine2N2, value); } + void setEngine1N1(FLOAT64 value) { set_named_variable_value(Engine1N1, value); } + void setEngine2N1(FLOAT64 value) { set_named_variable_value(Engine2N1, value); } + void setEngineIdleN1(FLOAT64 value) { set_named_variable_value(EngineIdleN1, value); } + void setEngineIdleN3(FLOAT64 value) { set_named_variable_value(EngineIdleN3, value); } + void setEngineIdleFF(FLOAT64 value) { set_named_variable_value(EngineIdleFF, value); } + void setEngineIdleEGT(FLOAT64 value) { set_named_variable_value(EngineIdleEGT, value); } + void setEngine1EGT(FLOAT64 value) { set_named_variable_value(Engine1EGT, value); } + void setEngine2EGT(FLOAT64 value) { set_named_variable_value(Engine2EGT, value); } + void setEngine1Oil(FLOAT64 value) { set_named_variable_value(Engine1Oil, value); } + void setEngine2Oil(FLOAT64 value) { set_named_variable_value(Engine2Oil, value); } + void setEngine1TotalOil(FLOAT64 value) { set_named_variable_value(Engine1TotalOil, value); } + void setEngine2TotalOil(FLOAT64 value) { set_named_variable_value(Engine2TotalOil, value); } + void setEngine1FF(FLOAT64 value) { set_named_variable_value(Engine1FF, value); } + void setEngine2FF(FLOAT64 value) { set_named_variable_value(Engine2FF, value); } + void setEngine1PreFF(FLOAT64 value) { set_named_variable_value(Engine1PreFF, value); } + void setEngine2PreFF(FLOAT64 value) { set_named_variable_value(Engine2PreFF, value); } + void setEngineImbalance(FLOAT64 value) { set_named_variable_value(EngineImbalance, value); } + void setFuelUsedEngine1(FLOAT64 value) { set_named_variable_value(FuelUsedEngine1, value); } + void setFuelUsedEngine2(FLOAT64 value) { set_named_variable_value(FuelUsedEngine2, value); } + void setFuelLeftPre(FLOAT64 value) { set_named_variable_value(FuelLeftPre, value); } + void setFuelRightPre(FLOAT64 value) { set_named_variable_value(FuelRightPre, value); } + void setFuelAuxLeftPre(FLOAT64 value) { set_named_variable_value(FuelAuxLeftPre, value); } + void setFuelAuxRightPre(FLOAT64 value) { set_named_variable_value(FuelAuxRightPre, value); } + void setFuelCenterPre(FLOAT64 value) { set_named_variable_value(FuelCenterPre, value); } + // void setFuelTrimPre(FLOAT64 value) { set_named_variable_value(FuelTrimPre, value); }; + void setEngine1State(FLOAT64 value) { set_named_variable_value(Engine1State, value); } + void setEngine2State(FLOAT64 value) { set_named_variable_value(Engine2State, value); } + void setEngine1Timer(FLOAT64 value) { set_named_variable_value(Engine1Timer, value); } + void setEngine2Timer(FLOAT64 value) { set_named_variable_value(Engine2Timer, value); } + void setPumpStateEngine1(FLOAT64 value) { set_named_variable_value(PumpStateEngine1, value); } + void setPumpStateEngine2(FLOAT64 value) { set_named_variable_value(PumpStateEngine2, value); } + void setThrustLimitIdle(FLOAT64 value) { set_named_variable_value(ThrustLimitIdle, value); } + void setThrustLimitToga(FLOAT64 value) { set_named_variable_value(ThrustLimitToga, value); } + void setThrustLimitFlex(FLOAT64 value) { set_named_variable_value(ThrustLimitFlex, value); } + void setThrustLimitClimb(FLOAT64 value) { set_named_variable_value(ThrustLimitClimb, value); } + void setThrustLimitMct(FLOAT64 value) { set_named_variable_value(ThrustLimitMct, value); } + + // Collection of SimVar/LVar 'get' Functions + FLOAT64 getDeveloperState() { return get_named_variable_value(DevVar); } + FLOAT64 getIsReady() { return get_named_variable_value(IsReady); } + FLOAT64 getFlexTemp() { return get_named_variable_value(FlexTemp); } + FLOAT64 getEngine1N3() { return get_named_variable_value(Engine1N3); } + FLOAT64 getEngine2N3() { return get_named_variable_value(Engine2N3); } + FLOAT64 getEngine1N2() { return get_named_variable_value(Engine1N2); } + FLOAT64 getEngine2N2() { return get_named_variable_value(Engine2N2); } + FLOAT64 getEngine1N1() { return get_named_variable_value(Engine1N1); } + FLOAT64 getEngine2N1() { return get_named_variable_value(Engine2N1); } + FLOAT64 getEngineIdleN1() { return get_named_variable_value(EngineIdleN1); } + FLOAT64 getEngineIdleN3() { return get_named_variable_value(EngineIdleN3); } + FLOAT64 getEngineIdleFF() { return get_named_variable_value(EngineIdleFF); } + FLOAT64 getEngineIdleEGT() { return get_named_variable_value(EngineIdleEGT); } + FLOAT64 getEngine1FF() { return get_named_variable_value(Engine1FF); } + FLOAT64 getEngine2FF() { return get_named_variable_value(Engine2FF); } + FLOAT64 getEngine1EGT() { return get_named_variable_value(Engine1EGT); } + FLOAT64 getEngine2EGT() { return get_named_variable_value(Engine2EGT); } + FLOAT64 getEngine1Oil() { return get_named_variable_value(Engine1Oil); } + FLOAT64 getEngine2Oil() { return get_named_variable_value(Engine2Oil); } + FLOAT64 getEngine1TotalOil() { return get_named_variable_value(Engine1TotalOil); } + FLOAT64 getEngine2TotalOil() { return get_named_variable_value(Engine2TotalOil); } + FLOAT64 getEngine1PreFF() { return get_named_variable_value(Engine1PreFF); } + FLOAT64 getEngine2PreFF() { return get_named_variable_value(Engine2PreFF); } + FLOAT64 getEngineImbalance() { return get_named_variable_value(EngineImbalance); } + FLOAT64 getWAI() { return get_named_variable_value(WingAntiIce); } + FLOAT64 getFuelUsedEngine1() { return get_named_variable_value(FuelUsedEngine1); } + FLOAT64 getFuelUsedEngine2() { return get_named_variable_value(FuelUsedEngine2); } + FLOAT64 getFuelLeftPre() { return get_named_variable_value(FuelLeftPre); } + FLOAT64 getFuelRightPre() { return get_named_variable_value(FuelRightPre); } + FLOAT64 getFuelAuxLeftPre() { return get_named_variable_value(FuelAuxLeftPre); } + FLOAT64 getFuelAuxRightPre() { return get_named_variable_value(FuelAuxRightPre); } + FLOAT64 getFuelCenterPre() { return get_named_variable_value(FuelCenterPre); } + // FLOAT64 getFuelTrimPre() { return get_named_variable_value(FuelTrimPre); } + FLOAT64 getRefuelRate() { return get_named_variable_value(RefuelRate); } + FLOAT64 getRefuelStartedByUser() { return get_named_variable_value(RefuelStartedByUser); } + FLOAT64 getPumpStateEngine1() { return get_named_variable_value(PumpStateEngine1); } + FLOAT64 getPumpStateEngine2() { return get_named_variable_value(PumpStateEngine2); } + FLOAT64 getPacksState1() { return get_named_variable_value(PacksState1); } + FLOAT64 getPacksState2() { return get_named_variable_value(PacksState2); } + FLOAT64 getThrustLimitType() { return get_named_variable_value(ThrustLimitType); } + + FLOAT64 getCN1(int index) { return aircraft_varget(CorrectedN1, m_Units->Percent, index); } + FLOAT64 getCN2(int index) { return aircraft_varget(CorrectedN2, m_Units->Percent, index); } + FLOAT64 getN1(int index) { return aircraft_varget(N1, m_Units->Percent, index); } + FLOAT64 getN2(int index) { return aircraft_varget(N2, m_Units->Percent, index); } + FLOAT64 getOilPsi(int index) { return aircraft_varget(OilPSI, m_Units->Psi, index); } + FLOAT64 getOilTemp(int index) { return aircraft_varget(OilTemp, m_Units->Celsius, index); } + FLOAT64 getThrust(int index) { return aircraft_varget(Thrust, m_Units->Pounds, index); } + FLOAT64 getEngine1State() { return get_named_variable_value(Engine1State); } + FLOAT64 getEngine2State() { return get_named_variable_value(Engine2State); } + FLOAT64 getEngine1Timer() { return get_named_variable_value(Engine1Timer); } + FLOAT64 getEngine2Timer() { return get_named_variable_value(Engine2Timer); } + FLOAT64 getFF(int index) { return aircraft_varget(correctedFF, m_Units->Pph, index); } + FLOAT64 getMach() { return aircraft_varget(AirSpeedMach, m_Units->Mach, 0); } + FLOAT64 getPlaneAltitude() { return aircraft_varget(PlaneAltitude, m_Units->Feet, 0); } + FLOAT64 getPlaneAltitudeAGL() { return aircraft_varget(PlaneAltitudeAGL, m_Units->Feet, 0); } + FLOAT64 getPressureAltitude() { return aircraft_varget(PressureAltitude, m_Units->Feet, 0); } + FLOAT64 getVerticalSpeed() { return aircraft_varget(VerticalSpeed, m_Units->FeetMin, 0); } + FLOAT64 getAmbientTemperature() { return aircraft_varget(AmbientTemp, m_Units->Celsius, 0); } + FLOAT64 getAmbientPressure() { return aircraft_varget(AmbientPressure, m_Units->Millibars, 0); } + FLOAT64 getStdTemperature() { return aircraft_varget(StdTemp, m_Units->Celsius, 0); } + FLOAT64 getSimOnGround() { return aircraft_varget(SimOnGround, m_Units->Bool, 0); } + FLOAT64 getFuelTankQuantity(int index) { return aircraft_varget(FuelTankQuantity, m_Units->Gallons, index); } + FLOAT64 getFuelTotalQuantity() { return aircraft_varget(FuelTotalQuantity, m_Units->Gallons, 0); } + FLOAT64 getEmptyWeight() { return aircraft_varget(EmptyWeight, m_Units->Pounds, 0); } + FLOAT64 getTotalWeight() { return aircraft_varget(TotalWeight, m_Units->Pounds, 0); } + FLOAT64 getFuelWeightGallon() { return aircraft_varget(FuelWeightGallon, m_Units->Pounds, 0); } + FLOAT64 getEngineTime(int index) { return aircraft_varget(EngineTime, m_Units->Seconds, index); } + FLOAT64 getEngineStarter(int index) { return aircraft_varget(EngineStarter, m_Units->Bool, index); } + FLOAT64 getEngineIgniter(int index) { return aircraft_varget(EngineIgniter, m_Units->Number, index); } + FLOAT64 getEngineCombustion(int index) { return aircraft_varget(EngineCombustion, m_Units->Bool, index); } + FLOAT64 getAnimDeltaTime() { return aircraft_varget(animDeltaTime, m_Units->Seconds, 0); } + FLOAT64 getNAI(int index) { return aircraft_varget(NacelleAntiIce, m_Units->Bool, index); } + FLOAT64 getPump(int index) { return aircraft_varget(FuelPump, m_Units->Number, index); } + FLOAT64 getValve(int index) { return aircraft_varget(FuelValve, m_Units->Number, index); } + /// @brief Gets a fuel line flow rate in gallons/hour + /// @param index Index of the fuel line + /// @return Fuel line flow rate in gallons/hour + FLOAT64 getLineFlow(int index) { return aircraft_varget(FuelLineFlow, m_Units->Gph, index); } + FLOAT64 getJunctionSetting(int index) { return aircraft_varget(FuelJunctionSetting, m_Units->Number, index); } +}; diff --git a/hdw-a339x-acj/src/wasm/fadec_a330/src/Tables.h b/hdw-a339x-acj/src/wasm/fadec_a330/src/Tables.h new file mode 100644 index 000000000..74c9802c9 --- /dev/null +++ b/hdw-a339x-acj/src/wasm/fadec_a330/src/Tables.h @@ -0,0 +1,69 @@ +#pragma once + +#include "SimVars.h" +#include "common.h" + +EngineRatios* ratios; + +/// +/// Table 1502 (CN3 vs correctedN1) representations with FSX nomenclature +/// +/// Returns CN3 - correctedN1 pair. +double table1502(int i, int j) { + double t[13][4] = {{16.012, 0.000, 0.000, 17.000}, {19.355, 1.845, 1.845, 17.345}, {22.874, 2.427, 2.427, 18.127}, + {50.147, 12.427, 12.427, 26.627}, {60.000, 18.500, 18.500, 33.728}, {67.742, 25.243, 25.243, 40.082}, + {73.021, 30.505, 30.505, 43.854}, {78.299, 39.779, 39.779, 48.899}, {81.642, 49.515, 49.515, 53.557}, + {85.337, 63.107, 63.107, 63.107}, {87.977, 74.757, 74.757, 74.757}, {97.800, 97.200, 97.200, 97.200}, + {118.000, 115.347, 115.347, 115.347}}; + + return t[i][j]; +} + +/// +/// Calculate expected CN3 at Idle +/// +double iCN3(double pressAltitude, double mach) { + double cn3 = 0; + + cn3 = 60.0 / (sqrt((288.15 - (1.98 * pressAltitude / 1000)) / 288.15) * sqrt(1 + (0.2 * powFBW(mach, 2)))); + + return cn3; +} + +/// +/// Calculate expected correctedN1 at Idle +/// +double iCN1(double pressAltitude, double mach, double ambientTemp) { + int i; + double cn1_lo = 0, cn1_hi = 0, cn1 = 0; + double cn3 = iCN3(pressAltitude, mach); + double cell = 0; + double cn3lo = 0, cn3hi = 0; + double cn1lolo = 0, cn1hilo = 0, cn1lohi = 0, cn1hihi = 0; + + for (i = 0; i < 13; i++) { + cell = table1502(i, 0); + if (cell > cn3) { + break; + } + } + + cn3lo = table1502(i - 1, 0); + cn3hi = table1502(i, 0); + + cn1lolo = table1502(i - 1, 1); + cn1hilo = table1502(i, 1); + + if (mach <= 0.2) { + cn1 = interpolate(cn3, cn3lo, cn3hi, cn1lolo, cn1hilo); + } else { + cn1lohi = table1502(i - 1, 3); + cn1hihi = table1502(i, 3); + + cn1_lo = interpolate(cn3, cn3lo, cn3hi, cn1lolo, cn1hilo); + cn1_hi = interpolate(cn3, cn3lo, cn3hi, cn1lohi, cn1hihi); + cn1 = interpolate(mach, 0.2, 0.9, cn1_lo, cn1_hi); + } + + return cn1; +} \ No newline at end of file diff --git a/hdw-a339x-acj/src/wasm/fadec_a330/src/ThrustLimits.h b/hdw-a339x-acj/src/wasm/fadec_a330/src/ThrustLimits.h new file mode 100644 index 000000000..85cbbe6e4 --- /dev/null +++ b/hdw-a339x-acj/src/wasm/fadec_a330/src/ThrustLimits.h @@ -0,0 +1,257 @@ +#pragma once + +#include "SimVars.h" +#include "common.h" + +double cas2mach(double cas, double ambientPressure) { + double k = 2188648.141; + double delta = ambientPressure / 1013; + double mach = sqrt((5 * pow(((pow(((pow(cas, 2) / k) + 1), 3.5) * (1 / delta)) - (1 / delta) + 1), 0.285714286)) - 5); + + return mach; +} + +static constexpr double limits[72][6] = { + {-2000, 48.000, 55.000, 81.351, 79.370, 61.535}, {-1000, 46.000, 55.000, 82.605, 80.120, 62.105}, + {0, 44.000, 55.000, 83.832, 80.776, 62.655}, {500, 42.000, 52.000, 84.210, 81.618, 62.655}, + {1000, 42.000, 52.000, 84.579, 81.712, 62.655}, {2000, 40.000, 50.000, 85.594, 82.720, 62.655}, + {3000, 36.000, 48.000, 86.657, 83.167, 61.960}, {4000, 32.000, 46.000, 87.452, 83.332, 61.206}, + {5000, 29.000, 44.000, 88.833, 84.166, 61.206}, {6000, 25.000, 42.000, 90.232, 84.815, 61.206}, + {7000, 21.000, 40.000, 91.711, 85.565, 61.258}, {8000, 17.000, 38.000, 93.247, 86.225, 61.777}, + {9000, 15.000, 36.000, 94.031, 86.889, 60.968}, {10000, 13.000, 34.000, 94.957, 88.044, 60.935}, + {11000, 12.000, 32.000, 95.295, 88.526, 59.955}, {12000, 11.000, 30.000, 95.568, 88.818, 58.677}, + {13000, 10.000, 28.000, 95.355, 88.819, 59.323}, {14000, 10.000, 26.000, 95.372, 89.311, 59.965}, + {15000, 8.000, 24.000, 95.686, 89.907, 58.723}, {16000, 5.000, 22.000, 96.160, 89.816, 57.189}, + {16600, 5.000, 22.000, 96.560, 89.816, 57.189}, {-2000, 47.751, 54.681, 84.117, 81.901, 63.498}, + {-1000, 45.771, 54.681, 85.255, 82.461, 63.920}, {0, 43.791, 54.681, 86.411, 83.021, 64.397}, + {500, 42.801, 52.701, 86.978, 83.740, 64.401}, {1000, 41.811, 52.701, 87.568, 83.928, 64.525}, + {2000, 38.841, 50.721, 88.753, 84.935, 64.489}, {3000, 36.861, 48.741, 89.930, 85.290, 63.364}, + {4000, 32.901, 46.761, 91.004, 85.836, 62.875}, {5000, 28.941, 44.781, 92.198, 86.293, 62.614}, + {6000, 24.981, 42.801, 93.253, 86.563, 62.290}, {7000, 21.022, 40.821, 94.273, 86.835, 61.952}, + {8000, 17.062, 38.841, 94.919, 87.301, 62.714}, {9000, 15.082, 36.861, 95.365, 87.676, 61.692}, + {10000, 13.102, 34.881, 95.914, 88.150, 60.906}, {11000, 12.112, 32.901, 96.392, 88.627, 59.770}, + {12000, 11.122, 30.921, 96.640, 89.206, 58.933}, {13000, 10.132, 28.941, 96.516, 89.789, 60.503}, + {14000, 9.142, 26.961, 96.516, 90.475, 62.072}, {15000, 9.142, 24.981, 96.623, 90.677, 59.333}, + {16000, 7.162, 23.001, 96.845, 90.783, 58.045}, {16600, 5.182, 21.022, 97.366, 91.384, 58.642}, + {-2000, 30.800, 56.870, 80.280, 72.000, 0.000}, {2000, 20.990, 48.157, 82.580, 74.159, 0.000}, + {5000, 16.139, 43.216, 84.642, 75.737, 0.000}, {8000, 7.342, 38.170, 86.835, 77.338, 0.000}, + {10000, 4.051, 34.518, 88.183, 77.999, 0.000}, {10000.1, 4.051, 34.518, 87.453, 77.353, 0.000}, + {12000, 0.760, 30.865, 88.303, 78.660, 0.000}, {15000, -4.859, 25.039, 89.748, 79.816, 0.000}, + {17000, -9.934, 19.813, 90.668, 80.895, 0.000}, {20000, -15.822, 13.676, 92.106, 81.894, 0.000}, + {24000, -22.750, 6.371, 93.651, 82.716, 0.000}, {27000, -29.105, -0.304, 93.838, 83.260, 0.000}, + {29314, -32.049, -3.377, 93.502, 82.962, 0.000}, {31000, -34.980, -6.452, 95.392, 84.110, 0.000}, + {35000, -45.679, -17.150, 96.104, 85.248, 0.000}, {39000, -45.679, -17.150, 96.205, 84.346, 0.000}, + {41500, -45.679, -17.150, 95.676, 83.745, 0.000}, {-1000, 26.995, 54.356, 82.465, 74.086, 0.000}, + {3000, 18.170, 45.437, 86.271, 77.802, 0.000}, {7000, 9.230, 40.266, 89.128, 79.604, 0.000}, + {11000, 4.019, 31.046, 92.194, 82.712, 0.000}, {15000, -5.226, 21.649, 95.954, 85.622, 0.000}, + {17000, -9.913, 20.702, 97.520, 85.816, 0.000}, {20000, -15.129, 15.321, 99.263, 86.770, 0.000}, + {22000, -19.947, 10.382, 98.977, 86.661, 0.000}, {25000, -25.397, 4.731, 98.440, 85.765, 0.000}, + {27000, -30.369, -0.391, 97.279, 85.556, 0.000}, {31000, -36.806, -7.165, 98.674, 86.650, 0.000}, + {35000, -43.628, -14.384, 98.386, 85.747, 0.000}, {39000, -47.286, -18.508, 97.278, 85.545, 0.000}}; + +/// +/// Finds top-row boundary in an array +/// +int finder(double altitude, int index) { + if (altitude < limits[index][0]) { + return index; + } else { + return finder(altitude, index + 1); + } +} + +/// +/// Calculates Bleed Air situation for engine adaptation +/// +double bleedTotal(int type, double altitude, double oat, double cp, double lp, double flexTemp, double ac, double nacelle, double wing) { + double n1Packs = 0; + double n1Nai = 0; + double n1Wai = 0; + double bleed = 0; + + if (flexTemp > lp && type <= 1) { + n1Packs = -0.6; + n1Nai = -0.7; + n1Wai = -0.7; + } else { + switch (type) { + case 0: + if (altitude < 8000) { + if (oat < cp) { + n1Packs = -0.4; + } else { + n1Packs = -0.5; + n1Nai = -0.6; + n1Wai = -0.7; + } + } else { + if (oat < cp) { + n1Packs = -0.6; + } else { + n1Packs = -0.7; + n1Nai = -0.8; + n1Wai = -0.8; + } + } + break; + case 1: + if (altitude < 8000) { + if (oat < cp) { + n1Packs = -0.4; + } else { + n1Packs = -0.4; + n1Nai = -0.6; + n1Wai = -0.6; + } + } else { + if (oat < cp) { + n1Packs = -0.6; + } else { + n1Packs = -0.6; + n1Nai = -0.7; + n1Wai = -0.8; + } + } + break; + case 2: + if (oat < cp) { + n1Packs = -0.2; + } else { + n1Packs = -0.3; + n1Nai = -0.8; + n1Wai = -0.4; + } + break; + case 3: + if (oat < cp) { + n1Packs = -0.6; + } else { + n1Packs = -0.6; + n1Nai = -0.9; + n1Wai = -1.2; + } + break; + } + } + + if (ac == 0) { + n1Packs = 0; + } + if (nacelle == 0) { + n1Nai = 0; + } + if (wing == 0) { + n1Wai = 0; + } + + bleed = n1Packs + n1Nai + n1Wai; + + return bleed; +} + +/// +/// Main N1 Limit Function +/// +/// 0-TO, 1-GA, 2-CLB, 3-MCT +/// +double +limitN1(int type, double altitude, double ambientTemp, double ambientPressure, double flexTemp, double ac, double nacelle, double wing) { + int rowMin = 0; + int rowMax = 0; + int loAltRow = 0; + int hiAltRow = 0; + double mach = 0; + double cp = 0; + double lp = 0; + double cn1 = 0; + double n1 = 0; + double cn1Flat = 0; + double cn1Last = 0; + double cn1Flex = 0; + double m = 0; + double b = 0; + double bleed = 0; + + // Set main variables per Limit Type + switch (type) { + case 0: + rowMin = 0; + rowMax = 20; + mach = 0; + break; + case 1: + rowMin = 21; + rowMax = 41; + mach = 0.225; + break; + case 2: + rowMin = 42; + rowMax = 58; + if (altitude <= 10000) { + mach = cas2mach(250, ambientPressure); + } else { + mach = cas2mach(300, ambientPressure); + if (mach > 0.78) + mach = 0.78; + } + break; + case 3: + rowMin = 59; + rowMax = 71; + mach = cas2mach(230, ambientPressure); + break; + } + + // Check for over/ underflows. Else, find top row value + if (altitude <= limits[rowMin][0]) { + hiAltRow = rowMin; + loAltRow = rowMin; + } else if (altitude >= limits[rowMax][0]) { + hiAltRow = rowMax; + loAltRow = rowMax; + } else { + hiAltRow = finder(altitude, rowMin); + loAltRow = hiAltRow - 1; + } + + // Define key table variables and interpolation + cp = interpolate(altitude, limits[loAltRow][0], limits[hiAltRow][0], limits[loAltRow][1], limits[hiAltRow][1]); + lp = interpolate(altitude, limits[loAltRow][0], limits[hiAltRow][0], limits[loAltRow][2], limits[hiAltRow][2]); + cn1Flat = interpolate(altitude, limits[loAltRow][0], limits[hiAltRow][0], limits[loAltRow][3], limits[hiAltRow][3]); + cn1Last = interpolate(altitude, limits[loAltRow][0], limits[hiAltRow][0], limits[loAltRow][4], limits[hiAltRow][4]); + cn1Flex = interpolate(altitude, limits[loAltRow][0], limits[hiAltRow][0], limits[loAltRow][5], limits[hiAltRow][5]); + + if (flexTemp > 0 && type <= 1) { // CN1 for Flex Case + if (flexTemp <= cp) { + cn1 = cn1Flat; + } else if (flexTemp > lp) { + m = (cn1Flex - cn1Last) / (100 - lp); + b = cn1Flex - m * 100; + cn1 = (m * flexTemp) + b; + } else { + m = (cn1Last - cn1Flat) / (lp - cp); + b = cn1Last - m * lp; + cn1 = (m * flexTemp) + b; + } + } + else { // CN1 for All other cases + if (ambientTemp <= cp) { + cn1 = cn1Flat; + } else { + m = (cn1Last - cn1Flat) / (lp - cp); + b = cn1Last - m * lp; + cn1 = (m * ambientTemp) + b; + } + } + + // Define bleed rating/ derating + bleed = bleedTotal(type, altitude, ambientTemp, cp, lp, flexTemp, ac, nacelle, wing); + + // Setting N1 + n1 = (cn1 * sqrt(ratios->theta2(mach, ambientTemp))) + bleed; + /*if (type == 3) { + std::cout << "FADEC: bleed= " << bleed << " cn1= " << cn1 << " theta2= " << sqrt(ratios->theta2(mach, ambientTemp)) + << " n1= " << n1 << std::endl; + }*/ + return n1; +} \ No newline at end of file diff --git a/hdw-a339x-acj/src/wasm/systems/a320_systems/src/air_conditioning.rs b/hdw-a339x-acj/src/wasm/systems/a320_systems/src/air_conditioning.rs index df06036ef..c33e7faa6 100644 --- a/hdw-a339x-acj/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/hdw-a339x-acj/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -14,12 +14,12 @@ use systems::{ AutoManFaultPushButton, NormalOnPushButton, OnOffFaultPushButton, OnOffPushButton, SpringLoadedSwitch, ValueKnob, }, + payload::NumberOfPassengers, pneumatic::PneumaticContainer, shared::{ random_number, update_iterator::MaxStepLoop, AverageExt, CabinAltitude, CabinSimulation, - ControllerSignal, ElectricalBusType, EngineBleedPushbutton, EngineCorrectedN1, - EngineFirePushButtons, EngineStartState, LgciuWeightOnWheels, PackFlowValveState, - PneumaticBleed, + ControllerSignal, ElectricalBusType, EngineCorrectedN1, EngineFirePushButtons, + EngineStartState, LgciuWeightOnWheels, PackFlowValveState, PneumaticBleed, }, simulation::{ InitContext, Read, SimulationElement, SimulationElementVisitor, SimulatorReader, @@ -33,7 +33,7 @@ use uom::si::{ velocity::knot, }; -use crate::payload::{A320Pax, NumberOfPassengers}; +use crate::payload::A320Pax; pub(super) struct A320AirConditioning { a320_cabin: A320Cabin, @@ -67,7 +67,6 @@ impl A320AirConditioning { engine_fire_push_buttons: &impl EngineFirePushButtons, number_of_passengers: &impl NumberOfPassengers, pneumatic: &(impl EngineStartState + PackFlowValveState + PneumaticBleed), - pneumatic_overhead: &impl EngineBleedPushbutton<2>, pressurization_overhead: &A320PressurizationOverheadPanel, lgciu: [&impl LgciuWeightOnWheels; 2], ) { @@ -80,7 +79,6 @@ impl A320AirConditioning { engines, engine_fire_push_buttons, pneumatic, - pneumatic_overhead, &self.a320_pressurization_system, pressurization_overhead, lgciu, @@ -200,13 +198,11 @@ impl A320Cabin { } fn update_number_of_passengers(&mut self, number_of_passengers: &impl NumberOfPassengers) { - self.number_of_passengers[1] = (number_of_passengers.number_of_passengers(A320Pax::A) - ) - as u8; - self.number_of_passengers[2] = (number_of_passengers.number_of_passengers(A320Pax::B) - + number_of_passengers.number_of_passengers(A320Pax::C) - ) - as u8; + self.number_of_passengers[1] = + (number_of_passengers.number_of_passengers(A320Pax::A.into()) + + number_of_passengers.number_of_passengers(A320Pax::B.into())) as u8; + self.number_of_passengers[2] = + number_of_passengers.number_of_passengers(A320Pax::C.into()) as u8; } } @@ -282,7 +278,6 @@ impl A320AirConditioningSystem { engines: [&impl EngineCorrectedN1; 2], engine_fire_push_buttons: &impl EngineFirePushButtons, pneumatic: &(impl EngineStartState + PackFlowValveState + PneumaticBleed), - pneumatic_overhead: &impl EngineBleedPushbutton<2>, pressurization: &impl CabinAltitude, pressurization_overhead: &A320PressurizationOverheadPanel, lgciu: [&impl LgciuWeightOnWheels; 2], @@ -295,7 +290,6 @@ impl A320AirConditioningSystem { engines, engine_fire_push_buttons, pneumatic, - pneumatic_overhead, pressurization, pressurization_overhead, lgciu, @@ -325,11 +319,11 @@ impl A320AirConditioningSystem { .update(context, &self.mixer_unit, &self.acsc); self.air_conditioning_overhead - .set_pack_pushbutton_fault(self.pack_fault_determination(pneumatic)); + .set_pack_pushbutton_fault(self.pack_fault_determination()); } - pub fn pack_fault_determination(&self, pneumatic: &impl PackFlowValveState) -> [bool; 2] { - self.acsc.pack_fault_determination(pneumatic) + pub fn pack_fault_determination(&self) -> [bool; 2] { + self.acsc.pack_fault_determination() } pub fn mix_packs_air_update(&mut self, pack_container: &mut [impl PneumaticContainer; 2]) { @@ -767,10 +761,10 @@ mod tests { use ntest::assert_about_eq; use systems::{ electrical::{test::TestElectricitySource, ElectricalBus, Electricity}, - overhead::AutoOffFaultPushButton, pneumatic::{ valve::{DefaultValve, PneumaticExhaust}, ControllablePneumaticValve, EngineModeSelector, EngineState, PneumaticPipe, Precooler, + PressureTransducer, }, shared::{ arinc429::{Arinc429Word, SignStatus}, @@ -920,7 +914,7 @@ mod tests { struct TestPayload; impl NumberOfPassengers for TestPayload { - fn number_of_passengers(&self, _ps: A320Pax) -> i8 { + fn number_of_passengers(&self, _ps: usize) -> i8 { 0 } } @@ -988,6 +982,9 @@ mod tests { fn pack_flow_valve_air_flow(&self, pack_id: usize) -> MassRate { self.packs[pack_id - 1].pack_flow_valve_air_flow() } + fn pack_flow_valve_inlet_pressure(&self, pack_id: usize) -> Option { + self.packs[pack_id - 1].pack_flow_valve_inlet_pressure() + } } impl SimulationElement for TestPneumatic { fn accept(&mut self, visitor: &mut V) { @@ -1094,6 +1091,7 @@ mod tests { pack_container: PneumaticPipe, exhaust: PneumaticExhaust, pack_flow_valve: DefaultValve, + pack_inlet_pressure_sensor: PressureTransducer, } impl TestPneumaticPackComplex { fn new(engine_number: usize) -> Self { @@ -1106,6 +1104,9 @@ mod tests { ), exhaust: PneumaticExhaust::new(0.3, 0.3, Pressure::new::(0.)), pack_flow_valve: DefaultValve::new_closed(), + pack_inlet_pressure_sensor: PressureTransducer::new( + ElectricalBusType::DirectCurrentEssentialShed, + ), } } fn update( @@ -1117,6 +1118,7 @@ mod tests { self.pack_flow_valve.update_open_amount( pack_flow_valve_signals.pack_flow_controller(self.engine_number), ); + self.pack_inlet_pressure_sensor.update(context, from); self.pack_flow_valve .update_move_fluid(context, from, &mut self.pack_container); self.exhaust @@ -1128,6 +1130,9 @@ mod tests { fn pack_flow_valve_air_flow(&self) -> MassRate { self.pack_flow_valve.fluid_flow() } + fn pack_flow_valve_inlet_pressure(&self) -> Option { + self.pack_inlet_pressure_sensor.signal() + } } impl PneumaticContainer for TestPneumaticPackComplex { fn pressure(&self) -> Pressure { @@ -1160,22 +1165,11 @@ mod tests { self.pack_container.update_temperature(temperature); } } + impl SimulationElement for TestPneumaticPackComplex { + fn accept(&mut self, visitor: &mut V) { + self.pack_inlet_pressure_sensor.accept(visitor); - struct TestPneumaticOverhead { - engine_1_bleed: AutoOffFaultPushButton, - engine_2_bleed: AutoOffFaultPushButton, - } - impl TestPneumaticOverhead { - fn new(context: &mut InitContext) -> Self { - Self { - engine_1_bleed: AutoOffFaultPushButton::new_auto(context, "PNEU_ENG_1_BLEED"), - engine_2_bleed: AutoOffFaultPushButton::new_auto(context, "PNEU_ENG_2_BLEED"), - } - } - } - impl EngineBleedPushbutton<2> for TestPneumaticOverhead { - fn engine_bleed_pushbuttons_are_auto(&self) -> [bool; 2] { - [self.engine_1_bleed.is_auto(), self.engine_2_bleed.is_auto()] + visitor.visit(self); } } @@ -1226,7 +1220,6 @@ mod tests { engine_fire_push_buttons: TestEngineFirePushButtons, payload: TestPayload, pneumatic: TestPneumatic, - pneumatic_overhead: TestPneumaticOverhead, pressurization_overhead: A320PressurizationOverheadPanel, lgciu1: TestLgciu, lgciu2: TestLgciu, @@ -1259,7 +1252,6 @@ mod tests { engine_fire_push_buttons: TestEngineFirePushButtons::new(), payload: TestPayload {}, pneumatic: TestPneumatic::new(context), - pneumatic_overhead: TestPneumaticOverhead::new(context), pressurization_overhead: A320PressurizationOverheadPanel::new(context), lgciu1: TestLgciu::new(false), lgciu2: TestLgciu::new(false), @@ -1359,7 +1351,6 @@ mod tests { &self.engine_fire_push_buttons, &self.payload, &self.pneumatic, - &self.pneumatic_overhead, &self.pressurization_overhead, [&self.lgciu1, &self.lgciu2], ); diff --git a/hdw-a339x-acj/src/wasm/systems/a320_systems/src/airframe/mod.rs b/hdw-a339x-acj/src/wasm/systems/a320_systems/src/airframe/mod.rs new file mode 100644 index 000000000..23e2a7984 --- /dev/null +++ b/hdw-a339x-acj/src/wasm/systems/a320_systems/src/airframe/mod.rs @@ -0,0 +1,214 @@ +use systems::{ + airframe::{CenterOfGravityData, WeightData}, + fuel::FuelPayload, + payload::{CargoPayload, LoadsheetInfo, PassengerPayload}, + simulation::{InitContext, SimulationElement, SimulationElementVisitor}, +}; +use uom::si::{f64::Mass, mass::kilogram}; + +#[cfg(test)] +mod test; + +pub struct A320Airframe { + center_of_gravity: CenterOfGravityData, + weight: WeightData, + // trim_horizontal_stabilizer: f64, +} +impl A320Airframe { + const LOADSHEET: LoadsheetInfo = LoadsheetInfo { + operating_empty_weight_kg: 279987., + operating_empty_position: (-28.31, 0., 0.), + per_pax_weight_kg: 84., + mean_aerodynamic_chord_size: 25.49, + leading_edge_mean_aerodynamic_chord: -21.63, + }; + + pub fn new(context: &mut InitContext) -> Self { + A320Airframe { + center_of_gravity: CenterOfGravityData::new(context), + weight: WeightData::new(context), + // trim_horizontal_stabilizer: 0.0, + } + } + + #[cfg(test)] + fn zero_fuel_weight_center_of_gravity(&self) -> f64 { + self.center_of_gravity.zero_fuel_weight_center_of_gravity() + } + + #[cfg(test)] + fn gross_weight_center_of_gravity(&self) -> f64 { + self.center_of_gravity.gross_weight_center_of_gravity() + } + + #[cfg(test)] + fn take_off_center_of_gravity(&self) -> f64 { + self.center_of_gravity.take_off_center_of_gravity() + } + + #[cfg(test)] + fn target_zero_fuel_weight_center_of_gravity(&self) -> f64 { + self.center_of_gravity + .target_zero_fuel_weight_center_of_gravity() + } + + #[cfg(test)] + fn target_gross_weight_center_of_gravity(&self) -> f64 { + self.center_of_gravity + .target_gross_weight_center_of_gravity() + } + + #[cfg(test)] + fn target_take_off_center_of_gravity(&self) -> f64 { + self.center_of_gravity.target_take_off_center_of_gravity() + } + + fn convert_cg(cg: f64) -> f64 { + -100. * (cg - Self::LOADSHEET.leading_edge_mean_aerodynamic_chord) + / Self::LOADSHEET.mean_aerodynamic_chord_size + } + + fn set_zero_fuel_weight_center_of_gravity(&mut self, zero_fuel_weight_cg: f64) { + let zero_fuel_weight_center_of_gravity = Self::convert_cg(zero_fuel_weight_cg); + self.center_of_gravity + .set_zero_fuel_weight_center_of_gravity(zero_fuel_weight_center_of_gravity) + } + + fn set_gross_weight_center_of_gravity(&mut self, gross_weight_cg: f64) { + let gross_weight_center_of_gravity = Self::convert_cg(gross_weight_cg); + self.center_of_gravity + .set_gross_weight_center_of_gravity(gross_weight_center_of_gravity); + } + + fn set_take_off_center_of_gravity(&mut self, take_off_cg: f64) { + let take_off_center_of_gravity = Self::convert_cg(take_off_cg); + self.center_of_gravity + .set_take_off_center_of_gravity(take_off_center_of_gravity); + } + + fn set_target_zero_fuel_weight_center_of_gravity(&mut self, tgt_zero_fuel_weight_cg: f64) { + let tgt_zero_fuel_weight_cg_percent_mac = Self::convert_cg(tgt_zero_fuel_weight_cg); + self.center_of_gravity + .set_target_zero_fuel_weight_center_of_gravity(tgt_zero_fuel_weight_cg_percent_mac) + } + + fn set_target_gross_weight_center_of_gravity(&mut self, tgt_gross_weight_cg: f64) { + let tgt_gross_weight_center_of_gravity = Self::convert_cg(tgt_gross_weight_cg); + self.center_of_gravity + .set_target_gross_weight_center_of_gravity(tgt_gross_weight_center_of_gravity); + } + + fn set_target_take_off_center_of_gravity(&mut self, tgt_tow_cg_percent_mac: f64) { + let tgt_tow_cg_percent_mac = Self::convert_cg(tgt_tow_cg_percent_mac); + self.center_of_gravity + .set_target_take_off_center_of_gravity(tgt_tow_cg_percent_mac); + } + + fn set_zero_fuel_weight(&mut self, zero_fuel_weight: Mass) { + self.weight.set_zero_fuel_weight(zero_fuel_weight); + } + + fn set_gross_weight(&mut self, gross_weight: Mass) { + self.weight.set_gross_weight(gross_weight); + } + + fn set_take_off_weight(&mut self, take_off_weight: Mass) { + self.weight.set_take_off_weight(take_off_weight); + } + + fn set_target_zero_fuel_weight(&mut self, tgt_zero_fuel_weight: Mass) { + self.weight + .set_target_zero_fuel_weight(tgt_zero_fuel_weight); + } + + fn set_target_gross_weight(&mut self, tgt_gross_weight: Mass) { + self.weight.set_target_gross_weight(tgt_gross_weight); + } + + fn set_target_take_off_weight(&mut self, tgt_take_off_weight: Mass) { + self.weight.set_target_take_off_weight(tgt_take_off_weight); + } + + pub(crate) fn update( + &mut self, + fuel_payload: &impl FuelPayload, + pax_payload: &impl PassengerPayload, + cargo_payload: &impl CargoPayload, + ) { + let total_pax = pax_payload.total_passenger_load(); + let total_cargo = cargo_payload.total_cargo_load(); + + let operating_empty_weight = + Mass::new::(Self::LOADSHEET.operating_empty_weight_kg); + + let empty_moment = Self::LOADSHEET.operating_empty_position.0 * operating_empty_weight; + let pax_moment = total_pax * pax_payload.fore_aft_center_of_gravity(); + let cargo_moment = total_cargo * cargo_payload.fore_aft_center_of_gravity(); + + let zero_fuel_weight_moment = empty_moment + pax_moment + cargo_moment; + let zero_fuel_weight = operating_empty_weight + total_pax + total_cargo; + let zero_fuel_weight_cg = + zero_fuel_weight_moment.get::() / zero_fuel_weight.get::(); + + self.set_zero_fuel_weight(zero_fuel_weight); + self.set_zero_fuel_weight_center_of_gravity(zero_fuel_weight_cg); + + let total_target_pax = pax_payload.total_target_passenger_load(); + let total_target_cargo = cargo_payload.total_target_cargo_load(); + + let pax_target_moment = total_target_pax * pax_payload.target_fore_aft_center_of_gravity(); + let cargo_target_moment = + total_target_cargo * cargo_payload.target_fore_aft_center_of_gravity(); + + let target_zero_fuel_weight_moment = empty_moment + pax_target_moment + cargo_target_moment; + let target_zero_fuel_weight = + operating_empty_weight + total_target_pax + total_target_cargo; + + let target_zero_fuel_weight_cg = target_zero_fuel_weight_moment.get::() + / target_zero_fuel_weight.get::(); + + self.set_target_zero_fuel_weight(target_zero_fuel_weight); + self.set_target_zero_fuel_weight_center_of_gravity(target_zero_fuel_weight_cg); + + let fuel = fuel_payload.total_load(); + let fuel_moment = fuel * fuel_payload.fore_aft_center_of_gravity(); + + let gross_weight_moment = zero_fuel_weight_moment + fuel_moment; + let gross_weight = zero_fuel_weight + fuel; + let gross_weight_cg = + gross_weight_moment.get::() / gross_weight.get::(); + + self.set_gross_weight(gross_weight); + self.set_gross_weight_center_of_gravity(gross_weight_cg); + + let target_gross_weight_moment = target_zero_fuel_weight_moment + fuel_moment; + let target_gross_weight = target_zero_fuel_weight + fuel; + let target_gross_weight_cg = + target_gross_weight_moment.get::() / target_gross_weight.get::(); + + self.set_target_gross_weight(target_gross_weight); + self.set_target_gross_weight_center_of_gravity(target_gross_weight_cg); + + // TODO: Implement Taxi Fuel Input/Calculation + + let tow = gross_weight; + let to_cg = gross_weight_cg; + + self.set_take_off_weight(tow); + self.set_take_off_center_of_gravity(to_cg); + + let target_tow = target_gross_weight; + let target_to_cg = target_gross_weight_cg; + + self.set_target_take_off_weight(target_tow); + self.set_target_take_off_center_of_gravity(target_to_cg); + } +} +impl SimulationElement for A320Airframe { + fn accept(&mut self, visitor: &mut T) { + self.center_of_gravity.accept(visitor); + self.weight.accept(visitor); + + visitor.visit(self); + } +} diff --git a/hdw-a339x-acj/src/wasm/systems/a320_systems/src/hydraulic/mod.rs b/hdw-a339x-acj/src/wasm/systems/a320_systems/src/hydraulic/mod.rs new file mode 100644 index 000000000..d968ba386 --- /dev/null +++ b/hdw-a339x-acj/src/wasm/systems/a320_systems/src/hydraulic/mod.rs @@ -0,0 +1,11828 @@ +use nalgebra::Vector3; + +use std::{fmt::Debug, fmt::Display, time::Duration}; + +use uom::si::{ + acceleration::meter_per_second_squared, + angle::degree, + angular_velocity::{radian_per_second, revolution_per_minute}, + electric_current::ampere, + f64::*, + length::meter, + mass::kilogram, + pressure::psi, + ratio::{percent, ratio}, + velocity::knot, + volume::{cubic_inch, gallon, liter}, + volume_rate::gallon_per_second, +}; + +use systems::{ + accept_iterable, + engine::Engine, + hydraulic::{ + aerodynamic_model::AerodynamicModel, + brake_circuit::{ + AutobrakeDecelerationGovernor, AutobrakeMode, AutobrakePanel, + BrakeAccumulatorCharacteristics, BrakeCircuit, BrakeCircuitController, + }, + cargo_doors::{CargoDoor, HydraulicDoorController}, + electrical_generator::{GeneratorControlUnit, HydraulicGeneratorMotor}, + flap_slat::FlapSlatAssembly, + landing_gear::{GearGravityExtension, GearSystemController, HydraulicGearSystem}, + linear_actuator::{ + Actuator, BoundedLinearLength, ElectroHydrostaticPowered, HydraulicAssemblyController, + HydraulicLinearActuatorAssembly, HydraulicLocking, LinearActuatedRigidBodyOnHingeAxis, + LinearActuator, LinearActuatorCharacteristics, LinearActuatorMode, + }, + nose_steering::{ + Pushback, SteeringActuator, SteeringAngleLimiter, SteeringController, + SteeringRatioToAngle, + }, + pumps::PumpCharacteristics, + pushback::PushbackTug, + reverser::{ReverserAssembly, ReverserFeedback, ReverserInterface}, + rudder_control::{ + AngularPositioningController, RudderMechanicalControl, YawDamperActuatorController, + }, + trimmable_horizontal_stabilizer::{ + ManualPitchTrimController, PitchTrimActuatorController, + TrimmableHorizontalStabilizerAssembly, + }, + Accumulator, ElectricPump, EngineDrivenPump, HeatingElement, HydraulicCircuit, + HydraulicCircuitController, HydraulicPressureSensors, PowerTransferUnit, + PowerTransferUnitCharacteristics, PowerTransferUnitController, PressureSwitch, + PressureSwitchType, PriorityValve, PumpController, RamAirTurbine, Reservoir, + }, + landing_gear::{GearSystemSensors, LandingGearControlInterfaceUnitSet, TiltingGear}, + overhead::{ + AutoOffFaultPushButton, AutoOnFaultPushButton, MomentaryOnPushButton, MomentaryPushButton, + }, + shared::{ + interpolation, random_from_normal_distribution, random_from_range, + update_iterator::MaxStepLoop, AdirsDiscreteOutputs, AirbusElectricPumpId, + AirbusEngineDrivenPumpId, DelayedFalseLogicGate, DelayedPulseTrueLogicGate, + DelayedTrueLogicGate, ElectricalBusType, ElectricalBuses, EmergencyElectricalRatPushButton, + EmergencyElectricalState, EmergencyGeneratorControlUnit, EmergencyGeneratorPower, + EngineFirePushButtons, GearWheel, HydraulicColor, LandingGearHandle, LgciuInterface, + LgciuWeightOnWheels, RamAirTurbineController, ReservoirAirPressure, ReverserPosition, + SectionPressure, TrimmableHorizontalStabilizer, + }, + simulation::{ + InitContext, Read, Reader, SimulationElement, SimulationElementVisitor, SimulatorReader, + SimulatorWriter, StartState, UpdateContext, VariableIdentifier, Write, + }, +}; + +mod flaps_computer; +use flaps_computer::SlatFlapComplex; + +#[cfg(test)] +use systems::hydraulic::PressureSwitchState; + +struct A330TiltingGearsFactory {} +impl A330TiltingGearsFactory { + fn new_a330_body_gear(context: &mut InitContext, is_left: bool) -> TiltingGear { + let mut x_offset_meters = 17.851249; + let y_offset_meters = -33.00; + let z_offset_meters = -16.89; + + if is_left { + x_offset_meters *= -1.; + } + + TiltingGear::new( + context, + Length::new::(0.280065), + if is_left { 1 } else { 2 }, + Vector3::new(x_offset_meters, y_offset_meters, z_offset_meters), + Angle::new::(9.89), + ) + } + + fn new_a330_tilt_assembly(context: &mut InitContext) -> A330TiltingGears { + let left_body_gear = Self::new_a330_body_gear(context, true); + let right_body_gear = Self::new_a330_body_gear(context, false); + + A330TiltingGears::new(left_body_gear, right_body_gear) + } +} + +struct A320HydraulicReservoirFactory {} +impl A320HydraulicReservoirFactory { + fn new_green_reservoir(context: &mut InitContext) -> Reservoir { + let reservoir_offset_when_gear_up = if context.start_gear_down() { + Volume::new::(0.) + } else { + Volume::new::(-1.3) + }; + + Reservoir::new( + context, + HydraulicColor::Green, + Volume::new::(23.), + Volume::new::(18.), + Volume::new::(3.6) + reservoir_offset_when_gear_up, + vec![PressureSwitch::new( + Pressure::new::(25.), + Pressure::new::(22.), + PressureSwitchType::Relative, + )], + Volume::new::(3.), + ) + } + + fn new_blue_reservoir(context: &mut InitContext) -> Reservoir { + Reservoir::new( + context, + HydraulicColor::Blue, + Volume::new::(10.), + Volume::new::(8.), + Volume::new::(1.56), + vec![PressureSwitch::new( + Pressure::new::(25.), + Pressure::new::(22.), + PressureSwitchType::Relative, + )], + Volume::new::(2.), + ) + } + + fn new_yellow_reservoir( + context: &mut InitContext, + fluid_volume_in_brake_accumulator: Volume, + ) -> Reservoir { + Reservoir::new( + context, + HydraulicColor::Yellow, + Volume::new::(20.), + Volume::new::(18.), + Volume::new::(3.8) - fluid_volume_in_brake_accumulator, + vec![PressureSwitch::new( + Pressure::new::(25.), + Pressure::new::(22.), + PressureSwitchType::Relative, + )], + Volume::new::(3.), + ) + } +} + +pub struct A320HydraulicCircuitFactory {} +impl A320HydraulicCircuitFactory { + const MIN_PRESS_EDP_SECTION_LO_HYST: f64 = 1740.0; + const MIN_PRESS_EDP_SECTION_HI_HYST: f64 = 2200.0; + const MIN_PRESS_BLUE_ELEC_PUMP_SECTION_LO_HYST: f64 = 1450.0; + const MIN_PRESS_BLUE_ELEC_PUMP_SECTION_HI_HYST: f64 = 1750.0; + const MIN_PRESS_PRESSURISED_LO_HYST: f64 = 1450.0; + const MIN_PRESS_PRESSURISED_HI_HYST: f64 = 1750.0; + + const YELLOW_GREEN_BLUE_PUMPS_INDEXES: usize = 0; + + const HYDRAULIC_TARGET_PRESSURE_PSI: f64 = 3000.; + + const PRIORITY_VALVE_PRESSURE_CUTOFF_PSI: f64 = 1842.; + const PRIORITY_VALVE_PRESSURE_OPENED_PSI: f64 = 2300.; + + // Nitrogen PSI precharge pressure + const ACCUMULATOR_GAS_PRE_CHARGE_PSI: f64 = 1885.0; + const ACCUMULATOR_MAX_VOLUME_GALLONS: f64 = 0.264; + + pub fn new_green_circuit(context: &mut InitContext) -> HydraulicCircuit { + let reservoir = A320HydraulicReservoirFactory::new_green_reservoir(context); + HydraulicCircuit::new( + context, + HydraulicColor::Green, + 1, + Ratio::new::(100.), + Volume::new::(10.), + reservoir, + Pressure::new::(Self::MIN_PRESS_PRESSURISED_LO_HYST), + Pressure::new::(Self::MIN_PRESS_PRESSURISED_HI_HYST), + Pressure::new::(Self::MIN_PRESS_EDP_SECTION_LO_HYST), + Pressure::new::(Self::MIN_PRESS_EDP_SECTION_HI_HYST), + true, + false, + false, + Pressure::new::(Self::HYDRAULIC_TARGET_PRESSURE_PSI), + PriorityValve::new( + Pressure::new::(Self::PRIORITY_VALVE_PRESSURE_CUTOFF_PSI), + Pressure::new::(Self::PRIORITY_VALVE_PRESSURE_OPENED_PSI), + ), + Pressure::new::(Self::ACCUMULATOR_GAS_PRE_CHARGE_PSI), + Volume::new::(Self::ACCUMULATOR_MAX_VOLUME_GALLONS), + ) + } + + pub fn new_blue_circuit(context: &mut InitContext) -> HydraulicCircuit { + let reservoir = A320HydraulicReservoirFactory::new_blue_reservoir(context); + HydraulicCircuit::new( + context, + HydraulicColor::Blue, + 1, + Ratio::new::(100.), + Volume::new::(8.), + reservoir, + Pressure::new::(Self::MIN_PRESS_PRESSURISED_LO_HYST), + Pressure::new::(Self::MIN_PRESS_PRESSURISED_HI_HYST), + Pressure::new::(Self::MIN_PRESS_BLUE_ELEC_PUMP_SECTION_LO_HYST), + Pressure::new::(Self::MIN_PRESS_BLUE_ELEC_PUMP_SECTION_HI_HYST), + false, + false, + false, + Pressure::new::(Self::HYDRAULIC_TARGET_PRESSURE_PSI), + PriorityValve::new( + Pressure::new::(Self::PRIORITY_VALVE_PRESSURE_CUTOFF_PSI), + Pressure::new::(Self::PRIORITY_VALVE_PRESSURE_OPENED_PSI), + ), + Pressure::new::(Self::ACCUMULATOR_GAS_PRE_CHARGE_PSI), + Volume::new::(Self::ACCUMULATOR_MAX_VOLUME_GALLONS), + ) + } + + pub fn new_yellow_circuit( + context: &mut InitContext, + fluid_volume_in_brake_accumulator: Volume, + ) -> HydraulicCircuit { + let reservoir = A320HydraulicReservoirFactory::new_yellow_reservoir( + context, + fluid_volume_in_brake_accumulator, + ); + HydraulicCircuit::new( + context, + HydraulicColor::Yellow, + 1, + Ratio::new::(100.), + Volume::new::(10.), + reservoir, + Pressure::new::(Self::MIN_PRESS_PRESSURISED_LO_HYST), + Pressure::new::(Self::MIN_PRESS_PRESSURISED_HI_HYST), + Pressure::new::(Self::MIN_PRESS_EDP_SECTION_LO_HYST), + Pressure::new::(Self::MIN_PRESS_EDP_SECTION_HI_HYST), + false, + true, + false, + Pressure::new::(Self::HYDRAULIC_TARGET_PRESSURE_PSI), + PriorityValve::new( + Pressure::new::(Self::PRIORITY_VALVE_PRESSURE_CUTOFF_PSI), + Pressure::new::(Self::PRIORITY_VALVE_PRESSURE_OPENED_PSI), + ), + Pressure::new::(Self::ACCUMULATOR_GAS_PRE_CHARGE_PSI), + Volume::new::(Self::ACCUMULATOR_MAX_VOLUME_GALLONS), + ) + } +} + +struct A320CargoDoorFactory {} +impl A320CargoDoorFactory { + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.05; + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.; + const FLOW_CONTROL_FORCE_GAIN: f64 = 200000.; + + fn a320_cargo_door_actuator( + context: &mut InitContext, + bounded_linear_length: &impl BoundedLinearLength, + ) -> LinearActuator { + LinearActuator::new( + context, + bounded_linear_length, + 2, + Length::new::(0.04422), + Length::new::(0.03366), + VolumeRate::new::(0.01), + 600000., + 15000., + 500., + 1000000., + Duration::from_millis(100), + [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], + [0., 0.2, 0.21, 0.79, 0.8, 1.], + Self::FLOW_CONTROL_PROPORTIONAL_GAIN, + Self::FLOW_CONTROL_INTEGRAL_GAIN, + Self::FLOW_CONTROL_FORCE_GAIN, + false, + false, + None, + None, + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ) + } + + /// Builds a cargo door body for A320 Neo + fn a320_cargo_door_body(is_locked: bool) -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(100. / 1000., 1855. / 1000., 2025. / 1000.); + let cg_offset = Vector3::new(0., -size[1] / 2., 0.); + + let control_arm = Vector3::new(-0.1597, -0.1614, 0.); + let anchor = Vector3::new(-0.7596, -0.086, 0.); + let axis_direction = Vector3::new(0., 0., 1.); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(130.), + size, + cg_offset, + cg_offset, + control_arm, + anchor, + Angle::new::(-23.), + Angle::new::(136.), + Angle::new::(-23.), + 100., + is_locked, + axis_direction, + ) + } + + /// Builds a cargo door assembly consisting of the door physical rigid body and the hydraulic actuator connected + /// to it + fn a320_cargo_door_assembly(context: &mut InitContext) -> HydraulicLinearActuatorAssembly<1> { + let cargo_door_body = Self::a320_cargo_door_body(true); + let cargo_door_actuator = Self::a320_cargo_door_actuator(context, &cargo_door_body); + HydraulicLinearActuatorAssembly::new([cargo_door_actuator], cargo_door_body) + } + + fn new_a320_cargo_door(context: &mut InitContext, id: &str) -> CargoDoor { + let assembly = Self::a320_cargo_door_assembly(context); + CargoDoor::new( + context, + id, + assembly, + Self::new_a320_cargo_door_aero_model(), + ) + } + + fn new_a320_cargo_door_aero_model() -> AerodynamicModel { + let body = Self::a320_cargo_door_body(false); + AerodynamicModel::new( + &body, + Some(Vector3::new(1., 0., 0.)), + Some(Vector3::new(0., 0., 1.)), + Some(Vector3::new(1., 0., 0.)), + Ratio::new::(1.), + ) + } +} + +struct A320AileronFactory {} +impl A320AileronFactory { + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.25; + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 3.; + const FLOW_CONTROL_FORCE_GAIN: f64 = 450000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 3500000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 1.; + + fn a320_aileron_actuator( + context: &mut InitContext, + bounded_linear_length: &impl BoundedLinearLength, + ) -> LinearActuator { + let actuator_characteristics = LinearActuatorCharacteristics::new( + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING / 3., + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING, + VolumeRate::new::(0.055), + Ratio::new::(Self::MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + // Aileron actuator real data: + // Max force of 4700DaN @ 3000psi. Max flow 3.302 US gal/min thus 0.055033333 gal/s + // This gives a 0.00227225 squared meter of piston surface + // This gives piston diameter of 0.0537878 meters + // We use 0 as rod diameter as this is a symmetrical actuator so same surface each side + LinearActuator::new( + context, + bounded_linear_length, + 1, + Length::new::(0.0537878), + Length::new::(0.), + actuator_characteristics.max_flow(), + 80000., + 1500., + 5000., + actuator_characteristics.slow_damping(), + Duration::from_millis(300), + [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], + [0., 0.2, 0.21, 0.79, 0.8, 1.], + Self::FLOW_CONTROL_PROPORTIONAL_GAIN, + Self::FLOW_CONTROL_INTEGRAL_GAIN, + Self::FLOW_CONTROL_FORCE_GAIN, + false, + false, + None, + None, + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ) + } + + /// Builds an aileron control surface body for A320 Neo + fn a320_aileron_body(init_drooped_down: bool) -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(3.325, 0.16, 0.58); + + // CG at half the size + let cg_offset = Vector3::new(0., 0., -0.5 * size[2]); + let aero_center = Vector3::new(0., 0., -0.4 * size[2]); + + let control_arm = Vector3::new(0., -0.0525, 0.); + let anchor = Vector3::new(0., -0.0525, 0.33); + + let init_position = if init_drooped_down { + Angle::new::(-25.) + } else { + Angle::new::(0.) + }; + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(24.65), + size, + cg_offset, + aero_center, + control_arm, + anchor, + Angle::new::(-25.), + Angle::new::(50.), + init_position, + 1., + false, + Vector3::new(1., 0., 0.), + ) + } + + /// Builds an aileron assembly consisting of the aileron physical rigid body and two hydraulic actuators connected + /// to it + fn a320_aileron_assembly( + context: &mut InitContext, + init_drooped_down: bool, + ) -> HydraulicLinearActuatorAssembly<2> { + let aileron_body = Self::a320_aileron_body(init_drooped_down); + + let aileron_actuator_outward = Self::a320_aileron_actuator(context, &aileron_body); + let aileron_actuator_inward = Self::a320_aileron_actuator(context, &aileron_body); + + HydraulicLinearActuatorAssembly::new( + [aileron_actuator_outward, aileron_actuator_inward], + aileron_body, + ) + } + + fn new_aileron(context: &mut InitContext, id: ActuatorSide) -> AileronAssembly { + let init_drooped_down = !context.is_in_flight(); + let assembly = Self::a320_aileron_assembly(context, init_drooped_down); + AileronAssembly::new(context, id, assembly, Self::new_a320_aileron_aero_model()) + } + + fn new_a320_aileron_aero_model() -> AerodynamicModel { + let body = Self::a320_aileron_body(true); + + // Aerodynamic object has a little rotation from horizontal direction so that at X° + // of wing AOA the aileron gets some X°+Y° AOA as the overwing pressure sucks the aileron up + AerodynamicModel::new( + &body, + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., 0.208, 0.978)), + Some(Vector3::new(0., 0.978, -0.208)), + Ratio::new::(1.), + ) + } +} + +struct A320SpoilerFactory {} +impl A320SpoilerFactory { + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.15; + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 2.; + const FLOW_CONTROL_FORCE_GAIN: f64 = 450000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 400000.; + + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 3.; + + fn a320_spoiler_actuator( + context: &mut InitContext, + bounded_linear_length: &impl BoundedLinearLength, + ) -> LinearActuator { + let actuator_characteristics = LinearActuatorCharacteristics::new( + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING / 5., + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING, + VolumeRate::new::(0.03), + Ratio::new::(Self::MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + LinearActuator::new( + context, + bounded_linear_length, + 1, + Length::new::(0.03), + Length::new::(0.), + actuator_characteristics.max_flow(), + 80000., + 1500., + 5000., + actuator_characteristics.slow_damping(), + Duration::from_millis(300), + [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], + [0., 0.2, 0.21, 0.79, 0.8, 1.], + Self::FLOW_CONTROL_PROPORTIONAL_GAIN, + Self::FLOW_CONTROL_INTEGRAL_GAIN, + Self::FLOW_CONTROL_FORCE_GAIN, + false, + true, + Some(( + AngularVelocity::new::(-10000.), + AngularVelocity::new::(0.), + )), + None, + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ) + } + + /// Builds a spoiler control surface body for A320 Neo + fn a320_spoiler_body() -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(1.785, 0.1, 0.685); + let cg_offset = Vector3::new(0., 0., -0.5 * size[2]); + let aero_center = Vector3::new(0., 0., -0.4 * size[2]); + + let control_arm = Vector3::new(0., -0.067 * size[2], -0.26 * size[2]); + let anchor = Vector3::new(0., -0.26 * size[2], 0.26 * size[2]); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(16.), + size, + cg_offset, + aero_center, + control_arm, + anchor, + Angle::new::(-10.), + Angle::new::(50.), + Angle::new::(-10.), + 50., + false, + Vector3::new(1., 0., 0.), + ) + } + + /// Builds a spoiler assembly consisting of the spoiler physical rigid body and one hydraulic actuator + fn a320_spoiler_assembly(context: &mut InitContext) -> HydraulicLinearActuatorAssembly<1> { + let spoiler_body = Self::a320_spoiler_body(); + + let spoiler_actuator = Self::a320_spoiler_actuator(context, &spoiler_body); + + HydraulicLinearActuatorAssembly::new([spoiler_actuator], spoiler_body) + } + + fn new_a320_spoiler_group(context: &mut InitContext, id: ActuatorSide) -> SpoilerGroup { + let spoiler_1 = Self::new_a320_spoiler_element(context, id, 1); + let spoiler_2 = Self::new_a320_spoiler_element(context, id, 2); + let spoiler_3 = Self::new_a320_spoiler_element(context, id, 3); + let spoiler_4 = Self::new_a320_spoiler_element(context, id, 4); + let spoiler_5 = Self::new_a320_spoiler_element(context, id, 5); + + match id { + ActuatorSide::Left => SpoilerGroup::new( + context, + "LEFT", + [spoiler_1, spoiler_2, spoiler_3, spoiler_4, spoiler_5], + ), + ActuatorSide::Right => SpoilerGroup::new( + context, + "RIGHT", + [spoiler_1, spoiler_2, spoiler_3, spoiler_4, spoiler_5], + ), + } + } + + fn new_a320_spoiler_element( + context: &mut InitContext, + id: ActuatorSide, + id_number: usize, + ) -> SpoilerElement { + let assembly = Self::a320_spoiler_assembly(context); + SpoilerElement::new( + context, + id, + id_number, + assembly, + Self::new_a320_spoiler_aero_model(), + ) + } + + fn new_a320_spoiler_aero_model() -> AerodynamicModel { + let body = Self::a320_spoiler_body(); + + // Lift vector and normal are rotated 10° to acount for air supposedly following + // wing profile that is 10° from horizontal + // It means that with headwind and spoiler retracted (-10°), spoiler generates no lift + AerodynamicModel::new( + &body, + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., -0.174, 0.985)), + Some(Vector3::new(0., 0.985, 0.174)), + Ratio::new::(1.), + ) + } +} + +struct A320ElevatorFactory {} +impl A320ElevatorFactory { + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 1.; + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.; + const FLOW_CONTROL_FORCE_GAIN: f64 = 450000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 15000000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 1.; + + fn a320_elevator_actuator( + context: &mut InitContext, + bounded_linear_length: &impl BoundedLinearLength, + ) -> LinearActuator { + let actuator_characteristics = LinearActuatorCharacteristics::new( + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING / 5., + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING, + VolumeRate::new::(0.029), + Ratio::new::(Self::MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + LinearActuator::new( + context, + bounded_linear_length, + 1, + Length::new::(0.0407), + Length::new::(0.), + actuator_characteristics.max_flow(), + 80000., + 1500., + 20000., + actuator_characteristics.slow_damping(), + Duration::from_millis(300), + [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], + [0., 0.2, 0.21, 0.79, 0.8, 1.], + Self::FLOW_CONTROL_PROPORTIONAL_GAIN, + Self::FLOW_CONTROL_INTEGRAL_GAIN, + Self::FLOW_CONTROL_FORCE_GAIN, + false, + false, + None, + None, + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ) + } + + /// Builds an aileron control surface body for A320 Neo + fn a320_elevator_body(init_drooped_down: bool) -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(6., 0.405, 1.125); + let cg_offset = Vector3::new(0., 0., -0.5 * size[2]); + let aero_center = Vector3::new(0., 0., -0.3 * size[2]); + + let control_arm = Vector3::new(0., -0.091, 0.); + let anchor = Vector3::new(0., -0.091, 0.41); + + let init_position = if init_drooped_down { + Angle::new::(-11.5) + } else { + Angle::new::(0.) + }; + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(58.6), + size, + cg_offset, + aero_center, + control_arm, + anchor, + Angle::new::(-11.5), + Angle::new::(27.5), + init_position, + 100., + false, + Vector3::new(1., 0., 0.), + ) + } + + /// Builds an aileron assembly consisting of the aileron physical rigid body and two hydraulic actuators connected + /// to it + fn a320_elevator_assembly( + context: &mut InitContext, + init_drooped_down: bool, + ) -> HydraulicLinearActuatorAssembly<2> { + let elevator_body = Self::a320_elevator_body(init_drooped_down); + + let elevator_actuator_outboard = Self::a320_elevator_actuator(context, &elevator_body); + let elevator_actuator_inbord = Self::a320_elevator_actuator(context, &elevator_body); + + HydraulicLinearActuatorAssembly::new( + [elevator_actuator_outboard, elevator_actuator_inbord], + elevator_body, + ) + } + + fn new_elevator(context: &mut InitContext, id: ActuatorSide) -> ElevatorAssembly { + let init_drooped_down = !context.is_in_flight(); + let assembly = Self::a320_elevator_assembly(context, init_drooped_down); + ElevatorAssembly::new(context, id, assembly, Self::new_a320_elevator_aero_model()) + } + + fn new_a320_elevator_aero_model() -> AerodynamicModel { + let body = Self::a320_elevator_body(true); + AerodynamicModel::new( + &body, + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., 0., 1.)), + Some(Vector3::new(0., 1., 0.)), + Ratio::new::(0.8), + ) + } +} + +struct A320RudderFactory {} +impl A320RudderFactory { + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 1.5; + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 2.; + const FLOW_CONTROL_FORCE_GAIN: f64 = 350000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 1000000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 1.; + + fn a320_rudder_actuator( + context: &mut InitContext, + bounded_linear_length: &impl BoundedLinearLength, + ) -> LinearActuator { + let actuator_characteristics = LinearActuatorCharacteristics::new( + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING / 4., + Self::MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING, + VolumeRate::new::(0.0792), + Ratio::new::(Self::MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + LinearActuator::new( + context, + bounded_linear_length, + 1, + Length::new::(0.06), + Length::new::(0.), + actuator_characteristics.max_flow(), + 80000., + 1500., + 10000., + actuator_characteristics.slow_damping(), + Duration::from_millis(300), + [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], + [0., 0.2, 0.21, 0.79, 0.8, 1.], + Self::FLOW_CONTROL_PROPORTIONAL_GAIN, + Self::FLOW_CONTROL_INTEGRAL_GAIN, + Self::FLOW_CONTROL_FORCE_GAIN, + false, + false, + None, + None, + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ) + } + + /// Builds an aileron control surface body for A320 Neo + fn a320_rudder_body(init_at_center: bool) -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(0.42, 6.65, 1.8); + let cg_offset = Vector3::new(0., 0.5 * size[1], -0.5 * size[2]); + let aero_center = Vector3::new(0., 0.5 * size[1], -0.3 * size[2]); + + let control_arm = Vector3::new(-0.144, 0., 0.); + let anchor = Vector3::new(-0.144, 0., 0.50); + + let randomized_init_position_angle_degree = if init_at_center { + 0. + } else { + random_from_range(-15., 15.) + }; + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(95.), + size, + cg_offset, + aero_center, + control_arm, + anchor, + Angle::new::(-25.), + Angle::new::(50.), + Angle::new::(randomized_init_position_angle_degree), + 100., + false, + Vector3::new(0., 1., 0.), + ) + } + + /// Builds an aileron assembly consisting of the aileron physical rigid body and two hydraulic actuators connected + /// to it + fn a320_rudder_assembly( + context: &mut InitContext, + init_at_center: bool, + ) -> HydraulicLinearActuatorAssembly<3> { + let rudder_body = Self::a320_rudder_body(init_at_center); + + let rudder_actuator_green = Self::a320_rudder_actuator(context, &rudder_body); + let rudder_actuator_blue = Self::a320_rudder_actuator(context, &rudder_body); + let rudder_actuator_yellow = Self::a320_rudder_actuator(context, &rudder_body); + + HydraulicLinearActuatorAssembly::new( + [ + rudder_actuator_green, + rudder_actuator_blue, + rudder_actuator_yellow, + ], + rudder_body, + ) + } + + fn new_rudder(context: &mut InitContext) -> RudderAssembly { + let init_at_center = context.start_state() == StartState::Taxi + || context.start_state() == StartState::Runway + || context.is_in_flight(); + + let assembly = Self::a320_rudder_assembly(context, init_at_center); + RudderAssembly::new(context, assembly, Self::new_a320_rudder_aero_model()) + } + + fn new_a320_rudder_aero_model() -> AerodynamicModel { + let body = Self::a320_rudder_body(true); + AerodynamicModel::new( + &body, + Some(Vector3::new(1., 0., 0.)), + Some(Vector3::new(0., 0., 1.)), + Some(Vector3::new(1., 0., 0.)), + Ratio::new::(0.4), + ) + } +} + +struct A320GearDoorFactory {} +impl A320GearDoorFactory { + fn a320_nose_gear_door_aerodynamics() -> AerodynamicModel { + // Faking the single door by only considering right door aerodynamics. + // Will work with headwind, but will cause strange behaviour with massive crosswind. + AerodynamicModel::new( + &Self::a320_nose_gear_door_body(), + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., -0.2, 1.)), + Some(Vector3::new(0., -1., -0.2)), + Ratio::new::(0.7), + ) + } + + fn a320_left_gear_door_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a320_left_gear_door_body(), + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., -0.1, 1.)), + Some(Vector3::new(0., 1., 0.1)), + Ratio::new::(0.7), + ) + } + + fn a320_right_gear_door_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a320_right_gear_door_body(), + Some(Vector3::new(0., 1., 0.)), + Some(Vector3::new(0., -0.1, 1.)), + Some(Vector3::new(0., 1., 0.1)), + Ratio::new::(0.7), + ) + } + + fn a320_nose_gear_door_actuator( + context: &mut InitContext, + bounded_linear_length: &impl BoundedLinearLength, + ) -> LinearActuator { + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.; + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.15; + const FLOW_CONTROL_FORCE_GAIN: f64 = 200000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 28000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 3.; + + let actuator_characteristics = LinearActuatorCharacteristics::new( + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 0.98, + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 1.02, + VolumeRate::new::(0.027), + Ratio::new::(MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + LinearActuator::new( + context, + bounded_linear_length, + 1, + Length::new::(0.0378), + Length::new::(0.023), + actuator_characteristics.max_flow(), + 20000., + 5000., + 2000., + actuator_characteristics.slow_damping(), + Duration::from_millis(100), + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], + [0., 0.15, 0.16, 0.84, 0.85, 1.], + FLOW_CONTROL_PROPORTIONAL_GAIN, + FLOW_CONTROL_INTEGRAL_GAIN, + FLOW_CONTROL_FORCE_GAIN, + true, + false, + None, + None, + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ) + } + + fn a320_main_gear_door_actuator( + context: &mut InitContext, + bounded_linear_length: &impl BoundedLinearLength, + ) -> LinearActuator { + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.; + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.7; + const FLOW_CONTROL_FORCE_GAIN: f64 = 200000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 30000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 5.; + + let actuator_characteristics = LinearActuatorCharacteristics::new( + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 0.98, + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 1.02, + VolumeRate::new::(0.09), + Ratio::new::(MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + LinearActuator::new( + context, + bounded_linear_length, + 1, + Length::new::(0.055), + Length::new::(0.03), + actuator_characteristics.max_flow(), + 200000., + 2500., + 2000., + actuator_characteristics.slow_damping(), + Duration::from_millis(100), + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], + [0., 0.07, 0.08, 0.9, 0.91, 1.], + FLOW_CONTROL_PROPORTIONAL_GAIN, + FLOW_CONTROL_INTEGRAL_GAIN, + FLOW_CONTROL_FORCE_GAIN, + true, + false, + None, + None, + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ) + } + + fn a320_left_gear_door_body() -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(-1.73, 0.02, 1.7); + let cg_offset = Vector3::new(2. / 3. * size[0], 0.1, 0.); + + let control_arm = Vector3::new(-0.76, 0., 0.); + let anchor = Vector3::new(-0.19, 0.23, 0.); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(50.), + size, + cg_offset, + cg_offset, + control_arm, + anchor, + Angle::new::(0.), + Angle::new::(85.), + Angle::new::(0.), + 150., + true, + Vector3::new(0., 0., 1.), + ) + } + + fn a320_right_gear_door_body() -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(1.73, 0.02, 1.7); + let cg_offset = Vector3::new(2. / 3. * size[0], 0.1, 0.); + + let control_arm = Vector3::new(0.76, 0., 0.); + let anchor = Vector3::new(0.19, 0.23, 0.); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(50.), + size, + cg_offset, + cg_offset, + control_arm, + anchor, + Angle::new::(-85.), + Angle::new::(85.), + Angle::new::(0.), + 150., + true, + Vector3::new(0., 0., 1.), + ) + } + + fn a320_nose_gear_door_body() -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(0.4, 0.02, 1.5); + let cg_offset = Vector3::new(-0.5 * size[0], 0., 0.); + + let control_arm = Vector3::new(-0.1465, 0., 0.); + let anchor = Vector3::new(-0.1465, 0.40, 0.); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(40.), + size, + cg_offset, + cg_offset, + control_arm, + anchor, + Angle::new::(0.), + Angle::new::(85.), + Angle::new::(0.), + 150., + true, + Vector3::new(0., 0., 1.), + ) + } + + fn a320_gear_door_assembly( + context: &mut InitContext, + wheel_id: GearWheel, + ) -> HydraulicLinearActuatorAssembly<1> { + let gear_door_body = match wheel_id { + GearWheel::NOSE => Self::a320_nose_gear_door_body(), + GearWheel::LEFT => Self::a320_left_gear_door_body(), + GearWheel::RIGHT => Self::a320_right_gear_door_body(), + }; + let gear_door_actuator = match wheel_id { + GearWheel::NOSE => Self::a320_nose_gear_door_actuator(context, &gear_door_body), + GearWheel::LEFT | GearWheel::RIGHT => { + Self::a320_main_gear_door_actuator(context, &gear_door_body) + } + }; + + HydraulicLinearActuatorAssembly::new([gear_door_actuator], gear_door_body) + } +} + +struct A320GearFactory {} +impl A320GearFactory { + fn a320_nose_gear_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a320_nose_gear_body(true), + Some(Vector3::new(0., 0., 1.)), + None, + None, + Ratio::new::(0.25), + ) + } + + fn a320_right_gear_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a320_right_gear_body(true), + Some(Vector3::new(0., 0., 1.)), + Some(Vector3::new(0.3, 0., 1.)), + Some(Vector3::new(1., 0., -0.3)), + Ratio::new::(0.7), + ) + } + + fn a320_left_gear_aerodynamics() -> AerodynamicModel { + AerodynamicModel::new( + &Self::a320_left_gear_body(true), + Some(Vector3::new(0., 0., 1.)), + Some(Vector3::new(-0.3, 0., 1.)), + Some(Vector3::new(-1., 0., -0.3)), + Ratio::new::(0.7), + ) + } + + fn a320_nose_gear_actuator( + context: &mut InitContext, + bounded_linear_length: &impl BoundedLinearLength, + ) -> LinearActuator { + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.; + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.3; + const FLOW_CONTROL_FORCE_GAIN: f64 = 250000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 900000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 3.; + + let actuator_characteristics = LinearActuatorCharacteristics::new( + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 0.98, + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 1.02, + VolumeRate::new::(0.053), + Ratio::new::(MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + LinearActuator::new( + context, + bounded_linear_length, + 1, + Length::new::(0.0792), + Length::new::(0.035), + actuator_characteristics.max_flow(), + 800000., + 150000., + 50000., + actuator_characteristics.slow_damping(), + Duration::from_millis(100), + [1., 1., 1., 1., 0.5, 0.5], + [0.5, 0.5, 1., 1., 1., 1.], + [0., 0.1, 0.11, 0.89, 0.9, 1.], + FLOW_CONTROL_PROPORTIONAL_GAIN, + FLOW_CONTROL_INTEGRAL_GAIN, + FLOW_CONTROL_FORCE_GAIN, + true, + false, + None, + None, + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ) + } + + fn a320_main_gear_actuator( + context: &mut InitContext, + bounded_linear_length: &impl BoundedLinearLength, + ) -> LinearActuator { + const FLOW_CONTROL_INTEGRAL_GAIN: f64 = 5.0; + const FLOW_CONTROL_PROPORTIONAL_GAIN: f64 = 0.3; + const FLOW_CONTROL_FORCE_GAIN: f64 = 250000.; + + const MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING: f64 = 2500000.; + const MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT: f64 = 5.; + + let actuator_characteristics = LinearActuatorCharacteristics::new( + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 0.98, + MAX_DAMPING_CONSTANT_FOR_SLOW_DAMPING * 1.02, + VolumeRate::new::(0.17), + Ratio::new::(MAX_FLOW_PRECISION_PER_ACTUATOR_PERCENT), + ); + + LinearActuator::new( + context, + bounded_linear_length, + 1, + Length::new::(0.145), + Length::new::(0.105), + actuator_characteristics.max_flow(), + 800000., + 350000., + 50000., + actuator_characteristics.slow_damping(), + Duration::from_millis(100), + [1., 1., 1., 1., 0.5, 0.5], + [0.2, 0.4, 1., 1., 1., 1.], + [0., 0.13, 0.17, 0.95, 0.96, 1.], + FLOW_CONTROL_PROPORTIONAL_GAIN, + FLOW_CONTROL_INTEGRAL_GAIN, + FLOW_CONTROL_FORCE_GAIN, + true, + false, + None, + None, + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ) + } + + fn a320_left_gear_body(init_downlocked: bool) -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(0.3, 3.453, 0.3); + let cg_offset = Vector3::new(0., -3. / 4. * size[1], 0.); + + let control_arm = Vector3::new(0.1815, 0.15, 0.); + let anchor = Vector3::new(0.26, 0.15, 0.); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(700.), + size, + cg_offset, + cg_offset, + control_arm, + anchor, + Angle::new::(0.), + Angle::new::(80.), + if init_downlocked { + Angle::new::(0.) + } else { + Angle::new::(80.) + }, + 150., + true, + Vector3::new(0., 0., 1.), + ) + } + + fn a320_right_gear_body(init_downlocked: bool) -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(0.3, 3.453, 0.3); + let cg_offset = Vector3::new(0., -3. / 4. * size[1], 0.); + + let control_arm = Vector3::new(-0.1815, 0.15, 0.); + let anchor = Vector3::new(-0.26, 0.15, 0.); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(700.), + size, + cg_offset, + cg_offset, + control_arm, + anchor, + Angle::new::(-80.), + Angle::new::(80.), + if init_downlocked { + Angle::new::(0.) + } else { + Angle::new::(-80.) + }, + 150., + true, + Vector3::new(0., 0., 1.), + ) + } + + fn a320_nose_gear_body(init_downlocked: bool) -> LinearActuatedRigidBodyOnHingeAxis { + let size = Vector3::new(0.3, 2.453, 0.3); + let cg_offset = Vector3::new(0., -2. / 3. * size[1], 0.); + + let control_arm = Vector3::new(0., -0.093, 0.212); + let anchor = Vector3::new(0., 0.56, 0.); + + LinearActuatedRigidBodyOnHingeAxis::new( + Mass::new::(300.), + size, + cg_offset, + cg_offset, + control_arm, + anchor, + Angle::new::(-101.), + Angle::new::(92.), + if init_downlocked { + Angle::new::(-9.) + } else { + Angle::new::(-101.) + }, + 150., + true, + Vector3::new(1., 0., 0.), + ) + } + + fn a320_gear_assembly( + context: &mut InitContext, + wheel_id: GearWheel, + init_downlocked: bool, + ) -> HydraulicLinearActuatorAssembly<1> { + let gear_body = match wheel_id { + GearWheel::NOSE => Self::a320_nose_gear_body(init_downlocked), + + GearWheel::LEFT => Self::a320_left_gear_body(init_downlocked), + + GearWheel::RIGHT => Self::a320_right_gear_body(init_downlocked), + }; + + let gear_actuator = match wheel_id { + GearWheel::NOSE => Self::a320_nose_gear_actuator(context, &gear_body), + + GearWheel::LEFT | GearWheel::RIGHT => { + Self::a320_main_gear_actuator(context, &gear_body) + } + }; + + HydraulicLinearActuatorAssembly::new([gear_actuator], gear_body) + } +} + +struct A320GearSystemFactory {} +impl A320GearSystemFactory { + fn a320_gear_system(context: &mut InitContext) -> HydraulicGearSystem { + let init_downlocked = context.start_gear_down(); + + let nose_door = A320GearDoorFactory::a320_gear_door_assembly(context, GearWheel::NOSE); + let left_door = A320GearDoorFactory::a320_gear_door_assembly(context, GearWheel::LEFT); + let right_door = A320GearDoorFactory::a320_gear_door_assembly(context, GearWheel::RIGHT); + + let nose_gear = + A320GearFactory::a320_gear_assembly(context, GearWheel::NOSE, init_downlocked); + let left_gear = + A320GearFactory::a320_gear_assembly(context, GearWheel::LEFT, init_downlocked); + let right_gear = + A320GearFactory::a320_gear_assembly(context, GearWheel::RIGHT, init_downlocked); + + HydraulicGearSystem::new( + context, + nose_door, + left_door, + right_door, + nose_gear, + left_gear, + right_gear, + A320GearDoorFactory::a320_left_gear_door_aerodynamics(), + A320GearDoorFactory::a320_right_gear_door_aerodynamics(), + A320GearDoorFactory::a320_nose_gear_door_aerodynamics(), + A320GearFactory::a320_left_gear_aerodynamics(), + A320GearFactory::a320_right_gear_aerodynamics(), + A320GearFactory::a320_nose_gear_aerodynamics(), + ) + } +} +struct A320PowerTransferUnitCharacteristics { + efficiency: Ratio, + + deactivation_delta_pressure: Pressure, + activation_delta_pressure: Pressure, + + shot_to_shot_variability: Ratio, +} +impl A320PowerTransferUnitCharacteristics { + // Randomisation parameters + // As ptu wear parameters are non linear, for now we simulate two normal distributions: + // -Nominal distribution which is the PTU you'll find in wide majority of planes + // -Worn out distribution, which is the PTU which is still acceptable but has degraded behaviour + const MEAN_ACTIVATION_DELTA_PRESSURE_PSI: f64 = 500.; + const STD_DEV_ACTIVATION_DELTA_PRESSURE_PSI: f64 = 5.; + + // Ratio of worn PTU : 0.1 means 10% of cases we get a worn out ptu + const WORN_PTU_CASE_PROBABILITY: f64 = 0.15; + + const WORN_MEAN_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 20.; + const WORN_STD_DEV_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 5.; + const WORN_MIN_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 5.; + const WORN_MAX_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 30.; + + const NOMINAL_MEAN_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 90.; + const NOMINAL_STD_DEV_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 15.; + const NOMINAL_MIN_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 5.; + const NOMINAL_MAX_DEACTIVATION_DELTA_PRESSURE_PSI: f64 = 180.; + + const WORN_EFFICIENCY_MEAN: f64 = 0.6; + const WORN_EFFICIENCY_STD_DEV: f64 = 0.06; + const NOMINAL_EFFICIENCY_MEAN: f64 = 0.85; + const NOMINAL_EFFICIENCY_STD_DEV: f64 = 0.04; + const EFFICIENCY_MIN_ALLOWED: f64 = 0.5; + const EFFICIENCY_MAX: f64 = 0.9; + + const SHOT_TO_SHOT_VARIABILITY_PERCENT_RATIO: f64 = 0.05; + + fn new_randomized() -> Self { + let randomized_is_ptu_worn_out = Self::randomized_is_ptu_worn_out(); + + Self { + efficiency: Self::randomized_efficiency(randomized_is_ptu_worn_out), + + deactivation_delta_pressure: Self::randomized_deactivation_delta_pressure( + randomized_is_ptu_worn_out, + ), + + activation_delta_pressure: Pressure::new::(random_from_normal_distribution( + Self::MEAN_ACTIVATION_DELTA_PRESSURE_PSI, + Self::STD_DEV_ACTIVATION_DELTA_PRESSURE_PSI, + )), + + shot_to_shot_variability: Ratio::new::( + Self::SHOT_TO_SHOT_VARIABILITY_PERCENT_RATIO, + ), + } + } + + #[cfg(test)] + fn new_worst_part_acceptable() -> Self { + Self { + efficiency: Ratio::new::(Self::EFFICIENCY_MIN_ALLOWED), + + deactivation_delta_pressure: Pressure::new::( + Self::WORN_MIN_DEACTIVATION_DELTA_PRESSURE_PSI, + ), + + activation_delta_pressure: Pressure::new::( + Self::MEAN_ACTIVATION_DELTA_PRESSURE_PSI + + 5. * Self::STD_DEV_ACTIVATION_DELTA_PRESSURE_PSI, + ), + + shot_to_shot_variability: Ratio::new::( + Self::SHOT_TO_SHOT_VARIABILITY_PERCENT_RATIO, + ), + } + } + + fn randomized_is_ptu_worn_out() -> bool { + random_from_range(0., 1.) < Self::WORN_PTU_CASE_PROBABILITY + } + + fn randomized_efficiency(is_worn_out: bool) -> Ratio { + if is_worn_out { + Ratio::new::( + random_from_normal_distribution( + Self::WORN_EFFICIENCY_MEAN, + Self::WORN_EFFICIENCY_STD_DEV, + ) + .max(Self::EFFICIENCY_MIN_ALLOWED) + .min(Self::EFFICIENCY_MAX), + ) + } else { + Ratio::new::( + random_from_normal_distribution( + Self::NOMINAL_EFFICIENCY_MEAN, + Self::NOMINAL_EFFICIENCY_STD_DEV, + ) + .max(Self::EFFICIENCY_MIN_ALLOWED) + .min(Self::EFFICIENCY_MAX), + ) + } + } + + fn randomized_deactivation_delta_pressure(is_worn_out: bool) -> Pressure { + if is_worn_out { + Pressure::new::( + random_from_normal_distribution( + Self::WORN_MEAN_DEACTIVATION_DELTA_PRESSURE_PSI, + Self::WORN_STD_DEV_DEACTIVATION_DELTA_PRESSURE_PSI, + ) + .min(Self::WORN_MAX_DEACTIVATION_DELTA_PRESSURE_PSI) + .max(Self::WORN_MIN_DEACTIVATION_DELTA_PRESSURE_PSI), + ) + } else { + Pressure::new::( + random_from_normal_distribution( + Self::NOMINAL_MEAN_DEACTIVATION_DELTA_PRESSURE_PSI, + Self::NOMINAL_STD_DEV_DEACTIVATION_DELTA_PRESSURE_PSI, + ) + .min(Self::NOMINAL_MAX_DEACTIVATION_DELTA_PRESSURE_PSI) + .max(Self::NOMINAL_MIN_DEACTIVATION_DELTA_PRESSURE_PSI), + ) + } + } +} +impl PowerTransferUnitCharacteristics for A320PowerTransferUnitCharacteristics { + fn efficiency(&self) -> Ratio { + self.efficiency + } + + fn deactivation_delta_pressure(&self) -> Pressure { + self.deactivation_delta_pressure + } + + fn activation_delta_pressure(&self) -> Pressure { + self.activation_delta_pressure + } + + fn shot_to_shot_variability(&self) -> Ratio { + self.shot_to_shot_variability + } +} + +pub(super) struct A320Hydraulic { + hyd_ptu_ecam_memo_id: VariableIdentifier, + ptu_high_pitch_sound_id: VariableIdentifier, + ptu_continuous_mode_id: VariableIdentifier, + + nose_steering: SteeringActuator, + + core_hydraulic_updater: MaxStepLoop, + + brake_steer_computer: A320HydraulicBrakeSteerComputerUnit, + + blue_circuit: HydraulicCircuit, + blue_circuit_controller: A320HydraulicCircuitController, + green_circuit: HydraulicCircuit, + green_circuit_controller: A320HydraulicCircuitController, + yellow_circuit: HydraulicCircuit, + yellow_circuit_controller: A320HydraulicCircuitController, + + engine_driven_pump_1: EngineDrivenPump, + engine_driven_pump_1_controller: A320EngineDrivenPumpController, + + engine_driven_pump_2: EngineDrivenPump, + engine_driven_pump_2_controller: A320EngineDrivenPumpController, + + blue_electric_pump: ElectricPump, + blue_electric_pump_controller: A320BlueElectricPumpController, + + yellow_electric_pump: ElectricPump, + yellow_electric_pump_controller: A320YellowElectricPumpController, + + pushback_tug: PushbackTug, + + ram_air_turbine: RamAirTurbine, + ram_air_turbine_controller: A320RamAirTurbineController, + + power_transfer_unit: PowerTransferUnit, + power_transfer_unit_controller: A320PowerTransferUnitController, + + braking_circuit_norm: BrakeCircuit, + braking_circuit_altn: BrakeCircuit, + braking_force: A320BrakingForce, + + flap_system: FlapSlatAssembly, + slat_system: FlapSlatAssembly, + slats_flaps_complex: SlatFlapComplex, + + gcu: GeneratorControlUnit, + emergency_gen: HydraulicGeneratorMotor, + + forward_cargo_door: CargoDoor, + forward_cargo_door_controller: HydraulicDoorController, + aft_cargo_door: CargoDoor, + aft_cargo_door_controller: HydraulicDoorController, + + elevator_system_controller: ElevatorSystemHydraulicController, + aileron_system_controller: AileronSystemHydraulicController, + + left_aileron: AileronAssembly, + right_aileron: AileronAssembly, + left_elevator: ElevatorAssembly, + right_elevator: ElevatorAssembly, + + rudder_mechanical_assembly: RudderSystemHydraulicController, + rudder: RudderAssembly, + + left_spoilers: SpoilerGroup, + right_spoilers: SpoilerGroup, + + gear_system_gravity_extension_controller: A320GravityExtension, + gear_system_hydraulic_controller: A320GearHydraulicController, + gear_system: HydraulicGearSystem, + + ptu_high_pitch_sound_active: DelayedFalseLogicGate, + + trim_controller: A320TrimInputController, + + trim_assembly: TrimmableHorizontalStabilizerAssembly, + + engine_reverser_control: [A320ReverserController; 2], + reversers_assembly: A320Reversers, + + tilting_gears: A330TiltingGears, +} +impl A320Hydraulic { + const HIGH_PITCH_PTU_SOUND_DELTA_PRESS_THRESHOLD_PSI: f64 = 2400.; + const HIGH_PITCH_PTU_SOUND_DURATION: Duration = Duration::from_millis(3000); + + const FLAP_FPPU_TO_SURFACE_ANGLE_BREAKPTS: [f64; 12] = [ + 0., 35.66, 69.32, 89.7, 105.29, 120.22, 145.51, 168.35, 189.87, 210.69, 231.25, 251.97, + ]; + const FLAP_FPPU_TO_SURFACE_ANGLE_DEGREES: [f64; 12] = + [0., 0., 2.5, 5., 7.5, 10., 15., 20., 25., 30., 35., 40.]; + + const SLAT_FPPU_TO_SURFACE_ANGLE_BREAKPTS: [f64; 12] = [ + 0., 66.83, 167.08, 222.27, 272.27, 334.16, 334.16, 334.16, 334.16, 334.16, 334.16, 334.16, + ]; + const SLAT_FPPU_TO_SURFACE_ANGLE_DEGREES: [f64; 12] = + [0., 5.4, 13.5, 18., 22., 27., 27., 27., 27., 27., 27., 27.]; + + const FORWARD_CARGO_DOOR_ID: &'static str = "FWD"; + const AFT_CARGO_DOOR_ID: &'static str = "AFT"; + + const ELECTRIC_PUMP_MAX_CURRENT_AMPERE: f64 = 45.; + const BLUE_ELEC_PUMP_CONTROL_POWER_BUS: ElectricalBusType = + ElectricalBusType::DirectCurrentEssential; + const BLUE_ELEC_PUMP_SUPPLY_POWER_BUS: ElectricalBusType = + ElectricalBusType::AlternatingCurrent(1); + + const YELLOW_ELEC_PUMP_CONTROL_POWER_BUS: ElectricalBusType = + ElectricalBusType::DirectCurrent(2); + const YELLOW_ELEC_PUMP_CONTROL_FROM_CARGO_DOOR_OPERATION_POWER_BUS: ElectricalBusType = + ElectricalBusType::DirectCurrentGndFltService; + const YELLOW_ELEC_PUMP_SUPPLY_POWER_BUS: ElectricalBusType = + ElectricalBusType::AlternatingCurrentGndFltService; + + const YELLOW_EDP_CONTROL_POWER_BUS1: ElectricalBusType = ElectricalBusType::DirectCurrent(2); + const YELLOW_EDP_CONTROL_POWER_BUS2: ElectricalBusType = + ElectricalBusType::DirectCurrentEssential; + const GREEN_EDP_CONTROL_POWER_BUS1: ElectricalBusType = + ElectricalBusType::DirectCurrentEssential; + + const PTU_CONTROL_POWER_BUS: ElectricalBusType = ElectricalBusType::DirectCurrentGndFltService; + + const RAT_CONTROL_SOLENOID1_POWER_BUS: ElectricalBusType = + ElectricalBusType::DirectCurrentHot(1); + const RAT_CONTROL_SOLENOID2_POWER_BUS: ElectricalBusType = + ElectricalBusType::DirectCurrentHot(2); + + const ALTERNATE_BRAKE_ACCUMULATOR_GAS_PRE_CHARGE: f64 = 1000.0; // Nitrogen PSI + + // Refresh rate of core hydraulic simulation + const HYDRAULIC_SIM_TIME_STEP: Duration = Duration::from_millis(10); + + pub(super) fn new(context: &mut InitContext) -> A320Hydraulic { + let brake_accumulator_charac = BrakeAccumulatorCharacteristics::new( + Volume::new::(1.0), + Pressure::new::(Self::ALTERNATE_BRAKE_ACCUMULATOR_GAS_PRE_CHARGE), + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + Ratio::new::(0.03), + ); + + A320Hydraulic { + hyd_ptu_ecam_memo_id: context.get_identifier("HYD_PTU_ON_ECAM_MEMO".to_owned()), + ptu_high_pitch_sound_id: context.get_identifier("HYD_PTU_HIGH_PITCH_SOUND".to_owned()), + ptu_continuous_mode_id: context.get_identifier("HYD_PTU_CONTINUOUS_MODE".to_owned()), + + nose_steering: SteeringActuator::new( + context, + Angle::new::(75.), + AngularVelocity::new::(0.35), + Length::new::(0.075), + Ratio::new::(0.18), + ), + + core_hydraulic_updater: MaxStepLoop::new(Self::HYDRAULIC_SIM_TIME_STEP), + + brake_steer_computer: A320HydraulicBrakeSteerComputerUnit::new(context), + + blue_circuit: A320HydraulicCircuitFactory::new_blue_circuit(context), + blue_circuit_controller: A320HydraulicCircuitController::new( + None, + HydraulicColor::Blue, + ), + green_circuit: A320HydraulicCircuitFactory::new_green_circuit(context), + green_circuit_controller: A320HydraulicCircuitController::new( + Some(1), + HydraulicColor::Green, + ), + yellow_circuit: A320HydraulicCircuitFactory::new_yellow_circuit( + context, + brake_accumulator_charac.volume_at_init(), + ), + yellow_circuit_controller: A320HydraulicCircuitController::new( + Some(2), + HydraulicColor::Yellow, + ), + + engine_driven_pump_1: EngineDrivenPump::new( + context, + AirbusEngineDrivenPumpId::Green, + PumpCharacteristics::a320_edp(), + ), + engine_driven_pump_1_controller: A320EngineDrivenPumpController::new( + context, + 1, + vec![Self::GREEN_EDP_CONTROL_POWER_BUS1], + ), + + engine_driven_pump_2: EngineDrivenPump::new( + context, + AirbusEngineDrivenPumpId::Yellow, + PumpCharacteristics::a320_edp(), + ), + engine_driven_pump_2_controller: A320EngineDrivenPumpController::new( + context, + 2, + vec![ + Self::YELLOW_EDP_CONTROL_POWER_BUS1, + Self::YELLOW_EDP_CONTROL_POWER_BUS2, + ], + ), + + blue_electric_pump: ElectricPump::new( + context, + AirbusElectricPumpId::Blue, + Self::BLUE_ELEC_PUMP_SUPPLY_POWER_BUS, + ElectricCurrent::new::(Self::ELECTRIC_PUMP_MAX_CURRENT_AMPERE), + PumpCharacteristics::a320_electric_pump(), + ), + blue_electric_pump_controller: A320BlueElectricPumpController::new( + context, + Self::BLUE_ELEC_PUMP_CONTROL_POWER_BUS, + ), + + yellow_electric_pump: ElectricPump::new( + context, + AirbusElectricPumpId::Yellow, + Self::YELLOW_ELEC_PUMP_SUPPLY_POWER_BUS, + ElectricCurrent::new::(Self::ELECTRIC_PUMP_MAX_CURRENT_AMPERE), + PumpCharacteristics::a320_electric_pump(), + ), + yellow_electric_pump_controller: A320YellowElectricPumpController::new( + context, + Self::YELLOW_ELEC_PUMP_CONTROL_POWER_BUS, + Self::YELLOW_ELEC_PUMP_CONTROL_FROM_CARGO_DOOR_OPERATION_POWER_BUS, + ), + + pushback_tug: PushbackTug::new(context), + + ram_air_turbine: RamAirTurbine::new(context, PumpCharacteristics::a320_rat()), + ram_air_turbine_controller: A320RamAirTurbineController::new( + Self::RAT_CONTROL_SOLENOID1_POWER_BUS, + Self::RAT_CONTROL_SOLENOID2_POWER_BUS, + ), + + power_transfer_unit: PowerTransferUnit::new( + context, + &A320PowerTransferUnitCharacteristics::new_randomized(), + ), + power_transfer_unit_controller: A320PowerTransferUnitController::new( + context, + Self::PTU_CONTROL_POWER_BUS, + ), + + braking_circuit_norm: BrakeCircuit::new( + context, + "NORM", + HydraulicColor::Green, + None, + Volume::new::(0.13), + ), + + // Alternate brakes accumulator in real A320 is 1.5 gal capacity. + // This is tuned down to 1.0 to match real world accumulator filling time + // as a faster accumulator response has too much unstability + braking_circuit_altn: BrakeCircuit::new( + context, + "ALTN", + HydraulicColor::Yellow, + Some(Accumulator::new_brake_accumulator(brake_accumulator_charac)), + Volume::new::(0.13), + ), + + braking_force: A320BrakingForce::new(context), + + flap_system: FlapSlatAssembly::new( + context, + "FLAPS", + Volume::new::(0.32), + AngularVelocity::new::(0.13), + Angle::new::(251.97), + Ratio::new::(140.), + Ratio::new::(16.632), + Ratio::new::(314.98), + Self::FLAP_FPPU_TO_SURFACE_ANGLE_BREAKPTS, + Self::FLAP_FPPU_TO_SURFACE_ANGLE_DEGREES, + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ), + slat_system: FlapSlatAssembly::new( + context, + "SLATS", + Volume::new::(0.32), + AngularVelocity::new::(0.13), + Angle::new::(334.16), + Ratio::new::(140.), + Ratio::new::(16.632), + Ratio::new::(314.98), + Self::SLAT_FPPU_TO_SURFACE_ANGLE_BREAKPTS, + Self::SLAT_FPPU_TO_SURFACE_ANGLE_DEGREES, + Pressure::new::(A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI), + ), + slats_flaps_complex: SlatFlapComplex::new(context), + + gcu: GeneratorControlUnit::default(), + + emergency_gen: HydraulicGeneratorMotor::new(context, Volume::new::(0.19)), + + forward_cargo_door: A320CargoDoorFactory::new_a320_cargo_door( + context, + Self::FORWARD_CARGO_DOOR_ID, + ), + forward_cargo_door_controller: HydraulicDoorController::new( + context, + Self::FORWARD_CARGO_DOOR_ID, + ), + + aft_cargo_door: A320CargoDoorFactory::new_a320_cargo_door( + context, + Self::AFT_CARGO_DOOR_ID, + ), + aft_cargo_door_controller: HydraulicDoorController::new( + context, + Self::AFT_CARGO_DOOR_ID, + ), + + elevator_system_controller: ElevatorSystemHydraulicController::new(context), + aileron_system_controller: AileronSystemHydraulicController::new(context), + + left_aileron: A320AileronFactory::new_aileron(context, ActuatorSide::Left), + right_aileron: A320AileronFactory::new_aileron(context, ActuatorSide::Right), + left_elevator: A320ElevatorFactory::new_elevator(context, ActuatorSide::Left), + right_elevator: A320ElevatorFactory::new_elevator(context, ActuatorSide::Right), + + rudder_mechanical_assembly: RudderSystemHydraulicController::new(context), + rudder: A320RudderFactory::new_rudder(context), + + left_spoilers: A320SpoilerFactory::new_a320_spoiler_group(context, ActuatorSide::Left), + right_spoilers: A320SpoilerFactory::new_a320_spoiler_group( + context, + ActuatorSide::Right, + ), + + gear_system_gravity_extension_controller: A320GravityExtension::new(context), + gear_system_hydraulic_controller: A320GearHydraulicController::new(), + gear_system: A320GearSystemFactory::a320_gear_system(context), + + ptu_high_pitch_sound_active: DelayedFalseLogicGate::new( + Self::HIGH_PITCH_PTU_SOUND_DURATION, + ), + + trim_controller: A320TrimInputController::new(context), + trim_assembly: TrimmableHorizontalStabilizerAssembly::new( + context, + Angle::new::(360. * -1.4), + Angle::new::(360. * 6.13), + Angle::new::(360. * -1.87), + Angle::new::(360. * 8.19), // 1.87 rotations down 6.32 up, + AngularVelocity::new::(5000.), + Ratio::new::(2035. / 6.13), + Angle::new::(-4.), + Angle::new::(17.5), + ), + + engine_reverser_control: [ + A320ReverserController::new(context, 1), + A320ReverserController::new(context, 2), + ], + reversers_assembly: A320Reversers::new(context), + tilting_gears: A330TiltingGearsFactory::new_a330_tilt_assembly(context), + } + } + + pub(super) fn update( + &mut self, + context: &UpdateContext, + engine1: &impl Engine, + engine2: &impl Engine, + overhead_panel: &A320HydraulicOverheadPanel, + autobrake_panel: &AutobrakePanel, + engine_fire_push_buttons: &impl EngineFirePushButtons, + lgcius: &LandingGearControlInterfaceUnitSet, + rat_and_emer_gen_man_on: &impl EmergencyElectricalRatPushButton, + emergency_elec: &(impl EmergencyElectricalState + EmergencyGeneratorPower), + reservoir_pneumatics: &impl ReservoirAirPressure, + adirs: &impl AdirsDiscreteOutputs, + ) { + self.core_hydraulic_updater.update(context); + + self.update_with_sim_rate( + context, + overhead_panel, + autobrake_panel, + rat_and_emer_gen_man_on, + emergency_elec, + lgcius.lgciu1(), + lgcius.lgciu2(), + engine1, + engine2, + ); + + for cur_time_step in self.core_hydraulic_updater { + self.update_physics( + &context.with_delta(cur_time_step), + rat_and_emer_gen_man_on, + emergency_elec, + lgcius, + adirs, + ); + + self.update_core_hydraulics( + &context.with_delta(cur_time_step), + engine1, + engine2, + overhead_panel, + engine_fire_push_buttons, + lgcius.lgciu1(), + lgcius.lgciu2(), + reservoir_pneumatics, + ); + } + + self.ptu_high_pitch_sound_active + .update(context, self.is_ptu_running_high_pitch_sound()); + } + + fn ptu_has_fault(&self) -> bool { + self.power_transfer_unit_controller + .has_air_pressure_low_fault() + || self.power_transfer_unit_controller.has_low_level_fault() + || self.power_transfer_unit_controller.has_overheat_fault() + } + + fn green_edp_has_fault(&self) -> bool { + self.engine_driven_pump_1_controller + .has_pressure_low_fault() + || self + .engine_driven_pump_1_controller + .has_air_pressure_low_fault() + || self.engine_driven_pump_1_controller.has_low_level_fault() + || self.engine_driven_pump_1_controller.has_overheat_fault() + } + + fn yellow_epump_has_fault(&self) -> bool { + self.yellow_electric_pump_controller + .has_pressure_low_fault() + || self + .yellow_electric_pump_controller + .has_air_pressure_low_fault() + || self.yellow_electric_pump_controller.has_low_level_fault() + || self.yellow_electric_pump_controller.has_overheat_fault() + } + + fn yellow_edp_has_fault(&self) -> bool { + self.engine_driven_pump_2_controller + .has_pressure_low_fault() + || self + .engine_driven_pump_2_controller + .has_air_pressure_low_fault() + || self.engine_driven_pump_2_controller.has_low_level_fault() + || self.engine_driven_pump_2_controller.has_overheat_fault() + } + + fn blue_epump_has_fault(&self) -> bool { + self.blue_electric_pump_controller.has_pressure_low_fault() + || self + .blue_electric_pump_controller + .has_air_pressure_low_fault() + || self.blue_electric_pump_controller.has_low_level_fault() + || self.blue_electric_pump_controller.has_overheat_fault() + } + + pub fn green_reservoir(&self) -> &Reservoir { + self.green_circuit.reservoir() + } + + pub fn blue_reservoir(&self) -> &Reservoir { + self.blue_circuit.reservoir() + } + + pub fn yellow_reservoir(&self) -> &Reservoir { + self.yellow_circuit.reservoir() + } + + pub fn reversers_position(&self) -> &[impl ReverserPosition] { + self.reversers_assembly.reversers_position() + } + + #[cfg(test)] + fn should_pressurise_yellow_pump_for_cargo_door_operation(&self) -> bool { + self.yellow_electric_pump_controller + .should_pressurise_for_cargo_door_operation() + } + + #[cfg(test)] + fn nose_wheel_steering_pin_is_inserted(&self) -> bool { + self.pushback_tug.is_nose_wheel_steering_pin_inserted() + } + + #[cfg(test)] + fn is_blue_pressure_switch_pressurised(&self) -> bool { + self.blue_circuit.system_section_pressure_switch() == PressureSwitchState::Pressurised + } + + #[cfg(test)] + fn is_green_pressure_switch_pressurised(&self) -> bool { + self.green_circuit.system_section_pressure_switch() == PressureSwitchState::Pressurised + } + + #[cfg(test)] + fn is_yellow_pressure_switch_pressurised(&self) -> bool { + self.yellow_circuit.system_section_pressure_switch() == PressureSwitchState::Pressurised + } + + // Updates at the same rate as the sim or at a fixed maximum time step if sim rate is too slow + fn update_physics( + &mut self, + context: &UpdateContext, + rat_and_emer_gen_man_on: &impl EmergencyElectricalRatPushButton, + emergency_elec: &(impl EmergencyElectricalState + EmergencyGeneratorPower), + lgcius: &LandingGearControlInterfaceUnitSet, + adirs: &impl AdirsDiscreteOutputs, + ) { + self.forward_cargo_door.update( + context, + &self.forward_cargo_door_controller, + self.yellow_circuit.system_section(), + ); + + self.aft_cargo_door.update( + context, + &self.aft_cargo_door_controller, + self.yellow_circuit.system_section(), + ); + + self.ram_air_turbine + .update_physics(context, self.blue_circuit.system_section()); + + self.gcu.update( + context, + &self.emergency_gen, + self.blue_circuit.system_section(), + emergency_elec, + rat_and_emer_gen_man_on, + lgcius.lgciu1(), + ); + + self.emergency_gen.update( + context, + self.blue_circuit.system_section(), + &self.gcu, + emergency_elec, + ); + + self.gear_system_hydraulic_controller.update( + adirs, + lgcius.lgciu1(), + lgcius.lgciu2(), + &self.gear_system_gravity_extension_controller, + ); + + self.trim_assembly.update( + context, + &self.trim_controller, + &self.trim_controller, + [ + self.green_circuit + .system_section() + .pressure_downstream_leak_valve(), + self.yellow_circuit + .system_section() + .pressure_downstream_leak_valve(), + ], + ); + + self.left_aileron.update( + context, + self.aileron_system_controller.left_controllers(), + self.blue_circuit.system_section(), + self.green_circuit.system_section(), + ); + + self.right_aileron.update( + context, + self.aileron_system_controller.right_controllers(), + self.blue_circuit.system_section(), + self.green_circuit.system_section(), + ); + + self.left_elevator.update( + context, + self.elevator_system_controller.left_controllers(), + self.blue_circuit.system_section(), + self.green_circuit.system_section(), + &self.trim_assembly, + ); + + self.right_elevator.update( + context, + self.elevator_system_controller.right_controllers(), + self.blue_circuit.system_section(), + self.yellow_circuit.system_section(), + &self.trim_assembly, + ); + + self.rudder.update( + context, + self.rudder_mechanical_assembly.rudder_controllers(), + self.green_circuit.system_section(), + self.blue_circuit.system_section(), + self.yellow_circuit.system_section(), + ); + + self.left_spoilers.update( + context, + self.green_circuit.system_section(), + self.blue_circuit.system_section(), + self.yellow_circuit.system_section(), + ); + + self.right_spoilers.update( + context, + self.green_circuit.system_section(), + self.blue_circuit.system_section(), + self.yellow_circuit.system_section(), + ); + + self.gear_system.update( + context, + &self.gear_system_hydraulic_controller, + lgcius.active_lgciu(), + self.green_circuit.system_section(), + ); + } + + fn update_with_sim_rate( + &mut self, + context: &UpdateContext, + overhead_panel: &A320HydraulicOverheadPanel, + autobrake_panel: &AutobrakePanel, + rat_and_emer_gen_man_on: &impl EmergencyElectricalRatPushButton, + emergency_elec_state: &impl EmergencyElectricalState, + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + engine1: &impl Engine, + engine2: &impl Engine, + ) { + self.tilting_gears.update(context); + + self.nose_steering.update( + context, + self.yellow_circuit.system_section(), + &self.brake_steer_computer, + &self.pushback_tug, + ); + + // Process brake logic (which circuit brakes) and send brake demands (how much) + self.brake_steer_computer.update( + context, + self.green_circuit.system_section(), + &self.braking_circuit_altn, + lgciu1, + lgciu2, + autobrake_panel, + engine1, + engine2, + ); + + // Updating rat stowed pos on all frames in case it's used for graphics + self.ram_air_turbine.update_position(&context.delta()); + + // Uses external conditions and momentary button: better to check each frame + self.ram_air_turbine_controller.update( + context, + overhead_panel, + rat_and_emer_gen_man_on, + emergency_elec_state, + ); + + self.pushback_tug.update(context); + + self.braking_force.update_forces( + context, + &self.braking_circuit_norm, + &self.braking_circuit_altn, + engine1, + engine2, + &self.pushback_tug, + ); + + self.slats_flaps_complex + .update(context, &self.flap_system, &self.slat_system); + + self.flap_system.update( + context, + self.slats_flaps_complex.flap_demand(), + self.slats_flaps_complex.flap_demand(), + self.green_circuit.system_section(), + self.yellow_circuit.system_section(), + ); + + self.slat_system.update( + context, + self.slats_flaps_complex.slat_demand(), + self.slats_flaps_complex.slat_demand(), + self.blue_circuit.system_section(), + self.green_circuit.system_section(), + ); + + self.forward_cargo_door_controller.update( + context, + &self.forward_cargo_door, + self.yellow_circuit.system_section(), + ); + + self.aft_cargo_door_controller.update( + context, + &self.aft_cargo_door, + self.yellow_circuit.system_section(), + ); + + self.slats_flaps_complex + .update(context, &self.flap_system, &self.slat_system); + + self.rudder_mechanical_assembly.update( + context, + self.green_circuit.system_section(), + self.blue_circuit.system_section(), + self.yellow_circuit.system_section(), + ); + } + + // For each hydraulic loop retrieves volumes from and to each actuator and pass it to the loops + fn update_actuators_volume(&mut self) { + self.update_green_actuators_volume(); + self.update_yellow_actuators_volume(); + self.update_blue_actuators_volume(); + } + + fn update_green_actuators_volume(&mut self) { + self.green_circuit + .update_system_actuator_volumes(&mut self.braking_circuit_norm); + + self.green_circuit.update_system_actuator_volumes( + self.left_aileron.actuator(AileronActuatorPosition::Green), + ); + self.green_circuit.update_system_actuator_volumes( + self.right_aileron.actuator(AileronActuatorPosition::Green), + ); + + self.green_circuit.update_system_actuator_volumes( + self.left_elevator + .actuator(LeftElevatorActuatorCircuit::Green as usize), + ); + + self.green_circuit + .update_system_actuator_volumes(self.rudder.actuator(RudderActuatorPosition::Green)); + + self.green_circuit + .update_system_actuator_volumes(self.flap_system.left_motor()); + self.green_circuit + .update_system_actuator_volumes(self.slat_system.right_motor()); + + self.green_circuit + .update_system_actuator_volumes(self.left_spoilers.actuator(0)); + self.green_circuit + .update_system_actuator_volumes(self.left_spoilers.actuator(4)); + + self.green_circuit + .update_system_actuator_volumes(self.right_spoilers.actuator(0)); + self.green_circuit + .update_system_actuator_volumes(self.right_spoilers.actuator(4)); + + for actuator in self.gear_system.all_actuators() { + self.green_circuit.update_system_actuator_volumes(actuator); + } + + self.green_circuit + .update_system_actuator_volumes(self.trim_assembly.left_motor()); + + self.green_circuit + .update_system_actuator_volumes(self.rudder_mechanical_assembly.green_actuator()); + + self.yellow_circuit + .update_system_actuator_volumes(self.reversers_assembly.green_actuator()); + } + + fn update_yellow_actuators_volume(&mut self) { + self.yellow_circuit + .update_system_actuator_volumes(&mut self.braking_circuit_altn); + + self.yellow_circuit + .update_system_actuator_volumes(self.flap_system.right_motor()); + + self.yellow_circuit + .update_system_actuator_volumes(self.forward_cargo_door.actuator()); + + self.yellow_circuit + .update_system_actuator_volumes(self.aft_cargo_door.actuator()); + + self.yellow_circuit + .update_system_actuator_volumes(&mut self.nose_steering); + + self.yellow_circuit.update_system_actuator_volumes( + self.right_elevator + .actuator(RightElevatorActuatorCircuit::Yellow as usize), + ); + + self.yellow_circuit + .update_system_actuator_volumes(self.rudder.actuator(RudderActuatorPosition::Yellow)); + + self.yellow_circuit + .update_system_actuator_volumes(self.left_spoilers.actuator(1)); + self.yellow_circuit + .update_system_actuator_volumes(self.left_spoilers.actuator(3)); + + self.yellow_circuit + .update_system_actuator_volumes(self.right_spoilers.actuator(1)); + self.yellow_circuit + .update_system_actuator_volumes(self.right_spoilers.actuator(3)); + + self.yellow_circuit + .update_system_actuator_volumes(self.trim_assembly.right_motor()); + + self.yellow_circuit + .update_system_actuator_volumes(self.rudder_mechanical_assembly.yellow_actuator()); + + self.yellow_circuit + .update_system_actuator_volumes(self.reversers_assembly.yellow_actuator()); + } + + fn update_blue_actuators_volume(&mut self) { + self.blue_circuit + .update_system_actuator_volumes(self.slat_system.left_motor()); + self.blue_circuit + .update_system_actuator_volumes(&mut self.emergency_gen); + + self.blue_circuit.update_system_actuator_volumes( + self.left_aileron.actuator(AileronActuatorPosition::Blue), + ); + self.blue_circuit.update_system_actuator_volumes( + self.right_aileron.actuator(AileronActuatorPosition::Blue), + ); + + self.blue_circuit.update_system_actuator_volumes( + self.left_elevator + .actuator(LeftElevatorActuatorCircuit::Blue as usize), + ); + self.blue_circuit.update_system_actuator_volumes( + self.right_elevator + .actuator(RightElevatorActuatorCircuit::Blue as usize), + ); + + self.blue_circuit + .update_system_actuator_volumes(self.rudder.actuator(RudderActuatorPosition::Blue)); + + self.blue_circuit + .update_system_actuator_volumes(self.left_spoilers.actuator(2)); + + self.blue_circuit + .update_system_actuator_volumes(self.right_spoilers.actuator(2)); + } + + // All the core hydraulics updates that needs to be done at the slowest fixed step rate + fn update_core_hydraulics( + &mut self, + context: &UpdateContext, + engine1: &impl Engine, + engine2: &impl Engine, + overhead_panel: &A320HydraulicOverheadPanel, + engine_fire_push_buttons: &impl EngineFirePushButtons, + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + reservoir_pneumatics: &impl ReservoirAirPressure, + ) { + // First update what is currently consumed and given back by each actuator + // Todo: might have to split the actuator volumes by expected number of loops + self.update_actuators_volume(); + + self.power_transfer_unit_controller.update( + context, + overhead_panel, + &self.forward_cargo_door_controller, + &self.aft_cargo_door_controller, + &self.pushback_tug, + lgciu2, + self.green_circuit.reservoir(), + self.yellow_circuit.reservoir(), + ); + self.power_transfer_unit.update( + context, + self.green_circuit.system_section(), + self.yellow_circuit.system_section(), + &self.power_transfer_unit_controller, + ); + + self.engine_driven_pump_1_controller.update( + overhead_panel, + engine_fire_push_buttons, + engine1, + &self.green_circuit, + lgciu1, + self.green_circuit.reservoir(), + ); + + self.engine_driven_pump_1.update( + context, + self.green_circuit + .pump_section(A320HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES), + self.green_circuit.reservoir(), + engine1.hydraulic_pump_output_speed(), + &self.engine_driven_pump_1_controller, + ); + + self.engine_driven_pump_2_controller.update( + overhead_panel, + engine_fire_push_buttons, + engine2, + &self.yellow_circuit, + lgciu2, + self.yellow_circuit.reservoir(), + ); + + self.engine_driven_pump_2.update( + context, + self.yellow_circuit + .pump_section(A320HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES), + self.yellow_circuit.reservoir(), + engine2.hydraulic_pump_output_speed(), + &self.engine_driven_pump_2_controller, + ); + + self.blue_electric_pump_controller.update( + overhead_panel, + &self.blue_circuit, + engine1, + engine2, + lgciu1, + lgciu2, + self.blue_circuit.reservoir(), + &self.blue_electric_pump, + ); + self.blue_electric_pump.update( + context, + self.blue_circuit + .pump_section(A320HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES), + self.blue_circuit.reservoir(), + &self.blue_electric_pump_controller, + ); + + self.yellow_electric_pump_controller.update( + context, + overhead_panel, + &self.forward_cargo_door_controller, + &self.aft_cargo_door_controller, + &self.yellow_circuit, + self.yellow_circuit.reservoir(), + &self.yellow_electric_pump, + ); + self.yellow_electric_pump.update( + context, + self.yellow_circuit.system_section(), + self.yellow_circuit.reservoir(), + &self.yellow_electric_pump_controller, + ); + + self.ram_air_turbine.update( + context, + self.blue_circuit.system_section(), + self.blue_circuit.reservoir(), + &self.ram_air_turbine_controller, + ); + + self.green_circuit_controller.update( + context, + engine_fire_push_buttons, + overhead_panel, + &self.yellow_electric_pump_controller, + ); + self.green_circuit.update( + context, + &mut [&mut self.engine_driven_pump_1], + None::<&mut ElectricPump>, + None::<&mut ElectricPump>, + Some(&self.power_transfer_unit), + &self.green_circuit_controller, + reservoir_pneumatics.green_reservoir_pressure(), + ); + + self.yellow_circuit_controller.update( + context, + engine_fire_push_buttons, + overhead_panel, + &self.yellow_electric_pump_controller, + ); + self.yellow_circuit.update( + context, + &mut [&mut self.engine_driven_pump_2], + Some(&mut self.yellow_electric_pump), + None::<&mut ElectricPump>, + Some(&self.power_transfer_unit), + &self.yellow_circuit_controller, + reservoir_pneumatics.yellow_reservoir_pressure(), + ); + + self.blue_circuit_controller.update( + context, + engine_fire_push_buttons, + overhead_panel, + &self.yellow_electric_pump_controller, + ); + self.blue_circuit.update( + context, + &mut [&mut self.blue_electric_pump], + Some(&mut self.ram_air_turbine), + None::<&mut ElectricPump>, + None, + &self.blue_circuit_controller, + reservoir_pneumatics.blue_reservoir_pressure(), + ); + + self.braking_circuit_norm.update( + context, + self.green_circuit.system_section(), + self.brake_steer_computer.norm_controller(), + ); + self.braking_circuit_altn.update( + context, + self.yellow_circuit.system_section(), + self.brake_steer_computer.alternate_controller(), + ); + + // TODO CHECK LGCIU USAGE for reversers + self.engine_reverser_control[0].update( + context, + engine1, + lgciu1, + self.reversers_assembly.reverser_feedback(0), + ); + self.engine_reverser_control[1].update( + context, + engine2, + lgciu2, + self.reversers_assembly.reverser_feedback(1), + ); + + self.reversers_assembly.update( + context, + &self.engine_reverser_control, + self.green_circuit.system_section(), + self.yellow_circuit.system_section(), + ) + } + + // Actual logic of HYD PTU memo computed here until done within FWS + fn should_show_hyd_ptu_message_on_ecam(&self) -> bool { + let ptu_valve_ctrol_off = !self.power_transfer_unit_controller.should_enable(); + let green_eng_pump_lo_pr = !self.green_circuit.pump_section_switch_pressurised( + A320HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES, + ); + + let yellow_sys_lo_pr = !self.yellow_circuit.system_section_switch_pressurised(); + + let yellow_sys_press_above_1450 = + self.yellow_circuit.system_section_pressure() > Pressure::new::(1450.); + + let green_sys_press_above_1450 = + self.green_circuit.system_section_pressure() > Pressure::new::(1450.); + + let green_sys_lo_pr = !self.green_circuit.system_section_switch_pressurised(); + + let yellow_eng_pump_lo_pr = !self.yellow_circuit.pump_section_switch_pressurised( + A320HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES, + ); + + let yellow_elec_pump_on = self.yellow_electric_pump_controller.should_pressurise(); + + let yellow_pump_state = yellow_eng_pump_lo_pr && !yellow_elec_pump_on; + + let yellow_press_node = yellow_sys_press_above_1450 || !yellow_sys_lo_pr; + let green_press_node = green_sys_press_above_1450 || !green_sys_lo_pr; + + let yellow_side_and = green_eng_pump_lo_pr && yellow_press_node && green_press_node; + let green_side_and = yellow_press_node && green_press_node && yellow_pump_state; + + !ptu_valve_ctrol_off && (yellow_side_and || green_side_and) + } + + // Function dedicated to sound so it triggers the high pitch PTU sound on specific PTU conditions + fn is_ptu_running_high_pitch_sound(&self) -> bool { + let is_ptu_rotating = self.power_transfer_unit.is_active_left_to_right() + || self.power_transfer_unit.is_active_right_to_left(); + + let absolute_delta_pressure = (self.green_circuit.system_section_pressure() + - self.yellow_circuit.system_section_pressure()) + .abs(); + + absolute_delta_pressure + > Pressure::new::(Self::HIGH_PITCH_PTU_SOUND_DELTA_PRESS_THRESHOLD_PSI) + && is_ptu_rotating + && !self.ptu_high_pitch_sound_active.output() + && !self.power_transfer_unit.is_in_continuous_mode() + } + + pub fn gear_system(&self) -> &impl GearSystemSensors { + &self.gear_system + } +} +impl SimulationElement for A320Hydraulic { + fn accept(&mut self, visitor: &mut T) { + self.engine_driven_pump_1.accept(visitor); + self.engine_driven_pump_1_controller.accept(visitor); + + self.engine_driven_pump_2.accept(visitor); + self.engine_driven_pump_2_controller.accept(visitor); + + self.blue_electric_pump.accept(visitor); + self.blue_electric_pump_controller.accept(visitor); + + self.yellow_electric_pump.accept(visitor); + self.yellow_electric_pump_controller.accept(visitor); + + self.forward_cargo_door_controller.accept(visitor); + self.forward_cargo_door.accept(visitor); + + self.aft_cargo_door_controller.accept(visitor); + self.aft_cargo_door.accept(visitor); + + self.pushback_tug.accept(visitor); + + self.ram_air_turbine.accept(visitor); + self.ram_air_turbine_controller.accept(visitor); + + self.power_transfer_unit.accept(visitor); + self.power_transfer_unit_controller.accept(visitor); + + self.blue_circuit.accept(visitor); + self.green_circuit.accept(visitor); + self.yellow_circuit.accept(visitor); + + self.brake_steer_computer.accept(visitor); + + self.braking_circuit_norm.accept(visitor); + self.braking_circuit_altn.accept(visitor); + self.braking_force.accept(visitor); + + self.emergency_gen.accept(visitor); + self.nose_steering.accept(visitor); + self.slats_flaps_complex.accept(visitor); + self.flap_system.accept(visitor); + self.slat_system.accept(visitor); + + self.elevator_system_controller.accept(visitor); + self.aileron_system_controller.accept(visitor); + + self.left_aileron.accept(visitor); + self.right_aileron.accept(visitor); + self.left_elevator.accept(visitor); + self.right_elevator.accept(visitor); + + self.rudder_mechanical_assembly.accept(visitor); + self.rudder.accept(visitor); + + self.left_spoilers.accept(visitor); + self.right_spoilers.accept(visitor); + + self.gear_system_gravity_extension_controller + .accept(visitor); + self.gear_system.accept(visitor); + + self.trim_controller.accept(visitor); + self.trim_assembly.accept(visitor); + + accept_iterable!(self.engine_reverser_control, visitor); + self.reversers_assembly.accept(visitor); + + self.tilting_gears.accept(visitor); + + visitor.visit(self); + } + + fn write(&self, writer: &mut SimulatorWriter) { + writer.write( + &self.hyd_ptu_ecam_memo_id, + self.should_show_hyd_ptu_message_on_ecam(), + ); + + writer.write( + &self.ptu_high_pitch_sound_id, + self.ptu_high_pitch_sound_active.output(), + ); + + // Two sound variables of ptu made exclusive with high pitch sound having priority + writer.write( + &self.ptu_continuous_mode_id, + self.power_transfer_unit.is_in_continuous_mode() + && !self.ptu_high_pitch_sound_active.output(), + ); + } +} +impl EmergencyGeneratorControlUnit for A320Hydraulic { + fn max_allowed_power(&self) -> Power { + self.gcu.max_allowed_power() + } + + fn motor_speed(&self) -> AngularVelocity { + self.gcu.motor_speed() + } +} + +struct A320GearHydraulicController { + safety_valve_should_open: bool, + cutoff_valve_should_open: bool, + vent_valves_should_open: bool, + doors_uplock_mechanical_release: bool, + gears_uplock_mechanical_release: bool, +} +impl A320GearHydraulicController { + fn new() -> Self { + Self { + safety_valve_should_open: true, + cutoff_valve_should_open: true, + vent_valves_should_open: false, + doors_uplock_mechanical_release: false, + gears_uplock_mechanical_release: false, + } + } + + fn update( + &mut self, + adirs: &impl AdirsDiscreteOutputs, + lgciu1: &(impl LgciuWeightOnWheels + LandingGearHandle), + lgciu2: &impl LgciuWeightOnWheels, + gear_gravity_extension: &impl GearGravityExtension, + ) { + self.update_safety_valve(adirs, lgciu1, lgciu2); + + self.update_safety_and_vent_valve(gear_gravity_extension); + + self.update_uplocks(gear_gravity_extension); + } + + fn update_uplocks(&mut self, gear_gravity_extension: &impl GearGravityExtension) { + self.doors_uplock_mechanical_release = + gear_gravity_extension.extension_handle_number_of_turns() >= 2; + self.gears_uplock_mechanical_release = + gear_gravity_extension.extension_handle_number_of_turns() >= 3; + } + + fn update_safety_and_vent_valve(&mut self, gear_gravity_extension: &impl GearGravityExtension) { + let one_or_more_handle_turns = + gear_gravity_extension.extension_handle_number_of_turns() >= 1; + + self.cutoff_valve_should_open = !one_or_more_handle_turns; + + self.vent_valves_should_open = one_or_more_handle_turns; + } + + fn update_safety_valve( + &mut self, + adirs: &impl AdirsDiscreteOutputs, + lgciu1: &(impl LgciuWeightOnWheels + LandingGearHandle), + lgciu2: &impl LgciuWeightOnWheels, + ) { + let speed_condition = + !adirs.low_speed_warning_4_260kts(1) || !adirs.low_speed_warning_4_260kts(3); + + let on_ground_condition = lgciu1.left_and_right_gear_compressed(true) + || lgciu2.left_and_right_gear_compressed(true); + + let self_maintained_gear_lever_condition = + self.safety_valve_should_open || lgciu1.gear_handle_is_down(); + + self.safety_valve_should_open = + (speed_condition || on_ground_condition) && self_maintained_gear_lever_condition; + } +} +impl GearSystemController for A320GearHydraulicController { + fn safety_valve_should_open(&self) -> bool { + self.safety_valve_should_open + } + + fn shut_off_valve_should_open(&self) -> bool { + self.cutoff_valve_should_open + } + + fn vent_valves_should_open(&self) -> bool { + self.vent_valves_should_open + } + + fn doors_uplocks_should_mechanically_unlock(&self) -> bool { + self.doors_uplock_mechanical_release + } + + fn gears_uplocks_should_mechanically_unlock(&self) -> bool { + self.gears_uplock_mechanical_release + } +} + +struct A320HydraulicCircuitController { + circuit_id: HydraulicColor, + engine_number: Option, + should_open_fire_shutoff_valve: bool, + should_open_leak_measurement_valve: bool, + cargo_door_in_use: DelayedFalseLogicGate, +} +impl A320HydraulicCircuitController { + const DELAY_TO_REOPEN_LEAK_VALVE_AFTER_CARGO_DOOR_USE: Duration = Duration::from_secs(15); + + fn new(engine_number: Option, circuit_id: HydraulicColor) -> Self { + Self { + circuit_id, + engine_number, + should_open_fire_shutoff_valve: true, + should_open_leak_measurement_valve: true, + cargo_door_in_use: DelayedFalseLogicGate::new( + Self::DELAY_TO_REOPEN_LEAK_VALVE_AFTER_CARGO_DOOR_USE, + ), + } + } + + fn update( + &mut self, + context: &UpdateContext, + engine_fire_push_buttons: &impl EngineFirePushButtons, + overhead_panel: &A320HydraulicOverheadPanel, + yellow_epump_controller: &A320YellowElectricPumpController, + ) { + self.cargo_door_in_use.update( + context, + yellow_epump_controller.should_pressurise_for_cargo_door_operation(), + ); + + if let Some(eng_number) = self.engine_number { + self.should_open_fire_shutoff_valve = !engine_fire_push_buttons.is_released(eng_number); + } + + self.update_leak_measurement_valve(context, overhead_panel); + } + + fn update_leak_measurement_valve( + &mut self, + context: &UpdateContext, + overhead_panel: &A320HydraulicOverheadPanel, + ) { + let measurement_valve_open_demand_raw = match &mut self.circuit_id { + HydraulicColor::Green => overhead_panel.green_leak_measurement_valve_is_on(), + HydraulicColor::Yellow => { + overhead_panel.yellow_leak_measurement_valve_is_on() + && !self.cargo_door_in_use.output() + } + HydraulicColor::Blue => overhead_panel.blue_leak_measurement_valve_is_on(), + }; + + self.should_open_leak_measurement_valve = measurement_valve_open_demand_raw + || self.plane_state_disables_leak_valve_closing(context); + } + + fn plane_state_disables_leak_valve_closing(&self, context: &UpdateContext) -> bool { + context.indicated_airspeed() >= Velocity::new::(100.) + } +} +impl HydraulicCircuitController for A320HydraulicCircuitController { + fn should_open_fire_shutoff_valve(&self, _: usize) -> bool { + // A320 only has one main pump per pump section thus index not useful + self.should_open_fire_shutoff_valve + } + + fn should_open_leak_measurement_valve(&self) -> bool { + self.should_open_leak_measurement_valve + } +} + +struct A320EngineDrivenPumpController { + green_pump_low_press_id: VariableIdentifier, + yellow_pump_low_press_id: VariableIdentifier, + + is_powered: bool, + powered_by: Vec, + engine_number: usize, + should_pressurise: bool, + has_pressure_low_fault: bool, + has_air_pressure_low_fault: bool, + has_low_level_fault: bool, + is_pressure_low: bool, + has_overheat_fault: bool, +} +impl A320EngineDrivenPumpController { + fn new( + context: &mut InitContext, + engine_number: usize, + powered_by: Vec, + ) -> Self { + Self { + green_pump_low_press_id: context + .get_identifier("HYD_GREEN_EDPUMP_LOW_PRESS".to_owned()), + yellow_pump_low_press_id: context + .get_identifier("HYD_YELLOW_EDPUMP_LOW_PRESS".to_owned()), + + is_powered: false, + powered_by, + engine_number, + should_pressurise: true, + + has_pressure_low_fault: false, + has_air_pressure_low_fault: false, + has_low_level_fault: false, + + is_pressure_low: true, + + has_overheat_fault: false, + } + } + + fn update_low_pressure( + &mut self, + engine: &impl Engine, + hydraulic_circuit: &impl HydraulicPressureSensors, + lgciu: &impl LgciuInterface, + ) { + self.is_pressure_low = self.should_pressurise() + && !hydraulic_circuit.pump_section_switch_pressurised( + A320HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES, + ); + + // Fault inhibited if on ground AND engine oil pressure is low (11KS1 elec relay) + self.has_pressure_low_fault = self.is_pressure_low + && (!(engine.oil_pressure_is_low() + && lgciu.right_gear_compressed(false) + && lgciu.left_gear_compressed(false))); + } + + fn update_low_air_pressure( + &mut self, + reservoir: &Reservoir, + overhead_panel: &A320HydraulicOverheadPanel, + ) { + self.has_air_pressure_low_fault = reservoir.is_low_air_pressure() + && overhead_panel.edp_push_button_is_auto(self.engine_number); + } + + fn update_low_level( + &mut self, + reservoir: &Reservoir, + overhead_panel: &A320HydraulicOverheadPanel, + ) { + self.has_low_level_fault = + reservoir.is_low_level() && overhead_panel.edp_push_button_is_auto(self.engine_number); + } + + fn update( + &mut self, + overhead_panel: &A320HydraulicOverheadPanel, + engine_fire_push_buttons: &impl EngineFirePushButtons, + engine: &impl Engine, + hydraulic_circuit: &impl HydraulicPressureSensors, + lgciu: &impl LgciuInterface, + reservoir: &Reservoir, + ) { + let mut should_pressurise_if_powered = false; + if overhead_panel.edp_push_button_is_auto(self.engine_number) + && !engine_fire_push_buttons.is_released(self.engine_number) + { + should_pressurise_if_powered = true; + } else if overhead_panel.edp_push_button_is_off(self.engine_number) + || engine_fire_push_buttons.is_released(self.engine_number) + { + should_pressurise_if_powered = false; + } + + // Inverted logic, no power means solenoid valve always leave pump in pressurise mode + self.should_pressurise = !self.is_powered || should_pressurise_if_powered; + + self.update_low_pressure(engine, hydraulic_circuit, lgciu); + + self.update_low_air_pressure(reservoir, overhead_panel); + + self.update_low_level(reservoir, overhead_panel); + + self.has_overheat_fault = reservoir.is_overheating(); + } + + fn has_pressure_low_fault(&self) -> bool { + self.has_pressure_low_fault + } + + fn has_air_pressure_low_fault(&self) -> bool { + self.has_air_pressure_low_fault + } + + fn has_low_level_fault(&self) -> bool { + self.has_low_level_fault + } + + fn has_overheat_fault(&self) -> bool { + self.has_overheat_fault + } +} +impl PumpController for A320EngineDrivenPumpController { + fn should_pressurise(&self) -> bool { + self.should_pressurise + } +} +impl SimulationElement for A320EngineDrivenPumpController { + fn write(&self, writer: &mut SimulatorWriter) { + if self.engine_number == 1 { + writer.write(&self.green_pump_low_press_id, self.is_pressure_low); + } else if self.engine_number == 2 { + writer.write(&self.yellow_pump_low_press_id, self.is_pressure_low); + } else { + panic!("The A320 only supports two engines."); + } + } + + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.is_powered = buses.any_is_powered(&self.powered_by); + } +} + +struct A320BlueElectricPumpController { + low_press_id: VariableIdentifier, + + is_powered: bool, + powered_by: ElectricalBusType, + should_pressurise: bool, + has_pressure_low_fault: bool, + has_air_pressure_low_fault: bool, + has_low_level_fault: bool, + is_pressure_low: bool, + has_overheat_fault: bool, +} +impl A320BlueElectricPumpController { + fn new(context: &mut InitContext, powered_by: ElectricalBusType) -> Self { + Self { + low_press_id: context.get_identifier("HYD_BLUE_EPUMP_LOW_PRESS".to_owned()), + + is_powered: false, + powered_by, + should_pressurise: false, + + has_pressure_low_fault: false, + has_air_pressure_low_fault: false, + has_low_level_fault: false, + + is_pressure_low: true, + + has_overheat_fault: false, + } + } + + fn update( + &mut self, + overhead_panel: &A320HydraulicOverheadPanel, + hydraulic_circuit: &impl HydraulicPressureSensors, + engine1: &impl Engine, + engine2: &impl Engine, + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + reservoir: &Reservoir, + elec_pump: &impl HeatingElement, + ) { + let mut should_pressurise_if_powered = false; + if overhead_panel.blue_epump_push_button.is_auto() { + if !lgciu1.nose_gear_compressed(false) + || engine1.is_above_minimum_idle() + || engine2.is_above_minimum_idle() + || overhead_panel.blue_epump_override_push_button_is_on() + { + should_pressurise_if_powered = true; + } else { + should_pressurise_if_powered = false; + } + } else if overhead_panel.blue_epump_push_button_is_off() { + should_pressurise_if_powered = false; + } + + self.should_pressurise = self.is_powered && should_pressurise_if_powered; + + self.update_low_pressure( + overhead_panel, + hydraulic_circuit, + engine1, + engine2, + lgciu1, + lgciu2, + ); + + self.update_low_air_pressure(reservoir, overhead_panel); + + self.update_low_level(reservoir, overhead_panel); + + // Elec pump has temperature sensor so we check also pump overheating state + self.has_overheat_fault = elec_pump.is_overheating() || reservoir.is_overheating(); + } + + fn update_low_pressure( + &mut self, + overhead_panel: &A320HydraulicOverheadPanel, + hydraulic_circuit: &impl HydraulicPressureSensors, + engine1: &impl Engine, + engine2: &impl Engine, + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + ) { + let is_both_engine_low_oil_pressure = + engine1.oil_pressure_is_low() && engine2.oil_pressure_is_low(); + + self.is_pressure_low = self.should_pressurise() + && !hydraulic_circuit.pump_section_switch_pressurised( + A320HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES, + ); + + self.has_pressure_low_fault = self.is_pressure_low + && (!(is_both_engine_low_oil_pressure + && lgciu1.left_gear_compressed(false) + && lgciu1.right_gear_compressed(false) + && lgciu2.left_gear_compressed(false) + && lgciu2.right_gear_compressed(false) + && !overhead_panel.blue_epump_override_push_button_is_on())); + } + + fn update_low_air_pressure( + &mut self, + reservoir: &Reservoir, + overhead_panel: &A320HydraulicOverheadPanel, + ) { + self.has_air_pressure_low_fault = + reservoir.is_low_air_pressure() && !overhead_panel.blue_epump_push_button_is_off(); + } + + fn update_low_level( + &mut self, + reservoir: &Reservoir, + overhead_panel: &A320HydraulicOverheadPanel, + ) { + self.has_low_level_fault = + reservoir.is_low_level() && !overhead_panel.blue_epump_push_button_is_off(); + } + + fn has_pressure_low_fault(&self) -> bool { + self.has_pressure_low_fault + } + + fn has_air_pressure_low_fault(&self) -> bool { + self.has_air_pressure_low_fault + } + + fn has_low_level_fault(&self) -> bool { + self.has_low_level_fault + } + + fn has_overheat_fault(&self) -> bool { + self.has_low_level_fault + } +} +impl PumpController for A320BlueElectricPumpController { + fn should_pressurise(&self) -> bool { + self.should_pressurise + } +} +impl SimulationElement for A320BlueElectricPumpController { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.low_press_id, self.is_pressure_low); + } + + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.is_powered = buses.is_powered(self.powered_by); + } +} + +struct A320YellowElectricPumpController { + low_press_id: VariableIdentifier, + + is_powered: bool, + powered_by: ElectricalBusType, + powered_by_when_cargo_door_operation: ElectricalBusType, + should_pressurise: bool, + has_pressure_low_fault: bool, + has_air_pressure_low_fault: bool, + has_low_level_fault: bool, + is_pressure_low: bool, + + is_required_for_cargo_door_operation: DelayedFalseLogicGate, + should_pressurise_for_cargo_door_operation: bool, + + low_pressure_hystereris: bool, + + has_overheat_fault: bool, +} +impl A320YellowElectricPumpController { + const DURATION_OF_YELLOW_PUMP_ACTIVATION_AFTER_CARGO_DOOR_OPERATION: Duration = + Duration::from_secs(20); + + const LOW_PRESS_HYSTERESIS_HIGH_PSI: f64 = 1750.; + const LOW_PRESS_HYSTERESIS_LOW_PSI: f64 = 1450.; + + fn new( + context: &mut InitContext, + powered_by: ElectricalBusType, + powered_by_when_cargo_door_operation: ElectricalBusType, + ) -> Self { + Self { + low_press_id: context.get_identifier("HYD_YELLOW_EPUMP_LOW_PRESS".to_owned()), + + is_powered: false, + powered_by, + powered_by_when_cargo_door_operation, + should_pressurise: false, + + has_pressure_low_fault: false, + has_air_pressure_low_fault: false, + has_low_level_fault: false, + + is_pressure_low: true, + is_required_for_cargo_door_operation: DelayedFalseLogicGate::new( + Self::DURATION_OF_YELLOW_PUMP_ACTIVATION_AFTER_CARGO_DOOR_OPERATION, + ), + should_pressurise_for_cargo_door_operation: false, + + low_pressure_hystereris: false, + + has_overheat_fault: false, + } + } + + fn update( + &mut self, + context: &UpdateContext, + overhead_panel: &A320HydraulicOverheadPanel, + forward_cargo_door_controller: &HydraulicDoorController, + aft_cargo_door_controller: &HydraulicDoorController, + hydraulic_circuit: &impl HydraulicPressureSensors, + reservoir: &Reservoir, + elec_pump: &impl HeatingElement, + ) { + self.update_cargo_door_logic( + context, + overhead_panel, + forward_cargo_door_controller, + aft_cargo_door_controller, + ); + + self.should_pressurise = (overhead_panel.yellow_epump_push_button.is_on() + || self.is_required_for_cargo_door_operation.output()) + && self.is_powered; + + self.update_low_pressure(hydraulic_circuit); + + self.update_low_air_pressure(reservoir, overhead_panel); + + self.update_low_level(reservoir, overhead_panel); + + // Elec pump has temperature sensor so we check also pump overheating state + self.has_overheat_fault = elec_pump.is_overheating() || reservoir.is_overheating(); + } + + fn update_low_pressure(&mut self, hydraulic_circuit: &impl HydraulicPressureSensors) { + self.update_low_pressure_hysteresis(hydraulic_circuit); + + self.is_pressure_low = self.should_pressurise() && !self.low_pressure_hystereris; + + self.has_pressure_low_fault = self.is_pressure_low; + } + + fn update_low_pressure_hysteresis( + &mut self, + hydraulic_circuit: &impl HydraulicPressureSensors, + ) { + if hydraulic_circuit + .system_section_pressure_transducer() + .get::() + > Self::LOW_PRESS_HYSTERESIS_HIGH_PSI + { + self.low_pressure_hystereris = true; + } else if hydraulic_circuit + .system_section_pressure_transducer() + .get::() + < Self::LOW_PRESS_HYSTERESIS_LOW_PSI + { + self.low_pressure_hystereris = false; + } + } + + fn update_cargo_door_logic( + &mut self, + context: &UpdateContext, + overhead_panel: &A320HydraulicOverheadPanel, + forward_cargo_door_controller: &HydraulicDoorController, + aft_cargo_door_controller: &HydraulicDoorController, + ) { + self.is_required_for_cargo_door_operation.update( + context, + forward_cargo_door_controller.should_pressurise_hydraulics() + || aft_cargo_door_controller.should_pressurise_hydraulics(), + ); + + self.should_pressurise_for_cargo_door_operation = + self.is_required_for_cargo_door_operation.output() + && !overhead_panel.yellow_epump_push_button.is_on(); + } + + fn update_low_air_pressure( + &mut self, + reservoir: &Reservoir, + overhead_panel: &A320HydraulicOverheadPanel, + ) { + self.has_air_pressure_low_fault = + reservoir.is_low_air_pressure() && !overhead_panel.yellow_epump_push_button_is_auto(); + } + + fn update_low_level( + &mut self, + reservoir: &Reservoir, + overhead_panel: &A320HydraulicOverheadPanel, + ) { + self.has_low_level_fault = + reservoir.is_low_level() && !overhead_panel.yellow_epump_push_button_is_auto(); + } + + fn has_pressure_low_fault(&self) -> bool { + self.has_pressure_low_fault + } + + fn has_air_pressure_low_fault(&self) -> bool { + self.has_air_pressure_low_fault + } + + fn has_low_level_fault(&self) -> bool { + self.has_low_level_fault + } + + fn has_overheat_fault(&self) -> bool { + self.has_overheat_fault + } + + fn should_pressurise_for_cargo_door_operation(&self) -> bool { + self.should_pressurise_for_cargo_door_operation + } +} +impl PumpController for A320YellowElectricPumpController { + fn should_pressurise(&self) -> bool { + self.should_pressurise + } +} +impl SimulationElement for A320YellowElectricPumpController { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.low_press_id, self.is_pressure_low); + } + + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + // Control of the pump is powered by dedicated bus OR manual operation of cargo door through another bus + self.is_powered = buses.is_powered(self.powered_by) + || (self.is_required_for_cargo_door_operation.output() + && buses.is_powered(self.powered_by_when_cargo_door_operation)) + } +} + +struct A320PowerTransferUnitController { + park_brake_lever_pos_id: VariableIdentifier, + general_eng_1_starter_active_id: VariableIdentifier, + general_eng_2_starter_active_id: VariableIdentifier, + + is_powered: bool, + powered_by: ElectricalBusType, + should_enable: bool, + should_inhibit_ptu_after_cargo_door_operation: DelayedFalseLogicGate, + + parking_brake_lever_pos: bool, + eng_1_master_on: bool, + eng_2_master_on: bool, + + has_air_pressure_low_fault: bool, + has_low_level_fault: bool, + has_overheat_fault: bool, +} +impl A320PowerTransferUnitController { + const DURATION_OF_PTU_INHIBIT_AFTER_CARGO_DOOR_OPERATION: Duration = Duration::from_secs(40); + + fn new(context: &mut InitContext, powered_by: ElectricalBusType) -> Self { + Self { + park_brake_lever_pos_id: context.get_identifier("PARK_BRAKE_LEVER_POS".to_owned()), + general_eng_1_starter_active_id: context + .get_identifier("GENERAL ENG STARTER ACTIVE:1".to_owned()), + general_eng_2_starter_active_id: context + .get_identifier("GENERAL ENG STARTER ACTIVE:2".to_owned()), + + is_powered: false, + powered_by, + should_enable: false, + should_inhibit_ptu_after_cargo_door_operation: DelayedFalseLogicGate::new( + Self::DURATION_OF_PTU_INHIBIT_AFTER_CARGO_DOOR_OPERATION, + ), + + parking_brake_lever_pos: false, + eng_1_master_on: false, + eng_2_master_on: false, + + has_air_pressure_low_fault: false, + has_low_level_fault: false, + has_overheat_fault: false, + } + } + + fn update( + &mut self, + context: &UpdateContext, + overhead_panel: &A320HydraulicOverheadPanel, + forward_cargo_door_controller: &HydraulicDoorController, + aft_cargo_door_controller: &HydraulicDoorController, + pushback_tug: &PushbackTug, + lgciu2: &impl LgciuInterface, + reservoir_left_side: &Reservoir, + reservoir_right_side: &Reservoir, + ) { + self.should_inhibit_ptu_after_cargo_door_operation.update( + context, + forward_cargo_door_controller.should_pressurise_hydraulics() + || aft_cargo_door_controller.should_pressurise_hydraulics(), + ); + + let ptu_inhibited = self.should_inhibit_ptu_after_cargo_door_operation.output() + && overhead_panel.yellow_epump_push_button_is_auto(); + + let should_enable_if_powered = overhead_panel.ptu_push_button_is_auto() + && (!lgciu2.nose_gear_compressed(false) + || self.eng_1_master_on && self.eng_2_master_on + || !self.eng_1_master_on && !self.eng_2_master_on + || (!self.parking_brake_lever_pos + && !pushback_tug.is_nose_wheel_steering_pin_inserted())) + && !ptu_inhibited; + + // When there is no power, the PTU is always ON. + self.should_enable = !self.is_powered || should_enable_if_powered; + + self.update_low_air_pressure(reservoir_left_side, reservoir_right_side, overhead_panel); + + self.update_low_level(reservoir_left_side, reservoir_right_side, overhead_panel); + + self.has_overheat_fault = + reservoir_left_side.is_overheating() || reservoir_right_side.is_overheating(); + } + + fn update_low_air_pressure( + &mut self, + reservoir_left_side: &Reservoir, + reservoir_right_side: &Reservoir, + overhead_panel: &A320HydraulicOverheadPanel, + ) { + self.has_air_pressure_low_fault = (reservoir_left_side.is_low_air_pressure() + || reservoir_right_side.is_low_air_pressure()) + && overhead_panel.ptu_push_button_is_auto(); + } + + fn update_low_level( + &mut self, + reservoir_left_side: &Reservoir, + reservoir_right_side: &Reservoir, + overhead_panel: &A320HydraulicOverheadPanel, + ) { + self.has_low_level_fault = (reservoir_left_side.is_low_level() + || reservoir_right_side.is_low_level()) + && overhead_panel.ptu_push_button_is_auto(); + } + + fn has_air_pressure_low_fault(&self) -> bool { + self.has_air_pressure_low_fault + } + + fn has_low_level_fault(&self) -> bool { + self.has_low_level_fault + } + + fn has_overheat_fault(&self) -> bool { + self.has_overheat_fault + } +} +impl PowerTransferUnitController for A320PowerTransferUnitController { + fn should_enable(&self) -> bool { + self.should_enable + } +} +impl SimulationElement for A320PowerTransferUnitController { + fn read(&mut self, reader: &mut SimulatorReader) { + self.parking_brake_lever_pos = reader.read(&self.park_brake_lever_pos_id); + self.eng_1_master_on = reader.read(&self.general_eng_1_starter_active_id); + self.eng_2_master_on = reader.read(&self.general_eng_2_starter_active_id); + } + + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.is_powered = buses.is_powered(self.powered_by); + } +} + +struct A320RamAirTurbineController { + is_solenoid_1_powered: bool, + solenoid_1_bus: ElectricalBusType, + + is_solenoid_2_powered: bool, + solenoid_2_bus: ElectricalBusType, + + should_deploy: bool, +} +impl A320RamAirTurbineController { + fn new(solenoid_1_bus: ElectricalBusType, solenoid_2_bus: ElectricalBusType) -> Self { + Self { + is_solenoid_1_powered: false, + solenoid_1_bus, + + is_solenoid_2_powered: false, + solenoid_2_bus, + + should_deploy: false, + } + } + + fn update( + &mut self, + context: &UpdateContext, + overhead_panel: &A320HydraulicOverheadPanel, + rat_and_emer_gen_man_on: &impl EmergencyElectricalRatPushButton, + emergency_elec_state: &impl EmergencyElectricalState, + ) { + let solenoid_1_should_trigger_deployment_if_powered = + overhead_panel.rat_man_on_push_button_is_pressed(); + + let solenoid_2_should_trigger_deployment_if_powered = + emergency_elec_state.is_in_emergency_elec() || rat_and_emer_gen_man_on.is_pressed(); + + // due to initialization issues the RAT will not deployed in any case when simulation has just started + self.should_deploy = context.is_sim_ready() + && ((self.is_solenoid_1_powered && solenoid_1_should_trigger_deployment_if_powered) + || (self.is_solenoid_2_powered && solenoid_2_should_trigger_deployment_if_powered)); + } +} +impl RamAirTurbineController for A320RamAirTurbineController { + fn should_deploy(&self) -> bool { + self.should_deploy + } +} +impl SimulationElement for A320RamAirTurbineController { + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.is_solenoid_1_powered = buses.is_powered(self.solenoid_1_bus); + self.is_solenoid_2_powered = buses.is_powered(self.solenoid_2_bus); + } +} + +struct A320BrakeSystemOutputs { + left_demand: Ratio, + right_demand: Ratio, + pressure_limit: Pressure, +} +impl A320BrakeSystemOutputs { + fn new() -> Self { + Self { + left_demand: Ratio::new::(0.), + right_demand: Ratio::new::(0.), + pressure_limit: Pressure::new::(3000.), + } + } + + fn set_pressure_limit(&mut self, pressure_limit: Pressure) { + self.pressure_limit = pressure_limit; + } + + fn set_brake_demands(&mut self, left_demand: Ratio, right_demand: Ratio) { + self.left_demand = left_demand + .min(Ratio::new::(1.)) + .max(Ratio::new::(0.)); + self.right_demand = right_demand + .min(Ratio::new::(1.)) + .max(Ratio::new::(0.)); + } + + fn set_no_demands(&mut self) { + self.left_demand = Ratio::new::(0.); + self.right_demand = Ratio::new::(0.); + } + + fn set_max_demands(&mut self) { + self.left_demand = Ratio::new::(1.); + self.right_demand = Ratio::new::(1.); + } + + fn left_demand(&self) -> Ratio { + self.left_demand + } + + fn right_demand(&self) -> Ratio { + self.right_demand + } +} +impl BrakeCircuitController for A320BrakeSystemOutputs { + fn pressure_limit(&self) -> Pressure { + self.pressure_limit + } + + fn left_brake_demand(&self) -> Ratio { + self.left_demand + } + + fn right_brake_demand(&self) -> Ratio { + self.right_demand + } +} + +struct A320HydraulicBrakeSteerComputerUnit { + park_brake_lever_pos_id: VariableIdentifier, + + antiskid_brakes_active_id: VariableIdentifier, + left_brake_pedal_input_id: VariableIdentifier, + right_brake_pedal_input_id: VariableIdentifier, + + ground_speed_id: VariableIdentifier, + + rudder_pedal_input_id: VariableIdentifier, + tiller_handle_input_id: VariableIdentifier, + tiller_pedal_disconnect_id: VariableIdentifier, + autopilot_nosewheel_demand_id: VariableIdentifier, + + autobrake_controller: A320AutobrakeController, + parking_brake_demand: bool, + + left_brake_pilot_input: Ratio, + right_brake_pilot_input: Ratio, + + norm_brake_outputs: A320BrakeSystemOutputs, + alternate_brake_outputs: A320BrakeSystemOutputs, + + normal_brakes_available: bool, + should_disable_auto_brake_when_retracting: DelayedTrueLogicGate, + anti_skid_activated: bool, + + tiller_pedal_disconnect: bool, + tiller_handle_position: Ratio, + rudder_pedal_position: Ratio, + autopilot_nosewheel_demand: Ratio, + + pedal_steering_limiter: SteeringAngleLimiter<5>, + pedal_input_map: SteeringRatioToAngle<6>, + tiller_steering_limiter: SteeringAngleLimiter<5>, + tiller_input_map: SteeringRatioToAngle<6>, + final_steering_position_request: Angle, + + ground_speed: Velocity, +} +impl A320HydraulicBrakeSteerComputerUnit { + const RUDDER_PEDAL_INPUT_GAIN: f64 = 32.; + const RUDDER_PEDAL_INPUT_MAP: [f64; 6] = [0., 1., 2., 32., 32., 32.]; + const RUDDER_PEDAL_INPUT_CURVE_MAP: [f64; 6] = [0., 0., 2., 6.4, 6.4, 6.4]; + const MAX_RUDDER_INPUT_INCLUDING_AUTOPILOT_DEGREE: f64 = 6.; + + const SPEED_MAP_FOR_PEDAL_ACTION_KNOT: [f64; 5] = [0., 40., 130., 1500.0, 2800.0]; + const STEERING_ANGLE_FOR_PEDAL_ACTION_DEGREE: [f64; 5] = [1., 1., 0., 0., 0.]; + + const TILLER_INPUT_GAIN: f64 = 75.; + const TILLER_INPUT_MAP: [f64; 6] = [0., 1., 20., 40., 66., 75.]; + const TILLER_INPUT_CURVE_MAP: [f64; 6] = [0., 0., 4., 15., 45., 74.]; + + const AUTOPILOT_STEERING_INPUT_GAIN: f64 = 6.; + + const SPEED_MAP_FOR_TILLER_ACTION_KNOT: [f64; 5] = [0., 20., 70., 1500.0, 2800.0]; + const STEERING_ANGLE_FOR_TILLER_ACTION_DEGREE: [f64; 5] = [1., 1., 0., 0., 0.]; + + const MAX_STEERING_ANGLE_DEMAND_DEGREES: f64 = 74.; + + // Minimum pressure hysteresis on green until main switched on ALTN brakes + // Feedback by Cpt. Chaos — 25/04/2021 #pilot-feedback + const MIN_PRESSURE_BRAKE_ALTN_HYST_LO: f64 = 1305.; + const MIN_PRESSURE_BRAKE_ALTN_HYST_HI: f64 = 2176.; + + // Min pressure when parking brake enabled. Lower normal braking is allowed to use pilot input as emergency braking + // Feedback by avteknisyan — 25/04/2021 #pilot-feedback + const MIN_PRESSURE_PARK_BRAKE_EMERGENCY: f64 = 507.; + + const AUTOBRAKE_GEAR_RETRACTION_DURATION_S: f64 = 3.; + + const PILOT_INPUT_DETECTION_TRESHOLD: f64 = 0.2; + + fn new(context: &mut InitContext) -> Self { + Self { + park_brake_lever_pos_id: context.get_identifier("PARK_BRAKE_LEVER_POS".to_owned()), + antiskid_brakes_active_id: context.get_identifier("ANTISKID BRAKES ACTIVE".to_owned()), + left_brake_pedal_input_id: context.get_identifier("LEFT_BRAKE_PEDAL_INPUT".to_owned()), + right_brake_pedal_input_id: context + .get_identifier("RIGHT_BRAKE_PEDAL_INPUT".to_owned()), + + ground_speed_id: context.get_identifier("GPS GROUND SPEED".to_owned()), + rudder_pedal_input_id: context.get_identifier("RUDDER_PEDAL_POSITION_RATIO".to_owned()), + tiller_handle_input_id: context.get_identifier("TILLER_HANDLE_POSITION".to_owned()), + tiller_pedal_disconnect_id: context + .get_identifier("TILLER_PEDAL_DISCONNECT".to_owned()), + autopilot_nosewheel_demand_id: context + .get_identifier("AUTOPILOT_NOSEWHEEL_DEMAND".to_owned()), + + autobrake_controller: A320AutobrakeController::new(context), + + parking_brake_demand: true, + left_brake_pilot_input: Ratio::new::(0.0), + right_brake_pilot_input: Ratio::new::(0.0), + norm_brake_outputs: A320BrakeSystemOutputs::new(), + alternate_brake_outputs: A320BrakeSystemOutputs::new(), + normal_brakes_available: false, + should_disable_auto_brake_when_retracting: DelayedTrueLogicGate::new( + Duration::from_secs_f64(Self::AUTOBRAKE_GEAR_RETRACTION_DURATION_S), + ), + anti_skid_activated: true, + + tiller_pedal_disconnect: false, + tiller_handle_position: Ratio::new::(0.), + rudder_pedal_position: Ratio::new::(0.), + autopilot_nosewheel_demand: Ratio::new::(0.), + + pedal_steering_limiter: SteeringAngleLimiter::new( + Self::SPEED_MAP_FOR_PEDAL_ACTION_KNOT, + Self::STEERING_ANGLE_FOR_PEDAL_ACTION_DEGREE, + ), + pedal_input_map: SteeringRatioToAngle::new( + Ratio::new::(Self::RUDDER_PEDAL_INPUT_GAIN), + Self::RUDDER_PEDAL_INPUT_MAP, + Self::RUDDER_PEDAL_INPUT_CURVE_MAP, + ), + tiller_steering_limiter: SteeringAngleLimiter::new( + Self::SPEED_MAP_FOR_TILLER_ACTION_KNOT, + Self::STEERING_ANGLE_FOR_TILLER_ACTION_DEGREE, + ), + tiller_input_map: SteeringRatioToAngle::new( + Ratio::new::(Self::TILLER_INPUT_GAIN), + Self::TILLER_INPUT_MAP, + Self::TILLER_INPUT_CURVE_MAP, + ), + final_steering_position_request: Angle::new::(0.), + + ground_speed: Velocity::new::(0.), + } + } + + fn allow_autobrake_arming(&self) -> bool { + self.anti_skid_activated && self.normal_brakes_available + } + + fn update_normal_braking_availability(&mut self, normal_braking_circuit_pressure: Pressure) { + if normal_braking_circuit_pressure.get::() > Self::MIN_PRESSURE_BRAKE_ALTN_HYST_HI + && (self.left_brake_pilot_input.get::() < Self::PILOT_INPUT_DETECTION_TRESHOLD + && self.right_brake_pilot_input.get::() + < Self::PILOT_INPUT_DETECTION_TRESHOLD) + { + self.normal_brakes_available = true; + } else if normal_braking_circuit_pressure.get::() + < Self::MIN_PRESSURE_BRAKE_ALTN_HYST_LO + { + self.normal_brakes_available = false; + } + } + + fn update_brake_pressure_limitation(&mut self) { + let yellow_manual_braking_input = self.left_brake_pilot_input + > self.alternate_brake_outputs.left_demand() + Ratio::new::(0.2) + || self.right_brake_pilot_input + > self.alternate_brake_outputs.right_demand() + Ratio::new::(0.2); + + // Nominal braking from pedals is limited to 2538psi + self.norm_brake_outputs + .set_pressure_limit(Pressure::new::(2538.)); + + let alternate_brake_pressure_limit = Pressure::new::(if self.parking_brake_demand { + // If no pilot action, standard park brake pressure limit + if !yellow_manual_braking_input { + 2103. + } else { + // Else manual action limited to a higher max nominal pressure + 2538. + } + } else if !self.anti_skid_activated { + 1160. + } else { + // Else if any manual braking we use standard limit + 2538. + }); + + self.alternate_brake_outputs + .set_pressure_limit(alternate_brake_pressure_limit); + } + + /// Updates brakes and nose steering demands + fn update( + &mut self, + context: &UpdateContext, + current_pressure: &impl SectionPressure, + alternate_circuit: &BrakeCircuit, + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + autobrake_panel: &AutobrakePanel, + engine1: &impl Engine, + engine2: &impl Engine, + ) { + self.update_steering_demands(lgciu1, engine1, engine2); + + self.update_normal_braking_availability(current_pressure.pressure()); + self.update_brake_pressure_limitation(); + + self.autobrake_controller.update( + context, + autobrake_panel, + self.allow_autobrake_arming(), + self.left_brake_pilot_input, + self.right_brake_pilot_input, + lgciu1, + lgciu2, + ); + + let is_in_flight_gear_lever_up = !(lgciu1.left_and_right_gear_compressed(true) + || lgciu2.left_and_right_gear_compressed(true) + || lgciu1.gear_handle_is_down()); + + self.should_disable_auto_brake_when_retracting + .update(context, is_in_flight_gear_lever_up); + + if is_in_flight_gear_lever_up { + if self.should_disable_auto_brake_when_retracting.output() { + self.norm_brake_outputs.set_no_demands(); + } else { + // Slight brake pressure to stop the spinning wheels (have no pressure data available yet, 0.2 is random one) + self.norm_brake_outputs + .set_brake_demands(Ratio::new::(0.2), Ratio::new::(0.2)); + } + + self.alternate_brake_outputs.set_no_demands(); + } else { + let green_used_for_brakes = self.normal_brakes_available + && self.anti_skid_activated + && !self.parking_brake_demand; + + if green_used_for_brakes { + // Final output on normal brakes is max(pilot demand , autobrake demand) to allow pilot override autobrake demand + self.norm_brake_outputs.set_brake_demands( + self.left_brake_pilot_input + .max(self.autobrake_controller.brake_output()), + self.right_brake_pilot_input + .max(self.autobrake_controller.brake_output()), + ); + + self.alternate_brake_outputs.set_no_demands(); + } else { + self.norm_brake_outputs.set_no_demands(); + + if !self.parking_brake_demand { + // Normal braking but using alternate circuit + self.alternate_brake_outputs.set_brake_demands( + self.left_brake_pilot_input, + self.right_brake_pilot_input, + ); + } else { + // Else we just use parking brake + self.alternate_brake_outputs.set_max_demands(); + + // Special case: parking brake on but yellow can't provide enough brakes: green are allowed to brake for emergency + if alternate_circuit.left_brake_pressure().get::() + < Self::MIN_PRESSURE_PARK_BRAKE_EMERGENCY + || alternate_circuit.right_brake_pressure().get::() + < Self::MIN_PRESSURE_PARK_BRAKE_EMERGENCY + { + self.norm_brake_outputs.set_brake_demands( + self.left_brake_pilot_input, + self.right_brake_pilot_input, + ); + } + } + } + } + } + + fn update_steering_demands( + &mut self, + lgciu1: &impl LgciuInterface, + engine1: &impl Engine, + engine2: &impl Engine, + ) { + let steer_angle_from_autopilot = Angle::new::( + self.autopilot_nosewheel_demand.get::() * Self::AUTOPILOT_STEERING_INPUT_GAIN, + ); + + let steer_angle_from_pedals = if self.tiller_pedal_disconnect { + Angle::new::(0.) + } else { + self.pedal_input_map + .angle_demand_from_input_demand(self.rudder_pedal_position) + }; + + // TODO Here ground speed would be probably computed from wheel sensor logic + let final_steer_rudder_plus_autopilot = self.pedal_steering_limiter.angle_from_speed( + self.ground_speed, + (steer_angle_from_pedals + steer_angle_from_autopilot) + .min(Angle::new::( + Self::MAX_RUDDER_INPUT_INCLUDING_AUTOPILOT_DEGREE, + )) + .max(Angle::new::( + -Self::MAX_RUDDER_INPUT_INCLUDING_AUTOPILOT_DEGREE, + )), + ); + + let steer_angle_from_tiller = self.tiller_steering_limiter.angle_from_speed( + self.ground_speed, + self.tiller_input_map + .angle_demand_from_input_demand(self.tiller_handle_position), + ); + + let is_both_engine_low_oil_pressure = + engine1.oil_pressure_is_low() && engine2.oil_pressure_is_low(); + + self.final_steering_position_request = if !is_both_engine_low_oil_pressure + && self.anti_skid_activated + && lgciu1.nose_gear_compressed(false) + { + (final_steer_rudder_plus_autopilot + steer_angle_from_tiller) + .min(Angle::new::( + Self::MAX_STEERING_ANGLE_DEMAND_DEGREES, + )) + .max(Angle::new::( + -Self::MAX_STEERING_ANGLE_DEMAND_DEGREES, + )) + } else { + Angle::new::(0.) + }; + } + + fn norm_controller(&self) -> &impl BrakeCircuitController { + &self.norm_brake_outputs + } + + fn alternate_controller(&self) -> &impl BrakeCircuitController { + &self.alternate_brake_outputs + } +} +impl SimulationElement for A320HydraulicBrakeSteerComputerUnit { + fn accept(&mut self, visitor: &mut T) { + self.autobrake_controller.accept(visitor); + visitor.visit(self); + } + + fn read(&mut self, reader: &mut SimulatorReader) { + self.parking_brake_demand = reader.read(&self.park_brake_lever_pos_id); + + self.anti_skid_activated = reader.read(&self.antiskid_brakes_active_id); + self.left_brake_pilot_input = + Ratio::new::(reader.read(&self.left_brake_pedal_input_id)); + self.right_brake_pilot_input = + Ratio::new::(reader.read(&self.right_brake_pedal_input_id)); + + self.tiller_handle_position = + Ratio::new::(reader.read(&self.tiller_handle_input_id)); + self.rudder_pedal_position = Ratio::new::(reader.read(&self.rudder_pedal_input_id)); + self.tiller_pedal_disconnect = reader.read(&self.tiller_pedal_disconnect_id); + self.ground_speed = reader.read(&self.ground_speed_id); + + self.autopilot_nosewheel_demand = + Ratio::new::(reader.read(&self.autopilot_nosewheel_demand_id)); + } +} +impl SteeringController for A320HydraulicBrakeSteerComputerUnit { + fn requested_position(&self) -> Angle { + self.final_steering_position_request + } +} + +struct A320BrakingForce { + brake_left_force_factor_id: VariableIdentifier, + brake_right_force_factor_id: VariableIdentifier, + trailing_edge_flaps_left_percent_id: VariableIdentifier, + trailing_edge_flaps_right_percent_id: VariableIdentifier, + + enabled_chocks_id: VariableIdentifier, + light_beacon_on_id: VariableIdentifier, + + left_braking_force: f64, + right_braking_force: f64, + + flap_position: f64, + + is_chocks_enabled: bool, + is_light_beacon_on: bool, +} +impl A320BrakingForce { + const REFERENCE_PRESSURE_FOR_MAX_FORCE: f64 = 2538.; + + const FLAPS_BREAKPOINTS: [f64; 3] = [0., 50., 100.]; + const FLAPS_PENALTY_PERCENT: [f64; 3] = [5., 5., 0.]; + + pub fn new(context: &mut InitContext) -> Self { + A320BrakingForce { + brake_left_force_factor_id: context + .get_identifier("BRAKE LEFT FORCE FACTOR".to_owned()), + brake_right_force_factor_id: context + .get_identifier("BRAKE RIGHT FORCE FACTOR".to_owned()), + trailing_edge_flaps_left_percent_id: context + .get_identifier("LEFT_FLAPS_POSITION_PERCENT".to_owned()), + trailing_edge_flaps_right_percent_id: context + .get_identifier("RIGHT_FLAPS_POSITION_PERCENT".to_owned()), + + enabled_chocks_id: context.get_identifier("MODEL_WHEELCHOCKS_ENABLED".to_owned()), + light_beacon_on_id: context.get_identifier("LIGHT BEACON".to_owned()), + + left_braking_force: 0., + right_braking_force: 0., + + flap_position: 0., + + is_chocks_enabled: false, + is_light_beacon_on: false, + } + } + + pub fn update_forces( + &mut self, + context: &UpdateContext, + norm_brakes: &BrakeCircuit, + altn_brakes: &BrakeCircuit, + engine1: &impl Engine, + engine2: &impl Engine, + pushback_tug: &PushbackTug, + ) { + // Base formula for output force is output_force[0:1] = 50 * sqrt(current_pressure) / Max_brake_pressure + // This formula gives a bit more punch for lower brake pressures (like 1000 psi alternate braking), as linear formula + // gives really too low brake force for 1000psi + + let left_force_norm = 50. * norm_brakes.left_brake_pressure().get::().sqrt() + / Self::REFERENCE_PRESSURE_FOR_MAX_FORCE; + let left_force_altn = 50. * altn_brakes.left_brake_pressure().get::().sqrt() + / Self::REFERENCE_PRESSURE_FOR_MAX_FORCE; + self.left_braking_force = left_force_norm + left_force_altn; + self.left_braking_force = self.left_braking_force.max(0.).min(1.); + + let right_force_norm = 50. * norm_brakes.right_brake_pressure().get::().sqrt() + / Self::REFERENCE_PRESSURE_FOR_MAX_FORCE; + let right_force_altn = 50. * altn_brakes.right_brake_pressure().get::().sqrt() + / Self::REFERENCE_PRESSURE_FOR_MAX_FORCE; + self.right_braking_force = right_force_norm + right_force_altn; + self.right_braking_force = self.right_braking_force.max(0.).min(1.); + + self.correct_with_flaps_state(context); + + self.update_chocks_braking(context, engine1, engine2, pushback_tug); + } + + fn correct_with_flaps_state(&mut self, context: &UpdateContext) { + let flap_correction = Ratio::new::(interpolation( + &Self::FLAPS_BREAKPOINTS, + &Self::FLAPS_PENALTY_PERCENT, + self.flap_position, + )); + + // Using airspeed with formula 0.1 * sqrt(airspeed) to get a 0 to 1 ratio to use our flap correction + // This way the less airspeed, the less our correction is used as it is an aerodynamic effect on brakes + let mut airspeed_corrective_factor = + 0.1 * context.indicated_airspeed().get::().abs().sqrt(); + airspeed_corrective_factor = airspeed_corrective_factor.min(1.0); + + let final_flaps_correction_with_speed = flap_correction * airspeed_corrective_factor; + + self.left_braking_force = self.left_braking_force + - (self.left_braking_force * final_flaps_correction_with_speed.get::()); + + self.right_braking_force = self.right_braking_force + - (self.right_braking_force * final_flaps_correction_with_speed.get::()); + } + + fn update_chocks_braking( + &mut self, + context: &UpdateContext, + engine1: &impl Engine, + engine2: &impl Engine, + pushback_tug: &PushbackTug, + ) { + let chocks_on_wheels = context.is_on_ground() + && engine1.corrected_n1().get::() < 3.5 + && engine2.corrected_n1().get::() < 3.5 + && !pushback_tug.is_nose_wheel_steering_pin_inserted() + && !self.is_light_beacon_on; + + if self.is_chocks_enabled && chocks_on_wheels { + self.left_braking_force = 1.; + self.right_braking_force = 1.; + } + } +} + +impl SimulationElement for A320BrakingForce { + fn write(&self, writer: &mut SimulatorWriter) { + // BRAKE XXXX FORCE FACTOR is the actual braking force we want the plane to generate in the simulator + writer.write(&self.brake_left_force_factor_id, self.left_braking_force); + writer.write(&self.brake_right_force_factor_id, self.right_braking_force); + } + + fn read(&mut self, reader: &mut SimulatorReader) { + let left_flap: f64 = reader.read(&self.trailing_edge_flaps_left_percent_id); + let right_flap: f64 = reader.read(&self.trailing_edge_flaps_right_percent_id); + self.flap_position = (left_flap + right_flap) / 2.; + + self.is_chocks_enabled = reader.read(&self.enabled_chocks_id); + self.is_light_beacon_on = reader.read(&self.light_beacon_on_id); + } +} + +/// Autobrake controller computes the state machine of the autobrake logic, and the deceleration target +/// that we expect for the plane +pub struct A320AutobrakeController { + armed_mode_id: VariableIdentifier, + armed_mode_id_set: VariableIdentifier, + decel_light_id: VariableIdentifier, + active_id: VariableIdentifier, + ground_spoilers_out_sec1_id: VariableIdentifier, + ground_spoilers_out_sec2_id: VariableIdentifier, + ground_spoilers_out_sec3_id: VariableIdentifier, + external_disarm_event_id: VariableIdentifier, + + deceleration_governor: AutobrakeDecelerationGovernor, + + target: Acceleration, + mode: AutobrakeMode, + + arming_is_allowed_by_bcu: bool, + left_brake_pedal_input: Ratio, + right_brake_pedal_input: Ratio, + + ground_spoilers_are_deployed: bool, + last_ground_spoilers_are_deployed: bool, + + should_disarm_after_time_in_flight: DelayedPulseTrueLogicGate, + should_reject_max_mode_after_time_in_flight: DelayedTrueLogicGate, + + external_disarm_event: bool, +} +impl A320AutobrakeController { + const DURATION_OF_FLIGHT_TO_DISARM_AUTOBRAKE_SECS: f64 = 10.; + + // Dynamic decel target map versus time for any mode that needs it + const LOW_MODE_DECEL_PROFILE_ACCEL_MS2: [f64; 4] = [4., 4., 0., -2.]; + const LOW_MODE_DECEL_PROFILE_TIME_S: [f64; 4] = [0., 1.99, 2., 3.]; + + const MED_MODE_DECEL_PROFILE_ACCEL_MS2: [f64; 5] = [4., 4., 0., -2., -3.]; + const MED_MODE_DECEL_PROFILE_TIME_S: [f64; 5] = [0., 1.99, 2., 2.5, 3.]; + + const MAX_MODE_DECEL_TARGET_MS2: f64 = -6.; + const OFF_MODE_DECEL_TARGET_MS2: f64 = 5.; + + const MARGIN_PERCENT_TO_TARGET_TO_SHOW_DECEL_IN_LO_MED: f64 = 80.; + const TARGET_TO_SHOW_DECEL_IN_MAX_MS2: f64 = -2.7; + + fn new(context: &mut InitContext) -> A320AutobrakeController { + A320AutobrakeController { + armed_mode_id: context.get_identifier("AUTOBRAKES_ARMED_MODE".to_owned()), + armed_mode_id_set: context.get_identifier("AUTOBRAKES_ARMED_MODE_SET".to_owned()), + decel_light_id: context.get_identifier("AUTOBRAKES_DECEL_LIGHT".to_owned()), + active_id: context.get_identifier("AUTOBRAKES_ACTIVE".to_owned()), + ground_spoilers_out_sec1_id: context + .get_identifier("SEC_1_GROUND_SPOILER_OUT".to_owned()), + ground_spoilers_out_sec2_id: context + .get_identifier("SEC_2_GROUND_SPOILER_OUT".to_owned()), + ground_spoilers_out_sec3_id: context + .get_identifier("SEC_3_GROUND_SPOILER_OUT".to_owned()), + external_disarm_event_id: context.get_identifier("AUTOBRAKE_DISARM".to_owned()), + + deceleration_governor: AutobrakeDecelerationGovernor::new(), + target: Acceleration::new::(0.), + mode: AutobrakeMode::NONE, + arming_is_allowed_by_bcu: context.is_in_flight(), + left_brake_pedal_input: Ratio::new::(0.), + right_brake_pedal_input: Ratio::new::(0.), + ground_spoilers_are_deployed: false, + last_ground_spoilers_are_deployed: false, + should_disarm_after_time_in_flight: DelayedPulseTrueLogicGate::new( + Duration::from_secs_f64(Self::DURATION_OF_FLIGHT_TO_DISARM_AUTOBRAKE_SECS), + ) + .starting_as(context.is_in_flight(), false), + should_reject_max_mode_after_time_in_flight: DelayedTrueLogicGate::new( + Duration::from_secs_f64(Self::DURATION_OF_FLIGHT_TO_DISARM_AUTOBRAKE_SECS), + ) + .starting_as(context.is_in_flight()), + external_disarm_event: false, + } + } + + fn spoilers_retracted_during_this_update(&self) -> bool { + !self.ground_spoilers_are_deployed && self.last_ground_spoilers_are_deployed + } + + fn brake_output(&self) -> Ratio { + Ratio::new::(self.deceleration_governor.output()) + } + + fn determine_mode( + &mut self, + context: &UpdateContext, + autobrake_panel: &AutobrakePanel, + ) -> AutobrakeMode { + if self.should_disarm(context) { + AutobrakeMode::NONE + } else { + match autobrake_panel.pressed_mode() { + Some(mode) if self.mode == mode => AutobrakeMode::NONE, + Some(mode) + if mode != AutobrakeMode::MAX + || !self.should_reject_max_mode_after_time_in_flight.output() => + { + mode + } + Some(_) | None => self.mode, + } + } + } + + fn should_engage_deceleration_governor(&self, context: &UpdateContext) -> bool { + self.is_armed() && self.ground_spoilers_are_deployed && !self.should_disarm(context) + } + + fn is_armed(&self) -> bool { + self.mode != AutobrakeMode::NONE + } + + fn is_decelerating(&self) -> bool { + match self.mode { + AutobrakeMode::NONE => false, + AutobrakeMode::LOW | AutobrakeMode::MED => { + self.deceleration_demanded() + && self + .deceleration_governor + .is_on_target(Ratio::new::( + Self::MARGIN_PERCENT_TO_TARGET_TO_SHOW_DECEL_IN_LO_MED, + )) + } + _ => { + self.deceleration_demanded() + && self.deceleration_governor.decelerating_at_or_above_rate( + Acceleration::new::( + Self::TARGET_TO_SHOW_DECEL_IN_MAX_MS2, + ), + ) + } + } + } + + fn deceleration_demanded(&self) -> bool { + self.deceleration_governor.is_engaged() + && self.target.get::() < 0. + } + + fn should_disarm_due_to_pedal_input(&self) -> bool { + match self.mode { + AutobrakeMode::NONE => false, + AutobrakeMode::LOW | AutobrakeMode::MED => { + self.left_brake_pedal_input > Ratio::new::(53.) + || self.right_brake_pedal_input > Ratio::new::(53.) + || (self.left_brake_pedal_input > Ratio::new::(11.) + && self.right_brake_pedal_input > Ratio::new::(11.)) + } + AutobrakeMode::MAX => { + self.left_brake_pedal_input > Ratio::new::(77.) + || self.right_brake_pedal_input > Ratio::new::(77.) + || (self.left_brake_pedal_input > Ratio::new::(53.) + && self.right_brake_pedal_input > Ratio::new::(53.)) + } + _ => false, + } + } + + fn should_disarm(&self, context: &UpdateContext) -> bool { + // when a simulation is started in flight, some values need to be ignored for a certain time to ensure + // an unintended disarm is not happening + (self.deceleration_governor.is_engaged() && self.should_disarm_due_to_pedal_input()) + || (context.is_sim_ready() && !self.arming_is_allowed_by_bcu) + || self.spoilers_retracted_during_this_update() + || self.should_disarm_after_time_in_flight.output() + || self.external_disarm_event + || (self.mode == AutobrakeMode::MAX + && self.should_reject_max_mode_after_time_in_flight.output()) + } + + fn calculate_target(&mut self) -> Acceleration { + Acceleration::new::(match self.mode { + AutobrakeMode::NONE => Self::OFF_MODE_DECEL_TARGET_MS2, + AutobrakeMode::LOW => interpolation( + &Self::LOW_MODE_DECEL_PROFILE_TIME_S, + &Self::LOW_MODE_DECEL_PROFILE_ACCEL_MS2, + self.deceleration_governor.time_engaged().as_secs_f64(), + ), + AutobrakeMode::MED => interpolation( + &Self::MED_MODE_DECEL_PROFILE_TIME_S, + &Self::MED_MODE_DECEL_PROFILE_ACCEL_MS2, + self.deceleration_governor.time_engaged().as_secs_f64(), + ), + AutobrakeMode::MAX => Self::MAX_MODE_DECEL_TARGET_MS2, + _ => Self::OFF_MODE_DECEL_TARGET_MS2, + }) + } + + fn update_input_conditions( + &mut self, + context: &UpdateContext, + allow_arming: bool, + pedal_input_left: Ratio, + pedal_input_right: Ratio, + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + ) { + let in_flight_lgciu1 = + !lgciu1.right_gear_compressed(false) && !lgciu1.left_gear_compressed(false); + let in_flight_lgciu2 = + !lgciu2.right_gear_compressed(false) && !lgciu2.left_gear_compressed(false); + + self.should_disarm_after_time_in_flight + .update(context, in_flight_lgciu1 && in_flight_lgciu2); + self.should_reject_max_mode_after_time_in_flight + .update(context, in_flight_lgciu1 && in_flight_lgciu2); + + self.arming_is_allowed_by_bcu = allow_arming; + self.left_brake_pedal_input = pedal_input_left; + self.right_brake_pedal_input = pedal_input_right; + } + + fn update( + &mut self, + context: &UpdateContext, + autobrake_panel: &AutobrakePanel, + allow_arming: bool, + pedal_input_left: Ratio, + pedal_input_right: Ratio, + lgciu1: &impl LgciuInterface, + lgciu2: &impl LgciuInterface, + ) { + self.update_input_conditions( + context, + allow_arming, + pedal_input_left, + pedal_input_right, + lgciu1, + lgciu2, + ); + self.mode = self.determine_mode(context, autobrake_panel); + + self.deceleration_governor + .engage_when(self.should_engage_deceleration_governor(context)); + + self.target = self.calculate_target(); + self.deceleration_governor.update(context, self.target); + } +} +impl SimulationElement for A320AutobrakeController { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.armed_mode_id, self.mode as u8 as f64); + writer.write(&self.armed_mode_id_set, -1.); + writer.write(&self.decel_light_id, self.is_decelerating()); + writer.write(&self.active_id, self.deceleration_demanded()); + } + + fn read(&mut self, reader: &mut SimulatorReader) { + self.last_ground_spoilers_are_deployed = self.ground_spoilers_are_deployed; + let sec_1_gnd_splrs_out = reader.read(&self.ground_spoilers_out_sec1_id); + let sec_2_gnd_splrs_out = reader.read(&self.ground_spoilers_out_sec2_id); + let sec_3_gnd_splrs_out = reader.read(&self.ground_spoilers_out_sec3_id); + self.ground_spoilers_are_deployed = (sec_1_gnd_splrs_out + && (sec_3_gnd_splrs_out || sec_2_gnd_splrs_out)) + || (sec_2_gnd_splrs_out && sec_3_gnd_splrs_out); + self.external_disarm_event = reader.read(&self.external_disarm_event_id); + + // Reading current mode in sim to initialize correct mode if sim changes it (from .FLT files for example) + let readed_mode = reader.read_f64(&self.armed_mode_id_set); + if readed_mode >= 0.0 { + self.mode = readed_mode.into(); + } + } +} + +pub(super) struct A320HydraulicOverheadPanel { + edp1_push_button: AutoOffFaultPushButton, + edp2_push_button: AutoOffFaultPushButton, + blue_epump_push_button: AutoOffFaultPushButton, + ptu_push_button: AutoOffFaultPushButton, + rat_push_button: MomentaryPushButton, + yellow_epump_push_button: AutoOnFaultPushButton, + blue_epump_override_push_button: MomentaryOnPushButton, + + green_leak_measurement_push_button: AutoOffFaultPushButton, + blue_leak_measurement_push_button: AutoOffFaultPushButton, + yellow_leak_measurement_push_button: AutoOffFaultPushButton, +} +impl A320HydraulicOverheadPanel { + pub(super) fn new(context: &mut InitContext) -> A320HydraulicOverheadPanel { + A320HydraulicOverheadPanel { + edp1_push_button: AutoOffFaultPushButton::new_auto(context, "HYD_ENG_1_PUMP"), + edp2_push_button: AutoOffFaultPushButton::new_auto(context, "HYD_ENG_2_PUMP"), + blue_epump_push_button: AutoOffFaultPushButton::new_auto(context, "HYD_EPUMPB"), + ptu_push_button: AutoOffFaultPushButton::new_auto(context, "HYD_PTU"), + rat_push_button: MomentaryPushButton::new(context, "HYD_RAT_MAN_ON"), + yellow_epump_push_button: AutoOnFaultPushButton::new_auto(context, "HYD_EPUMPY"), + blue_epump_override_push_button: MomentaryOnPushButton::new(context, "HYD_EPUMPY_OVRD"), + + green_leak_measurement_push_button: AutoOffFaultPushButton::new_auto( + context, + "HYD_LEAK_MEASUREMENT_G", + ), + blue_leak_measurement_push_button: AutoOffFaultPushButton::new_auto( + context, + "HYD_LEAK_MEASUREMENT_B", + ), + yellow_leak_measurement_push_button: AutoOffFaultPushButton::new_auto( + context, + "HYD_LEAK_MEASUREMENT_Y", + ), + } + } + + fn update_blue_override_state(&mut self) { + if self.blue_epump_push_button.is_off() { + self.blue_epump_override_push_button.turn_off(); + } + } + + pub(super) fn update(&mut self, hyd: &A320Hydraulic) { + self.edp1_push_button.set_fault(hyd.green_edp_has_fault()); + self.edp2_push_button.set_fault(hyd.yellow_edp_has_fault()); + self.blue_epump_push_button + .set_fault(hyd.blue_epump_has_fault()); + self.yellow_epump_push_button + .set_fault(hyd.yellow_epump_has_fault()); + self.ptu_push_button.set_fault(hyd.ptu_has_fault()); + + self.update_blue_override_state(); + } + + fn yellow_epump_push_button_is_auto(&self) -> bool { + self.yellow_epump_push_button.is_auto() + } + + fn ptu_push_button_is_auto(&self) -> bool { + self.ptu_push_button.is_auto() + } + + fn edp_push_button_is_auto(&self, number: usize) -> bool { + match number { + 1 => self.edp1_push_button.is_auto(), + 2 => self.edp2_push_button.is_auto(), + _ => panic!("The A320 only supports two engines."), + } + } + + fn edp_push_button_is_off(&self, number: usize) -> bool { + match number { + 1 => self.edp1_push_button.is_off(), + 2 => self.edp2_push_button.is_off(), + _ => panic!("The A320 only supports two engines."), + } + } + + fn blue_epump_override_push_button_is_on(&self) -> bool { + self.blue_epump_override_push_button.is_on() + } + + fn blue_epump_push_button_is_off(&self) -> bool { + self.blue_epump_push_button.is_off() + } + + fn rat_man_on_push_button_is_pressed(&self) -> bool { + self.rat_push_button.is_pressed() + } + + fn blue_leak_measurement_valve_is_on(&self) -> bool { + self.blue_leak_measurement_push_button.is_auto() + } + + fn green_leak_measurement_valve_is_on(&self) -> bool { + self.green_leak_measurement_push_button.is_auto() + } + + fn yellow_leak_measurement_valve_is_on(&self) -> bool { + self.yellow_leak_measurement_push_button.is_auto() + } +} +impl SimulationElement for A320HydraulicOverheadPanel { + fn accept(&mut self, visitor: &mut T) { + self.edp1_push_button.accept(visitor); + self.edp2_push_button.accept(visitor); + self.blue_epump_push_button.accept(visitor); + self.ptu_push_button.accept(visitor); + self.rat_push_button.accept(visitor); + self.yellow_epump_push_button.accept(visitor); + self.blue_epump_override_push_button.accept(visitor); + + self.green_leak_measurement_push_button.accept(visitor); + self.blue_leak_measurement_push_button.accept(visitor); + self.yellow_leak_measurement_push_button.accept(visitor); + + visitor.visit(self); + } + + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + if !buses.is_powered(A320Hydraulic::BLUE_ELEC_PUMP_CONTROL_POWER_BUS) + || !buses.is_powered(A320Hydraulic::BLUE_ELEC_PUMP_SUPPLY_POWER_BUS) + { + self.blue_epump_override_push_button.turn_off(); + } + } +} + +struct AileronController { + mode: LinearActuatorMode, + requested_position: Ratio, +} +impl AileronController { + fn new() -> Self { + Self { + mode: LinearActuatorMode::ClosedCircuitDamping, + + requested_position: Ratio::new::(0.), + } + } + + fn set_mode(&mut self, mode: LinearActuatorMode) { + self.mode = mode; + } + + /// Receives a [0;1] position request, 0 is down 1 is up + fn set_requested_position(&mut self, requested_position: Ratio) { + self.requested_position = requested_position + .min(Ratio::new::(1.)) + .max(Ratio::new::(0.)); + } +} +impl HydraulicAssemblyController for AileronController { + fn requested_mode(&self) -> LinearActuatorMode { + self.mode + } + + fn requested_position(&self) -> Ratio { + self.requested_position + } + + fn should_lock(&self) -> bool { + false + } + + fn requested_lock_position(&self) -> Ratio { + Ratio::default() + } +} +impl HydraulicLocking for AileronController {} +impl ElectroHydrostaticPowered for AileronController {} + +struct AileronSystemHydraulicController { + left_aileron_blue_actuator_solenoid_id: VariableIdentifier, + right_aileron_blue_actuator_solenoid_id: VariableIdentifier, + left_aileron_green_actuator_solenoid_id: VariableIdentifier, + right_aileron_green_actuator_solenoid_id: VariableIdentifier, + + left_aileron_blue_actuator_position_demand_id: VariableIdentifier, + right_aileron_blue_actuator_position_demand_id: VariableIdentifier, + left_aileron_green_actuator_position_demand_id: VariableIdentifier, + right_aileron_green_actuator_position_demand_id: VariableIdentifier, + + left_aileron_controllers: [AileronController; 2], + right_aileron_controllers: [AileronController; 2], +} +impl AileronSystemHydraulicController { + fn new(context: &mut InitContext) -> Self { + Self { + left_aileron_blue_actuator_solenoid_id: context + .get_identifier("LEFT_AIL_BLUE_SERVO_SOLENOID_ENERGIZED".to_owned()), + right_aileron_blue_actuator_solenoid_id: context + .get_identifier("RIGHT_AIL_BLUE_SERVO_SOLENOID_ENERGIZED".to_owned()), + left_aileron_green_actuator_solenoid_id: context + .get_identifier("LEFT_AIL_GREEN_SERVO_SOLENOID_ENERGIZED".to_owned()), + right_aileron_green_actuator_solenoid_id: context + .get_identifier("RIGHT_AIL_GREEN_SERVO_SOLENOID_ENERGIZED".to_owned()), + + left_aileron_blue_actuator_position_demand_id: context + .get_identifier("LEFT_AIL_BLUE_COMMANDED_POSITION".to_owned()), + right_aileron_blue_actuator_position_demand_id: context + .get_identifier("RIGHT_AIL_BLUE_COMMANDED_POSITION".to_owned()), + left_aileron_green_actuator_position_demand_id: context + .get_identifier("LEFT_AIL_GREEN_COMMANDED_POSITION".to_owned()), + right_aileron_green_actuator_position_demand_id: context + .get_identifier("RIGHT_AIL_GREEN_COMMANDED_POSITION".to_owned()), + + // Controllers are in outward->inward order, so for aileron [Blue circuit, Green circuit] + left_aileron_controllers: [AileronController::new(), AileronController::new()], + right_aileron_controllers: [AileronController::new(), AileronController::new()], + } + } + + fn left_controllers( + &self, + ) -> &[impl HydraulicAssemblyController + HydraulicLocking + ElectroHydrostaticPowered] { + &self.left_aileron_controllers[..] + } + + fn right_controllers( + &self, + ) -> &[impl HydraulicAssemblyController + HydraulicLocking + ElectroHydrostaticPowered] { + &self.right_aileron_controllers[..] + } + + fn update_aileron_controllers_positions( + &mut self, + left_position_requests: [Ratio; 2], + right_position_requests: [Ratio; 2], + ) { + self.left_aileron_controllers[AileronActuatorPosition::Blue as usize] + .set_requested_position(left_position_requests[AileronActuatorPosition::Blue as usize]); + self.left_aileron_controllers[AileronActuatorPosition::Green as usize] + .set_requested_position( + left_position_requests[AileronActuatorPosition::Green as usize], + ); + + self.right_aileron_controllers[AileronActuatorPosition::Blue as usize] + .set_requested_position( + right_position_requests[AileronActuatorPosition::Blue as usize], + ); + self.right_aileron_controllers[AileronActuatorPosition::Green as usize] + .set_requested_position( + right_position_requests[AileronActuatorPosition::Green as usize], + ); + } + + /// Will drive mode from solenoid state + /// -If energized actuator controls position + /// -If not energized actuator is slaved in damping + /// -We differentiate case of all actuators in damping mode where we set a more dampened + /// mode to reach realistic slow droop speed. + fn update_aileron_controllers_solenoids( + &mut self, + left_solenoids_energized: [bool; 2], + right_solenoids_energized: [bool; 2], + ) { + if left_solenoids_energized.iter().any(|x| *x) { + self.left_aileron_controllers[AileronActuatorPosition::Blue as usize].set_mode( + Self::aileron_actuator_mode_from_solenoid( + left_solenoids_energized[AileronActuatorPosition::Blue as usize], + ), + ); + self.left_aileron_controllers[AileronActuatorPosition::Green as usize].set_mode( + Self::aileron_actuator_mode_from_solenoid( + left_solenoids_energized[AileronActuatorPosition::Green as usize], + ), + ); + } else { + for controller in &mut self.left_aileron_controllers { + controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + } + + if right_solenoids_energized.iter().any(|x| *x) { + self.right_aileron_controllers[AileronActuatorPosition::Blue as usize].set_mode( + Self::aileron_actuator_mode_from_solenoid( + right_solenoids_energized[AileronActuatorPosition::Blue as usize], + ), + ); + self.right_aileron_controllers[AileronActuatorPosition::Green as usize].set_mode( + Self::aileron_actuator_mode_from_solenoid( + right_solenoids_energized[AileronActuatorPosition::Green as usize], + ), + ); + } else { + for controller in &mut self.right_aileron_controllers { + controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + } + } + + fn aileron_actuator_mode_from_solenoid(solenoid_energized: bool) -> LinearActuatorMode { + if solenoid_energized { + LinearActuatorMode::PositionControl + } else { + LinearActuatorMode::ActiveDamping + } + } + + fn aileron_actuator_position_from_surface_angle(surface_angle: Angle) -> Ratio { + Ratio::new::(surface_angle.get::() / 50. + 0.5) + } +} +impl SimulationElement for AileronSystemHydraulicController { + fn read(&mut self, reader: &mut SimulatorReader) { + // Note that we reverse left, as positions are just passed through msfs for now + self.update_aileron_controllers_positions( + [ + Self::aileron_actuator_position_from_surface_angle(-Angle::new::( + reader.read(&self.left_aileron_blue_actuator_position_demand_id), + )), + Self::aileron_actuator_position_from_surface_angle(-Angle::new::( + reader.read(&self.left_aileron_green_actuator_position_demand_id), + )), + ], + [ + Self::aileron_actuator_position_from_surface_angle(Angle::new::( + reader.read(&self.right_aileron_blue_actuator_position_demand_id), + )), + Self::aileron_actuator_position_from_surface_angle(Angle::new::( + reader.read(&self.right_aileron_green_actuator_position_demand_id), + )), + ], + ); + + self.update_aileron_controllers_solenoids( + [ + reader.read(&self.left_aileron_blue_actuator_solenoid_id), + reader.read(&self.left_aileron_green_actuator_solenoid_id), + ], + [ + reader.read(&self.right_aileron_blue_actuator_solenoid_id), + reader.read(&self.right_aileron_green_actuator_solenoid_id), + ], + ); + } +} + +struct ElevatorSystemHydraulicController { + left_elevator_blue_actuator_solenoid_id: VariableIdentifier, + right_elevator_blue_actuator_solenoid_id: VariableIdentifier, + left_elevator_green_actuator_solenoid_id: VariableIdentifier, + right_elevator_yellow_actuator_solenoid_id: VariableIdentifier, + + left_elevator_blue_actuator_position_demand_id: VariableIdentifier, + right_elevator_blue_actuator_position_demand_id: VariableIdentifier, + left_elevator_green_actuator_position_demand_id: VariableIdentifier, + right_elevator_yellow_actuator_position_demand_id: VariableIdentifier, + + left_controllers: [AileronController; 2], + right_controllers: [AileronController; 2], +} +impl ElevatorSystemHydraulicController { + fn new(context: &mut InitContext) -> Self { + Self { + left_elevator_blue_actuator_solenoid_id: context + .get_identifier("LEFT_ELEV_BLUE_SERVO_SOLENOID_ENERGIZED".to_owned()), + right_elevator_blue_actuator_solenoid_id: context + .get_identifier("RIGHT_ELEV_BLUE_SERVO_SOLENOID_ENERGIZED".to_owned()), + left_elevator_green_actuator_solenoid_id: context + .get_identifier("LEFT_ELEV_GREEN_SERVO_SOLENOID_ENERGIZED".to_owned()), + right_elevator_yellow_actuator_solenoid_id: context + .get_identifier("RIGHT_ELEV_YELLOW_SERVO_SOLENOID_ENERGIZED".to_owned()), + + left_elevator_blue_actuator_position_demand_id: context + .get_identifier("LEFT_ELEV_BLUE_COMMANDED_POSITION".to_owned()), + right_elevator_blue_actuator_position_demand_id: context + .get_identifier("RIGHT_ELEV_BLUE_COMMANDED_POSITION".to_owned()), + left_elevator_green_actuator_position_demand_id: context + .get_identifier("LEFT_ELEV_GREEN_COMMANDED_POSITION".to_owned()), + right_elevator_yellow_actuator_position_demand_id: context + .get_identifier("RIGHT_ELEV_YELLOW_COMMANDED_POSITION".to_owned()), + + // Controllers are in outboard->inboard order + left_controllers: [AileronController::new(), AileronController::new()], + right_controllers: [AileronController::new(), AileronController::new()], + } + } + + fn left_controllers( + &self, + ) -> &[impl HydraulicAssemblyController + HydraulicLocking + ElectroHydrostaticPowered] { + &self.left_controllers[..] + } + + fn right_controllers( + &self, + ) -> &[impl HydraulicAssemblyController + HydraulicLocking + ElectroHydrostaticPowered] { + &self.right_controllers[..] + } + + fn update_elevator_controllers_positions( + &mut self, + left_position_requests: [Ratio; 2], + right_position_requests: [Ratio; 2], + ) { + self.left_controllers[LeftElevatorActuatorCircuit::Blue as usize].set_requested_position( + left_position_requests[LeftElevatorActuatorCircuit::Blue as usize], + ); + self.left_controllers[LeftElevatorActuatorCircuit::Green as usize].set_requested_position( + left_position_requests[LeftElevatorActuatorCircuit::Green as usize], + ); + + self.right_controllers[RightElevatorActuatorCircuit::Blue as usize].set_requested_position( + right_position_requests[RightElevatorActuatorCircuit::Blue as usize], + ); + self.right_controllers[RightElevatorActuatorCircuit::Yellow as usize] + .set_requested_position( + right_position_requests[RightElevatorActuatorCircuit::Yellow as usize], + ); + } + + fn update_elevator_controllers_solenoids( + &mut self, + left_solenoids_energized: [bool; 2], + right_solenoids_energized: [bool; 2], + ) { + if left_solenoids_energized.iter().all(|x| *x) { + for controller in &mut self.left_controllers { + controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + } else { + self.left_controllers[LeftElevatorActuatorCircuit::Blue as usize].set_mode( + Self::elevator_actuator_mode_from_solenoid( + left_solenoids_energized[LeftElevatorActuatorCircuit::Blue as usize], + ), + ); + self.left_controllers[LeftElevatorActuatorCircuit::Green as usize].set_mode( + Self::elevator_actuator_mode_from_solenoid( + left_solenoids_energized[LeftElevatorActuatorCircuit::Green as usize], + ), + ); + } + + if right_solenoids_energized.iter().all(|x| *x) { + for controller in &mut self.right_controllers { + controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + } else { + self.right_controllers[RightElevatorActuatorCircuit::Blue as usize].set_mode( + Self::elevator_actuator_mode_from_solenoid( + right_solenoids_energized[RightElevatorActuatorCircuit::Blue as usize], + ), + ); + self.right_controllers[RightElevatorActuatorCircuit::Yellow as usize].set_mode( + Self::elevator_actuator_mode_from_solenoid( + right_solenoids_energized[RightElevatorActuatorCircuit::Yellow as usize], + ), + ); + } + } + + fn elevator_actuator_mode_from_solenoid(solenoid_energized: bool) -> LinearActuatorMode { + // Elevator has reverted logic + if !solenoid_energized { + LinearActuatorMode::PositionControl + } else { + LinearActuatorMode::ActiveDamping + } + } + + fn elevator_actuator_position_from_surface_angle(surface_angle: Angle) -> Ratio { + Ratio::new::( + (-surface_angle.get::() / 47. + 17. / 47.) + .min(1.) + .max(0.), + ) + } +} +impl SimulationElement for ElevatorSystemHydraulicController { + fn read(&mut self, reader: &mut SimulatorReader) { + self.update_elevator_controllers_positions( + [ + Self::elevator_actuator_position_from_surface_angle(Angle::new::( + reader.read(&self.left_elevator_blue_actuator_position_demand_id), + )), + Self::elevator_actuator_position_from_surface_angle(Angle::new::( + reader.read(&self.left_elevator_green_actuator_position_demand_id), + )), + ], + [ + Self::elevator_actuator_position_from_surface_angle(Angle::new::( + reader.read(&self.right_elevator_blue_actuator_position_demand_id), + )), + Self::elevator_actuator_position_from_surface_angle(Angle::new::( + reader.read(&self.right_elevator_yellow_actuator_position_demand_id), + )), + ], + ); + + self.update_elevator_controllers_solenoids( + [ + reader.read(&self.left_elevator_blue_actuator_solenoid_id), + reader.read(&self.left_elevator_green_actuator_solenoid_id), + ], + [ + reader.read(&self.right_elevator_blue_actuator_solenoid_id), + reader.read(&self.right_elevator_yellow_actuator_solenoid_id), + ], + ); + } +} + +struct A320YawDamperController { + id_position_request: VariableIdentifier, + id_energized: VariableIdentifier, + + angle_demand: Angle, + is_solenoid_energized: bool, +} +impl A320YawDamperController { + fn new(context: &mut InitContext, hyd_circuit: HydraulicColor) -> Self { + Self { + id_position_request: context + .get_identifier(format!("YAW_DAMPER_{}_COMMANDED_POSITION", hyd_circuit)) + .to_owned(), + id_energized: context + .get_identifier(format!( + "YAW_DAMPER_{}_SERVO_SOLENOID_ENERGIZED", + hyd_circuit + )) + .to_owned(), + + angle_demand: Angle::default(), + is_solenoid_energized: false, + } + } +} +impl YawDamperActuatorController for A320YawDamperController { + fn is_solenoid_energized(&self) -> bool { + self.is_solenoid_energized + } + + fn angle_request(&self) -> Angle { + // Reversing angle as FBW negative is right, whereas in systems negative is left + -self.angle_demand + } +} +impl SimulationElement for A320YawDamperController { + fn read(&mut self, reader: &mut SimulatorReader) { + self.is_solenoid_energized = reader.read(&self.id_energized); + self.angle_demand = reader.read(&self.id_position_request); + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +enum RudderComponent { + Limiter, + Trim, +} +impl Display for RudderComponent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Limiter => write!(f, "TRAVEL_LIM"), + Self::Trim => write!(f, "TRIM"), + } + } +} + +struct A320RudderTrimTravelLimiterController { + id_active_mode_1: VariableIdentifier, + id_active_mode_2: VariableIdentifier, + id_commanded_position_1: VariableIdentifier, + id_commanded_position_2: VariableIdentifier, + + id_emergency_reset_1: Option, + id_emergency_reset_2: Option, + + commanded_position: [Angle; 2], + active_mode: [bool; 2], + is_emergency_reset: bool, + + component_type: RudderComponent, +} +impl A320RudderTrimTravelLimiterController { + fn new(context: &mut InitContext, component_type: RudderComponent) -> Self { + Self { + id_active_mode_1: context + .get_identifier(format!("RUDDER_{}_1_ACTIVE_MODE_COMMANDED", component_type)), + id_active_mode_2: context + .get_identifier(format!("RUDDER_{}_2_ACTIVE_MODE_COMMANDED", component_type)), + id_commanded_position_1: context + .get_identifier(format!("RUDDER_{}_1_COMMANDED_POSITION", component_type)), + id_commanded_position_2: context + .get_identifier(format!("RUDDER_{}_2_COMMANDED_POSITION", component_type)), + + id_emergency_reset_1: if component_type == RudderComponent::Limiter { + Some(context.get_identifier("FAC_1_RTL_EMER_RESET".to_owned())) + } else { + None + }, + id_emergency_reset_2: if component_type == RudderComponent::Limiter { + Some(context.get_identifier("FAC_2_RTL_EMER_RESET".to_owned())) + } else { + None + }, + + commanded_position: [Angle::default(); 2], + active_mode: [false; 2], + is_emergency_reset: false, + + component_type, + } + } +} +impl AngularPositioningController for A320RudderTrimTravelLimiterController { + fn commanded_position(&self) -> [Angle; 2] { + self.commanded_position + } + + fn energised_motor(&self) -> [bool; 2] { + self.active_mode + } + + fn emergency_reset_active(&self) -> bool { + self.is_emergency_reset + } +} +impl SimulationElement for A320RudderTrimTravelLimiterController { + fn read(&mut self, reader: &mut SimulatorReader) { + self.commanded_position[0] = reader.read(&self.id_commanded_position_1); + self.commanded_position[1] = reader.read(&self.id_commanded_position_2); + + // Trim commands from FBW is negative for right positive for left, opposite convention in hydraulics + if self.component_type == RudderComponent::Trim { + self.commanded_position = self.commanded_position.map(|p| -p); + } + + self.active_mode[0] = reader.read(&self.id_active_mode_1); + self.active_mode[1] = reader.read(&self.id_active_mode_2); + + // Only limiter has an emergency reset system + if self.component_type == RudderComponent::Limiter { + self.is_emergency_reset = reader.read(&self.id_emergency_reset_1.unwrap()) + || reader.read(&self.id_emergency_reset_2.unwrap()); + } + } +} + +struct RudderSystemHydraulicController { + rudder_pedal_control_input_id: VariableIdentifier, + rudder_pedal_position_id: VariableIdentifier, + + rudder_position_requested: Angle, + + yaw_damper_control: [A320YawDamperController; 2], + trim_control: A320RudderTrimTravelLimiterController, + travel_limiter_control: A320RudderTrimTravelLimiterController, + + rudder_mechanical_assembly: RudderMechanicalControl, + + green_press_control_avail: bool, + blue_press_control_avail: bool, + yellow_press_control_avail: bool, + + rudder_controllers: [AileronController; 3], +} +impl RudderSystemHydraulicController { + const RUDDER_MAX_TRAVEL_DEGREES: f64 = 50.; + + const MIN_PRESSURE_LO_HYST_FOR_ACTIVE_CONTROL_PSI: f64 = 700.; + const MIN_PRESSURE_HI_HYST_FOR_ACTIVE_CONTROL_PSI: f64 = 1200.; + + fn new(context: &mut InitContext) -> Self { + Self { + rudder_pedal_control_input_id: context + .get_identifier("RUDDER_PEDAL_POSITION".to_owned()), + rudder_pedal_position_id: context + .get_identifier("RUDDER_PEDAL_ANIMATION_POSITION".to_owned()), + + rudder_position_requested: Angle::default(), + + yaw_damper_control: [ + A320YawDamperController::new(context, HydraulicColor::Green), + A320YawDamperController::new(context, HydraulicColor::Yellow), + ], + trim_control: A320RudderTrimTravelLimiterController::new( + context, + RudderComponent::Trim, + ), + travel_limiter_control: A320RudderTrimTravelLimiterController::new( + context, + RudderComponent::Limiter, + ), + + rudder_mechanical_assembly: RudderMechanicalControl::new(context), + + green_press_control_avail: false, + blue_press_control_avail: false, + yellow_press_control_avail: false, + + // Controllers are in [ Green circuit, Blue circuit, Yellow circuit] order + rudder_controllers: [ + AileronController::new(), + AileronController::new(), + AileronController::new(), + ], + } + } + + fn ratio_demand_from_angle_demand(&self) -> Ratio { + Ratio::new::( + (self + .rudder_mechanical_assembly + .final_actuators_input() + .get::() + + Self::RUDDER_MAX_TRAVEL_DEGREES / 2.) + / Self::RUDDER_MAX_TRAVEL_DEGREES, + ) + } + + fn update_rudder_requested_position(&mut self) { + let final_ratio_demand = self.ratio_demand_from_angle_demand(); + + for controller in &mut self.rudder_controllers { + controller.set_requested_position(final_ratio_demand); + } + } + + fn set_rudder_no_position_control(&mut self) { + for controller in &mut self.rudder_controllers { + controller.set_mode(LinearActuatorMode::ClosedCircuitDamping); + } + } + + fn set_rudder_position_control(&mut self) { + if self.green_press_control_avail { + self.rudder_controllers[RudderActuatorPosition::Green as usize] + .set_mode(LinearActuatorMode::PositionControl); + } else { + self.rudder_controllers[RudderActuatorPosition::Green as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + } + + if self.blue_press_control_avail { + self.rudder_controllers[RudderActuatorPosition::Blue as usize] + .set_mode(LinearActuatorMode::PositionControl); + } else { + self.rudder_controllers[RudderActuatorPosition::Blue as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + } + + if self.yellow_press_control_avail { + self.rudder_controllers[RudderActuatorPosition::Yellow as usize] + .set_mode(LinearActuatorMode::PositionControl); + } else { + self.rudder_controllers[RudderActuatorPosition::Yellow as usize] + .set_mode(LinearActuatorMode::ActiveDamping); + } + } + + fn update_rudder_control_state(&mut self) { + if self.any_hyd_available() { + self.set_rudder_position_control(); + } else { + self.set_rudder_no_position_control(); + } + } + + fn any_hyd_available(&self) -> bool { + self.green_press_control_avail + || self.blue_press_control_avail + || self.yellow_press_control_avail + } + + fn update_pressure_availability( + &mut self, + green_pressure: &impl SectionPressure, + blue_pressure: &impl SectionPressure, + yellow_pressure: &impl SectionPressure, + ) { + if green_pressure.pressure_downstream_leak_valve().get::() + > Self::MIN_PRESSURE_HI_HYST_FOR_ACTIVE_CONTROL_PSI + { + self.green_press_control_avail = true; + } else if green_pressure.pressure_downstream_leak_valve().get::() + < Self::MIN_PRESSURE_LO_HYST_FOR_ACTIVE_CONTROL_PSI + { + self.green_press_control_avail = false; + } + + if blue_pressure.pressure_downstream_leak_valve().get::() + > Self::MIN_PRESSURE_HI_HYST_FOR_ACTIVE_CONTROL_PSI + { + self.blue_press_control_avail = true; + } else if blue_pressure.pressure_downstream_leak_valve().get::() + < Self::MIN_PRESSURE_LO_HYST_FOR_ACTIVE_CONTROL_PSI + { + self.blue_press_control_avail = false; + } + + if yellow_pressure + .pressure_downstream_leak_valve() + .get::() + > Self::MIN_PRESSURE_HI_HYST_FOR_ACTIVE_CONTROL_PSI + { + self.yellow_press_control_avail = true; + } else if yellow_pressure + .pressure_downstream_leak_valve() + .get::() + < Self::MIN_PRESSURE_LO_HYST_FOR_ACTIVE_CONTROL_PSI + { + self.yellow_press_control_avail = false; + } + } + + fn update( + &mut self, + context: &UpdateContext, + green_pressure: &impl SectionPressure, + blue_pressure: &impl SectionPressure, + yellow_pressure: &impl SectionPressure, + ) { + self.update_pressure_availability(green_pressure, blue_pressure, yellow_pressure); + + self.update_rudder_requested_position(); + + self.rudder_mechanical_assembly.update( + context, + self.rudder_position_requested, + &self.trim_control, + &self.travel_limiter_control, + &self.yaw_damper_control, + [ + green_pressure.pressure_downstream_leak_valve(), + yellow_pressure.pressure_downstream_leak_valve(), + ], + ); + + self.update_rudder_control_state(); + } + + fn rudder_controllers( + &self, + ) -> &[impl HydraulicAssemblyController + HydraulicLocking + ElectroHydrostaticPowered] { + &self.rudder_controllers[..] + } + + fn green_actuator(&mut self) -> &mut impl Actuator { + self.rudder_mechanical_assembly.yaw_damper_green_actuator() + } + + fn yellow_actuator(&mut self) -> &mut impl Actuator { + self.rudder_mechanical_assembly.yaw_damper_yellow_actuator() + } +} +impl SimulationElement for RudderSystemHydraulicController { + fn accept(&mut self, visitor: &mut T) { + accept_iterable!(self.yaw_damper_control, visitor); + self.trim_control.accept(visitor); + self.travel_limiter_control.accept(visitor); + self.rudder_mechanical_assembly.accept(visitor); + + visitor.visit(self); + } + + fn read(&mut self, reader: &mut SimulatorReader) { + let rudder_position_pedal_demand: Angle = reader.read(&self.rudder_pedal_control_input_id); + + self.rudder_position_requested = + rudder_position_pedal_demand * (Self::RUDDER_MAX_TRAVEL_DEGREES / 2. / 100.); + } + + fn write(&self, writer: &mut SimulatorWriter) { + let pedal_position = self + .rudder_mechanical_assembly + .pedal_position() + .get::() + * 100. + / (Self::RUDDER_MAX_TRAVEL_DEGREES / 2.); + writer.write(&self.rudder_pedal_position_id, pedal_position); + } +} + +#[derive(PartialEq, Clone, Copy)] +enum ActuatorSide { + Left, + Right, +} + +#[derive(PartialEq, Clone, Copy)] +enum AileronActuatorPosition { + Blue = 0, + Green = 1, +} + +enum RudderActuatorPosition { + Green = 0, + Blue = 1, + Yellow = 2, +} + +enum LeftElevatorActuatorCircuit { + Blue = 0, + Green = 1, +} + +enum RightElevatorActuatorCircuit { + Blue = 0, + Yellow = 1, +} + +struct AileronAssembly { + hydraulic_assembly: HydraulicLinearActuatorAssembly<2>, + + position_id: VariableIdentifier, + + position: Ratio, + + aerodynamic_model: AerodynamicModel, +} +impl AileronAssembly { + fn new( + context: &mut InitContext, + id: ActuatorSide, + hydraulic_assembly: HydraulicLinearActuatorAssembly<2>, + aerodynamic_model: AerodynamicModel, + ) -> Self { + Self { + hydraulic_assembly, + position_id: match id { + ActuatorSide::Left => context.get_identifier("HYD_AIL_LEFT_DEFLECTION".to_owned()), + ActuatorSide::Right => { + context.get_identifier("HYD_AIL_RIGHT_DEFLECTION".to_owned()) + } + }, + position: Ratio::new::(0.), + aerodynamic_model, + } + } + + fn actuator(&mut self, circuit_position: AileronActuatorPosition) -> &mut impl Actuator { + self.hydraulic_assembly.actuator(circuit_position as usize) + } + + fn update( + &mut self, + context: &UpdateContext, + aileron_controllers: &[impl HydraulicAssemblyController + + HydraulicLocking + + ElectroHydrostaticPowered], + current_pressure_outward: &impl SectionPressure, + current_pressure_inward: &impl SectionPressure, + ) { + self.aerodynamic_model + .update_body(context, self.hydraulic_assembly.body()); + self.hydraulic_assembly.update( + context, + aileron_controllers, + [ + current_pressure_outward.pressure_downstream_leak_valve(), + current_pressure_inward.pressure_downstream_leak_valve(), + ], + ); + + self.position = self.hydraulic_assembly.position_normalized(); + } +} +impl SimulationElement for AileronAssembly { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.position_id, self.position.get::()); + } +} + +struct ElevatorAssembly { + hydraulic_assembly: HydraulicLinearActuatorAssembly<2>, + + position_id: VariableIdentifier, + + position: Ratio, + + aerodynamic_model: AerodynamicModel, +} +impl ElevatorAssembly { + fn new( + context: &mut InitContext, + id: ActuatorSide, + hydraulic_assembly: HydraulicLinearActuatorAssembly<2>, + aerodynamic_model: AerodynamicModel, + ) -> Self { + Self { + hydraulic_assembly, + position_id: match id { + ActuatorSide::Left => context.get_identifier("HYD_ELEV_LEFT_DEFLECTION".to_owned()), + ActuatorSide::Right => { + context.get_identifier("HYD_ELEV_RIGHT_DEFLECTION".to_owned()) + } + }, + position: Ratio::new::(0.), + aerodynamic_model, + } + } + + fn actuator(&mut self, circuit_position: usize) -> &mut impl Actuator { + self.hydraulic_assembly.actuator(circuit_position) + } + + fn update( + &mut self, + context: &UpdateContext, + elevator_controllers: &[impl HydraulicAssemblyController + + HydraulicLocking + + ElectroHydrostaticPowered], + current_pressure_outward: &impl SectionPressure, + current_pressure_inward: &impl SectionPressure, + ths: &impl TrimmableHorizontalStabilizer, + ) { + self.hydraulic_assembly.set_trim_offset(ths.trim_angle()); + + self.aerodynamic_model + .update_body(context, self.hydraulic_assembly.body()); + self.hydraulic_assembly.update( + context, + elevator_controllers, + [ + current_pressure_outward.pressure_downstream_leak_valve(), + current_pressure_inward.pressure_downstream_leak_valve(), + ], + ); + + self.position = self.hydraulic_assembly.position_normalized(); + } +} +impl SimulationElement for ElevatorAssembly { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.position_id, self.position.get::()); + } +} + +struct RudderAssembly { + hydraulic_assembly: HydraulicLinearActuatorAssembly<3>, + name_id: VariableIdentifier, + + position: Ratio, + + aerodynamic_model: AerodynamicModel, +} +impl RudderAssembly { + fn new( + context: &mut InitContext, + hydraulic_assembly: HydraulicLinearActuatorAssembly<3>, + aerodynamic_model: AerodynamicModel, + ) -> Self { + Self { + hydraulic_assembly, + + name_id: context.get_identifier("HYD_RUD_DEFLECTION".to_owned()), + + position: Ratio::new::(0.5), + + aerodynamic_model, + } + } + + fn actuator(&mut self, circuit_position: RudderActuatorPosition) -> &mut impl Actuator { + self.hydraulic_assembly.actuator(circuit_position as usize) + } + + fn update( + &mut self, + context: &UpdateContext, + rudder_controllers: &[impl HydraulicAssemblyController + + HydraulicLocking + + ElectroHydrostaticPowered], + current_pressure_green: &impl SectionPressure, + current_pressure_blue: &impl SectionPressure, + current_pressure_yellow: &impl SectionPressure, + ) { + self.aerodynamic_model + .update_body(context, self.hydraulic_assembly.body()); + + self.hydraulic_assembly.update( + context, + rudder_controllers, + [ + current_pressure_green.pressure_downstream_leak_valve(), + current_pressure_blue.pressure_downstream_leak_valve(), + current_pressure_yellow.pressure_downstream_leak_valve(), + ], + ); + + self.position = self.hydraulic_assembly.position_normalized(); + } +} +impl SimulationElement for RudderAssembly { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.name_id, self.position.get::()); + } +} + +struct SpoilerElement { + hydraulic_assembly: HydraulicLinearActuatorAssembly<1>, + + position_id: VariableIdentifier, + + position: Ratio, + + aerodynamic_model: AerodynamicModel, +} +impl SpoilerElement { + fn new( + context: &mut InitContext, + id: ActuatorSide, + id_num: usize, + hydraulic_assembly: HydraulicLinearActuatorAssembly<1>, + aerodynamic_model: AerodynamicModel, + ) -> Self { + Self { + hydraulic_assembly, + position_id: match id { + ActuatorSide::Left => { + context.get_identifier(format!("HYD_SPOILER_{}_LEFT_DEFLECTION", id_num)) + } + ActuatorSide::Right => { + context.get_identifier(format!("HYD_SPOILER_{}_RIGHT_DEFLECTION", id_num)) + } + }, + position: Ratio::new::(0.), + aerodynamic_model, + } + } + + fn actuator(&mut self) -> &mut impl Actuator { + self.hydraulic_assembly.actuator(0) + } + + fn update( + &mut self, + context: &UpdateContext, + spoiler_controller: &(impl HydraulicAssemblyController + + HydraulicLocking + + ElectroHydrostaticPowered), + current_pressure: Pressure, + ) { + self.aerodynamic_model + .update_body(context, self.hydraulic_assembly.body()); + self.hydraulic_assembly.update( + context, + std::slice::from_ref(spoiler_controller), + [current_pressure], + ); + + self.position = self.hydraulic_assembly.position_normalized(); + } +} +impl SimulationElement for SpoilerElement { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.position_id, self.position.get::()); + } +} + +struct SpoilerGroup { + spoilers: [SpoilerElement; 5], + hydraulic_controllers: [SpoilerController; 5], +} +impl SpoilerGroup { + fn new(context: &mut InitContext, spoiler_side: &str, spoilers: [SpoilerElement; 5]) -> Self { + Self { + spoilers, + hydraulic_controllers: [ + SpoilerController::new(context, spoiler_side, 1), + SpoilerController::new(context, spoiler_side, 2), + SpoilerController::new(context, spoiler_side, 3), + SpoilerController::new(context, spoiler_side, 4), + SpoilerController::new(context, spoiler_side, 5), + ], + } + } + + fn update( + &mut self, + context: &UpdateContext, + green_section: &impl SectionPressure, + blue_section: &impl SectionPressure, + yellow_section: &impl SectionPressure, + ) { + self.spoilers[0].update( + context, + &self.hydraulic_controllers[0], + green_section.pressure_downstream_leak_valve(), + ); + self.spoilers[1].update( + context, + &self.hydraulic_controllers[1], + yellow_section.pressure_downstream_leak_valve(), + ); + self.spoilers[2].update( + context, + &self.hydraulic_controllers[2], + blue_section.pressure_downstream_leak_valve(), + ); + self.spoilers[3].update( + context, + &self.hydraulic_controllers[3], + yellow_section.pressure_downstream_leak_valve(), + ); + self.spoilers[4].update( + context, + &self.hydraulic_controllers[4], + green_section.pressure_downstream_leak_valve(), + ); + } + + fn actuator(&mut self, spoiler_id: usize) -> &mut impl Actuator { + self.spoilers[spoiler_id].actuator() + } +} +impl SimulationElement for SpoilerGroup { + fn accept(&mut self, visitor: &mut T) { + for controller in &mut self.hydraulic_controllers { + controller.accept(visitor); + } + + for spoiler in &mut self.spoilers { + spoiler.accept(visitor); + } + + visitor.visit(self); + } +} + +struct SpoilerController { + position_demand_id: VariableIdentifier, + requested_position: Ratio, +} +impl SpoilerController { + fn new(context: &mut InitContext, spoiler_side: &str, spoiler_id_number: usize) -> Self { + Self { + position_demand_id: context.get_identifier(format!( + "{}_SPOILER_{}_COMMANDED_POSITION", + spoiler_side, spoiler_id_number + )), + + requested_position: Ratio::new::(0.), + } + } + + fn spoiler_actuator_position_from_surface_angle(surface_angle: Angle) -> Ratio { + Ratio::new::((surface_angle.get::() / 50.).min(1.).max(0.)) + } +} +impl HydraulicAssemblyController for SpoilerController { + fn requested_mode(&self) -> LinearActuatorMode { + LinearActuatorMode::PositionControl + } + + fn requested_position(&self) -> Ratio { + self.requested_position + } + + fn should_lock(&self) -> bool { + false + } + + fn requested_lock_position(&self) -> Ratio { + Ratio::default() + } +} +impl SimulationElement for SpoilerController { + fn read(&mut self, reader: &mut SimulatorReader) { + self.requested_position = + Self::spoiler_actuator_position_from_surface_angle(Angle::new::( + reader.read(&self.position_demand_id), + )); + } +} +impl HydraulicLocking for SpoilerController {} +impl ElectroHydrostaticPowered for SpoilerController {} + +struct A320GravityExtension { + gear_gravity_extension_handle_position_id: VariableIdentifier, + + handle_angle: Angle, +} +impl A320GravityExtension { + fn new(context: &mut InitContext) -> Self { + Self { + gear_gravity_extension_handle_position_id: context + .get_identifier("GRAVITYGEAR_ROTATE_PCT".to_owned()), + + handle_angle: Angle::default(), + } + } +} +impl GearGravityExtension for A320GravityExtension { + fn extension_handle_number_of_turns(&self) -> u8 { + (self.handle_angle.get::() / 360.).floor() as u8 + } +} +impl SimulationElement for A320GravityExtension { + fn read(&mut self, reader: &mut SimulatorReader) { + let handle_percent: f64 = reader.read(&self.gear_gravity_extension_handle_position_id); + + self.handle_angle = Angle::new::(handle_percent * 3.6) + .max(Angle::new::(0.)) + .min(Angle::new::(360. * 3.)); + } +} + +struct A320TrimInputController { + motor1_active_id: VariableIdentifier, + motor2_active_id: VariableIdentifier, + motor3_active_id: VariableIdentifier, + + motor1_position_id: VariableIdentifier, + motor2_position_id: VariableIdentifier, + motor3_position_id: VariableIdentifier, + + manual_control_active_id: VariableIdentifier, + manual_control_speed_id: VariableIdentifier, + + motor_active: [bool; 3], + motor_position: [Angle; 3], + + manual_control: bool, + manual_control_speed: AngularVelocity, +} +impl A320TrimInputController { + fn new(context: &mut InitContext) -> Self { + Self { + motor1_active_id: context.get_identifier("THS_1_ACTIVE_MODE_COMMANDED".to_owned()), + motor2_active_id: context.get_identifier("THS_2_ACTIVE_MODE_COMMANDED".to_owned()), + motor3_active_id: context.get_identifier("THS_3_ACTIVE_MODE_COMMANDED".to_owned()), + + motor1_position_id: context.get_identifier("THS_1_COMMANDED_POSITION".to_owned()), + motor2_position_id: context.get_identifier("THS_2_COMMANDED_POSITION".to_owned()), + motor3_position_id: context.get_identifier("THS_3_COMMANDED_POSITION".to_owned()), + + manual_control_active_id: context + .get_identifier("THS_MANUAL_CONTROL_ACTIVE".to_owned()), + manual_control_speed_id: context.get_identifier("THS_MANUAL_CONTROL_SPEED".to_owned()), + + motor_active: [false; 3], + motor_position: [Angle::default(); 3], + + manual_control: false, + manual_control_speed: AngularVelocity::default(), + } + } +} +impl PitchTrimActuatorController for A320TrimInputController { + fn commanded_position(&self) -> Angle { + for (idx, motor_active) in self.motor_active.iter().enumerate() { + if *motor_active { + return self.motor_position[idx]; + } + } + + Angle::default() + } + + fn energised_motor(&self) -> [bool; 3] { + self.motor_active + } +} +impl ManualPitchTrimController for A320TrimInputController { + fn is_manually_moved(&self) -> bool { + self.manual_control || self.manual_control_speed.get::() != 0. + } + + fn moving_speed(&self) -> AngularVelocity { + self.manual_control_speed + } +} +impl SimulationElement for A320TrimInputController { + fn read(&mut self, reader: &mut SimulatorReader) { + self.motor_active[0] = reader.read(&self.motor1_active_id); + self.motor_active[1] = reader.read(&self.motor2_active_id); + self.motor_active[2] = reader.read(&self.motor3_active_id); + + self.motor_position[0] = reader.read(&self.motor1_position_id); + self.motor_position[1] = reader.read(&self.motor2_position_id); + self.motor_position[2] = reader.read(&self.motor3_position_id); + + self.manual_control = reader.read(&self.manual_control_active_id); + self.manual_control_speed = reader.read(&self.manual_control_speed_id); + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +enum ReverserControlState { + StowedOff, + StowedOn, + TransitOpening, + TransitClosing, + FullyOpened, +} + +struct A320ReverserController { + throttle_lever_angle_id: VariableIdentifier, + + throttle_lever_angle: Angle, + + state: ReverserControlState, + + tertiary_lock_from_sec_should_unlock: DelayedFalseLogicGate, +} +impl A320ReverserController { + fn new(context: &mut InitContext, engine_number: usize) -> Self { + Self { + throttle_lever_angle_id: context + .get_identifier(format!("AUTOTHRUST_TLA:{}", engine_number)), + + throttle_lever_angle: Angle::default(), + state: ReverserControlState::StowedOff, + + tertiary_lock_from_sec_should_unlock: DelayedFalseLogicGate::new(Duration::from_secs( + 40, + )), + } + } + + fn update( + &mut self, + context: &UpdateContext, + engine: &impl Engine, + lgciu: &impl LgciuWeightOnWheels, + reverser_feedback: &impl ReverserFeedback, + ) { + let is_confirmed_stowed_available_for_deploy = + reverser_feedback.position_sensor().get::() <= 0.1 + && reverser_feedback.proximity_sensor_all_stowed() + && !reverser_feedback.pressure_switch_pressurised(); + + let deploy_authorized = engine.corrected_n2().get::() > 50. + && lgciu.left_and_right_gear_compressed(false); + + let allow_power_to_valves = self.throttle_lever_angle.get::() <= -3.8 + && lgciu.left_and_right_gear_compressed(false); + + let allow_isolation_valve_to_open = self.throttle_lever_angle.get::() <= -4.3; + + let allow_directional_valve_to_deploy = + allow_isolation_valve_to_open && reverser_feedback.pressure_switch_pressurised(); + + // TODO: this should come from a SEC output, this is a placeholder of the SEC output + self.tertiary_lock_from_sec_should_unlock + .update(context, self.throttle_lever_angle.get::() <= -3.); + + self.state = match self.state { + ReverserControlState::StowedOff => { + if deploy_authorized + && is_confirmed_stowed_available_for_deploy + && allow_power_to_valves + { + ReverserControlState::StowedOn + } else { + self.state + } + } + ReverserControlState::StowedOn => { + if !allow_power_to_valves { + ReverserControlState::StowedOff + } else if allow_isolation_valve_to_open { + ReverserControlState::TransitOpening + } else { + self.state + } + } + ReverserControlState::TransitOpening => { + if reverser_feedback.proximity_sensor_all_deployed() { + ReverserControlState::FullyOpened + } else if !deploy_authorized || !allow_power_to_valves { + ReverserControlState::TransitClosing + } else { + self.state + } + } + ReverserControlState::TransitClosing => { + if reverser_feedback.proximity_sensor_all_stowed() { + ReverserControlState::StowedOff + } else if allow_directional_valve_to_deploy && deploy_authorized { + ReverserControlState::TransitOpening + } else { + self.state + } + } + ReverserControlState::FullyOpened => { + if !deploy_authorized || !allow_power_to_valves { + ReverserControlState::TransitClosing + } else { + self.state + } + } + }; + } +} +impl SimulationElement for A320ReverserController { + fn read(&mut self, reader: &mut SimulatorReader) { + self.throttle_lever_angle = reader.read(&self.throttle_lever_angle_id); + } +} +impl ReverserInterface for A320ReverserController { + fn should_unlock(&self) -> bool { + self.tertiary_lock_from_sec_should_unlock.output() + } + + fn should_power_valves(&self) -> bool { + self.state != ReverserControlState::StowedOff + } + + fn should_isolate_hydraulics(&self) -> bool { + self.state == ReverserControlState::StowedOff + || self.state == ReverserControlState::StowedOn + } + + fn should_deploy_reverser(&self) -> bool { + self.state == ReverserControlState::TransitOpening + || self.state == ReverserControlState::FullyOpened + } +} +impl Debug for A320ReverserController { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "\nREV Controller => STATE: {:?}/ should_unlock {:?}/ should_power_valves {:?} / should_isolate_hydraulics {:?} / should_deploy_reverser{:?}", + self.state, self.should_unlock(), + self.should_power_valves(), + self.should_isolate_hydraulics(),self.should_deploy_reverser(), + ) + } +} + +struct A320Reversers { + reverser_1_position_id: VariableIdentifier, + reverser_2_position_id: VariableIdentifier, + + reverser_1_in_transition_id: VariableIdentifier, + reverser_2_in_transition_id: VariableIdentifier, + + reverser_1_deployed_id: VariableIdentifier, + reverser_2_deployed_id: VariableIdentifier, + + reversers: [ReverserAssembly; 2], + + reversers_in_transition: [bool; 2], + reversers_deployed: [bool; 2], +} +impl A320Reversers { + // TODO Check busses and power, placeholder only for now + const REVERSER_1_SUPPLY_POWER_BUS: ElectricalBusType = ElectricalBusType::AlternatingCurrent(1); + const REVERSER_2_SUPPLY_POWER_BUS: ElectricalBusType = ElectricalBusType::AlternatingCurrent(2); + + const REVERSER_1_PRIMARY_VALVES_SUPPLY_POWER_BUS: ElectricalBusType = + ElectricalBusType::DirectCurrent(1); + const REVERSER_1_SECONDARY_VALVES_SUPPLY_POWER_BUS: ElectricalBusType = + ElectricalBusType::DirectCurrent(2); + + const REVERSER_2_PRIMARY_VALVES_SUPPLY_POWER_BUS: ElectricalBusType = + ElectricalBusType::DirectCurrent(1); + const REVERSER_2_SECONDARY_VALVES_SUPPLY_POWER_BUS: ElectricalBusType = + ElectricalBusType::DirectCurrent(2); + + fn new(context: &mut InitContext) -> Self { + Self { + reverser_1_position_id: context.get_identifier("REVERSER_1_POSITION".to_owned()), + reverser_2_position_id: context.get_identifier("REVERSER_2_POSITION".to_owned()), + + reverser_1_in_transition_id: context.get_identifier("REVERSER_1_DEPLOYING".to_owned()), + reverser_2_in_transition_id: context.get_identifier("REVERSER_2_DEPLOYING".to_owned()), + + reverser_1_deployed_id: context.get_identifier("REVERSER_1_DEPLOYED".to_owned()), + reverser_2_deployed_id: context.get_identifier("REVERSER_2_DEPLOYED".to_owned()), + + reversers: [ + ReverserAssembly::new( + Pressure::new::( + A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI, + ), + Pressure::new::( + A320HydraulicCircuitFactory::MIN_PRESS_PRESSURISED_HI_HYST, + ), + Pressure::new::( + A320HydraulicCircuitFactory::MIN_PRESS_PRESSURISED_LO_HYST, + ), + Self::REVERSER_1_SUPPLY_POWER_BUS, + Self::REVERSER_1_PRIMARY_VALVES_SUPPLY_POWER_BUS, + Self::REVERSER_1_SECONDARY_VALVES_SUPPLY_POWER_BUS, + ), + ReverserAssembly::new( + Pressure::new::( + A320HydraulicCircuitFactory::HYDRAULIC_TARGET_PRESSURE_PSI, + ), + Pressure::new::( + A320HydraulicCircuitFactory::MIN_PRESS_PRESSURISED_HI_HYST, + ), + Pressure::new::( + A320HydraulicCircuitFactory::MIN_PRESS_PRESSURISED_LO_HYST, + ), + Self::REVERSER_2_SUPPLY_POWER_BUS, + Self::REVERSER_2_PRIMARY_VALVES_SUPPLY_POWER_BUS, + Self::REVERSER_2_SECONDARY_VALVES_SUPPLY_POWER_BUS, + ), + ], + reversers_in_transition: [false, false], + reversers_deployed: [false, false], + } + } + + fn update( + &mut self, + context: &UpdateContext, + reverser_controllers: &[impl ReverserInterface; 2], + left_reverser_section: &impl SectionPressure, + right_reverser_section: &impl SectionPressure, + ) { + self.reversers[0].update( + context, + &reverser_controllers[0], + left_reverser_section.pressure(), + ); + + self.reversers[1].update( + context, + &reverser_controllers[1], + right_reverser_section.pressure(), + ); + + self.update_sensors_state(); + } + + fn update_sensors_state(&mut self) { + for (idx, reverser) in self.reversers.iter().enumerate() { + self.reversers_deployed[idx] = reverser.proximity_sensor_all_deployed(); + + self.reversers_in_transition[idx] = !reverser.proximity_sensor_all_deployed() + && !reverser.proximity_sensor_all_stowed(); + } + } + + fn reverser_feedback(&self, reverser_index: usize) -> &impl ReverserFeedback { + &self.reversers[reverser_index] + } + + fn reversers_position(&self) -> &[impl ReverserPosition] { + &self.reversers[..] + } + + fn yellow_actuator(&mut self) -> &mut impl Actuator { + self.reversers[1].actuator() + } + + fn green_actuator(&mut self) -> &mut impl Actuator { + self.reversers[0].actuator() + } +} +impl SimulationElement for A320Reversers { + fn accept(&mut self, visitor: &mut T) { + accept_iterable!(self.reversers, visitor); + + visitor.visit(self); + } + + fn write(&self, writer: &mut SimulatorWriter) { + writer.write( + &self.reverser_1_position_id, + self.reversers[0].reverser_position().get::(), + ); + writer.write( + &self.reverser_2_position_id, + self.reversers[1].reverser_position().get::(), + ); + + writer.write( + &self.reverser_1_in_transition_id, + self.reversers_in_transition[0], + ); + writer.write( + &self.reverser_2_in_transition_id, + self.reversers_in_transition[1], + ); + + writer.write(&self.reverser_1_deployed_id, self.reversers_deployed[0]); + writer.write(&self.reverser_2_deployed_id, self.reversers_deployed[1]); + } +} + +struct A330TiltingGears { + left_body_gear: TiltingGear, + right_body_gear: TiltingGear, +} +impl A330TiltingGears { + fn new(left_body_gear: TiltingGear, right_body_gear: TiltingGear) -> Self { + Self { + left_body_gear, + right_body_gear, + } + } + + fn update(&mut self, context: &UpdateContext) { + self.left_body_gear.update(context); + self.right_body_gear.update(context); + } +} +impl SimulationElement for A330TiltingGears { + fn accept(&mut self, visitor: &mut T) { + self.left_body_gear.accept(visitor); + self.right_body_gear.accept(visitor); + + visitor.visit(self); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod a320_hydraulics { + use super::*; + use systems::{ + electrical::{ + test::TestElectricitySource, ElectricalBus, Electricity, ElectricitySource, + ExternalPowerSource, + }, + engine::{trent_engine::TrentEngine, EngineFireOverheadPanel}, + failures::FailureType, + hydraulic::{ + cargo_doors::{DoorControlState, HydraulicDoorController}, + electrical_generator::TestGenerator, + }, + landing_gear::{GearSystemState, LandingGear, LandingGearControlInterfaceUnitSet}, + shared::{ + EmergencyElectricalState, EmergencyGeneratorControlUnit, LgciuId, PotentialOrigin, + }, + simulation::{ + test::{ReadByName, SimulationTestBed, TestBed, WriteByName}, + Aircraft, InitContext, + }, + }; + + use uom::si::{ + angle::degree, + angle::radian, + electric_potential::volt, + length::foot, + mass_density::kilogram_per_cubic_meter, + ratio::{percent, ratio}, + volume::liter, + }; + + struct A320TestEmergencyElectricalOverheadPanel { + rat_and_emer_gen_man_on: MomentaryPushButton, + } + + impl A320TestEmergencyElectricalOverheadPanel { + pub fn new(context: &mut InitContext) -> Self { + A320TestEmergencyElectricalOverheadPanel { + rat_and_emer_gen_man_on: MomentaryPushButton::new( + context, + "EMER_ELEC_RAT_AND_EMER_GEN", + ), + } + } + } + impl SimulationElement for A320TestEmergencyElectricalOverheadPanel { + fn accept(&mut self, visitor: &mut T) { + self.rat_and_emer_gen_man_on.accept(visitor); + + visitor.visit(self); + } + } + impl EmergencyElectricalRatPushButton for A320TestEmergencyElectricalOverheadPanel { + fn is_pressed(&self) -> bool { + self.rat_and_emer_gen_man_on.is_pressed() + } + } + + #[derive(Default)] + struct A320TestAdirus { + airspeed: Velocity, + } + impl A320TestAdirus { + fn update(&mut self, context: &UpdateContext) { + self.airspeed = context.true_airspeed() + } + } + impl AdirsDiscreteOutputs for A320TestAdirus { + fn low_speed_warning_1_104kts(&self, _: usize) -> bool { + self.airspeed.get::() > 104. + } + + fn low_speed_warning_2_54kts(&self, _: usize) -> bool { + self.airspeed.get::() > 54. + } + + fn low_speed_warning_3_159kts(&self, _: usize) -> bool { + self.airspeed.get::() > 159. + } + + fn low_speed_warning_4_260kts(&self, _: usize) -> bool { + self.airspeed.get::() > 260. + } + } + + struct A320TestPneumatics { + pressure: Pressure, + } + impl A320TestPneumatics { + pub fn new() -> Self { + Self { + pressure: Pressure::new::(50.), + } + } + + fn set_nominal_air_pressure(&mut self) { + self.pressure = Pressure::new::(50.); + } + + fn set_low_air_pressure(&mut self) { + self.pressure = Pressure::new::(1.); + } + } + impl ReservoirAirPressure for A320TestPneumatics { + fn green_reservoir_pressure(&self) -> Pressure { + self.pressure + } + + fn blue_reservoir_pressure(&self) -> Pressure { + self.pressure + } + + fn yellow_reservoir_pressure(&self) -> Pressure { + self.pressure + } + } + + struct A320TestElectrical { + airspeed: Velocity, + all_ac_lost: bool, + emergency_generator: TestGenerator, + } + impl A320TestElectrical { + pub fn new() -> Self { + A320TestElectrical { + airspeed: Velocity::new::(100.), + all_ac_lost: false, + emergency_generator: TestGenerator::default(), + } + } + + fn update( + &mut self, + gcu: &impl EmergencyGeneratorControlUnit, + context: &UpdateContext, + ) { + self.airspeed = context.indicated_airspeed(); + self.emergency_generator.update(gcu); + } + } + impl EmergencyElectricalState for A320TestElectrical { + fn is_in_emergency_elec(&self) -> bool { + self.all_ac_lost && self.airspeed >= Velocity::new::(100.) + } + } + impl EmergencyGeneratorPower for A320TestElectrical { + fn generated_power(&self) -> Power { + self.emergency_generator.generated_power() + } + } + impl SimulationElement for A320TestElectrical { + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.all_ac_lost = !buses.is_powered(ElectricalBusType::AlternatingCurrent(1)) + && !buses.is_powered(ElectricalBusType::AlternatingCurrent(2)); + } + } + struct A320HydraulicsTestAircraft { + pneumatics: A320TestPneumatics, + engine_1: TrentEngine, + engine_2: TrentEngine, + hydraulics: A320Hydraulic, + overhead: A320HydraulicOverheadPanel, + autobrake_panel: AutobrakePanel, + emergency_electrical_overhead: A320TestEmergencyElectricalOverheadPanel, + engine_fire_overhead: EngineFireOverheadPanel<2>, + landing_gear: LandingGear, + lgcius: LandingGearControlInterfaceUnitSet, + adirus: A320TestAdirus, + electrical: A320TestElectrical, + ext_pwr: ExternalPowerSource, + + powered_source_ac: TestElectricitySource, + ac_ground_service_bus: ElectricalBus, + dc_ground_service_bus: ElectricalBus, + ac_1_bus: ElectricalBus, + ac_2_bus: ElectricalBus, + dc_1_bus: ElectricalBus, + dc_2_bus: ElectricalBus, + dc_ess_bus: ElectricalBus, + dc_hot_1_bus: ElectricalBus, + dc_hot_2_bus: ElectricalBus, + + // Electric buses states to be able to kill them dynamically + is_ac_ground_service_powered: bool, + is_dc_ground_service_powered: bool, + is_ac_1_powered: bool, + is_ac_2_powered: bool, + is_dc_1_powered: bool, + is_dc_2_powered: bool, + is_dc_ess_powered: bool, + is_dc_hot_1_powered: bool, + is_dc_hot_2_powered: bool, + } + impl A320HydraulicsTestAircraft { + fn new(context: &mut InitContext) -> Self { + Self { + pneumatics: A320TestPneumatics::new(), + engine_1: TrentEngine::new(context, 1), + engine_2: TrentEngine::new(context, 2), + hydraulics: A320Hydraulic::new(context), + overhead: A320HydraulicOverheadPanel::new(context), + autobrake_panel: AutobrakePanel::new(context), + emergency_electrical_overhead: A320TestEmergencyElectricalOverheadPanel::new( + context, + ), + engine_fire_overhead: EngineFireOverheadPanel::new(context), + landing_gear: LandingGear::new(context), + lgcius: LandingGearControlInterfaceUnitSet::new( + context, + ElectricalBusType::DirectCurrentEssential, + ElectricalBusType::DirectCurrentGndFltService, + ), + adirus: A320TestAdirus::default(), + electrical: A320TestElectrical::new(), + ext_pwr: ExternalPowerSource::new(context, 1), + powered_source_ac: TestElectricitySource::powered( + context, + PotentialOrigin::EngineGenerator(1), + ), + ac_ground_service_bus: ElectricalBus::new( + context, + ElectricalBusType::AlternatingCurrentGndFltService, + ), + dc_ground_service_bus: ElectricalBus::new( + context, + ElectricalBusType::DirectCurrentGndFltService, + ), + ac_1_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(1)), + ac_2_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(2)), + dc_1_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(1)), + dc_2_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(2)), + dc_ess_bus: ElectricalBus::new( + context, + ElectricalBusType::DirectCurrentEssential, + ), + dc_hot_1_bus: ElectricalBus::new( + context, + ElectricalBusType::DirectCurrentHot(1), + ), + dc_hot_2_bus: ElectricalBus::new( + context, + ElectricalBusType::DirectCurrentHot(2), + ), + is_ac_ground_service_powered: true, + is_dc_ground_service_powered: true, + is_ac_1_powered: true, + is_ac_2_powered: true, + is_dc_1_powered: true, + is_dc_2_powered: true, + is_dc_ess_powered: true, + is_dc_hot_1_powered: true, + is_dc_hot_2_powered: true, + } + } + + fn is_rat_commanded_to_deploy(&self) -> bool { + self.hydraulics.ram_air_turbine_controller.should_deploy() + } + + fn is_emergency_gen_at_nominal_speed(&self) -> bool { + self.hydraulics.gcu.is_at_nominal_speed() + } + + fn is_green_edp_commanded_on(&self) -> bool { + self.hydraulics + .engine_driven_pump_1_controller + .should_pressurise() + } + + fn is_yellow_edp_commanded_on(&self) -> bool { + self.hydraulics + .engine_driven_pump_2_controller + .should_pressurise() + } + + fn get_yellow_brake_accumulator_fluid_volume(&self) -> Volume { + self.hydraulics + .braking_circuit_altn + .accumulator_fluid_volume() + } + + fn is_nws_pin_inserted(&self) -> bool { + self.hydraulics.nose_wheel_steering_pin_is_inserted() + } + + fn is_cargo_powering_yellow_epump(&self) -> bool { + self.hydraulics + .should_pressurise_yellow_pump_for_cargo_door_operation() + } + + fn is_yellow_epump_controller_pressurising(&self) -> bool { + self.hydraulics + .yellow_electric_pump_controller + .should_pressurise() + } + + fn is_blue_epump_controller_pressurising(&self) -> bool { + self.hydraulics + .blue_electric_pump_controller + .should_pressurise() + } + + fn is_edp1_green_pump_controller_pressurising(&self) -> bool { + self.hydraulics + .engine_driven_pump_1_controller + .should_pressurise() + } + + fn is_edp2_yellow_pump_controller_pressurising(&self) -> bool { + self.hydraulics + .engine_driven_pump_2_controller + .should_pressurise() + } + + fn is_ptu_controller_activating_ptu(&self) -> bool { + self.hydraulics + .power_transfer_unit_controller + .should_enable() + } + + fn is_ptu_enabled(&self) -> bool { + self.hydraulics.power_transfer_unit.is_enabled() + } + + fn is_blue_pressure_switch_pressurised(&self) -> bool { + self.hydraulics.is_blue_pressure_switch_pressurised() + } + + fn is_green_pressure_switch_pressurised(&self) -> bool { + self.hydraulics.is_green_pressure_switch_pressurised() + } + + fn is_yellow_pressure_switch_pressurised(&self) -> bool { + self.hydraulics.is_yellow_pressure_switch_pressurised() + } + + fn is_yellow_leak_meas_valve_commanded_open(&self) -> bool { + self.hydraulics + .yellow_circuit_controller + .should_open_leak_measurement_valve() + } + + fn is_blue_leak_meas_valve_commanded_open(&self) -> bool { + self.hydraulics + .blue_circuit_controller + .should_open_leak_measurement_valve() + } + + fn is_green_leak_meas_valve_commanded_open(&self) -> bool { + self.hydraulics + .green_circuit_controller + .should_open_leak_measurement_valve() + } + + fn nose_steering_position(&self) -> Angle { + self.hydraulics.nose_steering.position_feedback() + } + + fn is_cargo_fwd_door_locked_up(&self) -> bool { + self.hydraulics + .forward_cargo_door_controller + .control_state() + == DoorControlState::UpLocked + } + + fn set_ac_bus_1_is_powered(&mut self, bus_is_alive: bool) { + self.is_ac_1_powered = bus_is_alive; + } + + fn set_ac_bus_2_is_powered(&mut self, bus_is_alive: bool) { + self.is_ac_2_powered = bus_is_alive; + } + + fn set_dc_ground_service_is_powered(&mut self, bus_is_alive: bool) { + self.is_dc_ground_service_powered = bus_is_alive; + } + + fn set_ac_ground_service_is_powered(&mut self, bus_is_alive: bool) { + self.is_ac_ground_service_powered = bus_is_alive; + } + + fn set_dc_bus_2_is_powered(&mut self, bus_is_alive: bool) { + self.is_dc_2_powered = bus_is_alive; + } + + fn set_dc_ess_is_powered(&mut self, bus_is_alive: bool) { + self.is_dc_ess_powered = bus_is_alive; + } + + fn use_worst_case_ptu(&mut self) { + self.hydraulics.power_transfer_unit.update_characteristics( + &A320PowerTransferUnitCharacteristics::new_worst_part_acceptable(), + ); + } + } + + impl Aircraft for A320HydraulicsTestAircraft { + fn update_before_power_distribution( + &mut self, + _: &UpdateContext, + electricity: &mut Electricity, + ) { + self.powered_source_ac + .power_with_potential(ElectricPotential::new::(115.)); + electricity.supplied_by(&self.powered_source_ac); + + if self.is_ac_1_powered { + electricity.flow(&self.powered_source_ac, &self.ac_1_bus); + } + + if self.is_ac_2_powered { + electricity.flow(&self.powered_source_ac, &self.ac_2_bus); + } + + if self.is_ac_ground_service_powered { + electricity.flow(&self.powered_source_ac, &self.ac_ground_service_bus); + } + + if self.is_dc_ground_service_powered { + electricity.flow(&self.powered_source_ac, &self.dc_ground_service_bus); + } + + if self.is_dc_1_powered { + electricity.flow(&self.powered_source_ac, &self.dc_1_bus); + } + + if self.is_dc_2_powered { + electricity.flow(&self.powered_source_ac, &self.dc_2_bus); + } + + if self.is_dc_ess_powered { + electricity.flow(&self.powered_source_ac, &self.dc_ess_bus); + } + + if self.is_dc_hot_1_powered { + electricity.flow(&self.powered_source_ac, &self.dc_hot_1_bus); + } + + if self.is_dc_hot_2_powered { + electricity.flow(&self.powered_source_ac, &self.dc_hot_2_bus); + } + } + + fn update_after_power_distribution(&mut self, context: &UpdateContext) { + self.electrical.update(&self.hydraulics.gcu, context); + + self.adirus.update(context); + + self.lgcius.update( + context, + &self.landing_gear, + &self.hydraulics.gear_system, + self.ext_pwr.output_potential().is_powered(), + ); + + self.hydraulics.update( + context, + &self.engine_1, + &self.engine_2, + &self.overhead, + &self.autobrake_panel, + &self.engine_fire_overhead, + &self.lgcius, + &self.emergency_electrical_overhead, + &self.electrical, + &self.pneumatics, + &self.adirus, + ); + + self.overhead.update(&self.hydraulics); + } + } + impl SimulationElement for A320HydraulicsTestAircraft { + fn accept(&mut self, visitor: &mut T) { + self.engine_1.accept(visitor); + self.engine_2.accept(visitor); + self.landing_gear.accept(visitor); + self.lgcius.accept(visitor); + self.hydraulics.accept(visitor); + self.autobrake_panel.accept(visitor); + self.overhead.accept(visitor); + self.engine_fire_overhead.accept(visitor); + self.emergency_electrical_overhead.accept(visitor); + self.electrical.accept(visitor); + self.ext_pwr.accept(visitor); + + visitor.visit(self); + } + } + + struct A320HydraulicsTestBed { + test_bed: SimulationTestBed, + } + impl A320HydraulicsTestBed { + fn new_with_start_state(start_state: StartState) -> Self { + Self { + test_bed: SimulationTestBed::new_with_start_state( + start_state, + A320HydraulicsTestAircraft::new, + ), + } + } + + fn run_one_tick(mut self) -> Self { + self.run_with_delta(A320Hydraulic::HYDRAULIC_SIM_TIME_STEP); + self + } + + fn run_waiting_for(mut self, delta: Duration) -> Self { + self.test_bed.run_multiple_frames(delta); + self + } + + fn is_green_edp_commanded_on(&self) -> bool { + self.query(|a| a.is_green_edp_commanded_on()) + } + + fn is_yellow_edp_commanded_on(&self) -> bool { + self.query(|a| a.is_yellow_edp_commanded_on()) + } + + fn is_ptu_enabled(&self) -> bool { + self.query(|a| a.is_ptu_enabled()) + } + + fn is_blue_pressure_switch_pressurised(&self) -> bool { + self.query(|a| a.is_blue_pressure_switch_pressurised()) + } + + fn is_green_pressure_switch_pressurised(&self) -> bool { + self.query(|a| a.is_green_pressure_switch_pressurised()) + } + + fn is_yellow_pressure_switch_pressurised(&self) -> bool { + self.query(|a| a.is_yellow_pressure_switch_pressurised()) + } + + fn is_flaps_moving(&mut self) -> bool { + self.read_by_name("IS_FLAPS_MOVING") + } + + fn is_slats_moving(&mut self) -> bool { + self.read_by_name("IS_SLATS_MOVING") + } + + fn nose_steering_position(&self) -> Angle { + self.query(|a| a.nose_steering_position()) + } + + fn is_cargo_fwd_door_locked_down(&mut self) -> bool { + self.read_by_name("FWD_DOOR_CARGO_LOCKED") + } + + fn is_cargo_fwd_door_locked_up(&self) -> bool { + self.query(|a| a.is_cargo_fwd_door_locked_up()) + } + + fn cargo_fwd_door_position(&mut self) -> f64 { + self.read_by_name("FWD_DOOR_CARGO_POSITION") + } + + fn cargo_aft_door_position(&mut self) -> f64 { + self.read_by_name("AFT_DOOR_CARGO_POSITION") + } + + fn green_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_GREEN_SYSTEM_1_SECTION_PRESSURE") + } + + fn blue_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_BLUE_SYSTEM_1_SECTION_PRESSURE") + } + + fn yellow_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_YELLOW_SYSTEM_1_SECTION_PRESSURE") + } + + fn get_yellow_reservoir_volume(&mut self) -> Volume { + self.read_by_name("HYD_YELLOW_RESERVOIR_LEVEL") + } + + fn is_green_edp_press_low(&mut self) -> bool { + self.read_by_name("HYD_GREEN_EDPUMP_LOW_PRESS") + } + + fn green_edp_has_fault(&mut self) -> bool { + self.read_by_name("OVHD_HYD_ENG_1_PUMP_PB_HAS_FAULT") + } + + fn yellow_edp_has_fault(&mut self) -> bool { + self.read_by_name("OVHD_HYD_ENG_2_PUMP_PB_HAS_FAULT") + } + + fn is_yellow_edp_press_low(&mut self) -> bool { + self.read_by_name("HYD_YELLOW_EDPUMP_LOW_PRESS") + } + + fn is_yellow_epump_press_low(&mut self) -> bool { + self.read_by_name("HYD_YELLOW_EPUMP_LOW_PRESS") + } + + fn is_blue_epump_press_low(&mut self) -> bool { + self.read_by_name("HYD_BLUE_EPUMP_LOW_PRESS") + } + + fn blue_epump_has_fault(&mut self) -> bool { + self.read_by_name("OVHD_HYD_EPUMPB_PB_HAS_FAULT") + } + + fn yellow_epump_has_fault(&mut self) -> bool { + self.read_by_name("OVHD_HYD_EPUMPY_PB_HAS_FAULT") + } + + fn yellow_reservoir_has_overheat_fault(&mut self) -> bool { + self.read_by_name("HYD_YELLOW_RESERVOIR_OVHT") + } + + fn green_reservoir_has_overheat_fault(&mut self) -> bool { + self.read_by_name("HYD_GREEN_RESERVOIR_OVHT") + } + + fn ptu_has_fault(&mut self) -> bool { + self.read_by_name("OVHD_HYD_PTU_PB_HAS_FAULT") + } + + fn blue_epump_override_is_on(&mut self) -> bool { + self.read_by_name("OVHD_HYD_EPUMPY_OVRD_IS_ON") + } + + fn get_brake_left_yellow_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_BRAKE_ALTN_LEFT_PRESS") + } + + fn get_brake_right_yellow_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_BRAKE_ALTN_RIGHT_PRESS") + } + + fn get_green_reservoir_volume(&mut self) -> Volume { + self.read_by_name("HYD_GREEN_RESERVOIR_LEVEL") + } + + fn get_blue_reservoir_volume(&mut self) -> Volume { + self.read_by_name("HYD_BLUE_RESERVOIR_LEVEL") + } + + fn autobrake_mode(&mut self) -> AutobrakeMode { + ReadByName::::read_by_name( + self, + "AUTOBRAKES_ARMED_MODE", + ) + .into() + } + + fn get_brake_left_green_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_BRAKE_NORM_LEFT_PRESS") + } + + fn get_brake_right_green_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_BRAKE_NORM_RIGHT_PRESS") + } + + fn get_brake_yellow_accumulator_pressure(&mut self) -> Pressure { + self.read_by_name("HYD_BRAKE_ALTN_ACC_PRESS") + } + + fn get_brake_yellow_accumulator_fluid_volume(&self) -> Volume { + self.query(|a| a.get_yellow_brake_accumulator_fluid_volume()) + } + + fn get_rat_position(&mut self) -> f64 { + self.read_by_name("RAT_STOW_POSITION") + } + + fn get_rat_rpm(&mut self) -> f64 { + self.read_by_name("A32NX_RAT_RPM") + } + + fn get_left_aileron_position(&mut self) -> Ratio { + Ratio::new::(self.read_by_name("HYD_AIL_LEFT_DEFLECTION")) + } + + fn get_right_aileron_position(&mut self) -> Ratio { + Ratio::new::(self.read_by_name("HYD_AIL_RIGHT_DEFLECTION")) + } + + fn get_left_elevator_position(&mut self) -> Ratio { + Ratio::new::(self.read_by_name("HYD_ELEV_LEFT_DEFLECTION")) + } + + fn get_mean_right_spoilers_position(&mut self) -> Ratio { + (Ratio::new::(self.read_by_name("HYD_SPOILER_1_RIGHT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_2_RIGHT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_3_RIGHT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_4_RIGHT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_5_RIGHT_DEFLECTION"))) + / 5. + } + + fn get_mean_left_spoilers_position(&mut self) -> Ratio { + (Ratio::new::(self.read_by_name("HYD_SPOILER_1_LEFT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_2_LEFT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_3_LEFT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_4_LEFT_DEFLECTION")) + + Ratio::new::(self.read_by_name("HYD_SPOILER_5_LEFT_DEFLECTION"))) + / 5. + } + + fn get_right_elevator_position(&mut self) -> Ratio { + Ratio::new::(self.read_by_name("HYD_ELEV_RIGHT_DEFLECTION")) + } + + fn get_rudder_position(&mut self) -> Ratio { + Ratio::new::(self.read_by_name("HYD_RUD_DEFLECTION")) + } + + fn get_nose_steering_ratio(&mut self) -> Ratio { + Ratio::new::(self.read_by_name("NOSE_WHEEL_POSITION_RATIO")) + } + + fn get_reverser_1_position(&mut self) -> Ratio { + Ratio::new::(self.read_by_name("REVERSER_1_POSITION")) + } + + fn get_reverser_2_position(&mut self) -> Ratio { + Ratio::new::(self.read_by_name("REVERSER_2_POSITION")) + } + + fn rat_deploy_commanded(&self) -> bool { + self.query(|a| a.is_rat_commanded_to_deploy()) + } + + fn is_emergency_gen_at_nominal_speed(&self) -> bool { + self.query(|a| a.is_emergency_gen_at_nominal_speed()) + } + + fn is_fire_valve_eng1_closed(&mut self) -> bool { + !ReadByName::::read_by_name( + self, + "HYD_GREEN_PUMP_1_FIRE_VALVE_OPENED", + ) && !self.query(|a| { + a.hydraulics.green_circuit.is_fire_shutoff_valve_open( + A320HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES, + ) + }) + } + + fn is_fire_valve_eng2_closed(&mut self) -> bool { + !ReadByName::::read_by_name( + self, + "HYD_YELLOW_PUMP_1_FIRE_VALVE_OPENED", + ) && !self.query(|a| { + a.hydraulics.green_circuit.is_fire_shutoff_valve_open( + A320HydraulicCircuitFactory::YELLOW_GREEN_BLUE_PUMPS_INDEXES, + ) + }) + } + + fn is_yellow_leak_meas_valve_commanded_open(&mut self) -> bool { + self.query(|a| a.is_yellow_leak_meas_valve_commanded_open()) + } + + fn is_green_leak_meas_valve_commanded_open(&mut self) -> bool { + self.query(|a| a.is_green_leak_meas_valve_commanded_open()) + } + + fn is_blue_leak_meas_valve_commanded_open(&mut self) -> bool { + self.query(|a| a.is_blue_leak_meas_valve_commanded_open()) + } + + fn green_leak_meas_valve_closed(mut self) -> Self { + self.write_by_name("OVHD_HYD_LEAK_MEASUREMENT_G_PB_IS_AUTO", false); + self + } + + fn blue_leak_meas_valve_closed(mut self) -> Self { + self.write_by_name("OVHD_HYD_LEAK_MEASUREMENT_B_PB_IS_AUTO", false); + self + } + + fn yellow_leak_meas_valve_closed(mut self) -> Self { + self.write_by_name("OVHD_HYD_LEAK_MEASUREMENT_Y_PB_IS_AUTO", false); + self + } + + fn engines_off(self) -> Self { + self.stop_eng1().stop_eng2() + } + + fn external_power(mut self, is_connected: bool) -> Self { + self.write_by_name("EXTERNAL POWER AVAILABLE:1", is_connected); + + if is_connected { + self = self.on_the_ground(); + } + self + } + + fn with_worst_case_ptu(mut self) -> Self { + self.command(|a| a.use_worst_case_ptu()); + self + } + + fn on_the_ground(mut self) -> Self { + self.set_indicated_altitude(Length::new::(0.)); + self.set_on_ground(true); + self.set_indicated_airspeed(Velocity::new::(5.)); + + self + } + + fn on_the_ground_after_touchdown(mut self) -> Self { + self.set_indicated_altitude(Length::new::(0.)); + self.set_on_ground(true); + self.set_indicated_airspeed(Velocity::new::(100.)); + self + } + + fn air_press_low(mut self) -> Self { + self.command(|a| a.pneumatics.set_low_air_pressure()); + self + } + + fn air_press_nominal(mut self) -> Self { + self.command(|a| a.pneumatics.set_nominal_air_pressure()); + self + } + + fn rotates_on_runway(mut self) -> Self { + self.set_indicated_altitude(Length::new::(0.)); + self.set_on_ground(false); + self.set_indicated_airspeed(Velocity::new::(135.)); + self.write_by_name( + LandingGear::GEAR_CENTER_COMPRESSION, + Ratio::new::(0.5), + ); + self.write_by_name(LandingGear::GEAR_LEFT_COMPRESSION, Ratio::new::(0.8)); + self.write_by_name( + LandingGear::GEAR_RIGHT_COMPRESSION, + Ratio::new::(0.8), + ); + self + } + + fn in_flight(mut self) -> Self { + self.set_on_ground(false); + self.set_indicated_altitude(Length::new::(2500.)); + self.set_indicated_airspeed(Velocity::new::(180.)); + self.set_true_airspeed(Velocity::new::(180.)); + self.set_ambient_air_density(MassDensity::new::(1.22)); + self.start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .set_gear_lever_up() + .set_park_brake(false) + .external_power(false) + } + + fn sim_not_ready(mut self) -> Self { + self.set_sim_is_ready(false); + self + } + + fn sim_ready(mut self) -> Self { + self.set_sim_is_ready(true); + self + } + + fn set_tiller_demand(mut self, steering_ratio: Ratio) -> Self { + self.write_by_name("TILLER_HANDLE_POSITION", steering_ratio.get::()); + self + } + + fn set_autopilot_steering_demand(mut self, steering_ratio: Ratio) -> Self { + self.write_by_name("AUTOPILOT_NOSEWHEEL_DEMAND", steering_ratio.get::()); + self + } + + fn set_eng1_fire_button(mut self, is_active: bool) -> Self { + self.write_by_name("FIRE_BUTTON_ENG1", is_active); + self + } + + fn set_eng2_fire_button(mut self, is_active: bool) -> Self { + self.write_by_name("FIRE_BUTTON_ENG2", is_active); + self + } + + fn open_fwd_cargo_door(mut self) -> Self { + self.write_by_name("FWD_DOOR_CARGO_OPEN_REQ", 1.); + self + } + + fn close_fwd_cargo_door(mut self) -> Self { + self.write_by_name("FWD_DOOR_CARGO_OPEN_REQ", 0.); + self + } + + fn set_pushback_state(mut self, is_pushed_back: bool) -> Self { + if is_pushed_back { + self.write_by_name("PUSHBACK STATE", 0.); + } else { + self.write_by_name("PUSHBACK STATE", 3.); + } + self + } + + fn set_pushback_angle(mut self, angle: Angle) -> Self { + self.write_by_name("PUSHBACK ANGLE", angle.get::()); + self + } + + fn is_nw_disc_memo_shown(&mut self) -> bool { + self.read_by_name("HYD_NW_STRG_DISC_ECAM_MEMO") + } + + fn is_ptu_running_high_pitch_sound(&mut self) -> bool { + self.read_by_name("HYD_PTU_HIGH_PITCH_SOUND") + } + + fn start_eng1(mut self, n2: Ratio) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:1", true); + self.write_by_name("ENGINE_N2:1", n2); + self.write_by_name("ENGINE_N3:1", n2); + self.write_by_name("TURB ENG CORRECTED N2:1", n2); + + self + } + + fn start_eng2(mut self, n2: Ratio) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:2", true); + self.write_by_name("ENGINE_N2:2", n2); + self.write_by_name("ENGINE_N3:2", n2); + self.write_by_name("TURB ENG CORRECTED N2:2", n2); + + self + } + + fn stop_eng1(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:1", false); + self.write_by_name("ENGINE_N2:1", 0.); + self.write_by_name("ENGINE_N3:1", 0.); + self.write_by_name("TURB ENG CORRECTED N2:1", 0.); + + self + } + + fn stopping_eng1(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:1", false); + self.write_by_name("ENGINE_N2:1", 25.); + self.write_by_name("ENGINE_N3:1", 25.); + self.write_by_name("TURB ENG CORRECTED N2:1", 25.); + + self + } + + fn stop_eng2(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:2", false); + self.write_by_name("ENGINE_N2:2", 0.); + self.write_by_name("ENGINE_N3:2", 0.); + self.write_by_name("TURB ENG CORRECTED N2:2", 0.); + + self + } + + fn set_throttles_idle(mut self) -> Self { + self.write_by_name("AUTOTHRUST_TLA:1", 0.); + self.write_by_name("AUTOTHRUST_TLA:2", 0.); + + self + } + + fn eng1_throttle_reverse_idle(mut self) -> Self { + self.write_by_name("AUTOTHRUST_TLA:1", -6.); + + self + } + + fn eng1_throttle_reverse_full(mut self) -> Self { + self.write_by_name("AUTOTHRUST_TLA:1", -20.); + + self + } + + fn eng2_throttle_reverse_idle(mut self) -> Self { + self.write_by_name("AUTOTHRUST_TLA:2", -6.); + + self + } + + fn eng2_throttle_reverse_full(mut self) -> Self { + self.write_by_name("AUTOTHRUST_TLA:2", -20.); + + self + } + + fn stopping_eng2(mut self) -> Self { + self.write_by_name("GENERAL ENG STARTER ACTIVE:2", false); + self.write_by_name("ENGINE_N2:2", 25.); + self.write_by_name("ENGINE_N3:2", 25.); + + self + } + + fn set_park_brake(mut self, is_set: bool) -> Self { + self.write_by_name("PARK_BRAKE_LEVER_POS", is_set); + self + } + + fn set_gear_lever_up(mut self) -> Self { + // One tick is needed so lever up can be evaluated + self.write_by_name("GEAR_LEVER_POSITION_REQUEST", false); + self = self.run_one_tick(); + + self + } + + fn set_gear_lever_down(mut self) -> Self { + self.write_by_name("GEAR_LEVER_POSITION_REQUEST", true); + + self + } + + fn set_anti_skid(mut self, is_set: bool) -> Self { + self.write_by_name("ANTISKID BRAKES ACTIVE", is_set); + self + } + + fn set_yellow_e_pump(mut self, is_auto: bool) -> Self { + self.write_by_name("OVHD_HYD_EPUMPY_PB_IS_AUTO", is_auto); + self + } + + fn set_blue_e_pump(mut self, is_auto: bool) -> Self { + self.write_by_name("OVHD_HYD_EPUMPB_PB_IS_AUTO", is_auto); + self + } + + fn set_blue_e_pump_ovrd_pressed(mut self, is_pressed: bool) -> Self { + self.write_by_name("OVHD_HYD_EPUMPY_OVRD_IS_PRESSED", is_pressed); + self + } + + fn set_green_ed_pump(mut self, is_auto: bool) -> Self { + self.write_by_name("OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO", is_auto); + self + } + + fn set_yellow_ed_pump(mut self, is_auto: bool) -> Self { + self.write_by_name("OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO", is_auto); + self + } + + fn set_ptu_state(mut self, is_auto: bool) -> Self { + self.write_by_name("OVHD_HYD_PTU_PB_IS_AUTO", is_auto); + self + } + + fn set_flaps_handle_position(mut self, pos: u8) -> Self { + self.write_by_name("FLAPS_HANDLE_INDEX", pos as f64); + self + } + + fn get_flaps_left_position_percent(&mut self) -> f64 { + self.read_by_name("LEFT_FLAPS_POSITION_PERCENT") + } + + fn get_flaps_right_position_percent(&mut self) -> f64 { + self.read_by_name("RIGHT_FLAPS_POSITION_PERCENT") + } + + fn get_slats_left_position_percent(&mut self) -> f64 { + self.read_by_name("LEFT_SLATS_POSITION_PERCENT") + } + + fn get_slats_right_position_percent(&mut self) -> f64 { + self.read_by_name("RIGHT_SLATS_POSITION_PERCENT") + } + + fn get_real_gear_position(&mut self, wheel_id: GearWheel) -> Ratio { + match wheel_id { + GearWheel::NOSE => self.read_by_name("GEAR_CENTER_POSITION"), + GearWheel::LEFT => self.read_by_name("GEAR_LEFT_POSITION"), + GearWheel::RIGHT => self.read_by_name("GEAR_RIGHT_POSITION"), + } + } + + fn get_real_gear_door_position(&mut self, wheel_id: GearWheel) -> Ratio { + match wheel_id { + GearWheel::NOSE => self.read_by_name("GEAR_DOOR_CENTER_POSITION"), + GearWheel::LEFT => self.read_by_name("GEAR_DOOR_LEFT_POSITION"), + GearWheel::RIGHT => self.read_by_name("GEAR_DOOR_RIGHT_POSITION"), + } + } + + fn is_all_gears_really_up(&mut self) -> bool { + self.get_real_gear_position(GearWheel::NOSE) <= Ratio::new::(0.01) + && self.get_real_gear_position(GearWheel::LEFT) <= Ratio::new::(0.01) + && self.get_real_gear_position(GearWheel::RIGHT) <= Ratio::new::(0.01) + } + + fn is_all_gears_really_down(&mut self) -> bool { + self.get_real_gear_position(GearWheel::NOSE) >= Ratio::new::(0.99) + && self.get_real_gear_position(GearWheel::LEFT) >= Ratio::new::(0.99) + && self.get_real_gear_position(GearWheel::RIGHT) >= Ratio::new::(0.99) + } + + fn is_all_doors_really_up(&mut self) -> bool { + self.get_real_gear_door_position(GearWheel::NOSE) <= Ratio::new::(0.01) + && self.get_real_gear_door_position(GearWheel::LEFT) + <= Ratio::new::(0.01) + && self.get_real_gear_door_position(GearWheel::RIGHT) + <= Ratio::new::(0.01) + } + + fn is_all_doors_really_down(&mut self) -> bool { + self.get_real_gear_door_position(GearWheel::NOSE) >= Ratio::new::(0.9) + && self.get_real_gear_door_position(GearWheel::LEFT) >= Ratio::new::(0.9) + && self.get_real_gear_door_position(GearWheel::RIGHT) + >= Ratio::new::(0.9) + } + + fn ac_bus_1_lost(mut self) -> Self { + self.command(|a| a.set_ac_bus_1_is_powered(false)); + self + } + + fn ac_bus_2_lost(mut self) -> Self { + self.command(|a| a.set_ac_bus_2_is_powered(false)); + self + } + + fn dc_ground_service_lost(mut self) -> Self { + self.command(|a| a.set_dc_ground_service_is_powered(false)); + self + } + fn dc_ground_service_avail(mut self) -> Self { + self.command(|a| a.set_dc_ground_service_is_powered(true)); + self + } + + fn ac_ground_service_lost(mut self) -> Self { + self.command(|a| a.set_ac_ground_service_is_powered(false)); + self + } + + fn dc_bus_2_lost(mut self) -> Self { + self.command(|a| a.set_dc_bus_2_is_powered(false)); + self + } + + fn dc_ess_lost(mut self) -> Self { + self.command(|a| a.set_dc_ess_is_powered(false)); + self + } + + fn dc_ess_active(mut self) -> Self { + self.command(|a| a.set_dc_ess_is_powered(true)); + self + } + + fn set_cold_dark_inputs(self) -> Self { + self.set_eng1_fire_button(false) + .set_eng2_fire_button(false) + .set_blue_e_pump(true) + .set_yellow_e_pump(true) + .set_green_ed_pump(true) + .set_yellow_ed_pump(true) + .set_ptu_state(true) + .set_park_brake(true) + .set_anti_skid(true) + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .set_gear_lever_down() + .set_pushback_state(false) + .air_press_nominal() + .set_elac1_actuators_energized() + .set_ailerons_neutral() + .set_elevator_neutral() + .set_throttles_idle() + } + + fn set_left_brake(mut self, position: Ratio) -> Self { + self.write_by_name("LEFT_BRAKE_PEDAL_INPUT", position); + self + } + + fn set_right_brake(mut self, position: Ratio) -> Self { + self.write_by_name("RIGHT_BRAKE_PEDAL_INPUT", position); + self + } + + fn set_autobrake_disarmed_with_set_variable(mut self) -> Self { + self.write_by_name("AUTOBRAKES_ARMED_MODE_SET", 0); + self + } + + fn set_autobrake_low_with_set_variable(mut self) -> Self { + self.write_by_name("AUTOBRAKES_ARMED_MODE_SET", 1); + self + } + + fn set_autobrake_med_with_set_variable(mut self) -> Self { + self.write_by_name("AUTOBRAKES_ARMED_MODE_SET", 2); + self + } + + fn set_autobrake_max_with_set_variable(mut self) -> Self { + self.write_by_name("AUTOBRAKES_ARMED_MODE_SET", 3); + self + } + + fn set_autobrake_low(mut self) -> Self { + self.write_by_name("OVHD_AUTOBRK_LOW_ON_IS_PRESSED", true); + self = self.run_one_tick(); + self.write_by_name("OVHD_AUTOBRK_LOW_ON_IS_PRESSED", false); + self + } + + fn set_autobrake_med(mut self) -> Self { + self.write_by_name("OVHD_AUTOBRK_MED_ON_IS_PRESSED", true); + self = self.run_one_tick(); + self.write_by_name("OVHD_AUTOBRK_MED_ON_IS_PRESSED", false); + self + } + + fn set_autobrake_max(mut self) -> Self { + self.write_by_name("OVHD_AUTOBRK_MAX_ON_IS_PRESSED", true); + self = self.run_one_tick(); + self.write_by_name("OVHD_AUTOBRK_MAX_ON_IS_PRESSED", false); + self + } + + fn set_deploy_ground_spoilers(mut self) -> Self { + self.write_by_name("SEC_1_GROUND_SPOILER_OUT", true); + self.write_by_name("SEC_2_GROUND_SPOILER_OUT", true); + self.write_by_name("SEC_3_GROUND_SPOILER_OUT", true); + self + } + + fn set_retract_ground_spoilers(mut self) -> Self { + self.write_by_name("SEC_1_GROUND_SPOILER_OUT", false); + self.write_by_name("SEC_2_GROUND_SPOILER_OUT", false); + self.write_by_name("SEC_3_GROUND_SPOILER_OUT", false); + self + } + + fn set_ailerons_neutral(mut self) -> Self { + self.write_by_name("LEFT_AIL_BLUE_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_AIL_BLUE_COMMANDED_POSITION", 0.); + self.write_by_name("LEFT_AIL_GREEN_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_AIL_GREEN_COMMANDED_POSITION", 0.); + self + } + + fn set_elevator_neutral(mut self) -> Self { + self.write_by_name("LEFT_ELEV_BLUE_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_ELEV_BLUE_COMMANDED_POSITION", 0.); + self.write_by_name("LEFT_ELEV_GREEN_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_ELEV_YELLOW_COMMANDED_POSITION", 0.); + self + } + + fn set_ailerons_left_turn(mut self) -> Self { + self.write_by_name("LEFT_AIL_BLUE_COMMANDED_POSITION", -25.); + self.write_by_name("RIGHT_AIL_BLUE_COMMANDED_POSITION", -25.); + self.write_by_name("LEFT_AIL_GREEN_COMMANDED_POSITION", -25.); + self.write_by_name("RIGHT_AIL_GREEN_COMMANDED_POSITION", -25.); + self + } + + fn set_elac1_actuators_energized(mut self) -> Self { + self.write_by_name("LEFT_AIL_BLUE_SERVO_SOLENOID_ENERGIZED", 1.); + self.write_by_name("RIGHT_AIL_BLUE_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("LEFT_AIL_GREEN_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("RIGHT_AIL_GREEN_SERVO_SOLENOID_ENERGIZED", 1.); + + self.write_by_name("LEFT_ELEV_BLUE_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("RIGHT_ELEV_BLUE_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("LEFT_ELEV_GREEN_SERVO_SOLENOID_ENERGIZED", 1.); + self.write_by_name("RIGHT_ELEV_YELLOW_SERVO_SOLENOID_ENERGIZED", 1.); + self + } + + fn set_elac_actuators_de_energized(mut self) -> Self { + self.write_by_name("LEFT_AIL_BLUE_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("RIGHT_AIL_BLUE_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("LEFT_AIL_GREEN_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("RIGHT_AIL_GREEN_SERVO_SOLENOID_ENERGIZED", 0.); + + self.write_by_name("LEFT_ELEV_BLUE_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("RIGHT_ELEV_BLUE_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("LEFT_ELEV_GREEN_SERVO_SOLENOID_ENERGIZED", 0.); + self.write_by_name("RIGHT_ELEV_YELLOW_SERVO_SOLENOID_ENERGIZED", 0.); + + self.write_by_name("LEFT_ELEV_BLUE_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_ELEV_BLUE_COMMANDED_POSITION", 0.); + self.write_by_name("LEFT_ELEV_GREEN_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_ELEV_YELLOW_COMMANDED_POSITION", 0.); + self + } + + fn set_left_spoilers_out(mut self) -> Self { + self.write_by_name("LEFT_SPOILER_1_COMMANDED_POSITION", 50.); + self.write_by_name("LEFT_SPOILER_2_COMMANDED_POSITION", 50.); + self.write_by_name("LEFT_SPOILER_3_COMMANDED_POSITION", 50.); + self.write_by_name("LEFT_SPOILER_4_COMMANDED_POSITION", 50.); + self.write_by_name("LEFT_SPOILER_5_COMMANDED_POSITION", 50.); + self + } + + fn set_left_spoilers_in(mut self) -> Self { + self.write_by_name("LEFT_SPOILER_1_COMMANDED_POSITION", 0.); + self.write_by_name("LEFT_SPOILER_2_COMMANDED_POSITION", 0.); + self.write_by_name("LEFT_SPOILER_3_COMMANDED_POSITION", 0.); + self.write_by_name("LEFT_SPOILER_4_COMMANDED_POSITION", 0.); + self.write_by_name("LEFT_SPOILER_5_COMMANDED_POSITION", 0.); + self + } + + fn set_right_spoilers_out(mut self) -> Self { + self.write_by_name("RIGHT_SPOILER_1_COMMANDED_POSITION", 50.); + self.write_by_name("RIGHT_SPOILER_2_COMMANDED_POSITION", 50.); + self.write_by_name("RIGHT_SPOILER_3_COMMANDED_POSITION", 50.); + self.write_by_name("RIGHT_SPOILER_4_COMMANDED_POSITION", 50.); + self.write_by_name("RIGHT_SPOILER_5_COMMANDED_POSITION", 50.); + self + } + + fn set_right_spoilers_in(mut self) -> Self { + self.write_by_name("RIGHT_SPOILER_1_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_SPOILER_2_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_SPOILER_3_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_SPOILER_4_COMMANDED_POSITION", 0.); + self.write_by_name("RIGHT_SPOILER_5_COMMANDED_POSITION", 0.); + self + } + + fn set_ailerons_right_turn(mut self) -> Self { + self.write_by_name("LEFT_AIL_BLUE_COMMANDED_POSITION", 25.); + self.write_by_name("RIGHT_AIL_BLUE_COMMANDED_POSITION", 25.); + self.write_by_name("LEFT_AIL_GREEN_COMMANDED_POSITION", 25.); + self.write_by_name("RIGHT_AIL_GREEN_COMMANDED_POSITION", 25.); + self + } + + fn gear_system_state(&self) -> GearSystemState { + self.query(|a| a.lgcius.active_lgciu().gear_system_state()) + } + + fn set_elevator_full_up(mut self) -> Self { + self.write_by_name("LEFT_ELEV_BLUE_COMMANDED_POSITION", -30.); + self.write_by_name("RIGHT_ELEV_BLUE_COMMANDED_POSITION", -30.); + self.write_by_name("LEFT_ELEV_GREEN_COMMANDED_POSITION", -30.); + self.write_by_name("RIGHT_ELEV_YELLOW_COMMANDED_POSITION", -30.); + self + } + + fn set_elevator_full_down(mut self) -> Self { + self.write_by_name("LEFT_ELEV_BLUE_COMMANDED_POSITION", 17.); + self.write_by_name("RIGHT_ELEV_BLUE_COMMANDED_POSITION", 17.); + self.write_by_name("LEFT_ELEV_GREEN_COMMANDED_POSITION", 17.); + self.write_by_name("RIGHT_ELEV_YELLOW_COMMANDED_POSITION", 17.); + self + } + + fn empty_brake_accumulator_using_park_brake(mut self) -> Self { + self = self + .set_park_brake(true) + .run_waiting_for(Duration::from_secs(1)); + + let mut number_of_loops = 0; + while self + .get_brake_yellow_accumulator_fluid_volume() + .get::() + > 0.001 + { + self = self + .set_park_brake(false) + .run_waiting_for(Duration::from_secs(1)) + .set_park_brake(true) + .run_waiting_for(Duration::from_secs(1)); + number_of_loops += 1; + assert!(number_of_loops < 20); + } + + self = self + .set_park_brake(false) + .run_waiting_for(Duration::from_secs(1)) + .set_park_brake(true) + .run_waiting_for(Duration::from_secs(1)); + + self + } + + fn empty_brake_accumulator_using_pedal_brake(mut self) -> Self { + let mut number_of_loops = 0; + while self + .get_brake_yellow_accumulator_fluid_volume() + .get::() + > 0.001 + { + self = self + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)) + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + number_of_loops += 1; + assert!(number_of_loops < 50); + } + + self = self + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)) + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + self + } + + fn load_brake_accumulator(mut self) -> Self { + let mut number_of_loops = 0; + while self.get_brake_yellow_accumulator_pressure().get::() <= 2900. { + self = self + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(2)); + number_of_loops += 1; + assert!(number_of_loops < 50); + } + + // Let yellow epump spool down + self = self + .set_yellow_e_pump(true) + .run_waiting_for(Duration::from_secs(5)); + + self + } + + fn turn_emergency_gear_extension_n_turns(mut self, number_of_turns: u8) -> Self { + self.write_by_name("GRAVITYGEAR_ROTATE_PCT", number_of_turns as f64 * 100.); + self + } + + fn stow_emergency_gear_extension(mut self) -> Self { + self.write_by_name("GRAVITYGEAR_ROTATE_PCT", 0.); + self + } + + fn press_blue_epump_override_button_once(self) -> Self { + self.set_blue_e_pump_ovrd_pressed(true) + .run_one_tick() + .set_blue_e_pump_ovrd_pressed(false) + .run_one_tick() + } + } + impl TestBed for A320HydraulicsTestBed { + type Aircraft = A320HydraulicsTestAircraft; + + fn test_bed(&self) -> &SimulationTestBed { + &self.test_bed + } + + fn test_bed_mut(&mut self) -> &mut SimulationTestBed { + &mut self.test_bed + } + } + + fn test_bed_on_ground() -> A320HydraulicsTestBed { + A320HydraulicsTestBed::new_with_start_state(StartState::Apron) + } + + fn test_bed_in_flight() -> A320HydraulicsTestBed { + A320HydraulicsTestBed::new_with_start_state(StartState::Cruise) + } + + fn test_bed_on_ground_with() -> A320HydraulicsTestBed { + test_bed_on_ground() + } + + fn test_bed_in_flight_with() -> A320HydraulicsTestBed { + test_bed_in_flight() + } + + #[test] + fn pressure_state_at_init_one_simulation_step() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.is_ptu_enabled()); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(50.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn pressure_state_after_5s() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_secs(5)); + + assert!(test_bed.is_ptu_enabled()); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(50.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn ptu_inhibited_by_overhead_off_push_button() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Enabled on cold start + assert!(test_bed.is_ptu_enabled()); + + // Ptu push button disables PTU accordingly + test_bed = test_bed.set_ptu_state(false).run_one_tick(); + assert!(!test_bed.is_ptu_enabled()); + test_bed = test_bed.set_ptu_state(true).run_one_tick(); + assert!(test_bed.is_ptu_enabled()); + } + + #[test] + fn ptu_inhibited_on_ground_when_only_one_engine_on_and_park_brake_on() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + + assert!(!test_bed.is_ptu_enabled()); + + test_bed = test_bed.set_park_brake(false).run_one_tick(); + assert!(test_bed.is_ptu_enabled()); + + test_bed = test_bed.set_park_brake(true).run_one_tick(); + assert!(!test_bed.is_ptu_enabled()); + } + + #[test] + fn ptu_inhibited_on_ground_is_activated_when_nose_gear_in_air() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + + assert!(!test_bed.is_ptu_enabled()); + + test_bed = test_bed.rotates_on_runway().run_one_tick(); + assert!(test_bed.is_ptu_enabled()); + } + + #[test] + fn ptu_unpowered_cant_inhibit() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Enabled on cold start + assert!(test_bed.is_ptu_enabled()); + + // Ptu push button disables PTU accordingly + test_bed = test_bed.set_ptu_state(false).run_one_tick(); + assert!(!test_bed.is_ptu_enabled()); + + // No power on closing valve : ptu become active + test_bed = test_bed.dc_ground_service_lost().run_one_tick(); + assert!(test_bed.is_ptu_enabled()); + + test_bed = test_bed.dc_ground_service_avail().run_one_tick(); + assert!(!test_bed.is_ptu_enabled()); + } + + #[test] + fn ptu_cargo_operation_inhibit() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Enabled on cold start + assert!(test_bed.is_ptu_enabled()); + + // Ptu disabled from cargo operation + test_bed = test_bed.open_fwd_cargo_door().run_waiting_for( + Duration::from_secs(5) + HydraulicDoorController::DELAY_UNLOCK_TO_HYDRAULIC_CONTROL, + ); + + assert!(!test_bed.is_ptu_enabled()); + test_bed = test_bed.run_waiting_for( + Duration::from_secs(25) + A320PowerTransferUnitController::DURATION_OF_PTU_INHIBIT_AFTER_CARGO_DOOR_OPERATION, + ); // Should re enabled after 40s + assert!(test_bed.is_ptu_enabled()); + } + + #[test] + fn nose_wheel_pin_detection() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(!test_bed.query(|a| a.is_nws_pin_inserted())); + assert!(!test_bed.is_nw_disc_memo_shown()); + + test_bed = test_bed.set_pushback_state(true).run_one_tick(); + assert!(test_bed.query(|a| a.is_nws_pin_inserted())); + assert!(test_bed.is_nw_disc_memo_shown()); + + test_bed = test_bed + .set_pushback_state(false) + .run_waiting_for(Duration::from_secs(1)); + assert!(test_bed.query(|a| a.is_nws_pin_inserted())); + assert!(test_bed.is_nw_disc_memo_shown()); + + test_bed = test_bed.set_pushback_state(false).run_waiting_for( + PushbackTug::DURATION_AFTER_WHICH_NWS_PIN_IS_REMOVED_AFTER_PUSHBACK, + ); + + assert!(!test_bed.query(|a| a.is_nws_pin_inserted())); + assert!(!test_bed.is_nw_disc_memo_shown()); + } + + #[test] + fn cargo_door_yellow_epump_powering() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(!test_bed.query(|a| a.is_cargo_powering_yellow_epump())); + + // Need to wait for operator to first unlock, then activate hydraulic control + test_bed = test_bed.open_fwd_cargo_door().run_waiting_for( + Duration::from_secs(5) + HydraulicDoorController::DELAY_UNLOCK_TO_HYDRAULIC_CONTROL, + ); + assert!(test_bed.query(|a| a.is_cargo_powering_yellow_epump())); + + // Wait for the door to fully open + test_bed = test_bed.run_waiting_for(Duration::from_secs(25)); + assert!(test_bed.is_cargo_fwd_door_locked_up()); + + test_bed = test_bed.run_waiting_for( + A320YellowElectricPumpController::DURATION_OF_YELLOW_PUMP_ACTIVATION_AFTER_CARGO_DOOR_OPERATION, + ); + + assert!(!test_bed.query(|a| a.is_cargo_powering_yellow_epump())); + } + + #[test] + fn ptu_pressurise_green_from_yellow_epump() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Enabled on cold start + assert!(test_bed.is_ptu_enabled()); + + // Yellow epump ON / Waiting 25s + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(25)); + + assert!(test_bed.is_ptu_enabled()); + + // Now we should have pressure in yellow and green + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.green_pressure() < Pressure::new::(3100.)); + + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(50.)); + assert!(test_bed.blue_pressure() > Pressure::new::(-50.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2000.)); + assert!(test_bed.yellow_pressure() < Pressure::new::(3100.)); + + // Ptu push button disables PTU / green press should fall + test_bed = test_bed + .set_ptu_state(false) + .run_waiting_for(Duration::from_secs(20)); + assert!(!test_bed.is_ptu_enabled()); + + // Now we should have pressure in yellow only + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(500.)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(50.)); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2000.)); + } + + #[test] + fn ptu_pressurise_green_from_yellow_epump_and_edp2() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .start_eng2(Ratio::new::(100.)) + .set_park_brake(false) + .set_yellow_e_pump(false) + .set_yellow_ed_pump(true) // Else Ptu inhibited by parking brake + .run_waiting_for(Duration::from_secs(25)); + + assert!(test_bed.is_ptu_enabled()); + + // Now we should have pressure in yellow and green + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.green_pressure() < Pressure::new::(3100.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2000.)); + assert!(test_bed.yellow_pressure() < Pressure::new::(3100.)); + } + + #[test] + fn green_edp_buildup() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Starting eng 1 + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_one_tick(); + + // ALMOST No pressure + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(1000.)); + + // Blue is auto run from engine master switches logic + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(1000.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(1000.)); + + // Waiting for 5s pressure should be at 3000 psi + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(5)); + + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2900.)); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(50.)); + + // Stoping engine, pressure should fall in 20s + test_bed = test_bed + .stop_eng1() + .run_waiting_for(Duration::from_secs(20)); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(500.)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(200.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn green_edp_no_fault_on_ground_eng_off() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_millis(500)); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_green_edp_commanded_on()); + // EDP should have no fault + assert!(!test_bed.green_edp_has_fault()); + } + + #[test] + fn green_edp_fault_not_on_ground_eng_off() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .engines_off() + .run_one_tick(); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_green_edp_commanded_on()); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + // EDP should have a fault as we are in flight + assert!(test_bed.green_edp_has_fault()); + } + + #[test] + fn green_edp_fault_on_ground_eng_starting() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_millis(500)); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_green_edp_commanded_on()); + // EDP should have no fault + assert!(!test_bed.green_edp_has_fault()); + + test_bed = test_bed + .start_eng1(Ratio::new::(3.)) + .run_one_tick(); + + assert!(!test_bed.green_edp_has_fault()); + + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_one_tick(); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_edp_has_fault()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(10)); + + // When finally pressurised no fault + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(!test_bed.green_edp_has_fault()); + } + + #[test] + fn yellow_edp_no_fault_on_ground_eng_off() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_millis(500)); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_yellow_edp_commanded_on()); + // EDP should have no fault + assert!(!test_bed.yellow_edp_has_fault()); + } + + #[test] + fn yellow_edp_fault_not_on_ground_eng_off() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .engines_off() + .run_one_tick(); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_yellow_edp_commanded_on()); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + // EDP should have a fault as we are in flight + assert!(test_bed.yellow_edp_has_fault()); + } + + #[test] + fn yellow_edp_fault_on_ground_eng_starting() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_millis(500)); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_yellow_edp_commanded_on()); + // EDP should have no fault + assert!(!test_bed.yellow_edp_has_fault()); + + test_bed = test_bed + .start_eng2(Ratio::new::(3.)) + .run_one_tick(); + + assert!(!test_bed.yellow_edp_has_fault()); + + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_edp_has_fault()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(10)); + + // When finally pressurised no fault + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(!test_bed.yellow_edp_has_fault()); + } + + #[test] + fn blue_epump_no_fault_on_ground_eng_starting() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_millis(500)); + + // Blue epump should have no fault + assert!(!test_bed.blue_epump_has_fault()); + + test_bed = test_bed + .start_eng2(Ratio::new::(3.)) + .run_one_tick(); + + assert!(!test_bed.blue_epump_has_fault()); + + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_epump_has_fault()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(10)); + + // When finally pressurised no fault + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(!test_bed.blue_epump_has_fault()); + } + + #[test] + fn blue_epump_fault_on_ground_using_override() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_millis(500)); + + // Blue epump should have no fault + assert!(!test_bed.blue_epump_has_fault()); + + test_bed = test_bed.press_blue_epump_override_button_once(); + assert!(test_bed.blue_epump_override_is_on()); + + // As we use override, this bypasses eng off fault inhibit so we have a fault + assert!(test_bed.blue_epump_has_fault()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(10)); + + // When finally pressurised no fault + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(!test_bed.blue_epump_has_fault()); + } + + #[test] + fn green_edp_press_low_engine_off_to_on() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_millis(500)); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_green_edp_commanded_on()); + + // EDP should be LOW pressure state + assert!(test_bed.is_green_edp_press_low()); + + // Starting eng 1 N2 is low at start + test_bed = test_bed + .start_eng1(Ratio::new::(3.)) + .run_one_tick(); + + // Engine commanded on but pressure couldn't rise enough: we are in fault low + assert!(test_bed.is_green_edp_press_low()); + + // Waiting for 5s pressure should be at 3000 psi + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(25)); + + // No more fault LOW expected + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2900.)); + assert!(!test_bed.is_green_edp_press_low()); + + // Stoping pump, no fault expected + test_bed = test_bed + .set_green_ed_pump(false) + .run_waiting_for(Duration::from_secs(1)); + assert!(!test_bed.is_green_edp_press_low()); + } + + #[test] + fn green_edp_press_low_engine_on_to_off() { + let mut test_bed = test_bed_on_ground_with() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(75.)) + .run_waiting_for(Duration::from_secs(5)); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_green_edp_commanded_on()); + assert!(test_bed.is_green_pressure_switch_pressurised()); + // EDP should not be in fault low when engine running and pressure is ok + assert!(!test_bed.is_green_edp_press_low()); + + // Stoping eng 1 with N2 still turning + test_bed = test_bed.stopping_eng1().run_one_tick(); + + // Edp should still be in pressurized mode but as engine just stopped no fault + assert!(test_bed.is_green_edp_commanded_on()); + assert!(!test_bed.is_green_edp_press_low()); + + // Waiting for 25s pressure should drop and still no fault + test_bed = test_bed + .stop_eng1() + .run_waiting_for(Duration::from_secs(25)); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(500.)); + assert!(test_bed.is_green_edp_press_low()); + } + + #[test] + fn yellow_edp_press_low_engine_on_to_off() { + let mut test_bed = test_bed_on_ground_with() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng2(Ratio::new::(75.)) + .run_waiting_for(Duration::from_secs(5)); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_yellow_edp_commanded_on()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + // EDP should not be in fault low when engine running and pressure is ok + assert!(!test_bed.is_yellow_edp_press_low()); + + // Stoping eng 2 with N2 still turning + test_bed = test_bed.stopping_eng2().run_one_tick(); + + // Edp should still be in pressurized mode but as engine just stopped no fault + assert!(test_bed.is_yellow_edp_commanded_on()); + assert!(!test_bed.is_yellow_edp_press_low()); + + // Waiting for 25s pressure should drop and still no fault + test_bed = test_bed + .stop_eng2() + .run_waiting_for(Duration::from_secs(25)); + + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(500.)); + assert!(test_bed.is_yellow_edp_press_low()); + } + + #[test] + fn yellow_edp_press_low_engine_off_to_on() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_yellow_edp_commanded_on()); + + // EDP should be LOW pressure state + assert!(test_bed.is_yellow_edp_press_low()); + + // Starting eng 2 N2 is low at start + test_bed = test_bed + .start_eng2(Ratio::new::(3.)) + .run_one_tick(); + + // Engine commanded on but pressure couldn't rise enough: we are in fault low + assert!(test_bed.is_yellow_edp_press_low()); + + // Waiting for 5s pressure should be at 3000 psi + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(5)); + + // No more fault LOW expected + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + assert!(!test_bed.is_yellow_edp_press_low()); + + // Stoping pump, no fault expected + test_bed = test_bed + .set_yellow_ed_pump(false) + .run_waiting_for(Duration::from_secs(1)); + assert!(!test_bed.is_yellow_edp_press_low()); + } + + #[test] + fn yellow_edp_press_low_engine_off_to_on_with_e_pump() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(false) + .set_yellow_e_pump(false) + .run_one_tick(); + + // EDP should be commanded on even without engine running + assert!(test_bed.is_yellow_edp_commanded_on()); + + // EDP should be LOW pressure state + assert!(test_bed.is_yellow_edp_press_low()); + + // Waiting for 20s pressure should be at 3000 psi + test_bed = test_bed.run_waiting_for(Duration::from_secs(20)); + + // Yellow pressurised but edp still off, we expect fault LOW press + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + assert!(test_bed.is_yellow_edp_press_low()); + + // Starting eng 2 N2 is low at start + test_bed = test_bed + .start_eng2(Ratio::new::(3.)) + .run_one_tick(); + + // Engine commanded on but pressure couldn't rise enough: we are in fault low + assert!(test_bed.is_yellow_edp_press_low()); + + // Waiting for 5s pressure should be at 3000 psi in EDP section + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(5)); + + // No more fault LOW expected + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + assert!(!test_bed.is_yellow_edp_press_low()); + } + + #[test] + fn green_edp_press_low_engine_off_to_on_with_ptu() { + let mut test_bed = test_bed_on_ground_with() + .on_the_ground() + .set_cold_dark_inputs() + .with_worst_case_ptu() + .set_park_brake(false) + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + + // EDP should be LOW pressure state + assert!(test_bed.is_green_edp_press_low()); + + // Waiting for 20s pressure should be at 2300+ psi thanks to ptu + test_bed = test_bed.run_waiting_for(Duration::from_secs(20)); + + // Yellow pressurised by engine2, green presurised from ptu we expect fault LOW press on EDP1 + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2500.)); + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2300.)); + assert!(test_bed.is_green_edp_press_low()); + + // Starting eng 1 N2 is low at start + test_bed = test_bed + .start_eng1(Ratio::new::(3.)) + .run_one_tick(); + + // Engine commanded on but pressure couldn't rise enough: we are in fault low + assert!(test_bed.is_green_edp_press_low()); + + // Waiting for 5s pressure should be at 3000 psi in EDP section + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(5)); + + // No more fault LOW expected + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2900.)); + assert!(!test_bed.is_green_edp_press_low()); + } + + #[test] + fn yellow_epump_press_low_at_pump_on() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // EDP should not be in fault low when cold start + assert!(!test_bed.is_yellow_epump_press_low()); + + // Starting epump + test_bed = test_bed.set_yellow_e_pump(false).run_one_tick(); + + // Pump commanded on but pressure couldn't rise enough: we are in fault low + assert!(test_bed.is_yellow_epump_press_low()); + + // Waiting for 20s pressure should be at 3000 psi + test_bed = test_bed.run_waiting_for(Duration::from_secs(20)); + + // No more fault LOW expected + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2500.)); + assert!(!test_bed.is_yellow_epump_press_low()); + + // Stoping epump, no fault expected + test_bed = test_bed + .set_yellow_e_pump(true) + .run_waiting_for(Duration::from_secs(1)); + assert!(!test_bed.is_yellow_epump_press_low()); + } + + #[test] + fn blue_epump_press_low_at_pump_on() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // EDP should not be in fault low when cold start + assert!(!test_bed.is_blue_epump_press_low()); + + // Starting epump + test_bed = test_bed.press_blue_epump_override_button_once(); + assert!(test_bed.blue_epump_override_is_on()); + + // Pump commanded on but pressure couldn't rise enough: we are in fault low + assert!(test_bed.is_blue_epump_press_low()); + + // Waiting for 10s pressure should be at 3000 psi + test_bed = test_bed.run_waiting_for(Duration::from_secs(10)); + + // No more fault LOW expected + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() > Pressure::new::(2900.)); + assert!(!test_bed.is_blue_epump_press_low()); + + // Stoping epump, no fault expected + test_bed = test_bed.press_blue_epump_override_button_once(); + assert!(!test_bed.blue_epump_override_is_on()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(1)); + assert!(!test_bed.is_blue_epump_press_low()); + } + + #[test] + fn blue_epump_override_switches_to_off_when_losing_relay_power_and_stays_off() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Starting epump + test_bed = test_bed + .press_blue_epump_override_button_once() + .run_waiting_for(Duration::from_secs(10)); + assert!(test_bed.blue_epump_override_is_on()); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + // Killing the bus corresponding to the latching relay of blue pump override push button + // It should set the override state back to off without touching the push button + test_bed = test_bed.dc_ess_lost().run_one_tick(); + assert!(!test_bed.blue_epump_override_is_on()); + + // Stays off even powered back + test_bed = test_bed.dc_ess_active().run_one_tick(); + assert!(!test_bed.blue_epump_override_is_on()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(10)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + } + + #[test] + fn blue_epump_override_switches_to_off_when_pump_forced_off_on_hyd_panel() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Starting epump + test_bed = test_bed + .press_blue_epump_override_button_once() + .run_waiting_for(Duration::from_secs(10)); + assert!(test_bed.blue_epump_override_is_on()); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + test_bed = test_bed.set_blue_e_pump(false).run_one_tick(); + assert!(!test_bed.blue_epump_override_is_on()); + } + + #[test] + fn edp_deactivation() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(false) + .run_one_tick(); + + // Starting eng 1 and eng 2 + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + + // ALMOST No pressure + assert!(test_bed.green_pressure() < Pressure::new::(1000.)); + assert!(test_bed.yellow_pressure() < Pressure::new::(1000.)); + + // Waiting for 5s pressure should be at 3000 psi + test_bed = test_bed.run_waiting_for(Duration::from_secs(5)); + + assert!(test_bed.green_pressure() > Pressure::new::(2900.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + + // Stoping edp1, pressure should fall in 20s + test_bed = test_bed + .set_green_ed_pump(false) + .run_waiting_for(Duration::from_secs(20)); + + assert!(test_bed.green_pressure() < Pressure::new::(500.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + + // Stoping edp2, pressure should fall in 20s + test_bed = test_bed + .set_yellow_ed_pump(false) + .run_waiting_for(Duration::from_secs(20)); + + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(test_bed.yellow_pressure() < Pressure::new::(500.)); + } + + #[test] + fn yellow_edp_buildup() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Starting eng 1 + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_one_tick(); + // ALMOST No pressure + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + + // Blue is auto run + assert!(test_bed.blue_pressure() < Pressure::new::(1000.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(1000.)); + + // Waiting for 5s pressure should be at 3000 psi + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(5)); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2800.)); + + // Stoping engine, pressure should fall in 20s + test_bed = test_bed + .stop_eng2() + .run_waiting_for(Duration::from_secs(20)); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(200.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(500.)); + } + + #[test] + fn when_yellow_edp_solenoid_main_power_bus_unavailable_backup_bus_keeps_pump_in_unpressurised_state( + ) { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(15)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + // Stoping EDP manually + test_bed = test_bed + .set_yellow_ed_pump(false) + .run_waiting_for(Duration::from_secs(15)); + + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .dc_bus_2_lost() + .run_waiting_for(Duration::from_secs(15)); + + // Yellow solenoid has backup power from DC ESS BUS + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + } + + #[test] + fn when_yellow_edp_solenoid_both_bus_unpowered_yellow_hydraulic_system_is_pressurised() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(15)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + // Stoping EDP manually + test_bed = test_bed + .set_yellow_ed_pump(false) + .run_waiting_for(Duration::from_secs(15)); + + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .dc_ess_lost() + .dc_bus_2_lost() + .run_waiting_for(Duration::from_secs(15)); + + // Now solenoid defaults to pressurised without power + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + } + + #[test] + fn when_green_edp_solenoid_unpowered_yellow_hydraulic_system_is_pressurised() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(15)); + + assert!(test_bed.is_green_pressure_switch_pressurised()); + + // Stoping EDP manually + test_bed = test_bed + .set_green_ed_pump(false) + .run_waiting_for(Duration::from_secs(15)); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + + test_bed = test_bed + .dc_ess_lost() + .run_waiting_for(Duration::from_secs(15)); + + // Now solenoid defaults to pressurised + assert!(test_bed.is_green_pressure_switch_pressurised()); + } + + #[test] + #[ignore] + // Checks numerical stability of reservoir level: level should remain after multiple pressure cycles + fn yellow_circuit_reservoir_coherency() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(false) + // Park brake off to not use fluid in brakes + .set_park_brake(false) + .run_one_tick(); + + // Starting epump wait for pressure rise to make sure system is primed including brake accumulator + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(20)); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(3500.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(2500.)); + + // Shutdown and wait for pressure stabilisation + test_bed = test_bed + .set_yellow_e_pump(true) + .run_waiting_for(Duration::from_secs(50)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(-50.)); + + let reservoir_level_after_priming = test_bed.get_yellow_reservoir_volume(); + + let total_fluid_res_plus_accumulator_before_loops = reservoir_level_after_priming + + test_bed.get_brake_yellow_accumulator_fluid_volume(); + + // Now doing cycles of pressurisation on EDP and ePump + for _ in 1..6 { + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(50)); + + assert!(test_bed.yellow_pressure() < Pressure::new::(3100.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(2500.)); + + let mut current_res_level = test_bed.get_yellow_reservoir_volume(); + assert!(current_res_level < reservoir_level_after_priming); + + test_bed = test_bed + .stop_eng2() + .run_waiting_for(Duration::from_secs(50)); + assert!(test_bed.yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(-50.)); + + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(50)); + + assert!(test_bed.yellow_pressure() < Pressure::new::(3500.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(2500.)); + + current_res_level = test_bed.get_yellow_reservoir_volume(); + assert!(current_res_level < reservoir_level_after_priming); + + test_bed = test_bed + .set_yellow_e_pump(true) + .run_waiting_for(Duration::from_secs(50)); + assert!(test_bed.yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(-50.)); + } + let total_fluid_res_plus_accumulator_after_loops = test_bed + .get_yellow_reservoir_volume() + + test_bed.get_brake_yellow_accumulator_fluid_volume(); + + let total_fluid_difference = total_fluid_res_plus_accumulator_before_loops + - total_fluid_res_plus_accumulator_after_loops; + + // Make sure no more deviation than 0.001 gallon is lost after full pressure and unpressurized states + assert!(total_fluid_difference.get::().abs() < 0.001); + } + + #[test] + #[ignore] + // Checks numerical stability of reservoir level: level should remain after multiple pressure cycles + fn green_circuit_reservoir_coherency() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(false) + .run_one_tick(); + + // Starting EDP wait for pressure rise to make sure system is primed + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(20)); + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(3500.)); + assert!(test_bed.green_pressure() > Pressure::new::(2500.)); + + // Shutdown and wait for pressure stabilisation + test_bed = test_bed + .stop_eng1() + .run_waiting_for(Duration::from_secs(50)); + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(test_bed.green_pressure() > Pressure::new::(-50.)); + + let reservoir_level_after_priming = test_bed.get_green_reservoir_volume(); + + // Now doing cycles of pressurisation on EDP + for _ in 1..6 { + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(50)); + + assert!(test_bed.green_pressure() < Pressure::new::(3500.)); + assert!(test_bed.green_pressure() > Pressure::new::(2500.)); + + let current_res_level = test_bed.get_green_reservoir_volume(); + assert!(current_res_level < reservoir_level_after_priming); + + test_bed = test_bed + .stop_eng1() + .run_waiting_for(Duration::from_secs(50)); + assert!(test_bed.green_pressure() < Pressure::new::(50.)); + assert!(test_bed.green_pressure() > Pressure::new::(-50.)); + } + + let total_fluid_difference = + reservoir_level_after_priming - test_bed.get_green_reservoir_volume(); + + // Make sure no more deviation than 0.001 gallon is lost after full pressure and unpressurized states + assert!(total_fluid_difference.get::().abs() < 0.001); + } + + #[test] + #[ignore] + // Checks numerical stability of reservoir level: level should remain after multiple pressure cycles + fn blue_circuit_reservoir_coherency() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // Starting blue_epump wait for pressure rise to make sure system is primed + test_bed = test_bed.press_blue_epump_override_button_once(); + assert!(test_bed.blue_epump_override_is_on()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(20)); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(3500.)); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + + // Shutdown and wait for pressure stabilisation + test_bed = test_bed.press_blue_epump_override_button_once(); + assert!(!test_bed.blue_epump_override_is_on()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs(50)); + + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() < Pressure::new::(50.)); + assert!(test_bed.blue_pressure() > Pressure::new::(-50.)); + + let reservoir_level_after_priming = test_bed.get_blue_reservoir_volume(); + + // Now doing cycles of pressurisation on epump relying on auto run of epump when eng is on + for _ in 1..6 { + test_bed = test_bed + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(50)); + + assert!(test_bed.blue_pressure() < Pressure::new::(3500.)); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + + let current_res_level = test_bed.get_blue_reservoir_volume(); + assert!(current_res_level < reservoir_level_after_priming); + + test_bed = test_bed + .stop_eng1() + .run_waiting_for(Duration::from_secs(50)); + assert!(test_bed.blue_pressure() < Pressure::new::(50.)); + assert!(test_bed.blue_pressure() > Pressure::new::(-50.)); + + // Now engine 2 is used + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(50)); + + assert!(test_bed.blue_pressure() < Pressure::new::(3500.)); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + + let current_res_level = test_bed.get_blue_reservoir_volume(); + assert!(current_res_level < reservoir_level_after_priming); + + test_bed = test_bed + .stop_eng2() + .run_waiting_for(Duration::from_secs(50)); + assert!(test_bed.blue_pressure() < Pressure::new::(50.)); + assert!(test_bed.blue_pressure() > Pressure::new::(-50.)); + } + + let total_fluid_difference = + reservoir_level_after_priming - test_bed.get_blue_reservoir_volume(); + + // Make sure no more deviation than 0.001 gallon is lost after full pressure and unpressurized states + assert!(total_fluid_difference.get::().abs() < 0.001); + } + + #[test] + fn yellow_green_edp_firevalve() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + // PTU would mess up the test + test_bed = test_bed.set_ptu_state(false).run_one_tick(); + assert!(!test_bed.is_ptu_enabled()); + + assert!(!test_bed.is_fire_valve_eng1_closed()); + assert!(!test_bed.is_fire_valve_eng2_closed()); + + // Starting eng 1 + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .start_eng1(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(5)); + + // Waiting for 5s pressure should be at 3000 psi + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2900.)); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2800.)); + + assert!(!test_bed.is_fire_valve_eng1_closed()); + assert!(!test_bed.is_fire_valve_eng2_closed()); + + // Green shutoff valve + test_bed = test_bed + .set_eng1_fire_button(true) + .run_waiting_for(Duration::from_secs(20)); + + assert!(test_bed.is_fire_valve_eng1_closed()); + assert!(!test_bed.is_fire_valve_eng2_closed()); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(500.)); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + + // Yellow shutoff valve + test_bed = test_bed + .set_eng2_fire_button(true) + .run_waiting_for(Duration::from_secs(20)); + + assert!(test_bed.is_fire_valve_eng1_closed()); + assert!(test_bed.is_fire_valve_eng2_closed()); + + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() < Pressure::new::(500.)); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.blue_pressure() > Pressure::new::(2500.)); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() < Pressure::new::(500.)); + } + + #[test] + fn yellow_brake_accumulator() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_secs(5)); + + // Getting accumulator pressure on cold start + let mut accumulator_pressure = test_bed.get_brake_yellow_accumulator_pressure(); + + // No brakes on green, no more pressure than in accumulator on yellow + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!( + test_bed.get_brake_left_yellow_pressure() + < accumulator_pressure + Pressure::new::(50.) + ); + assert!( + test_bed.get_brake_right_yellow_pressure() + < accumulator_pressure + Pressure::new::(50.) + ); + + // No brakes even if we brake on green, no more than accumulator pressure on yellow + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(5)); + + accumulator_pressure = test_bed.get_brake_yellow_accumulator_pressure(); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!( + test_bed.get_brake_left_yellow_pressure() + < accumulator_pressure + Pressure::new::(50.) + ); + assert!( + test_bed.get_brake_right_yellow_pressure() + < accumulator_pressure + Pressure::new::(50.) + ); + assert!( + test_bed.get_brake_yellow_accumulator_pressure() + < accumulator_pressure + Pressure::new::(50.) + ); + + // Park brake off, loading accumulator, we expect no brake pressure but accumulator loaded + test_bed = test_bed + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .set_park_brake(false) + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(30)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2500.)); + assert!(test_bed.yellow_pressure() < Pressure::new::(3500.)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + assert!(test_bed.get_brake_yellow_accumulator_pressure() > Pressure::new::(2500.)); + + // Park brake on, loaded accumulator, we expect brakes on yellow side only + test_bed = test_bed + .set_park_brake(true) + .run_waiting_for(Duration::from_secs(3)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() > Pressure::new::(2000.)); + assert!(test_bed.get_brake_right_yellow_pressure() > Pressure::new::(2000.)); + + assert!(test_bed.get_brake_yellow_accumulator_pressure() > Pressure::new::(2500.)); + } + + #[test] + fn norm_brake_vs_altn_brake() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_secs(5)); + + // Getting accumulator pressure on cold start + let accumulator_pressure = test_bed.get_brake_yellow_accumulator_pressure(); + + // No brakes + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!( + test_bed.get_brake_left_yellow_pressure() + < accumulator_pressure + Pressure::new::(50.) + ); + assert!( + test_bed.get_brake_right_yellow_pressure() + < accumulator_pressure + Pressure::new::(50.) + ); + + test_bed = test_bed + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .set_park_brake(false) + .run_waiting_for(Duration::from_secs(5)); + + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + // No brakes if we don't brake + test_bed = test_bed + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + // Braking cause green braking system to rise + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(3500.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(3500.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + // Disabling Askid causes alternate braking to work and release green brakes + test_bed = test_bed + .set_anti_skid(false) + .run_waiting_for(Duration::from_secs(2)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() > Pressure::new::(950.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(3500.)); + assert!(test_bed.get_brake_right_yellow_pressure() > Pressure::new::(950.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(3500.)); + } + + #[test] + fn no_norm_brake_inversion() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .set_park_brake(false) + .run_waiting_for(Duration::from_secs(5)); + + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + // Braking left + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + // Braking right + test_bed = test_bed + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn no_alternate_brake_inversion() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .set_park_brake(false) + .run_waiting_for(Duration::from_secs(5)); + + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + // Disabling Askid causes alternate braking to work and release green brakes + test_bed = test_bed + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(100.)) + .set_anti_skid(false) + .run_waiting_for(Duration::from_secs(2)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() > Pressure::new::(950.)); + + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(2)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() > Pressure::new::(950.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn auto_brake_at_gear_retraction() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .set_park_brake(false) + .run_waiting_for(Duration::from_secs(15)); + + // No brake inputs + test_bed = test_bed + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + // Positive climb, gear up + test_bed = test_bed + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(1)); + + // Check auto brake is active + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(50.)); + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(1500.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(1500.)); + + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + // Check no more autobrakes after 3s + test_bed = test_bed.run_waiting_for(Duration::from_secs(3)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn alternate_brake_accumulator_is_emptying_while_braking() { + let mut test_bed = test_bed_on_ground_with() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .set_park_brake(false) + .run_waiting_for(Duration::from_secs(15)); + + // Check we got yellow pressure and brake accumulator loaded + assert!(test_bed.yellow_pressure() >= Pressure::new::(2500.)); + assert!( + test_bed.get_brake_yellow_accumulator_pressure() >= Pressure::new::(2500.) + ); + + // Disabling green and yellow side so accumulator stop being able to reload + test_bed = test_bed + .set_ptu_state(false) + .set_yellow_ed_pump(false) + .set_green_ed_pump(false) + .set_yellow_e_pump(true) + .run_waiting_for(Duration::from_secs(30)); + + assert!(test_bed.yellow_pressure() <= Pressure::new::(100.)); + assert!(test_bed.green_pressure() <= Pressure::new::(100.)); + assert!( + test_bed.get_brake_yellow_accumulator_pressure() >= Pressure::new::(2500.) + ); + + // Now using brakes and check accumulator gets empty + test_bed = test_bed + .empty_brake_accumulator_using_pedal_brake() + .run_waiting_for(Duration::from_secs(1)); + + assert!( + test_bed.get_brake_yellow_accumulator_pressure() <= Pressure::new::(1000.) + ); + assert!( + test_bed.get_brake_yellow_accumulator_fluid_volume() <= Volume::new::(0.01) + ); + } + + #[test] + fn brakes_inactive_in_flight() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(10)); + + // No brake inputs + test_bed = test_bed + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + + // Now full brakes + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)); + + // Check no action on brakes + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn brakes_norm_active_in_flight_gear_down() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(10)); + + // Now full brakes gear down + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs(1)); + + // Brakes norm should work normally + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(50.)); + + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn brakes_alternate_active_in_flight_gear_down() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(10)); + + // Now full brakes gear down + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .set_gear_lever_down() + .set_anti_skid(false) + .run_waiting_for(Duration::from_secs(1)); + + // Brakes norm should work normally + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + + assert!(test_bed.get_brake_left_yellow_pressure() > Pressure::new::(900.)); + assert!(test_bed.get_brake_right_yellow_pressure() > Pressure::new::(900.)); + } + + #[test] + // Testing that green for brakes is only available if park brake is on while altn pressure is at too low level + fn brake_logic_green_backup_emergency() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .load_brake_accumulator() + .set_cold_dark_inputs() + .run_one_tick(); + + // Setting on ground with yellow side hydraulics off + // This should prevent yellow accumulator to fill + test_bed = test_bed + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .set_park_brake(true) + .set_ptu_state(false) + .set_yellow_e_pump(true) + .set_yellow_ed_pump(false) + .run_waiting_for(Duration::from_secs(15)); + + // Braking but park is on: no output on green brakes expected + test_bed = test_bed + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_left_yellow_pressure() > Pressure::new::(500.)); + assert!(test_bed.get_brake_right_yellow_pressure() > Pressure::new::(500.)); + + // With no more fluid in yellow accumulator, green should work as emergency + test_bed = test_bed + .empty_brake_accumulator_using_park_brake() + .set_left_brake(Ratio::new::(100.)) + .set_right_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn autobrakes_arms_in_flight_lo_or_med() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(12)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + + test_bed = test_bed + .set_autobrake_low() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::LOW); + + test_bed = test_bed + .set_autobrake_med() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + } + + #[test] + fn autobrakes_arming_according_to_set_variable() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(10)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + + // set autobrake to LOW + test_bed = test_bed + .set_autobrake_low_with_set_variable() + .run_waiting_for(Duration::from_secs(1)); + assert!(test_bed.autobrake_mode() == AutobrakeMode::LOW); + + // using the set variable again is still resulting in LOW + // and not disarming + test_bed = test_bed + .set_autobrake_low_with_set_variable() + .run_waiting_for(Duration::from_secs(1)); + assert!(test_bed.autobrake_mode() == AutobrakeMode::LOW); + + // set autobrake to MED + test_bed = test_bed + .set_autobrake_med_with_set_variable() + .run_waiting_for(Duration::from_secs(1)); + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + + // set autobrake to MAX + test_bed = test_bed + .set_autobrake_max_with_set_variable() + .run_waiting_for(Duration::from_secs(1)); + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + + // set autobrake to DISARMED + test_bed = test_bed + .set_autobrake_disarmed_with_set_variable() + .run_waiting_for(Duration::from_secs(1)); + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + } + + #[test] + fn autobrakes_disarms_if_green_pressure_low() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(12)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + + test_bed = test_bed + .set_autobrake_low() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::LOW); + + test_bed = test_bed + .set_ptu_state(false) + .stop_eng1() + .run_waiting_for(Duration::from_secs(20)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + } + + #[test] + fn autobrakes_does_not_disarm_if_askid_off_but_sim_not_ready() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .sim_not_ready() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(12)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + + test_bed = test_bed + .set_autobrake_med() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + + // sim is not ready --> no disarm + test_bed = test_bed + .set_anti_skid(false) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + + // sim is now ready --> disarm expected + test_bed = test_bed.sim_ready().run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + } + + #[test] + fn autobrakes_disarms_if_askid_off() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(12)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + + test_bed = test_bed + .set_autobrake_med() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + + test_bed = test_bed + .set_anti_skid(false) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + } + + #[test] + fn autobrakes_max_wont_arm_in_flight() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs(15)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + + test_bed = test_bed + .set_autobrake_max() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + + // using the set variable should also not work + test_bed = test_bed + .set_autobrake_max_with_set_variable() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + } + + #[test] + fn autobrakes_taxiing_wont_disarm_when_braking() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .start_eng1(Ratio::new::(60.)) + .start_eng2(Ratio::new::(60.)) + .run_waiting_for(Duration::from_secs(3)); + + test_bed = test_bed + .set_autobrake_max() + .set_right_brake(Ratio::new::(100.)) + .set_left_brake(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + } + + #[test] + fn autobrakes_activates_on_ground_on_spoiler_deploy() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(3)); + + test_bed = test_bed + .set_autobrake_max() + .set_deploy_ground_spoilers() + .run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + assert!(test_bed.get_brake_left_yellow_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_yellow_pressure() < Pressure::new::(50.)); + } + + #[test] + fn autobrakes_disengage_on_spoiler_retract() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(3)); + + test_bed = test_bed + .set_autobrake_max() + .set_deploy_ground_spoilers() + .run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + + test_bed = test_bed + .set_retract_ground_spoilers() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + } + + #[test] + // Should disable with one pedal > 61° over max range of 79.4° thus 77% + fn autobrakes_max_disengage_at_77_on_one_pedal_input() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(3)); + + test_bed = test_bed + .set_autobrake_max() + .set_deploy_ground_spoilers() + .run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_left_brake(Ratio::new::(70.)) + .run_waiting_for(Duration::from_secs(1)) + .set_left_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_left_brake(Ratio::new::(78.)) + .run_waiting_for(Duration::from_secs(1)) + .set_left_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + } + + #[test] + fn autobrakes_max_disengage_at_52_on_both_pedal_input() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)); + + test_bed = test_bed + .set_autobrake_max() + .set_deploy_ground_spoilers() + .run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_left_brake(Ratio::new::(55.)) + .run_waiting_for(Duration::from_secs(1)) + .set_left_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_left_brake(Ratio::new::(55.)) + .set_right_brake(Ratio::new::(55.)) + .run_waiting_for(Duration::from_secs(1)) + .set_left_brake(Ratio::new::(0.)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + } + + #[test] + // Should disable with one pedals > 42° over max range of 79.4° thus 52% + fn autobrakes_med_disengage_at_52_on_one_pedal_input() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(3)); + + test_bed = test_bed + .set_autobrake_med() + .set_deploy_ground_spoilers() + .run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_right_brake(Ratio::new::(50.)) + .run_waiting_for(Duration::from_secs(1)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_right_brake(Ratio::new::(55.)) + .run_waiting_for(Duration::from_secs(1)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + } + + #[test] + fn autobrakes_med_disengage_at_11_on_both_pedal_input() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(3)); + + test_bed = test_bed + .set_autobrake_med() + .set_deploy_ground_spoilers() + .run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_right_brake(Ratio::new::(15.)) + .run_waiting_for(Duration::from_secs(1)) + .set_right_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(1000.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(1000.)); + + test_bed = test_bed + .set_right_brake(Ratio::new::(15.)) + .set_left_brake(Ratio::new::(15.)) + .run_waiting_for(Duration::from_secs(1)) + .set_right_brake(Ratio::new::(0.)) + .set_left_brake(Ratio::new::(0.)) + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + assert!(test_bed.get_brake_left_green_pressure() < Pressure::new::(50.)); + assert!(test_bed.get_brake_right_green_pressure() < Pressure::new::(50.)); + } + + #[test] + fn autobrakes_max_disarm_after_10s_in_flight() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_park_brake(false) + .start_eng1(Ratio::new::(100.)) + .start_eng2(Ratio::new::(100.)) + .run_waiting_for(Duration::from_secs(1)); + + test_bed = test_bed + .set_autobrake_max() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + + test_bed = test_bed.in_flight().run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MAX); + + test_bed = test_bed.in_flight().run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::NONE); + } + + #[test] + fn autobrakes_does_not_disarm_after_10s_when_started_in_flight() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs(1)); + + test_bed = test_bed + .set_autobrake_med_with_set_variable() + .run_waiting_for(Duration::from_secs(1)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + + test_bed = test_bed.in_flight().run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + + test_bed = test_bed.in_flight().run_waiting_for(Duration::from_secs(6)); + + assert!(test_bed.autobrake_mode() == AutobrakeMode::MED); + } + + #[test] + fn controller_blue_epump_activates_when_no_weight_on_nose_wheel() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(!test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + + test_bed = test_bed.rotates_on_runway().run_one_tick(); + + assert!(test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + } + + #[test] + fn controller_blue_epump_split_engine_states() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(!test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + + test_bed = test_bed + .start_eng1(Ratio::new::(65.)) + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + + test_bed = test_bed + .start_eng2(Ratio::new::(65.)) + .stop_eng1() + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + } + + #[test] + fn controller_blue_epump_on_off_engines() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(65.)) + .start_eng2(Ratio::new::(65.)) + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + + test_bed = test_bed.stop_eng1().stop_eng2().run_one_tick(); + + assert!(!test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + } + + #[test] + fn controller_blue_epump_override() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .press_blue_epump_override_button_once() + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + + test_bed = test_bed + .press_blue_epump_override_button_once() + .run_one_tick(); + + assert!(!test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + } + + #[test] + fn controller_blue_epump_override_without_power_shall_not_run_blue_pump() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(65.)) + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + + test_bed = test_bed.dc_ess_lost().run_one_tick(); + + assert!(!test_bed.query(|a| a.is_blue_epump_controller_pressurising())); + } + + #[test] + fn controller_yellow_epump_is_activated_by_overhead_button() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(!test_bed.query(|a| a.is_yellow_epump_controller_pressurising())); + + test_bed = test_bed.set_yellow_e_pump(false).run_one_tick(); + + assert!(test_bed.query(|a| a.is_yellow_epump_controller_pressurising())); + + test_bed = test_bed.set_yellow_e_pump(true).run_one_tick(); + + assert!(!test_bed.query(|a| a.is_yellow_epump_controller_pressurising())); + } + + #[test] + fn controller_yellow_epump_unpowered_cant_command_pump() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_yellow_e_pump(false) + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_yellow_epump_controller_pressurising())); + + test_bed = test_bed.dc_bus_2_lost().run_one_tick(); + + assert!(!test_bed.query(|a| a.is_yellow_epump_controller_pressurising())); + } + + #[test] + fn controller_yellow_epump_can_operate_from_cargo_door_without_main_control_power_bus() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(!test_bed.query(|a| a.is_cargo_powering_yellow_epump())); + + test_bed = test_bed + .dc_ground_service_lost() + .open_fwd_cargo_door() + .run_waiting_for( + Duration::from_secs(5) + + HydraulicDoorController::DELAY_UNLOCK_TO_HYDRAULIC_CONTROL, + ); + + assert!(test_bed.query(|a| a.is_cargo_powering_yellow_epump())); + } + + #[test] + fn controller_engine_driven_pump1_overhead_button_logic_with_eng_on() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_edp1_green_pump_controller_pressurising())); + + test_bed = test_bed + .start_eng1(Ratio::new::(65.)) + .run_one_tick(); + assert!(test_bed.query(|a| a.is_edp1_green_pump_controller_pressurising())); + + test_bed = test_bed.set_green_ed_pump(false).run_one_tick(); + assert!(!test_bed.query(|a| a.is_edp1_green_pump_controller_pressurising())); + + test_bed = test_bed.set_green_ed_pump(true).run_one_tick(); + assert!(test_bed.query(|a| a.is_edp1_green_pump_controller_pressurising())); + } + + #[test] + fn controller_engine_driven_pump1_fire_overhead_released_stops_pump() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(65.)) + .start_eng2(Ratio::new::(65.)) + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_edp1_green_pump_controller_pressurising())); + + test_bed = test_bed.set_eng1_fire_button(true).run_one_tick(); + assert!(!test_bed.query(|a| a.is_edp1_green_pump_controller_pressurising())); + } + + #[test] + fn controller_engine_driven_pump2_overhead_button_logic_with_eng_on() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_edp2_yellow_pump_controller_pressurising())); + + test_bed = test_bed + .start_eng2(Ratio::new::(65.)) + .run_one_tick(); + assert!(test_bed.query(|a| a.is_edp2_yellow_pump_controller_pressurising())); + + test_bed = test_bed.set_yellow_ed_pump(false).run_one_tick(); + assert!(!test_bed.query(|a| a.is_edp2_yellow_pump_controller_pressurising())); + + test_bed = test_bed.set_yellow_ed_pump(true).run_one_tick(); + assert!(test_bed.query(|a| a.is_edp2_yellow_pump_controller_pressurising())); + } + + #[test] + fn controller_engine_driven_pump2_fire_overhead_released_stops_pump() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(65.)) + .start_eng2(Ratio::new::(65.)) + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_edp2_yellow_pump_controller_pressurising())); + + test_bed = test_bed.set_eng2_fire_button(true).run_one_tick(); + assert!(!test_bed.query(|a| a.is_edp2_yellow_pump_controller_pressurising())); + } + + #[test] + fn controller_ptu_on_off_with_overhead_pushbutton() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + + test_bed = test_bed.set_ptu_state(false).run_one_tick(); + + assert!(!test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + + test_bed = test_bed.set_ptu_state(true).run_one_tick(); + + assert!(test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + } + + #[test] + fn controller_ptu_off_when_cargo_door_is_moved() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + + test_bed = test_bed.open_fwd_cargo_door().run_waiting_for( + Duration::from_secs(5) + HydraulicDoorController::DELAY_UNLOCK_TO_HYDRAULIC_CONTROL, + ); + + assert!(!test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + + // Ptu should reactivate after door fully opened + 40s + test_bed = test_bed.run_waiting_for(Duration::from_secs(25) + Duration::from_secs(41)); + + assert!(test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + } + + #[test] + fn controller_ptu_disabled_when_tug_attached() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + + test_bed = test_bed + .start_eng1(Ratio::new::(65.)) + .set_park_brake(false) + .run_one_tick(); + + assert!(test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + + test_bed = test_bed.set_pushback_state(true).run_one_tick(); + + assert!(!test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + + // Ptu should reactivate after 15ish seconds + test_bed = test_bed + .set_pushback_state(false) + .run_waiting_for(Duration::from_secs(16)); + + assert!(test_bed.query(|a| a.is_ptu_controller_activating_ptu())); + } + + #[test] + fn rat_does_not_deploy_on_ground_at_eng_off() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(10)); + + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.get_rat_position() <= 0.); + assert!(test_bed.get_rat_rpm() <= 1.); + + test_bed = test_bed + .ac_bus_1_lost() + .ac_bus_2_lost() + .run_waiting_for(Duration::from_secs(2)); + + // RAT has not deployed + assert!(test_bed.get_rat_position() <= 0.); + assert!(test_bed.get_rat_rpm() <= 1.); + } + + #[test] + fn rat_does_not_deploy_when_sim_not_ready() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .sim_not_ready() + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(10)); + + // AC off, sim not ready -> RAT should not deploy + test_bed = test_bed + .ac_bus_1_lost() + .ac_bus_2_lost() + .run_waiting_for(Duration::from_secs(2)); + + assert!(!test_bed.rat_deploy_commanded()); + + // AC off, sim ready -> RAT should deploy + test_bed = test_bed.sim_ready().run_waiting_for(Duration::from_secs(2)); + + assert!(test_bed.rat_deploy_commanded()); + } + + #[test] + fn rat_deploys_on_both_ac_lost() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(10)); + + assert!(!test_bed.rat_deploy_commanded()); + + test_bed = test_bed + .ac_bus_1_lost() + .run_waiting_for(Duration::from_secs(2)); + + assert!(!test_bed.rat_deploy_commanded()); + + // Now all AC off should deploy RAT in flight + test_bed = test_bed + .ac_bus_1_lost() + .ac_bus_2_lost() + .run_waiting_for(Duration::from_secs(2)); + + assert!(test_bed.rat_deploy_commanded()); + } + + #[test] + fn blue_epump_unavailable_if_unpowered() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(10)); + + // Blue epump working + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + test_bed = test_bed + .ac_bus_2_lost() + .run_waiting_for(Duration::from_secs(25)); + + // Blue epump still working as it's not plugged on AC2 + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + test_bed = test_bed + .ac_bus_1_lost() + .run_waiting_for(Duration::from_secs(25)); + + // Blue epump has stopped + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + } + + #[test] + fn yellow_epump_unavailable_if_unpowered() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(10)); + + // Yellow epump working + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .ac_bus_2_lost() + .ac_bus_1_lost() + .run_waiting_for(Duration::from_secs(25)); + + // Yellow epump still working as not plugged on AC2 or AC1 + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .ac_ground_service_lost() + .run_waiting_for(Duration::from_secs(25)); + + // Yellow epump has stopped + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + } + + #[test] + fn flaps_and_slats_declare_moving() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .load_brake_accumulator() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_secs(5)); + + test_bed = test_bed + .set_yellow_e_pump(false) + .set_ptu_state(false) + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(5)); + + // Only yellow press so only flaps can move + assert!(test_bed.is_flaps_moving()); + assert!(!test_bed.is_slats_moving()); + + // Now slats can move through ptu + test_bed = test_bed + .set_ptu_state(true) + .run_waiting_for(Duration::from_secs(5)); + + assert!(test_bed.is_flaps_moving()); + assert!(test_bed.is_slats_moving()); + } + + #[test] + fn yellow_epump_can_deploy_flaps_and_slats_on_worst_case_ptu() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .with_worst_case_ptu() + .run_one_tick(); + + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(10)); + + // Yellow epump working + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(90)); + + assert!(test_bed.get_flaps_left_position_percent() > 99.); + assert!(test_bed.get_flaps_right_position_percent() > 99.); + assert!(test_bed.get_slats_left_position_percent() > 99.); + assert!(test_bed.get_slats_right_position_percent() > 99.); + } + + #[test] + fn yellow_epump_no_ptu_can_deploy_flaps_less_33s() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(false) + .run_one_tick(); + + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs(20)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(32)); + + assert!(test_bed.get_flaps_left_position_percent() > 99.); + assert!(test_bed.get_flaps_right_position_percent() > 99.); + assert!(test_bed.get_slats_left_position_percent() < 1.); + assert!(test_bed.get_slats_right_position_percent() < 1.); + } + + #[test] + fn blue_epump_can_deploy_slats_in_less_50_s_and_no_flaps() { + let mut test_bed = test_bed_on_ground_with() + .on_the_ground() + .set_cold_dark_inputs() + .set_blue_e_pump_ovrd_pressed(true) + .run_waiting_for(Duration::from_secs(5)); + + // Blue epump is on + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(50)); + + assert!(test_bed.get_flaps_left_position_percent() <= 1.); + assert!(test_bed.get_flaps_right_position_percent() <= 1.); + assert!(test_bed.get_slats_left_position_percent() > 99.); + assert!(test_bed.get_slats_right_position_percent() > 99.); + } + + #[test] + fn blue_epump_cannot_deploy_slats_in_less_28_s_and_no_flaps() { + let mut test_bed = test_bed_on_ground_with() + .on_the_ground() + .set_cold_dark_inputs() + .set_blue_e_pump_ovrd_pressed(true) + .run_waiting_for(Duration::from_secs(5)); + + // Blue epump is on + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(28)); + + assert!(test_bed.get_flaps_left_position_percent() <= 1.); + assert!(test_bed.get_flaps_right_position_percent() <= 1.); + assert!(test_bed.get_slats_left_position_percent() < 99.); + assert!(test_bed.get_slats_right_position_percent() < 99.); + } + + #[test] + fn yellow_plus_blue_epumps_can_deploy_flaps_and_slats() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .set_yellow_e_pump(false) + .set_blue_e_pump_ovrd_pressed(true) + .run_waiting_for(Duration::from_secs(15)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(45)); + + assert!(test_bed.get_flaps_left_position_percent() > 99.); + assert!(test_bed.get_flaps_right_position_percent() > 99.); + assert!(test_bed.get_slats_left_position_percent() > 99.); + assert!(test_bed.get_slats_right_position_percent() > 99.); + } + + #[test] + fn no_pressure_no_flap_slats() { + let mut test_bed = test_bed_on_ground_with() + .on_the_ground() + .set_cold_dark_inputs() + .run_waiting_for(Duration::from_secs(5)); + + test_bed = test_bed + .set_flaps_handle_position(4) + .run_waiting_for(Duration::from_secs(10)); + + assert!(test_bed.get_flaps_left_position_percent() <= 1.); + assert!(test_bed.get_flaps_right_position_percent() <= 1.); + assert!(test_bed.get_slats_left_position_percent() <= 1.); + assert!(test_bed.get_slats_right_position_percent() <= 1.); + + assert!(!test_bed.is_slats_moving()); + assert!(!test_bed.is_flaps_moving()); + } + + #[test] + fn emergency_gen_is_started_on_both_ac_lost_in_flight() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs(10)); + + assert!(!test_bed.is_emergency_gen_at_nominal_speed()); + + test_bed = test_bed + .ac_bus_1_lost() + .run_waiting_for(Duration::from_secs(2)); + + assert!(!test_bed.is_emergency_gen_at_nominal_speed()); + + // Now all AC off should deploy RAT in flight + test_bed = test_bed + .ac_bus_1_lost() + .ac_bus_2_lost() + .run_waiting_for(Duration::from_secs(8)); + + assert!(test_bed.is_emergency_gen_at_nominal_speed()); + } + + #[test] + fn cargo_door_stays_closed_at_init() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() == 0.); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(15.)); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() == 0.); + } + + #[test] + fn cargo_door_unlocks_when_commanded() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() == 0.); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() == 0.); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(!test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() >= 0.); + } + + #[test] + fn cargo_door_controller_opens_the_door() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() == 0.); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(!test_bed.is_cargo_fwd_door_locked_down()); + + let current_position_unlocked = test_bed.cargo_fwd_door_position(); + + test_bed = test_bed.open_fwd_cargo_door().run_waiting_for( + HydraulicDoorController::DELAY_UNLOCK_TO_HYDRAULIC_CONTROL + Duration::from_secs(5), + ); + + assert!(test_bed.cargo_fwd_door_position() > current_position_unlocked); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(30.)); + + assert!(test_bed.cargo_fwd_door_position() > 0.85); + } + + #[test] + fn fwd_cargo_door_controller_opens_fwd_door_only() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() == 0.); + assert!(test_bed.cargo_aft_door_position() == 0.); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(30.)); + + assert!(test_bed.cargo_fwd_door_position() > 0.85); + assert!(test_bed.cargo_aft_door_position() == 0.); + } + + #[test] + fn cargo_door_opened_uses_correct_reservoir_amount() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_yellow_e_pump(false) + .set_ptu_state(false) + .run_waiting_for(Duration::from_secs_f64(20.)); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + + let pressurised_yellow_level_door_closed = test_bed.get_yellow_reservoir_volume(); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(40.)); + + assert!(!test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() > 0.85); + + let pressurised_yellow_level_door_opened = test_bed.get_yellow_reservoir_volume(); + + let volume_used_liter = (pressurised_yellow_level_door_closed + - pressurised_yellow_level_door_opened) + .get::(); + + // For one cargo door we expect losing between 0.6 to 0.8 liter of fluid into the two actuators + assert!((0.6..=0.8).contains(&volume_used_liter)); + } + + #[test] + fn cargo_door_controller_closes_the_door() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(30.)); + + assert!(!test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() > 0.85); + + test_bed = test_bed + .close_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(60.)); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() <= 0.); + } + + #[test] + fn cargo_door_controller_closes_the_door_after_yellow_pump_auto_shutdown() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(30.)); + + assert!(!test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() > 0.85); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(30.)); + + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + + test_bed = test_bed + .close_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(30.)); + + assert!(test_bed.is_cargo_fwd_door_locked_down()); + assert!(test_bed.cargo_fwd_door_position() <= 0.); + } + + #[test] + fn nose_steering_responds_to_tiller_demand_if_yellow_pressure_and_engines() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_yellow_e_pump(false) + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs_f64(1.)); + + test_bed = test_bed + .set_tiller_demand(Ratio::new::(1.)) + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.nose_steering_position().get::() >= 73.9); + assert!(test_bed.nose_steering_position().get::() <= 74.1); + + test_bed = test_bed + .set_tiller_demand(Ratio::new::(-1.)) + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.nose_steering_position().get::() <= -73.9); + assert!(test_bed.nose_steering_position().get::() >= -74.1); + } + + #[test] + fn nose_steering_does_not_move_if_yellow_pressure_but_no_engine() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_yellow_e_pump(false) + .run_one_tick(); + + test_bed = test_bed + .set_tiller_demand(Ratio::new::(1.)) + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.nose_steering_position().get::() <= 0.1); + assert!(test_bed.nose_steering_position().get::() >= -0.1); + + test_bed = test_bed + .set_tiller_demand(Ratio::new::(-1.)) + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.nose_steering_position().get::() <= 0.1); + assert!(test_bed.nose_steering_position().get::() >= -0.1); + } + + #[test] + fn nose_steering_does_not_move_when_a_skid_off() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_yellow_e_pump(false) + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .set_anti_skid(false) + .run_one_tick(); + + test_bed = test_bed + .set_tiller_demand(Ratio::new::(1.)) + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.nose_steering_position().get::() >= -0.1); + assert!(test_bed.nose_steering_position().get::() <= 0.1); + } + + #[test] + fn nose_steering_centers_itself_when_a_skid_off() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_yellow_e_pump(false) + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .set_anti_skid(true) + .run_waiting_for(Duration::from_secs_f64(1.)); + + test_bed = test_bed + .set_tiller_demand(Ratio::new::(1.)) + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.nose_steering_position().get::() >= 70.); + + test_bed = test_bed + .set_tiller_demand(Ratio::new::(1.)) + .set_anti_skid(false) + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.nose_steering_position().get::() <= 0.1); + assert!(test_bed.nose_steering_position().get::() >= -0.1); + } + + #[test] + fn nose_steering_responds_to_autopilot_demand() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_secs_f64(1.)); + + test_bed = test_bed + .set_autopilot_steering_demand(Ratio::new::(1.5)) + .run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.nose_steering_position().get::() >= 5.9); + assert!(test_bed.nose_steering_position().get::() <= 6.1); + + test_bed = test_bed + .set_autopilot_steering_demand(Ratio::new::(-1.8)) + .run_waiting_for(Duration::from_secs_f64(4.)); + + assert!(test_bed.nose_steering_position().get::() <= -5.9); + assert!(test_bed.nose_steering_position().get::() >= -6.1); + } + + #[test] + fn ptu_pressurise_green_from_yellow_edp() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .start_eng2(Ratio::new::(60.)) + .set_park_brake(false) + .set_ptu_state(false) + .run_waiting_for(Duration::from_secs(25)); + + assert!(!test_bed.is_ptu_enabled()); + + test_bed = test_bed + .set_ptu_state(true) + .run_waiting_for(Duration::from_secs(10)); + + // Now we should have pressure in yellow and green + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.green_pressure() < Pressure::new::(3100.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2000.)); + assert!(test_bed.yellow_pressure() < Pressure::new::(3100.)); + } + + #[test] + fn ptu_pressurise_yellow_from_green_edp() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .start_eng1(Ratio::new::(60.)) + .set_park_brake(false) + .set_ptu_state(false) + .run_waiting_for(Duration::from_secs(25)); + + assert!(!test_bed.is_ptu_enabled()); + + test_bed = test_bed + .set_ptu_state(true) + .run_waiting_for(Duration::from_secs(25)); + + // Now we should have pressure in yellow and green + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.green_pressure() > Pressure::new::(2000.)); + assert!(test_bed.green_pressure() < Pressure::new::(3100.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.yellow_pressure() > Pressure::new::(2000.)); + assert!(test_bed.yellow_pressure() < Pressure::new::(3100.)); + } + + #[test] + fn yellow_epump_has_cavitation_at_low_air_press() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(false) + .run_one_tick(); + + test_bed = test_bed + .air_press_nominal() + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.yellow_pressure().get::() > 2900.); + + test_bed = test_bed + .air_press_low() + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.yellow_pressure().get::() < 2000.); + } + + #[test] + fn low_air_press_fault_causes_ptu_fault() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_millis(1000)); + + assert!(!test_bed.ptu_has_fault()); + assert!(!test_bed.green_edp_has_fault()); + assert!(!test_bed.yellow_edp_has_fault()); + + test_bed = test_bed + .air_press_low() + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.green_edp_has_fault()); + assert!(test_bed.yellow_edp_has_fault()); + assert!(test_bed.ptu_has_fault()); + } + + #[test] + fn low_air_press_fault_causes_yellow_blue_epump_fault() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .start_eng1(Ratio::new::(80.)) + .start_eng2(Ratio::new::(80.)) + .run_waiting_for(Duration::from_millis(5000)); + + assert!(!test_bed.blue_epump_has_fault()); + assert!(!test_bed.yellow_epump_has_fault()); + + test_bed = test_bed + .air_press_low() + .run_waiting_for(Duration::from_secs_f64(10.)); + + // Blue pump is on but yellow is off: only blue fault expected + assert!(test_bed.blue_epump_has_fault()); + assert!(!test_bed.yellow_epump_has_fault()); + + test_bed = test_bed + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.yellow_epump_has_fault()); + } + + #[test] + fn no_yellow_epump_fault_after_brake_accumulator_is_filled() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_millis(8000)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(!test_bed.yellow_epump_has_fault()); + } + + #[test] + fn ailerons_are_dropped_down_in_cold_and_dark() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + assert!(test_bed.get_left_aileron_position().get::() < 0.1); + assert!(test_bed.get_right_aileron_position().get::() < 0.1); + } + + #[test] + fn ailerons_do_not_respond_in_cold_and_dark() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .set_ailerons_left_turn() + .run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.get_left_aileron_position().get::() < 0.1); + assert!(test_bed.get_right_aileron_position().get::() < 0.1); + + test_bed = test_bed + .set_ailerons_right_turn() + .run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.get_left_aileron_position().get::() < 0.1); + assert!(test_bed.get_right_aileron_position().get::() < 0.1); + } + + #[test] + fn ailerons_do_not_respond_if_only_yellow_pressure() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(false) + .set_yellow_e_pump(false) + .run_one_tick(); + + test_bed = test_bed + .set_ailerons_left_turn() + .run_waiting_for(Duration::from_secs_f64(6.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_aileron_position().get::() < 0.1); + assert!(test_bed.get_right_aileron_position().get::() < 0.1); + } + + #[test] + fn ailerons_respond_if_green_pressure() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(true) + .load_brake_accumulator() + .set_yellow_e_pump(false) + .set_blue_e_pump_ovrd_pressed(true) + .run_one_tick(); + + test_bed = test_bed + .set_ailerons_left_turn() + .run_waiting_for(Duration::from_secs_f64(6.)); + + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_aileron_position().get::() > 0.9); + assert!(test_bed.get_right_aileron_position().get::() < 0.1); + + test_bed = test_bed + .set_ailerons_right_turn() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_aileron_position().get::() < 0.1); + assert!(test_bed.get_right_aileron_position().get::() > 0.9); + } + + #[test] + fn ailerons_droop_down_after_pressure_is_off() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(true) + .set_yellow_e_pump(false) + .set_blue_e_pump_ovrd_pressed(true) + .run_one_tick(); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(8.)); + + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_aileron_position().get::() > 0.45); + assert!(test_bed.get_right_aileron_position().get::() > 0.45); + + test_bed = test_bed + .set_ptu_state(false) + .set_yellow_e_pump(true) + .set_blue_e_pump(false) + .run_waiting_for(Duration::from_secs_f64(50.)); + + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_aileron_position().get::() < 0.42); + assert!(test_bed.get_right_aileron_position().get::() < 0.42); + } + + #[test] + fn elevators_droop_down_after_pressure_is_off() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_ptu_state(true) + .set_yellow_e_pump(false) + .set_blue_e_pump_ovrd_pressed(true) + .run_one_tick(); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(8.)); + + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_elevator_position().get::() > 0.35); + assert!(test_bed.get_right_elevator_position().get::() > 0.35); + + test_bed = test_bed + .set_ptu_state(false) + .set_yellow_e_pump(true) + .set_blue_e_pump(false) + .run_waiting_for(Duration::from_secs_f64(75.)); + + assert!(!test_bed.is_blue_pressure_switch_pressurised()); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(!test_bed.is_green_pressure_switch_pressurised()); + assert!(test_bed.get_left_elevator_position().get::() < 0.3); + assert!(test_bed.get_right_elevator_position().get::() < 0.3); + } + + #[test] + fn elevators_can_go_up_and_down_with_pressure() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_blue_e_pump_ovrd_pressed(true) + .run_one_tick(); + + test_bed = test_bed + .set_elevator_full_up() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + assert!(test_bed.get_left_elevator_position().get::() > 0.9); + assert!(test_bed.get_right_elevator_position().get::() > 0.9); + + test_bed = test_bed + .set_elevator_full_down() + .run_waiting_for(Duration::from_secs_f64(1.5)); + + assert!(test_bed.get_left_elevator_position().get::() < 0.1); + assert!(test_bed.get_right_elevator_position().get::() < 0.1); + } + + #[test] + fn elevators_centers_with_pressure_but_no_computer_command() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .set_blue_e_pump_ovrd_pressed(true) + .run_one_tick(); + + test_bed = test_bed + .set_elevator_full_up() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.is_blue_pressure_switch_pressurised()); + + assert!(test_bed.get_left_elevator_position().get::() > 0.9); + assert!(test_bed.get_right_elevator_position().get::() > 0.9); + + test_bed = test_bed + .set_elac_actuators_de_energized() + .run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.get_left_elevator_position().get::() < 0.4); + assert!(test_bed.get_right_elevator_position().get::() < 0.4); + + assert!(test_bed.get_left_elevator_position().get::() > 0.3); + assert!(test_bed.get_right_elevator_position().get::() > 0.3); + } + + #[test] + fn cargo_door_operation_closes_yellow_leak_meas_valve() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.yellow_pressure().get::() > 500.); + assert!(!test_bed.is_yellow_leak_meas_valve_commanded_open()); + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + } + + #[test] + fn cargo_door_operation_but_yellow_epump_on_opens_yellow_leak_meas_valve() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .open_fwd_cargo_door() + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs_f64(10.)); + + assert!(test_bed.yellow_pressure().get::() > 500.); + assert!(test_bed.is_yellow_leak_meas_valve_commanded_open()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + } + + #[test] + fn leak_meas_valve_cant_be_closed_in_flight() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .in_flight() + .green_leak_meas_valve_closed() + .blue_leak_meas_valve_closed() + .yellow_leak_meas_valve_closed() + .run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(test_bed.is_yellow_leak_meas_valve_commanded_open()); + assert!(test_bed.is_blue_leak_meas_valve_commanded_open()); + assert!(test_bed.is_green_leak_meas_valve_commanded_open()); + } + + #[test] + fn leak_meas_valve_can_be_closed_on_ground() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .green_leak_meas_valve_closed() + .blue_leak_meas_valve_closed() + .yellow_leak_meas_valve_closed() + .run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(!test_bed.is_yellow_leak_meas_valve_commanded_open()); + assert!(!test_bed.is_blue_leak_meas_valve_commanded_open()); + assert!(!test_bed.is_green_leak_meas_valve_commanded_open()); + } + + #[test] + fn leak_meas_valve_closed_on_ground_ptu_is_working() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .load_brake_accumulator() + .set_yellow_e_pump(false) + .run_one_tick(); + + test_bed = test_bed + .green_leak_meas_valve_closed() + .blue_leak_meas_valve_closed() + .yellow_leak_meas_valve_closed() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(!test_bed.is_yellow_leak_meas_valve_commanded_open()); + assert!(!test_bed.is_blue_leak_meas_valve_commanded_open()); + assert!(!test_bed.is_green_leak_meas_valve_commanded_open()); + + assert!(!test_bed.is_yellow_pressure_switch_pressurised()); + assert!(!test_bed.is_green_pressure_switch_pressurised()); + + assert!(test_bed.yellow_pressure().get::() > 2000.); + assert!(test_bed.green_pressure().get::() > 2000.); + } + + #[test] + fn nose_wheel_steers_with_pushback_tug() { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .set_pushback_state(true) + .set_pushback_angle(Angle::new::(80.)) + .run_waiting_for(Duration::from_secs_f64(0.5)); + + // Do not turn instantly in 0.5s + assert!( + test_bed.get_nose_steering_ratio() > Ratio::new::(0.) + && test_bed.get_nose_steering_ratio() < Ratio::new::(0.5) + ); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(5.)); + + // Has turned fully after 5s + assert!(test_bed.get_nose_steering_ratio() > Ratio::new::(0.9)); + + // Going left + test_bed = test_bed + .set_pushback_state(true) + .set_pushback_angle(Angle::new::(-80.)) + .run_waiting_for(Duration::from_secs_f64(0.5)); + + assert!(test_bed.get_nose_steering_ratio() > Ratio::new::(0.2)); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(5.)); + + // Has turned fully left after 5s + assert!(test_bed.get_nose_steering_ratio() < Ratio::new::(-0.9)); + } + + #[test] + fn high_pitch_ptu_simvar_on_ptu_first_start() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .start_eng2(Ratio::new::(60.)) + .run_waiting_for(Duration::from_secs(10)); + + assert!(!test_bed.is_ptu_enabled()); + assert!(test_bed.green_pressure() < Pressure::new::(100.)); + assert!(test_bed.yellow_pressure() > Pressure::new::(2900.)); + + test_bed = test_bed.set_park_brake(false).run_one_tick(); + + assert!(test_bed.is_ptu_enabled()); + assert!(test_bed.is_ptu_running_high_pitch_sound()); + } + + #[test] + fn nominal_gear_retraction_extension_cycles_in_flight() { + let mut test_bed = test_bed_on_ground_with().set_cold_dark_inputs().in_flight(); + + assert!(test_bed.gear_system_state() == GearSystemState::AllDownLocked); + + test_bed = test_bed + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(25.)); + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + + test_bed = test_bed + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(25.)); + assert!(test_bed.gear_system_state() == GearSystemState::AllDownLocked); + + test_bed = test_bed + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(25.)); + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + } + + #[test] + fn gear_retracts_using_yellow_epump_plus_ptu() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .with_worst_case_ptu() + .set_gear_lever_down() + .set_green_ed_pump(false) + .set_yellow_ed_pump(false) + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs_f64(15.)); + + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.gear_system_state() == GearSystemState::AllDownLocked); + + test_bed = test_bed.set_gear_lever_up(); + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(150.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + } + + #[test] + fn emergency_gear_extension_at_2_turns_open_doors() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .on_the_ground() + .turn_emergency_gear_extension_n_turns(1) + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.is_all_doors_really_up()); + + test_bed = test_bed + .turn_emergency_gear_extension_n_turns(2) + .run_waiting_for(Duration::from_secs_f64(25.)); + + assert!(test_bed.is_all_doors_really_down()); + } + + #[test] + fn emergency_gear_extension_at_3_turns_release_gear() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(25.)); + + assert!(test_bed.is_all_doors_really_up()); + assert!(test_bed.is_all_gears_really_up()); + + test_bed = test_bed + .set_green_ed_pump(false) + .set_ptu_state(false) + .turn_emergency_gear_extension_n_turns(3) + .run_waiting_for(Duration::from_secs_f64(35.)); + + assert!(test_bed.is_all_doors_really_down()); + assert!(test_bed.is_all_gears_really_down()); + } + + #[test] + fn complete_gear_cycle_do_not_change_fluid_volume() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllDownLocked); + + let initial_fluid_quantity = test_bed.get_green_reservoir_volume(); + + test_bed = test_bed + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(20.)); + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + assert!(test_bed.is_all_doors_really_up()); + + let uplocked_fluid_quantity = test_bed.get_green_reservoir_volume(); + + assert!(initial_fluid_quantity - uplocked_fluid_quantity > Volume::new::(1.)); + assert!(initial_fluid_quantity - uplocked_fluid_quantity < Volume::new::(2.)); + + test_bed = test_bed + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(20.)); + assert!(test_bed.gear_system_state() == GearSystemState::AllDownLocked); + assert!(test_bed.is_all_doors_really_up()); + + let downlocked_fluid_quantity = test_bed.get_green_reservoir_volume(); + assert!( + (initial_fluid_quantity - downlocked_fluid_quantity).abs() + < Volume::new::(0.01) + ); + } + + #[test] + fn reverting_emergency_extension_do_not_change_fluid_volume() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllDownLocked); + + test_bed = test_bed + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(20.)); + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + assert!(test_bed.is_all_doors_really_up()); + + let initial_uplocked_fluid_quantity = test_bed.get_green_reservoir_volume(); + + test_bed = test_bed + .set_gear_lever_down() + .turn_emergency_gear_extension_n_turns(3) + .run_waiting_for(Duration::from_secs_f64(30.)); + assert!(test_bed.is_all_gears_really_down()); + assert!(test_bed.is_all_doors_really_down()); + + test_bed = test_bed + .stow_emergency_gear_extension() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(20.)); + assert!(test_bed.is_all_gears_really_up()); + assert!(test_bed.is_all_doors_really_up()); + + let final_uplocked_fluid_quantity = test_bed.get_green_reservoir_volume(); + + assert!( + (initial_uplocked_fluid_quantity - final_uplocked_fluid_quantity).abs() + < Volume::new::(0.01) + ); + } + + #[test] + fn spoilers_move_to_requested_position() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .load_brake_accumulator() + .set_blue_e_pump_ovrd_pressed(true) + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.is_blue_pressure_switch_pressurised()); + assert!(test_bed.is_yellow_pressure_switch_pressurised()); + assert!(test_bed.is_green_pressure_switch_pressurised()); + + test_bed = test_bed + .set_left_spoilers_out() + .run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.get_mean_left_spoilers_position().get::() > 0.9); + assert!(test_bed.get_mean_right_spoilers_position().get::() < 0.01); + + test_bed = test_bed + .set_left_spoilers_in() + .set_right_spoilers_out() + .run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.get_mean_right_spoilers_position().get::() > 0.9); + assert!(test_bed.get_mean_left_spoilers_position().get::() < 0.01); + + test_bed = test_bed + .set_left_spoilers_in() + .set_right_spoilers_in() + .run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.get_mean_left_spoilers_position().get::() < 0.01); + assert!(test_bed.get_mean_right_spoilers_position().get::() < 0.01); + } + + #[test] + fn gear_init_up_if_spawning_in_air() { + let test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_one_tick(); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + } + + #[test] + fn gear_gravity_extension_reverted_has_correct_sequence_if_gear_lever_stays_up() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .with_worst_case_ptu() + .in_flight() + .run_one_tick(); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + + test_bed = test_bed + .turn_emergency_gear_extension_n_turns(3) + .run_waiting_for(Duration::from_secs_f64(35.)); + + assert!(test_bed.is_all_doors_really_down()); + assert!(test_bed.is_all_gears_really_down()); + + test_bed = test_bed + .stow_emergency_gear_extension() + .run_waiting_for(Duration::from_secs_f64(1.)); + + // Here expecing LGCIU to be unresponsive: everything stays down until gear lever command + assert!(test_bed.is_all_doors_really_down()); + assert!(test_bed.is_all_gears_really_down()); + + test_bed = test_bed + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.is_all_doors_really_up()); + assert!(test_bed.is_all_gears_really_down()); + + // After 5 seconds we expect gear being retracted and doors still down + test_bed = test_bed + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(5.)); + assert!(test_bed.gear_system_state() == GearSystemState::Retracting); + assert!(test_bed.is_all_doors_really_down()); + assert!(!test_bed.is_all_gears_really_down()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(15.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + assert!(test_bed.is_all_doors_really_up()); + assert!(test_bed.is_all_gears_really_up()); + } + + #[test] + fn gear_gravity_extension_reverted_has_correct_sequence_if_gear_lever_down() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .with_worst_case_ptu() + .in_flight() + .run_one_tick(); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + + test_bed = test_bed + .turn_emergency_gear_extension_n_turns(1) + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(3.)); + + test_bed = test_bed + .turn_emergency_gear_extension_n_turns(3) + .run_waiting_for(Duration::from_secs_f64(35.)); + + assert!(test_bed.is_all_doors_really_down()); + assert!(test_bed.is_all_gears_really_down()); + + test_bed = test_bed + .stow_emergency_gear_extension() + .run_waiting_for(Duration::from_secs_f64(5.)); + + // Doors expected to close + assert!(test_bed.is_all_doors_really_up()); + assert!(test_bed.is_all_gears_really_down()); + + // After 5 seconds we expect gear being retracted and doors still down + test_bed = test_bed + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(5.)); + assert!(test_bed.gear_system_state() == GearSystemState::Retracting); + assert!(test_bed.is_all_doors_really_down()); + assert!(!test_bed.is_all_gears_really_down()); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(15.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + assert!(test_bed.is_all_doors_really_up()); + assert!(test_bed.is_all_gears_really_up()); + } + + #[test] + fn aileron_init_centered_if_spawning_in_air() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_one_tick(); + + assert!(test_bed.get_left_aileron_position().get::() < 0.55); + assert!(test_bed.get_right_aileron_position().get::() < 0.55); + assert!(test_bed.get_left_aileron_position().get::() > 0.45); + assert!(test_bed.get_right_aileron_position().get::() > 0.45); + } + + #[test] + fn rudder_init_centered_if_spawning_in_air() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_one_tick(); + + assert!(test_bed.get_rudder_position().get::() > 0.49); + assert!(test_bed.get_rudder_position().get::() < 0.51); + } + + #[test] + fn elevator_init_centered_if_spawning_in_air() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_one_tick(); + + // Elevator deflection is assymetrical so middle is below 0.5 + assert!(test_bed.get_left_elevator_position().get::() < 0.45); + assert!(test_bed.get_right_elevator_position().get::() < 0.45); + assert!(test_bed.get_left_elevator_position().get::() > 0.35); + assert!(test_bed.get_right_elevator_position().get::() > 0.35); + } + + #[test] + fn leak_meas_valve_opens_after_yellow_pump_auto_shutdown_plus_a_delay_and_elevators_stay_drooped_down( + ) { + let mut test_bed = test_bed_on_ground_with() + .engines_off() + .on_the_ground() + .set_cold_dark_inputs() + .run_one_tick(); + + test_bed = test_bed + .open_fwd_cargo_door() + .run_waiting_for(Duration::from_secs_f64(45.)); + + // Cargo door no more powering yellow epump yet valve is still closed + assert!(!test_bed.is_yellow_leak_meas_valve_commanded_open()); + assert!(!test_bed.query(|a| a.is_cargo_powering_yellow_epump())); + + // Only reopens after a delay + test_bed = test_bed.run_waiting_for( + A320HydraulicCircuitController::DELAY_TO_REOPEN_LEAK_VALVE_AFTER_CARGO_DOOR_USE, + ); + assert!(test_bed.is_yellow_leak_meas_valve_commanded_open()); + + // Check elevators did stay drooped down after valve reopening + assert!(test_bed.get_left_elevator_position().get::() < 0.1); + assert!(test_bed.get_right_elevator_position().get::() < 0.1); + } + + #[test] + fn brakes_on_ground_work_after_emergency_extension() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + + test_bed = test_bed + .turn_emergency_gear_extension_n_turns(3) + .run_waiting_for(Duration::from_secs_f64(30.)); + assert!(test_bed.is_all_gears_really_down()); + assert!(test_bed.is_all_doors_really_down()); + + test_bed = test_bed + .on_the_ground_after_touchdown() + .set_left_brake(Ratio::new::(1.)) + .set_right_brake(Ratio::new::(1.)) + .run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.get_brake_left_green_pressure() > Pressure::new::(500.)); + assert!(test_bed.get_brake_right_green_pressure() > Pressure::new::(500.)); + } + + #[test] + fn gears_do_not_deploy_with_all_lgciu_failed() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(1.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + + test_bed.fail(FailureType::LgciuPowerSupply(LgciuId::Lgciu1)); + test_bed.fail(FailureType::LgciuPowerSupply(LgciuId::Lgciu2)); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(5.)); + assert!(test_bed.is_all_gears_really_up()); + assert!(test_bed.is_all_doors_really_up()); + + test_bed = test_bed + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.is_all_gears_really_up()); + assert!(test_bed.is_all_doors_really_up()); + } + + #[test] + fn empty_green_reservoir_causes_yellow_overheat_if_ptu_on() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs_f64(1.)); + + test_bed.fail(FailureType::ReservoirLeak(HydraulicColor::Green)); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(120.)); + assert!(test_bed.yellow_reservoir_has_overheat_fault()); + } + + #[test] + fn green_edp_off_do_not_causes_ptu_overheat_if_ptu_on_and_cycling_gear() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .with_worst_case_ptu() + .in_flight() + .set_green_ed_pump(false) + .run_waiting_for(Duration::from_secs_f64(1.)); + + test_bed = test_bed + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(35.)); + + assert!(!test_bed.ptu_has_fault()); + + test_bed = test_bed + .set_gear_lever_up() + .run_waiting_for(Duration::from_secs_f64(35.)); + + assert!(!test_bed.ptu_has_fault()); + + test_bed = test_bed + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(35.)); + + assert!(!test_bed.ptu_has_fault()); + } + + #[test] + fn empty_yellow_reservoir_causes_green_overheat_if_ptu_on() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs_f64(1.)); + + test_bed.fail(FailureType::ReservoirLeak(HydraulicColor::Yellow)); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(150.)); + assert!(test_bed.green_reservoir_has_overheat_fault()); + } + + #[test] + fn green_edp_overheat_failure_causes_green_reservoir_overheat() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs_f64(1.)); + + test_bed.fail(FailureType::EnginePumpOverheat( + AirbusEngineDrivenPumpId::Green, + )); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(120.)); + assert!(test_bed.green_reservoir_has_overheat_fault()); + } + + #[test] + fn green_edp_overheat_failure_do_not_causes_green_reservoir_overheat_if_unpressurised() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs_f64(1.)); + + test_bed.fail(FailureType::EnginePumpOverheat( + AirbusEngineDrivenPumpId::Green, + )); + + test_bed = test_bed + .set_green_ed_pump(false) + .run_waiting_for(Duration::from_secs_f64(120.)); + assert!(!test_bed.green_reservoir_has_overheat_fault()); + } + + #[test] + fn yellow_edp_overheat_failure_do_not_causes_yellow_reservoir_overheat_if_unpressurised() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs_f64(1.)); + + test_bed.fail(FailureType::EnginePumpOverheat( + AirbusEngineDrivenPumpId::Yellow, + )); + + test_bed = test_bed + .set_yellow_ed_pump(false) + .run_waiting_for(Duration::from_secs_f64(120.)); + assert!(!test_bed.yellow_reservoir_has_overheat_fault()); + } + + #[test] + fn gear_stays_uplocked_when_door_sensors_fails() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::UplockDoorNose1, + )); + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::UplockDoorNose2, + )); + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::DownlockDoorNose1, + )); + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::DownlockDoorNose2, + )); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.is_all_doors_really_up()); + assert!(test_bed.is_all_gears_really_up()); + } + + #[test] + fn gear_stays_uplocked_when_gear_sensors_fails() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllUpLocked); + + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::UplockGearNose1, + )); + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::UplockGearNose2, + )); + + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::DownlockGearNose1, + )); + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::DownlockGearNose2, + )); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.is_all_doors_really_up()); + assert!(test_bed.is_all_gears_really_up()); + } + + #[test] + fn gear_stays_downlocked_when_gear_sensors_fails() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllDownLocked); + + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::DownlockGearNose1, + )); + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::DownlockGearNose2, + )); + + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::UplockGearNose1, + )); + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::UplockGearNose2, + )); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(2.)); + + assert!(test_bed.is_all_doors_really_up()); + assert!(test_bed.is_all_gears_really_down()); + } + + #[test] + fn gear_stays_downlocked_when_door_sensors_fails() { + let mut test_bed = test_bed_on_ground_with() + .set_cold_dark_inputs() + .in_flight() + .set_gear_lever_down() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.gear_system_state() == GearSystemState::AllDownLocked); + + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::DownlockDoorNose1, + )); + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::DownlockDoorNose2, + )); + + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::UplockDoorNose1, + )); + test_bed.fail(FailureType::GearProxSensorDamage( + systems::shared::ProximityDetectorId::UplockDoorNose2, + )); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(0.1)); + + assert!(test_bed.is_all_doors_really_up()); + assert!(test_bed.is_all_gears_really_down()); + } + + #[test] + fn reversers_do_not_deploy_in_flight() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .eng1_throttle_reverse_full() + .eng2_throttle_reverse_full() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.get_reverser_1_position().get::() < 0.01); + assert!(test_bed.get_reverser_2_position().get::() < 0.01); + } + + #[test] + fn reversers_deploy_and_retract_on_ground_with_eng_on() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .on_the_ground() + .start_eng1(Ratio::new::(60.)) + .start_eng2(Ratio::new::(60.)) + .run_waiting_for(Duration::from_secs_f64(10.)); + + test_bed = test_bed + .eng1_throttle_reverse_full() + .eng2_throttle_reverse_full() + .run_waiting_for(Duration::from_secs_f64(3.)); + + assert!(test_bed.get_reverser_1_position().get::() > 0.99); + assert!(test_bed.get_reverser_2_position().get::() > 0.99); + + test_bed = test_bed + .set_throttles_idle() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.get_reverser_1_position().get::() < 0.01); + assert!(test_bed.get_reverser_2_position().get::() < 0.01); + } + + #[test] + fn reversers_deploy_and_retract_at_idle_reverse_on_ground_with_eng_on() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .on_the_ground() + .start_eng1(Ratio::new::(60.)) + .start_eng2(Ratio::new::(60.)) + .run_waiting_for(Duration::from_secs_f64(10.)); + + test_bed = test_bed + .eng1_throttle_reverse_idle() + .eng2_throttle_reverse_idle() + .run_waiting_for(Duration::from_secs_f64(3.)); + + assert!(test_bed.get_reverser_1_position().get::() > 0.99); + assert!(test_bed.get_reverser_2_position().get::() > 0.99); + + test_bed = test_bed + .set_throttles_idle() + .run_waiting_for(Duration::from_secs_f64(5.)); + + assert!(test_bed.get_reverser_1_position().get::() < 0.01); + assert!(test_bed.get_reverser_2_position().get::() < 0.01); + } + + #[test] + fn reversers_do_not_deploy_on_ground_with_pressure_but_eng_off() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .on_the_ground() + .set_yellow_e_pump(false) + .run_waiting_for(Duration::from_secs_f64(10.)); + + test_bed = test_bed + .eng1_throttle_reverse_full() + .eng2_throttle_reverse_full() + .run_waiting_for(Duration::from_secs_f64(3.)); + + assert!(test_bed.get_reverser_1_position().get::() < 0.01); + assert!(test_bed.get_reverser_2_position().get::() < 0.01); + } + } +} diff --git a/hdw-a339x-acj/src/wasm/systems/a320_systems/src/lib.rs b/hdw-a339x-acj/src/wasm/systems/a320_systems/src/lib.rs new file mode 100644 index 000000000..0f1ed0239 --- /dev/null +++ b/hdw-a339x-acj/src/wasm/systems/a320_systems/src/lib.rs @@ -0,0 +1,281 @@ +extern crate systems; + +mod air_conditioning; +mod airframe; +mod electrical; +mod fuel; +pub mod hydraulic; +mod navigation; +mod payload; +mod pneumatic; +mod power_consumption; + +use self::{ + air_conditioning::{A320AirConditioning, A320PressurizationOverheadPanel}, + fuel::A320Fuel, + payload::A320Payload, + pneumatic::{A320Pneumatic, A320PneumaticOverheadPanel}, +}; +use airframe::A320Airframe; +use electrical::{ + A320Electrical, A320ElectricalOverheadPanel, A320EmergencyElectricalOverheadPanel, + APU_START_MOTOR_BUS_TYPE, +}; +use hydraulic::{A320Hydraulic, A320HydraulicOverheadPanel}; +use navigation::A320RadioAltimeters; +use power_consumption::A320PowerConsumption; +use systems::enhanced_gpwc::EnhancedGroundProximityWarningComputer; +use systems::simulation::InitContext; +use uom::si::{f64::Length, length::nautical_mile}; + +use systems::{ + apu::{ + Aps3200ApuGenerator, Aps3200StartMotor, AuxiliaryPowerUnit, AuxiliaryPowerUnitFactory, + AuxiliaryPowerUnitFireOverheadPanel, AuxiliaryPowerUnitOverheadPanel, + }, + electrical::{Electricity, ElectricitySource, ExternalPowerSource}, + engine::{reverser_thrust::ReverserForce, trent_engine::TrentEngine, EngineFireOverheadPanel}, + hydraulic::brake_circuit::AutobrakePanel, + landing_gear::{LandingGear, LandingGearControlInterfaceUnitSet}, + navigation::adirs::{ + AirDataInertialReferenceSystem, AirDataInertialReferenceSystemOverheadPanel, + }, + shared::ElectricalBusType, + simulation::{Aircraft, SimulationElement, SimulationElementVisitor, UpdateContext}, +}; + +pub struct A320 { + adirs: AirDataInertialReferenceSystem, + adirs_overhead: AirDataInertialReferenceSystemOverheadPanel, + air_conditioning: A320AirConditioning, + apu: AuxiliaryPowerUnit, + apu_fire_overhead: AuxiliaryPowerUnitFireOverheadPanel, + apu_overhead: AuxiliaryPowerUnitOverheadPanel, + pneumatic_overhead: A320PneumaticOverheadPanel, + pressurization_overhead: A320PressurizationOverheadPanel, + electrical_overhead: A320ElectricalOverheadPanel, + emergency_electrical_overhead: A320EmergencyElectricalOverheadPanel, + payload: A320Payload, + airframe: A320Airframe, + fuel: A320Fuel, + engine_1: TrentEngine, + engine_2: TrentEngine, + engine_fire_overhead: EngineFireOverheadPanel<2>, + electrical: A320Electrical, + power_consumption: A320PowerConsumption, + ext_pwr: ExternalPowerSource, + lgcius: LandingGearControlInterfaceUnitSet, + hydraulic: A320Hydraulic, + hydraulic_overhead: A320HydraulicOverheadPanel, + autobrake_panel: AutobrakePanel, + landing_gear: LandingGear, + pneumatic: A320Pneumatic, + radio_altimeters: A320RadioAltimeters, + egpwc: EnhancedGroundProximityWarningComputer, + reverse_thrust: ReverserForce, +} +impl A320 { + pub fn new(context: &mut InitContext) -> A320 { + A320 { + adirs: AirDataInertialReferenceSystem::new(context), + adirs_overhead: AirDataInertialReferenceSystemOverheadPanel::new(context), + air_conditioning: A320AirConditioning::new(context), + apu: AuxiliaryPowerUnitFactory::new_aps3200( + context, + 1, + APU_START_MOTOR_BUS_TYPE, + ElectricalBusType::DirectCurrentBattery, + ElectricalBusType::DirectCurrentBattery, + ), + apu_fire_overhead: AuxiliaryPowerUnitFireOverheadPanel::new(context), + apu_overhead: AuxiliaryPowerUnitOverheadPanel::new(context), + pneumatic_overhead: A320PneumaticOverheadPanel::new(context), + pressurization_overhead: A320PressurizationOverheadPanel::new(context), + electrical_overhead: A320ElectricalOverheadPanel::new(context), + emergency_electrical_overhead: A320EmergencyElectricalOverheadPanel::new(context), + payload: A320Payload::new(context), + airframe: A320Airframe::new(context), + fuel: A320Fuel::new(context), + engine_1: TrentEngine::new(context, 1), + engine_2: TrentEngine::new(context, 2), + engine_fire_overhead: EngineFireOverheadPanel::new(context), + electrical: A320Electrical::new(context), + power_consumption: A320PowerConsumption::new(context), + ext_pwr: ExternalPowerSource::new(context, 1), + lgcius: LandingGearControlInterfaceUnitSet::new( + context, + ElectricalBusType::DirectCurrentEssential, + ElectricalBusType::DirectCurrentGndFltService, + ), + hydraulic: A320Hydraulic::new(context), + hydraulic_overhead: A320HydraulicOverheadPanel::new(context), + autobrake_panel: AutobrakePanel::new(context), + landing_gear: LandingGear::new(context), + pneumatic: A320Pneumatic::new(context), + radio_altimeters: A320RadioAltimeters::new(context), + egpwc: EnhancedGroundProximityWarningComputer::new( + context, + ElectricalBusType::DirectCurrent(1), + vec![ + Length::new::(10.0), + Length::new::(20.0), + Length::new::(40.0), + Length::new::(80.0), + Length::new::(160.0), + Length::new::(320.0), + ], + 0, + ), + reverse_thrust: ReverserForce::new(context), + } + } +} +impl Aircraft for A320 { + fn update_before_power_distribution( + &mut self, + context: &UpdateContext, + electricity: &mut Electricity, + ) { + self.apu.update_before_electrical( + context, + &self.apu_overhead, + &self.apu_fire_overhead, + self.pneumatic_overhead.apu_bleed_is_on(), + // This will be replaced when integrating the whole electrical system. + // For now we use the same logic as found in the JavaScript code; ignoring whether or not + // the engine generators are supplying electricity. + self.electrical_overhead.apu_generator_is_on() + && !(self.electrical_overhead.external_power_is_on() + && self.electrical_overhead.external_power_is_available()), + self.pneumatic.apu_bleed_air_valve(), + self.fuel.left_inner_tank_has_fuel_remaining(), + ); + + self.electrical.update( + context, + electricity, + &self.ext_pwr, + &self.electrical_overhead, + &self.emergency_electrical_overhead, + &mut self.apu, + &self.apu_overhead, + &self.engine_fire_overhead, + [&self.engine_1, &self.engine_2], + &self.hydraulic, + self.lgcius.lgciu1(), + ); + + self.electrical_overhead + .update_after_electrical(&self.electrical, electricity); + self.emergency_electrical_overhead + .update_after_electrical(context, &self.electrical); + self.payload.update(context); + self.airframe + .update(&self.fuel, &self.payload, &self.payload); + } + + fn update_after_power_distribution(&mut self, context: &UpdateContext) { + self.apu.update_after_power_distribution(); + self.apu_overhead.update_after_apu(&self.apu); + + self.lgcius.update( + context, + &self.landing_gear, + self.hydraulic.gear_system(), + self.ext_pwr.output_potential().is_powered(), + ); + + self.radio_altimeters.update(context); + + self.hydraulic.update( + context, + &self.engine_1, + &self.engine_2, + &self.hydraulic_overhead, + &self.autobrake_panel, + &self.engine_fire_overhead, + &self.lgcius, + &self.emergency_electrical_overhead, + &self.electrical, + &self.pneumatic, + &self.adirs, + ); + + self.reverse_thrust.update( + context, + [&self.engine_1, &self.engine_2], + self.hydraulic.reversers_position(), + ); + + self.pneumatic.update_hydraulic_reservoir_spatial_volumes( + self.hydraulic.green_reservoir(), + self.hydraulic.blue_reservoir(), + self.hydraulic.yellow_reservoir(), + ); + + self.hydraulic_overhead.update(&self.hydraulic); + + self.adirs.update(context, &self.adirs_overhead); + self.adirs_overhead.update(context, &self.adirs); + + self.power_consumption.update(context); + + self.pneumatic.update( + context, + [&self.engine_1, &self.engine_2], + &self.pneumatic_overhead, + &self.engine_fire_overhead, + &self.apu, + &self.air_conditioning, + [self.lgcius.lgciu1(), self.lgcius.lgciu2()], + ); + self.air_conditioning + .mix_packs_air_update(self.pneumatic.packs()); + self.air_conditioning.update( + context, + &self.adirs, + [&self.engine_1, &self.engine_2], + &self.engine_fire_overhead, + &self.payload, + &self.pneumatic, + &self.pressurization_overhead, + [self.lgcius.lgciu1(), self.lgcius.lgciu2()], + ); + + self.egpwc.update(&self.adirs, self.lgcius.lgciu1()); + } +} +impl SimulationElement for A320 { + fn accept(&mut self, visitor: &mut T) { + self.adirs.accept(visitor); + self.adirs_overhead.accept(visitor); + self.air_conditioning.accept(visitor); + self.apu.accept(visitor); + self.apu_fire_overhead.accept(visitor); + self.apu_overhead.accept(visitor); + self.payload.accept(visitor); + self.airframe.accept(visitor); + self.electrical_overhead.accept(visitor); + self.emergency_electrical_overhead.accept(visitor); + self.fuel.accept(visitor); + self.pneumatic_overhead.accept(visitor); + self.pressurization_overhead.accept(visitor); + self.engine_1.accept(visitor); + self.engine_2.accept(visitor); + self.engine_fire_overhead.accept(visitor); + self.electrical.accept(visitor); + self.power_consumption.accept(visitor); + self.ext_pwr.accept(visitor); + self.lgcius.accept(visitor); + self.radio_altimeters.accept(visitor); + self.autobrake_panel.accept(visitor); + self.hydraulic.accept(visitor); + self.hydraulic_overhead.accept(visitor); + self.landing_gear.accept(visitor); + self.pneumatic.accept(visitor); + self.egpwc.accept(visitor); + self.reverse_thrust.accept(visitor); + + visitor.visit(self); + } +} diff --git a/hdw-a339x-acj/src/wasm/systems/a320_systems/src/payload/mod.rs b/hdw-a339x-acj/src/wasm/systems/a320_systems/src/payload/mod.rs index 9b4698d0e..fa0875dc6 100644 --- a/hdw-a339x-acj/src/wasm/systems/a320_systems/src/payload/mod.rs +++ b/hdw-a339x-acj/src/wasm/systems/a320_systems/src/payload/mod.rs @@ -1,689 +1,351 @@ -use enum_map::{Enum, EnumMap}; -use lazy_static::lazy_static; +use nalgebra::Vector3; -use std::{cell::Cell, rc::Rc, time::Duration}; +use std::{cell::Cell, rc::Rc}; use uom::si::{f64::Mass, mass::kilogram}; use systems::{ - accept_iterable, - payload::{BoardingRate, Cargo, CargoInfo, GsxState, Pax, PaxInfo}, - simulation::{ - InitContext, Read, SimulationElement, SimulationElementVisitor, SimulatorReader, - SimulatorWriter, UpdateContext, VariableIdentifier, Write, + payload::{ + BoardingAgent, BoardingSounds, Cargo, CargoDeck, CargoInfo, CargoPayload, + NumberOfPassengers, PassengerDeck, PassengerPayload, Pax, PaxInfo, PayloadManager, }, + simulation::{InitContext, SimulationElement, SimulationElementVisitor, UpdateContext}, }; #[cfg(test)] -pub mod test; +mod test; -pub trait NumberOfPassengers { - fn number_of_passengers(&self, ps: A320Pax) -> i8; -} - -#[derive(Debug, Clone, Copy, Enum)] pub enum A320Pax { A, B, - C + C, } -impl A320Pax { - pub fn iterator() -> impl Iterator { - [A320Pax::A, A320Pax::B, A320Pax::C] - .iter() - .copied() +impl From for usize { + fn from(value: A320Pax) -> Self { + value as usize + } +} +impl From for A320Pax { + fn from(value: usize) -> Self { + match value { + 0 => A320Pax::A, + 1 => A320Pax::B, + 2 => A320Pax::C, + i => panic!("Cannot convert from {} to A320Pax.", i), + } } } -#[derive(Debug, Clone, Copy, Enum)] +#[cfg(test)] pub enum A320Cargo { FwdBaggage, AftContainer, AftBaggage, AftBulkLoose, } -impl A320Cargo { - pub fn iterator() -> impl Iterator { - [ - A320Cargo::FwdBaggage, - A320Cargo::AftContainer, - A320Cargo::AftBaggage, - A320Cargo::AftBulkLoose, - ] - .iter() - .copied() +#[cfg(test)] +impl From for usize { + fn from(value: A320Cargo) -> Self { + value as usize } } - -lazy_static! { - static ref A320_PAX: EnumMap = EnumMap::from_array([ - PaxInfo::new(30, "PAX_A", "PAYLOAD_STATION_1_REQ",), - PaxInfo::new(18, "PAX_B", "PAYLOAD_STATION_2_REQ",), - PaxInfo::new(42, "PAX_C", "PAYLOAD_STATION_3_REQ",) - ]); - static ref A320_CARGO: EnumMap = EnumMap::from_array([ - CargoInfo::new( - Mass::new::(5800.), - "CARGO_FWD_BAGGAGE_CONTAINER", - "PAYLOAD_STATION_4_REQ", - ), - CargoInfo::new( - Mass::new::(17061.), - "CARGO_AFT_CONTAINER", - "PAYLOAD_STATION_5_REQ", - ), - CargoInfo::new( - Mass::new::(18507.), - "CARGO_AFT_BAGGAGE", - "PAYLOAD_STATION_6_REQ", - ), - CargoInfo::new( - Mass::new::(3468.), - "CARGO_AFT_BULK_LOOSE", - "PAYLOAD_STATION_7_REQ", - ) - ]); -} - -pub struct A320BoardingSounds { - pax_board_id: VariableIdentifier, - pax_deboard_id: VariableIdentifier, - pax_complete_id: VariableIdentifier, - pax_ambience_id: VariableIdentifier, - pax_boarding: bool, - pax_deboarding: bool, - pax_complete: bool, - pax_ambience: bool, -} -impl A320BoardingSounds { - pub fn new( - pax_board_id: VariableIdentifier, - pax_deboard_id: VariableIdentifier, - pax_complete_id: VariableIdentifier, - pax_ambience_id: VariableIdentifier, - ) -> Self { - A320BoardingSounds { - pax_board_id, - pax_deboard_id, - pax_complete_id, - pax_ambience_id, - pax_boarding: false, - pax_deboarding: false, - pax_complete: false, - pax_ambience: false, +#[cfg(test)] +impl From for A320Cargo { + fn from(value: usize) -> Self { + match value { + 0 => A320Cargo::FwdBaggage, + 1 => A320Cargo::AftContainer, + 2 => A320Cargo::AftBaggage, + 3 => A320Cargo::AftBulkLoose, + i => panic!("Cannot convert from {} to A320Cargo.", i), } } - - fn start_pax_boarding(&mut self) { - self.pax_boarding = true; - } - - fn stop_pax_boarding(&mut self) { - self.pax_boarding = false; - } - - fn start_pax_deboarding(&mut self) { - self.pax_deboarding = true; - } - - fn stop_pax_deboarding(&mut self) { - self.pax_deboarding = false; - } - - fn start_pax_complete(&mut self) { - self.pax_complete = true; - } - - fn stop_pax_complete(&mut self) { - self.pax_complete = false; - } - - fn start_pax_ambience(&mut self) { - self.pax_ambience = true; - } - - fn stop_pax_ambience(&mut self) { - self.pax_ambience = false; - } -} -impl SimulationElement for A320BoardingSounds { - fn write(&self, writer: &mut SimulatorWriter) { - writer.write(&self.pax_board_id, self.pax_boarding); - writer.write(&self.pax_deboard_id, self.pax_deboarding); - writer.write(&self.pax_complete_id, self.pax_complete); - writer.write(&self.pax_ambience_id, self.pax_ambience); - } } pub struct A320Payload { - developer_state_id: VariableIdentifier, - is_boarding_id: VariableIdentifier, - board_rate_id: VariableIdentifier, - per_pax_weight_id: VariableIdentifier, - - is_gsx_enabled_id: VariableIdentifier, - gsx_boarding_state_id: VariableIdentifier, - gsx_deboarding_state_id: VariableIdentifier, - gsx_pax_boarding_id: VariableIdentifier, - gsx_pax_deboarding_id: VariableIdentifier, - gsx_cargo_boarding_pct_id: VariableIdentifier, - gsx_cargo_deboarding_pct_id: VariableIdentifier, - - developer_state: i8, - is_boarding: bool, - board_rate: BoardingRate, - per_pax_weight: Rc>, - - is_gsx_enabled: bool, - gsx_boarding_state: GsxState, - gsx_deboarding_state: GsxState, - gsx_pax_boarding: i32, - gsx_pax_deboarding: i32, - gsx_cargo_boarding_pct: f64, - gsx_cargo_deboarding_pct: f64, - - pax: Vec, - cargo: Vec, - boarding_sounds: A320BoardingSounds, - time: Duration, + payload_manager: PayloadManager<3, 2, 4>, } impl A320Payload { - const DEFAULT_PER_PAX_WEIGHT_KG: f64 = 84.; + // Note: These constants reflect flight_model.cfg values and will have to be updated in sync with the configuration + pub const DEFAULT_PER_PAX_WEIGHT_KG: f64 = 84.; + const A320_PAX: [PaxInfo<'_>; 3] = [ + PaxInfo { + max_pax: 36, + position: (40., 0., 0.), + pax_id: "PAX_A", + payload_id: "PAYLOAD_STATION_1_REQ", + }, + PaxInfo { + max_pax: 36, + position: (-50., 0., 0.), + pax_id: "PAX_B", + payload_id: "PAYLOAD_STATION_2_REQ", + }, + PaxInfo { + max_pax: 39, + position: (-70., 0., 0.), + pax_id: "PAX_C", + payload_id: "PAYLOAD_STATION_3_REQ", + }, + ]; + + const A320_CARGO: [CargoInfo<'_>; 4] = [ + CargoInfo { + max_cargo_kg: 5800., + position: (40., 0., 0.), + cargo_id: "CARGO_FWD_BAGGAGE_CONTAINER", + payload_id: "PAYLOAD_STATION_4_REQ", + }, + CargoInfo { + max_cargo_kg: 17061., + position: (0., 0., 0.), + cargo_id: "CARGO_AFT_CONTAINER", + payload_id: "PAYLOAD_STATION_5_REQ", + }, + CargoInfo { + max_cargo_kg: 18507., + position: (-55., 0., 0.), + cargo_id: "CARGO_AFT_BAGGAGE", + payload_id: "PAYLOAD_STATION_6_REQ", + }, + CargoInfo { + max_cargo_kg: 3468., + position: (-80., 0., 0.), + cargo_id: "CARGO_AFT_BULK_LOOSE", + payload_id: "PAYLOAD_STATION_7_REQ", + }, + ]; + pub fn new(context: &mut InitContext) -> Self { let per_pax_weight = Rc::new(Cell::new(Mass::new::( Self::DEFAULT_PER_PAX_WEIGHT_KG, ))); + let developer_state = Rc::new(Cell::new(0)); + let boarding_sounds = BoardingSounds::new(context); + let pax = Self::A320_PAX.map(|p| { + Pax::new( + context.get_identifier(p.pax_id.to_owned()), + context.get_identifier(format!("{}_DESIRED", p.pax_id)), + context.get_identifier(p.payload_id.to_owned()), + developer_state.clone(), + per_pax_weight.clone(), + Vector3::new(p.position.0, p.position.1, p.position.2), + p.max_pax, + ) + }); + + let cargo = Self::A320_CARGO.map(|c| { + Cargo::new( + context.get_identifier(c.cargo_id.to_owned()), + context.get_identifier(format!("{}_DESIRED", c.cargo_id)), + context.get_identifier(c.payload_id.to_owned()), + developer_state.clone(), + Vector3::new(c.position.0, c.position.1, c.position.2), + Mass::new::(c.max_cargo_kg), + ) + }); + let boarding_agents = [BoardingAgent::new([0, 1, 2]), BoardingAgent::new([2, 1, 0])]; + + let passenger_deck = PassengerDeck::new(context, pax, boarding_agents); + let cargo_deck = CargoDeck::new(cargo); - let mut pax = Vec::new(); - - for ps in A320Pax::iterator() { - pax.push(Pax::new( - context.get_identifier(A320_PAX[ps].pax_id.to_owned()), - context.get_identifier(format!("{}_DESIRED", A320_PAX[ps].pax_id).to_owned()), - context.get_identifier(A320_PAX[ps].payload_id.to_owned()), - Rc::clone(&per_pax_weight), - )); - } - - let mut cargo = Vec::new(); - for cs in A320Cargo::iterator() { - cargo.push(Cargo::new( - context.get_identifier(A320_CARGO[cs].cargo_id.to_owned()), - context.get_identifier(format!("{}_DESIRED", A320_CARGO[cs].cargo_id).to_owned()), - context.get_identifier(A320_CARGO[cs].payload_id.to_owned()), - )); - } A320Payload { - developer_state_id: context.get_identifier("DEVELOPER_STATE".to_owned()), - is_boarding_id: context.get_identifier("BOARDING_STARTED_BY_USR".to_owned()), - board_rate_id: context.get_identifier("BOARDING_RATE".to_owned()), - per_pax_weight_id: context.get_identifier("WB_PER_PAX_WEIGHT".to_owned()), - - is_gsx_enabled_id: context.get_identifier("GSX_PAYLOAD_SYNC_ENABLED".to_owned()), - gsx_boarding_state_id: context.get_identifier("FSDT_GSX_BOARDING_STATE".to_owned()), - gsx_deboarding_state_id: context.get_identifier("FSDT_GSX_DEBOARDING_STATE".to_owned()), - gsx_pax_boarding_id: context - .get_identifier("FSDT_GSX_NUMPASSENGERS_BOARDING_TOTAL".to_owned()), - gsx_pax_deboarding_id: context - .get_identifier("FSDT_GSX_NUMPASSENGERS_DEBOARDING_TOTAL".to_owned()), - gsx_cargo_boarding_pct_id: context - .get_identifier("FSDT_GSX_BOARDING_CARGO_PERCENT".to_owned()), - gsx_cargo_deboarding_pct_id: context - .get_identifier("FSDT_GSX_DEBOARDING_CARGO_PERCENT".to_owned()), - - developer_state: 0, - is_boarding: false, - board_rate: BoardingRate::Instant, - per_pax_weight, - is_gsx_enabled: false, - gsx_boarding_state: GsxState::None, - gsx_deboarding_state: GsxState::None, - gsx_pax_boarding: 0, - gsx_pax_deboarding: 0, - gsx_cargo_boarding_pct: 0., - gsx_cargo_deboarding_pct: 0., - boarding_sounds: A320BoardingSounds::new( - context.get_identifier("SOUND_PAX_BOARDING".to_owned()), - context.get_identifier("SOUND_PAX_DEBOARDING".to_owned()), - context.get_identifier("SOUND_BOARDING_COMPLETE".to_owned()), - context.get_identifier("SOUND_PAX_AMBIENCE".to_owned()), + payload_manager: PayloadManager::new( + context, + per_pax_weight, + developer_state, + boarding_sounds, + passenger_deck, + cargo_deck, + 1000, + 5000, ), - pax, - cargo, - time: Duration::from_nanos(0), } } pub(crate) fn update(&mut self, context: &UpdateContext) { - if !self.is_developer_state_active() { - self.ensure_payload_sync() - }; - - if self.is_gsx_enabled() { - self.stop_boarding(); - self.stop_boarding_sounds(); - self.update_extern_gsx(context); - } else { - self.update_intern(context); - } - } - - fn ensure_payload_sync(&mut self) { - for ps in A320Pax::iterator() { - if !self.pax_is_sync(ps) { - self.sync_pax(ps); - } - } - - for cs in A320Cargo::iterator() { - if !self.cargo_is_sync(cs) { - self.sync_cargo(cs); - } - } + self.payload_manager.update(context.delta()); } - fn update_extern_gsx(&mut self, context: &UpdateContext) { - self.update_gsx_deboarding(context); - self.update_gsx_boarding(context); - } - - fn update_gsx_deboarding(&mut self, _context: &UpdateContext) { - self.update_pax_ambience(); - match self.gsx_deboarding_state { - GsxState::None | GsxState::Available | GsxState::NotAvailable | GsxState::Bypassed => {} - GsxState::Requested => { - self.update_cargo_loaded(); - self.reset_all_pax_targets(); - self.reset_all_cargo_targets(); - } - GsxState::Completed => { - self.move_all_payload(); - self.reset_cargo_loaded(); - self.reset_all_cargo_targets(); - } - GsxState::Performing => { - self.move_all_pax_num( - self.total_pax_num() - (self.total_max_pax() - self.gsx_pax_deboarding), - ); - self.load_all_cargo_percent(100. - self.gsx_cargo_deboarding_pct); - } - } + fn pax_num(&self, ps: usize) -> i8 { + self.payload_manager.pax_num(ps) } - fn update_cargo_loaded(&mut self) { - for cs in A320Cargo::iterator() { - self.cargo[cs as usize].update_cargo_loaded() - } + #[cfg(test)] + fn total_pax_num(&self) -> i32 { + self.payload_manager.total_pax_num() } - fn reset_cargo_loaded(&mut self) { - for cs in A320Cargo::iterator() { - self.cargo[cs as usize].reset_cargo_loaded() - } + fn total_passenger_load(&self) -> Mass { + self.payload_manager.total_passenger_load() } - fn update_gsx_boarding(&mut self, _context: &UpdateContext) { - self.update_pax_ambience(); - match self.gsx_boarding_state { - GsxState::None - | GsxState::Available - | GsxState::NotAvailable - | GsxState::Bypassed - | GsxState::Requested => {} - GsxState::Completed => { - for cs in A320Cargo::iterator() { - self.move_all_cargo(cs); - } - } - GsxState::Performing => { - self.move_all_pax_num(self.gsx_pax_boarding - self.total_pax_num()); - self.load_all_cargo_percent(self.gsx_cargo_boarding_pct); - } - } + fn total_target_passenger_load(&self) -> Mass { + self.payload_manager.total_target_passenger_load() } - fn update_intern(&mut self, context: &UpdateContext) { - self.update_pax_ambience(); - - if !self.is_boarding { - self.time = Duration::from_nanos(0); - self.stop_boarding_sounds(); - return; - } - - let ms_delay = if self.board_rate() == BoardingRate::Instant { - 0 - } else if self.board_rate() == BoardingRate::Fast { - 1000 - } else { - 5000 - }; - - let delta_time = context.delta(); - self.time += delta_time; - if self.time.as_millis() > ms_delay { - self.time = Duration::from_nanos(0); - self.update_pax(); - self.update_cargo(); - } - // Check sound before updating boarding status - self.update_boarding_sounds(); - self.update_boarding_status(); + fn total_passenger_moment(&self) -> Vector3 { + self.payload_manager.total_passenger_moment() } - fn update_boarding_status(&mut self) { - if self.is_fully_loaded() { - self.is_boarding = false; - } + fn total_target_passenger_moment(&self) -> Vector3 { + self.payload_manager.total_target_passenger_moment() } - fn update_boarding_sounds(&mut self) { - let pax_board = self.is_pax_boarding(); - self.play_sound_pax_boarding(pax_board); - - let pax_deboard = self.is_pax_deboarding(); - self.play_sound_pax_deboarding(pax_deboard); - - let pax_complete = self.is_pax_loaded() && self.is_boarding(); - self.play_sound_pax_complete(pax_complete); + fn total_cargo_moment(&self) -> Vector3 { + self.payload_manager.total_cargo_moment() } - fn update_pax_ambience(&mut self) { - let pax_ambience = !self.has_no_pax(); - self.play_sound_pax_ambience(pax_ambience); + fn total_target_cargo_moment(&self) -> Vector3 { + self.payload_manager.total_target_cargo_moment() } - fn play_sound_pax_boarding(&mut self, playing: bool) { - if playing { - self.boarding_sounds.start_pax_boarding(); + fn passenger_center_of_gravity(&self) -> Vector3 { + let total_pax_load = self.total_passenger_load().get::(); + if total_pax_load > 0. { + self.total_passenger_moment() / total_pax_load } else { - self.boarding_sounds.stop_pax_boarding(); + Vector3::zeros() } } - fn play_sound_pax_deboarding(&mut self, playing: bool) { - if playing { - self.boarding_sounds.start_pax_deboarding(); + fn target_passenger_center_of_gravity(&self) -> Vector3 { + let total_target_pax_load = self.total_target_passenger_load().get::(); + if total_target_pax_load > 0. { + self.total_target_passenger_moment() / total_target_pax_load } else { - self.boarding_sounds.stop_pax_deboarding(); + Vector3::zeros() } } - fn play_sound_pax_complete(&mut self, playing: bool) { - if playing { - self.boarding_sounds.start_pax_complete(); + fn cargo_center_of_gravity(&self) -> Vector3 { + let total_cargo_load = self.total_cargo_load().get::(); + if total_cargo_load > 0. { + self.total_cargo_moment() / total_cargo_load } else { - self.boarding_sounds.stop_pax_complete(); + Vector3::zeros() } } - fn play_sound_pax_ambience(&mut self, playing: bool) { - if playing { - self.boarding_sounds.start_pax_ambience(); + fn target_cargo_center_of_gravity(&self) -> Vector3 { + let total_target_cargo_load = self.total_target_cargo_load().get::(); + if total_target_cargo_load > 0. { + self.total_target_cargo_moment() / total_target_cargo_load } else { - self.boarding_sounds.stop_pax_ambience(); - } - } - - fn stop_boarding_sounds(&mut self) { - self.boarding_sounds.stop_pax_boarding(); - self.boarding_sounds.stop_pax_deboarding(); - self.boarding_sounds.stop_pax_complete(); - } - - fn reset_all_pax_targets(&mut self) { - for ps in A320Pax::iterator() { - self.reset_pax_target(ps); - } - } - - fn move_all_pax_num(&mut self, pax_diff: i32) { - if pax_diff > 0 { - for _ in 0..pax_diff { - for ps in A320Pax::iterator() { - if self.pax_is_target(ps) { - continue; - } - self.move_one_pax(ps); - break; - } - } - } - } - - fn update_pax(&mut self) { - for ps in A320Pax::iterator() { - if self.pax_is_target(ps) { - continue; - } - if self.board_rate == BoardingRate::Instant { - self.move_all_pax(ps); - } else { - self.move_one_pax(ps); - break; - } - } - } - - fn reset_all_cargo_targets(&mut self) { - for cs in A320Cargo::iterator() { - self.reset_cargo_target(cs); - } - } - - fn update_cargo(&mut self) { - for cs in A320Cargo::iterator() { - if self.cargo_is_target(cs) { - continue; - } - if self.board_rate == BoardingRate::Instant { - self.move_all_cargo(cs); - } else { - self.move_one_cargo(cs); - break; - } - } - } - - fn is_developer_state_active(&self) -> bool { - self.developer_state > 0 - } - - fn is_pax_boarding(&self) -> bool { - for ps in A320Pax::iterator() { - if self.pax_num(ps) < self.pax_target_num(ps) && self.is_boarding() { - return true; - } - } - false - } - - fn is_pax_deboarding(&self) -> bool { - for ps in A320Pax::iterator() { - if self.pax_num(ps) > self.pax_target_num(ps) && self.is_boarding() { - return true; - } - } - false - } - - fn is_pax_loaded(&self) -> bool { - for ps in A320Pax::iterator() { - if !self.pax_is_target(ps) { - return false; - } - } - true - } - - fn is_cargo_loaded(&self) -> bool { - for cs in A320Cargo::iterator() { - if !self.cargo_is_target(cs) { - return false; - } - } - true - } - - fn is_fully_loaded(&self) -> bool { - self.is_pax_loaded() && self.is_cargo_loaded() - } - - fn has_no_pax(&mut self) -> bool { - for ps in A320Pax::iterator() { - let pax_num = 0; - if self.pax_num(ps) == pax_num { - return true; - } + Vector3::zeros() } - false } - fn board_rate(&self) -> BoardingRate { - self.board_rate + #[cfg(test)] + fn max_pax(&self, ps: usize) -> i8 { + self.payload_manager.max_pax(ps) } - fn pax_num(&self, ps: A320Pax) -> i8 { - self.pax[ps as usize].pax_num() as i8 + #[cfg(test)] + fn cargo(&self, cs: usize) -> Mass { + self.payload_manager.cargo(cs) } - fn total_pax_num(&self) -> i32 { - let mut pax_num = 0; - for ps in A320Pax::iterator() { - pax_num += self.pax_num(ps) as i32; - } - pax_num + #[cfg(test)] + fn cargo_payload(&self, cs: usize) -> Mass { + self.payload_manager.cargo_payload(cs) } - fn total_max_pax(&self) -> i32 { - let mut max_pax = 0; - for ps in A320Pax::iterator() { - max_pax += A320_PAX[ps].max_pax as i32; - } - max_pax + #[cfg(test)] + fn pax_payload(&self, ps: usize) -> Mass { + self.payload_manager.pax_payload(ps) } - fn pax_target_num(&self, ps: A320Pax) -> i8 { - self.pax[ps as usize].pax_target_num() as i8 + #[cfg(test)] + fn override_pax_payload(&mut self, ps: usize, payload: Mass) { + self.payload_manager.override_pax_payload(ps, payload) } - fn pax_is_sync(&mut self, ps: A320Pax) -> bool { - self.pax[ps as usize].payload_is_sync() + #[cfg(test)] + fn max_cargo(&self, cs: usize) -> Mass { + self.payload_manager.max_cargo(cs) } - fn pax_is_target(&self, ps: A320Pax) -> bool { - self.pax[ps as usize].pax_is_target() + #[cfg(test)] + fn sound_pax_boarding_playing(&self) -> bool { + self.payload_manager.sound_pax_boarding_playing() } - fn sync_pax(&mut self, ps: A320Pax) { - self.pax[ps as usize].load_payload(); + #[cfg(test)] + fn sound_pax_deboarding_playing(&self) -> bool { + self.payload_manager.sound_pax_deboarding_playing() } - fn move_all_pax(&mut self, ps: A320Pax) { - self.pax[ps as usize].move_all_pax(); + #[cfg(test)] + fn sound_pax_complete_playing(&self) -> bool { + self.payload_manager.sound_pax_complete_playing() } - fn move_one_pax(&mut self, ps: A320Pax) { - self.pax[ps as usize].move_one_pax(); - } - - fn reset_pax_target(&mut self, ps: A320Pax) { - self.pax[ps as usize].reset_pax_target(); + #[cfg(test)] + fn sound_pax_ambience_playing(&self) -> bool { + self.payload_manager.sound_pax_ambience_playing() } +} - fn reset_cargo_target(&mut self, cs: A320Cargo) { - self.cargo[cs as usize].reset_cargo_target(); +impl NumberOfPassengers for A320Payload { + fn number_of_passengers(&self, ps: usize) -> i8 { + self.pax_num(ps) } - - fn cargo_is_sync(&mut self, cs: A320Cargo) -> bool { - self.cargo[cs as usize].payload_is_sync() +} +impl PassengerPayload for A320Payload { + fn total_passenger_load(&self) -> Mass { + self.total_passenger_load() } - fn cargo_is_target(&self, cs: A320Cargo) -> bool { - self.cargo[cs as usize].cargo_is_target() + fn total_target_passenger_load(&self) -> Mass { + self.total_target_passenger_load() } - fn move_all_cargo(&mut self, cs: A320Cargo) { - self.cargo[cs as usize].move_all_cargo(); + fn center_of_gravity(&self) -> Vector3 { + self.passenger_center_of_gravity() } - fn move_one_cargo(&mut self, cs: A320Cargo) { - self.cargo[cs as usize].move_one_cargo(); + fn fore_aft_center_of_gravity(&self) -> f64 { + self.passenger_center_of_gravity().x } - fn load_all_cargo_percent(&mut self, percent: f64) { - for cs in A320Cargo::iterator() { - self.load_cargo_percent(cs, percent); - } + fn target_center_of_gravity(&self) -> Vector3 { + self.target_passenger_center_of_gravity() } - fn load_cargo_percent(&mut self, cs: A320Cargo, percent: f64) { - self.cargo[cs as usize].load_cargo_percent(percent) + fn target_fore_aft_center_of_gravity(&self) -> f64 { + self.target_passenger_center_of_gravity().x } - - fn move_all_payload(&mut self) { - for ps in A320Pax::iterator() { - self.move_all_pax(ps) - } - for cs in A320Cargo::iterator() { - self.move_all_cargo(cs) - } +} +impl CargoPayload for A320Payload { + fn total_cargo_load(&self) -> Mass { + self.payload_manager.total_cargo_load() } - fn sync_cargo(&mut self, cs: A320Cargo) { - self.cargo[cs as usize].load_payload(); + fn total_target_cargo_load(&self) -> Mass { + self.payload_manager.total_target_cargo_load() } - fn is_boarding(&self) -> bool { - self.is_boarding + fn center_of_gravity(&self) -> Vector3 { + self.cargo_center_of_gravity() } - fn is_gsx_enabled(&self) -> bool { - self.is_gsx_enabled + fn fore_aft_center_of_gravity(&self) -> f64 { + self.cargo_center_of_gravity().x } - fn stop_boarding(&mut self) { - self.is_boarding = false; + fn target_center_of_gravity(&self) -> Vector3 { + self.target_cargo_center_of_gravity() } - fn per_pax_weight(&self) -> Mass { - self.per_pax_weight.get() + fn target_fore_aft_center_of_gravity(&self) -> f64 { + self.target_cargo_center_of_gravity().x } } impl SimulationElement for A320Payload { fn accept(&mut self, visitor: &mut T) { - accept_iterable!(self.pax, visitor); - accept_iterable!(self.cargo, visitor); - self.boarding_sounds.accept(visitor); + self.payload_manager.accept(visitor); visitor.visit(self); } - - fn read(&mut self, reader: &mut SimulatorReader) { - self.developer_state = reader.read(&self.developer_state_id); - self.is_boarding = reader.read(&self.is_boarding_id); - self.board_rate = reader.read(&self.board_rate_id); - self.is_gsx_enabled = reader.read(&self.is_gsx_enabled_id); - self.gsx_boarding_state = reader.read(&self.gsx_boarding_state_id); - self.gsx_deboarding_state = reader.read(&self.gsx_deboarding_state_id); - self.gsx_pax_boarding = reader.read(&self.gsx_pax_boarding_id); - self.gsx_pax_deboarding = reader.read(&self.gsx_pax_deboarding_id); - self.gsx_cargo_boarding_pct = reader.read(&self.gsx_cargo_boarding_pct_id); - self.gsx_cargo_deboarding_pct = reader.read(&self.gsx_cargo_deboarding_pct_id); - self.per_pax_weight - .replace(Mass::new::(reader.read(&self.per_pax_weight_id))); - } - - fn write(&self, writer: &mut SimulatorWriter) { - writer.write(&self.is_boarding_id, self.is_boarding); - writer.write( - &self.per_pax_weight_id, - self.per_pax_weight().get::(), - ); - } -} -impl NumberOfPassengers for A320Payload { - fn number_of_passengers(&self, ps: A320Pax) -> i8 { - self.pax_num(ps) - } } diff --git a/hdw-a339x-acj/src/wasm/systems/a320_systems_wasm/src/lib.rs b/hdw-a339x-acj/src/wasm/systems/a320_systems_wasm/src/lib.rs index 5c3fb1fb3..38d3db547 100644 --- a/hdw-a339x-acj/src/wasm/systems/a320_systems_wasm/src/lib.rs +++ b/hdw-a339x-acj/src/wasm/systems/a320_systems_wasm/src/lib.rs @@ -60,6 +60,7 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { (ElectricalBusType::DirectCurrentGndFltService, 15), ])? .with_auxiliary_power_unit(Variable::named("OVHD_APU_START_PB_IS_AVAILABLE"), 8, 7)? + .with_engines(2)? .with_failures(vec![ (24_000, FailureType::TransformerRectifier(1)), (24_001, FailureType::TransformerRectifier(2)), @@ -267,7 +268,12 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { .provides_aircraft_variable("AMBIENT WIND Z", "meter per second", 0)? .provides_aircraft_variable("ANTISKID BRAKES ACTIVE", "Bool", 0)? .provides_aircraft_variable("EXTERNAL POWER AVAILABLE", "Bool", 1)? - .provides_aircraft_variable("FUEL TANK LEFT MAIN QUANTITY", "Pounds", 0)? + .provides_aircraft_variable("FUEL TANK CENTER QUANTITY", "gallons", 0)? + .provides_aircraft_variable("FUEL TANK LEFT MAIN QUANTITY", "gallons", 0)? + .provides_aircraft_variable("FUEL TANK LEFT AUX QUANTITY", "gallons", 0)? + .provides_aircraft_variable("FUEL TANK RIGHT MAIN QUANTITY", "gallons", 0)? + .provides_aircraft_variable("FUEL TANK RIGHT AUX QUANTITY", "gallons", 0)? + .provides_aircraft_variable("FUEL TOTAL QUANTITY WEIGHT", "Pounds", 0)? .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 0)? .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 1)? .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 2)? @@ -325,13 +331,6 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { .provides_aircraft_variable("PAYLOAD STATION WEIGHT", "Pounds", 5)? .provides_aircraft_variable("PAYLOAD STATION WEIGHT", "Pounds", 6)? .provides_aircraft_variable("PAYLOAD STATION WEIGHT", "Pounds", 7)? - .provides_aircraft_variable("PAYLOAD STATION WEIGHT", "Pounds", 8)? - .provides_aircraft_variable("PAYLOAD STATION WEIGHT", "Pounds", 9)? - .provides_aircraft_variable("PAYLOAD STATION WEIGHT", "Pounds", 10)? - .provides_aircraft_variable("PAYLOAD STATION WEIGHT", "Pounds", 11)? - .provides_aircraft_variable("PAYLOAD STATION WEIGHT", "Pounds", 12)? - .provides_aircraft_variable("PAYLOAD STATION WEIGHT", "Pounds", 13)? - .provides_aircraft_variable("PAYLOAD STATION WEIGHT", "Pounds", 14)? .provides_named_variable("FSDT_GSX_BOARDING_STATE")? .provides_named_variable("FSDT_GSX_DEBOARDING_STATE")? .provides_named_variable("FSDT_GSX_NUMPASSENGERS_BOARDING_TOTAL")? @@ -343,16 +342,6 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { Variable::aircraft("APU GENERATOR SWITCH", "Bool", 0), Variable::aspect("OVHD_ELEC_APU_GEN_PB_IS_ON"), ); - - builder.copy( - Variable::aircraft("BLEED AIR ENGINE", "Bool", 1), - Variable::aspect("OVHD_PNEU_ENG_1_BLEED_PB_IS_AUTO"), - ); - builder.copy( - Variable::aircraft("BLEED AIR ENGINE", "Bool", 2), - Variable::aspect("OVHD_PNEU_ENG_2_BLEED_PB_IS_AUTO"), - ); - builder.copy( Variable::aircraft("EXTERNAL POWER AVAILABLE", "Bool", 1), Variable::aspect("OVHD_ELEC_EXT_PWR_PB_IS_AVAILABLE"), diff --git a/hdw-a339x-acj/src/wasm/systems/a320_systems_wasm/src/payload.rs b/hdw-a339x-acj/src/wasm/systems/a320_systems_wasm/src/payload.rs index 915aed244..9a294cab2 100644 --- a/hdw-a339x-acj/src/wasm/systems/a320_systems_wasm/src/payload.rs +++ b/hdw-a339x-acj/src/wasm/systems/a320_systems_wasm/src/payload.rs @@ -35,14 +35,6 @@ pub(super) fn payload(builder: &mut MsfsAspectBuilder) -> Result<(), Box Result<(), Box Vec { vec![ @@ -108,8 +89,6 @@ impl VariablesToObject for Payload { Variable::aspect("PAYLOAD_STATION_5_REQ"), Variable::aspect("PAYLOAD_STATION_6_REQ"), Variable::aspect("PAYLOAD_STATION_7_REQ"), - Variable::aspect("PAYLOAD_STATION_8_REQ"), - Variable::aspect("PAYLOAD_STATION_9_REQ"), ] } @@ -121,8 +100,6 @@ impl VariablesToObject for Payload { self.payload_station_5 = values[4]; self.payload_station_6 = values[5]; self.payload_station_7 = values[6]; - self.payload_station_8 = values[7]; - self.payload_station_9 = values[8]; ObjectWrite::default() } diff --git a/hdw-a339x/src/base/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_A330neo/panel/panel.cfg b/hdw-a339x/src/base/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_A330neo/panel/panel.cfg index 9da8fc5a4..c1ce0abfe 100644 --- a/hdw-a339x/src/base/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_A330neo/panel/panel.cfg +++ b/hdw-a339x/src/base/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_A330neo/panel/panel.cfg @@ -122,7 +122,7 @@ size_mm = 1430,1000 pixel_size = 1430,1000 texture = $EFB -htmlgauge00 = A339X/EFB/efb.html, 0,0,1430,1000 +htmlgauge00 = A339X/EFB/efb.html?Airframe=A330_941, 0,0,1430,1000 [VCockpit17] size_mm = 0,0 diff --git a/hdw-a339x/src/systems/fmgc/src/guidance/vnav/EngineModel.ts b/hdw-a339x/src/systems/fmgc/src/guidance/vnav/EngineModel.ts index 1f79bda78..4dd3f8fd1 100644 --- a/hdw-a339x/src/systems/fmgc/src/guidance/vnav/EngineModel.ts +++ b/hdw-a339x/src/systems/fmgc/src/guidance/vnav/EngineModel.ts @@ -2,7 +2,7 @@ import { Common } from './common'; export class EngineModel { // In pounds of force. Used as a multiplier for results of table 1506 - static maxThrust = 72200; + static maxThrust = 72834; /** * Maximum N1 in CLB thrust diff --git a/hdw-a339x/src/systems/instruments/src/EFB/Efb.tsx b/hdw-a339x/src/systems/instruments/src/EFB/Efb.tsx index 4f1a59f92..e259ae576 100644 --- a/hdw-a339x/src/systems/instruments/src/EFB/Efb.tsx +++ b/hdw-a339x/src/systems/instruments/src/EFB/Efb.tsx @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-3.0 import React, { useEffect, useState } from 'react'; - import { useSimVar, useInterval, useInteractionEvent, usePersistentNumberProperty, usePersistentProperty } from '@flybywiresim/fbw-sdk'; import { Redirect, Route, Switch, useHistory } from 'react-router-dom'; import { Battery } from 'react-bootstrap-icons'; @@ -73,6 +72,8 @@ interface BatteryStatus { export const usePower = () => React.useContext(PowerContext); +export const getAirframeType = () => new URL(document.querySelectorAll('vcockpit-panel > *')[0].getAttribute('url')).searchParams.get('Airframe'); + const Efb = () => { const [powerState, setPowerState] = useState(PowerStates.SHUTOFF); const [absoluteTime] = useSimVar('E:ABSOLUTE TIME', 'seconds', 5000); diff --git a/hdw-a339x/src/systems/instruments/src/EFB/Enum/Airframe.ts b/hdw-a339x/src/systems/instruments/src/EFB/Enum/Airframe.ts new file mode 100644 index 000000000..8f6930b7f --- /dev/null +++ b/hdw-a339x/src/systems/instruments/src/EFB/Enum/Airframe.ts @@ -0,0 +1,6 @@ +export enum AC_TYPE { + A320_251N = 'A320-251N', + A330_941 = 'A330-941', + ACJ330_941 = 'ACJ330-941', + A380_842 = 'A380-842' +} diff --git a/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/A330_941/A330Payload.tsx b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/A330_941/A330Payload.tsx new file mode 100644 index 000000000..60ab43bbd --- /dev/null +++ b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/A330_941/A330Payload.tsx @@ -0,0 +1,628 @@ +/* eslint-disable max-len */ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { CloudArrowDown } from 'react-bootstrap-icons'; +import { SeatFlags, Units, usePersistentNumberProperty, usePersistentProperty, useSeatFlags, useSimVar } from '@flybywiresim/fbw-sdk'; +import { SeatOutlineBg } from 'instruments/src/EFB/Assets/SeatOutlineBg'; +import { BoardingInput, MiscParamsInput, PayloadInputTable } from '../PayloadElements'; +import { CargoWidget } from './CargoWidget'; +import { ChartWidget } from '../Chart/ChartWidget'; +import { CargoStationInfo, PaxStationInfo } from '../Seating/Constants'; +import { t } from '../../../../translation'; +import { TooltipWrapper } from '../../../../UtilComponents/TooltipWrapper'; +import Loadsheet from './a339x.json'; +import Card from '../../../../UtilComponents/Card/Card'; +import { SelectGroup, SelectItem } from '../../../../UtilComponents/Form/Select'; +import { SeatMapWidget } from '../Seating/SeatMapWidget'; +import { PromptModal, useModals } from '../../../../UtilComponents/Modals/Modals'; + +interface A330Props { + simbriefUnits: string, + simbriefBagWeight: number, + simbriefPaxWeight: number, + simbriefPax: number, + simbriefBag: number, + simbriefFreight: number, + simbriefDataLoaded: boolean, + massUnitForDisplay: string, + isOnGround: boolean, + + boardingStarted: boolean, + boardingRate: string, + setBoardingStarted: (boardingStarted: any) => void, + setBoardingRate: (boardingRate: any) => void, +} +export const A330Payload: React.FC = ({ + simbriefUnits, + simbriefBagWeight, + simbriefPaxWeight, + simbriefPax, + simbriefBag, + simbriefFreight, + simbriefDataLoaded, + massUnitForDisplay, + isOnGround, + boardingStarted, + boardingRate, + setBoardingStarted, + setBoardingRate, +}) => { + const { showModal } = useModals(); + + const [aFlags] = useSeatFlags(`L:${Loadsheet.seatMap[0].simVar}`, Loadsheet.seatMap[0].capacity, 509); + const [bFlags] = useSeatFlags(`L:${Loadsheet.seatMap[1].simVar}`, Loadsheet.seatMap[1].capacity, 513); + const [cFlags] = useSeatFlags(`L:${Loadsheet.seatMap[2].simVar}`, Loadsheet.seatMap[2].capacity, 527); + const [dFlags] = useSeatFlags(`L:${Loadsheet.seatMap[3].simVar}`, Loadsheet.seatMap[3].capacity, 539); + const [eFlags] = useSeatFlags(`L:${Loadsheet.seatMap[4].simVar}`, Loadsheet.seatMap[4].capacity, 549); + const [fFlags] = useSeatFlags(`L:${Loadsheet.seatMap[5].simVar}`, Loadsheet.seatMap[5].capacity, 563); + const [gFlags] = useSeatFlags(`L:${Loadsheet.seatMap[6].simVar}`, Loadsheet.seatMap[6].capacity, 571); + const [hFlags] = useSeatFlags(`L:${Loadsheet.seatMap[7].simVar}`, Loadsheet.seatMap[7].capacity, 579); + const [iFlags] = useSeatFlags(`L:${Loadsheet.seatMap[8].simVar}`, Loadsheet.seatMap[8].capacity, 587); + const [jFlags] = useSeatFlags(`L:${Loadsheet.seatMap[9].simVar}`, Loadsheet.seatMap[9].capacity, 593); + + const [aFlagsDesired, setAFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[0].simVar}_DESIRED`, Loadsheet.seatMap[0].capacity, 379); + const [bFlagsDesired, setBFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[1].simVar}_DESIRED`, Loadsheet.seatMap[1].capacity, 421); + const [cFlagsDesired, setCFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[2].simVar}_DESIRED`, Loadsheet.seatMap[2].capacity, 437); + const [dFlagsDesired, setDFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[3].simVar}_DESIRED`, Loadsheet.seatMap[3].capacity, 443); + const [eFlagsDesired, setEFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[4].simVar}_DESIRED`, Loadsheet.seatMap[4].capacity, 457); + const [fFlagsDesired, setFFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[5].simVar}_DESIRED`, Loadsheet.seatMap[5].capacity, 469); + const [gFlagsDesired, setGFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[6].simVar}_DESIRED`, Loadsheet.seatMap[6].capacity, 477); + const [hFlagsDesired, setHFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[7].simVar}_DESIRED`, Loadsheet.seatMap[7].capacity, 483); + const [iFlagsDesired, setIFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[8].simVar}_DESIRED`, Loadsheet.seatMap[8].capacity, 489); + const [jFlagsDesired, setJFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[9].simVar}_DESIRED`, Loadsheet.seatMap[9].capacity, 499); + + const activeFlags = useMemo(() => [aFlags, bFlags, cFlags, dFlags, eFlags, fFlags, gFlags, hFlags, iFlags, jFlags], [aFlags, bFlags, cFlags, dFlags, eFlags, fFlags, gFlags, hFlags, iFlags, jFlags]); + const desiredFlags = useMemo(() => [aFlagsDesired, bFlagsDesired, cFlagsDesired, dFlagsDesired, eFlagsDesired, fFlagsDesired, gFlagsDesired, hFlagsDesired, iFlagsDesired, jFlagsDesired], [aFlagsDesired, bFlagsDesired, cFlagsDesired, dFlagsDesired, eFlagsDesired, fFlagsDesired, gFlagsDesired, hFlagsDesired, iFlagsDesired, jFlagsDesired]); + const setDesiredFlags = useMemo(() => [setAFlagsDesired, setBFlagsDesired, setCFlagsDesired, setDFlagsDesired, setEFlagsDesired, setFFlagsDesired, setGFlagsDesired, setHFlagsDesired, setIFlagsDesired, setJFlagsDesired], []); + + const [fwdBag] = useSimVar(`L:${Loadsheet.cargoMap[0].simVar}`, 'Number', 719); + const [aftCont] = useSimVar(`L:${Loadsheet.cargoMap[1].simVar}`, 'Number', 743); + const [aftBag] = useSimVar(`L:${Loadsheet.cargoMap[2].simVar}`, 'Number', 769); + const [aftBulk] = useSimVar(`L:${Loadsheet.cargoMap[3].simVar}`, 'Number', 797); + + const [fwdBagDesired, setFwdBagDesired] = useSimVar(`L:${Loadsheet.cargoMap[0].simVar}_DESIRED`, 'Number', 619); + const [aftContDesired, setAftContDesired] = useSimVar(`L:${Loadsheet.cargoMap[1].simVar}_DESIRED`, 'Number', 631); + const [aftBagDesired, setAftBagDesired] = useSimVar(`L:${Loadsheet.cargoMap[2].simVar}_DESIRED`, 'Number', 641); + const [aftBulkDesired, setAftBulkDesired] = useSimVar(`L:${Loadsheet.cargoMap[3].simVar}_DESIRED`, 'Number', 677); + + const cargo = useMemo(() => [fwdBag, aftCont, aftBag, aftBulk], [fwdBag, aftCont, aftBag, aftBulk]); + const cargoDesired = useMemo(() => [fwdBagDesired, aftContDesired, aftBagDesired, aftBulkDesired], [fwdBagDesired, aftContDesired, aftBagDesired, aftBulkDesired]); + const setCargoDesired = useMemo(() => [setFwdBagDesired, setAftContDesired, setAftBagDesired, setAftBulkDesired], []); + + const [paxWeight, setPaxWeight] = useSimVar('L:A32NX_WB_PER_PAX_WEIGHT', 'Kilograms', 739); + const [paxBagWeight, setPaxBagWeight] = useSimVar('L:A32NX_WB_PER_BAG_WEIGHT', 'Kilograms', 797); + // const [destEfob] = useSimVar('L:A32NX_DESTINATION_FUEL_ON_BOARD', 'Kilograms', 5_000); + + const [emptyWeight] = useState(SimVar.GetSimVarValue('A:EMPTY WEIGHT', 'Kilograms')); + + const [seatMap] = useState(Loadsheet.seatMap); + const [cargoMap] = useState(Loadsheet.cargoMap); + + const maxPax = useMemo(() => seatMap.reduce((a, b) => a + b.capacity, 0), [seatMap]); + const maxCargo = useMemo(() => cargoMap.reduce((a, b) => a + b.weight, 0), [cargoMap]); + + // Calculate Total Pax from Pax Flags + const totalPax = useMemo(() => { + let p = 0; + activeFlags.forEach((flag) => { + p += flag.getTotalFilledSeats(); + }); + return p; + }, [...activeFlags]); + + const totalPaxDesired = useMemo(() => { + let p = 0; + desiredFlags.forEach((flag) => { + p += flag.getTotalFilledSeats(); + }); + return p; + }, [...desiredFlags]); + + const totalCargoDesired = useMemo(() => ((cargoDesired && cargoDesired.length > 0) ? cargoDesired.reduce((a, b) => a + b) : -1), [...cargoDesired]); + const totalCargo = useMemo(() => ((cargo && cargo.length > 0) ? cargo.reduce((a, b) => a + b) : -1), [...cargo]); + + // Units + // Weights + const [zfw] = useSimVar('L:A32NX_AIRFRAME_ZFW', 'number', 1_553); + const [zfwDesired] = useSimVar('L:A32NX_AIRFRAME_ZFW_DESIRED', 'number', 1_621); + const [gw] = useSimVar('L:A32NX_AIRFRAME_GW', 'number', 1_741); + const [gwDesired] = useSimVar('L:A32NX_AIRFRAME_GW_DESIRED', 'number', 1_787); + + // CG MAC + const [zfwCgMac] = useSimVar('L:A32NX_AIRFRAME_ZFW_CG_PERCENT_MAC', 'number', 1_223); + const [desiredZfwCgMac] = useSimVar('L:A32NX_AIRFRAME_ZFW_CG_PERCENT_MAC_DESIRED', 'number', 1_279); + const [gwCgMac] = useSimVar('L:A32NX_AIRFRAME_GW_CG_PERCENT_MAC', 'number', 1_301); + const [desiredGwCgMac] = useSimVar('L:A32NX_AIRFRAME_GW_CG_PERCENT_MAC_DESIRED', 'number', 1_447); + + const [showSimbriefButton, setShowSimbriefButton] = useState(false); + const [displayZfw, setDisplayZfw] = useState(true); + + // GSX + const [gsxPayloadSyncEnabled] = usePersistentNumberProperty('GSX_PAYLOAD_SYNC', 0); + const [_, setGsxNumPassengers] = useSimVar('L:FSDT_GSX_NUMPASSENGERS', 'Number', 223); + const [gsxBoardingState] = useSimVar('L:FSDT_GSX_BOARDING_STATE', 'Number', 227); + const [gsxDeBoardingState] = useSimVar('L:FSDT_GSX_DEBOARDING_STATE', 'Number', 229); + const gsxStates = { + AVAILABLE: 1, + NOT_AVAILABLE: 2, + BYPASSED: 3, + REQUESTED: 4, + PERFORMING: 5, + COMPLETED: 6, + }; + + const setSimBriefValues = () => { + if (simbriefUnits === 'kgs') { + setPaxBagWeight(simbriefBagWeight); + setPaxWeight(simbriefPaxWeight); + setTargetPax(simbriefPax > maxPax ? maxPax : simbriefPax); + setTargetCargo(simbriefBag, simbriefFreight, simbriefBagWeight); + } else { + setPaxBagWeight(Units.poundToKilogram(simbriefBagWeight)); + setPaxWeight(Units.poundToKilogram(simbriefPaxWeight)); + setTargetPax(simbriefPax); + setTargetCargo(simbriefBag, Units.poundToKilogram(simbriefFreight), Units.poundToKilogram(simbriefBagWeight)); + } + }; + + const [eng1Running] = useSimVar('ENG COMBUSTION:1', 'Bool', 6_581); + const [eng2Running] = useSimVar('ENG COMBUSTION:2', 'Bool', 6_397); + const [coldAndDark, setColdAndDark] = useState(true); + + const chooseDesiredSeats = useCallback((stationIndex: number, fillSeats: boolean = true, numChoose: number) => { + const seatFlags: SeatFlags = desiredFlags[stationIndex]; + if (fillSeats) { + seatFlags.fillEmptySeats(numChoose); + } else { + seatFlags.emptyFilledSeats(numChoose); + } + + setDesiredFlags[stationIndex](seatFlags); + }, [...desiredFlags]); + + const setTargetPax = useCallback((numOfPax: number) => { + setGsxNumPassengers(numOfPax); + + if (numOfPax === totalPaxDesired || numOfPax > maxPax || numOfPax < 0) return; + + let paxRemaining = numOfPax; + + const fillStation = (stationIndex: number, percent: number, paxToFill: number) => { + const sFlags: SeatFlags = desiredFlags[stationIndex]; + const toBeFilled = Math.min(Math.trunc(percent * paxToFill), seatMap[stationIndex].capacity); + + paxRemaining -= toBeFilled; + + const planSeatedPax = sFlags.getTotalFilledSeats(); + chooseDesiredSeats( + stationIndex, + (toBeFilled > planSeatedPax), + Math.abs(toBeFilled - planSeatedPax), + ); + }; + + for (let i = seatMap.length - 1; i > 0; i--) { + fillStation(i, seatMap[i].fill, numOfPax); + } + fillStation(0, 1, paxRemaining); + }, [maxPax, ...seatMap, totalPaxDesired]); + + const setTargetCargo = useCallback((numberOfPax: number, freight: number, perBagWeight: number = paxBagWeight) => { + const bagWeight = numberOfPax * perBagWeight; + const loadableCargoWeight = Math.min(bagWeight + Math.round(freight), maxCargo); + + let remainingWeight = loadableCargoWeight; + + async function fillCargo(station: number, percent: number, loadableCargoWeight: number) { + const c = Math.round(percent * loadableCargoWeight); + remainingWeight -= c; + setCargoDesired[station](c); + } + + for (let i = cargoDesired.length - 1; i > 0; i--) { + fillCargo(i, cargoMap[i].weight / maxCargo, loadableCargoWeight); + } + fillCargo(0, 1, remainingWeight); + }, [maxCargo, ...cargoMap, ...cargoDesired, paxBagWeight]); + + const processZfw = useCallback((newZfw) => { + let paxCargoWeight = newZfw - emptyWeight; + + // Load pax first + const pWeight = paxWeight + paxBagWeight; + const newPax = Math.max(Math.min(Math.round(paxCargoWeight / pWeight), maxPax), 0); + + paxCargoWeight -= newPax * pWeight; + const newCargo = Math.max(Math.min(paxCargoWeight, maxCargo), 0); + + setTargetPax(newPax); + setTargetCargo(newPax, newCargo); + }, [emptyWeight, paxWeight, paxBagWeight, maxPax, maxCargo]); + + const processGw = useCallback((newGw) => { + let paxCargoWeight = newGw - emptyWeight - (gw - zfw); // new gw - empty - total fuel + + // Load pax first + const pWeight = paxWeight + paxBagWeight; + const newPax = Math.max(Math.min(Math.round(paxCargoWeight / pWeight), maxPax), 0); + + paxCargoWeight -= newPax * pWeight; + const newCargo = Math.max(Math.min(paxCargoWeight, maxCargo), 0); + + setTargetPax(newPax); + setTargetCargo(newPax, newCargo); + }, [emptyWeight, paxWeight, paxBagWeight, maxPax, maxCargo, gw, zfw]); + + const onClickCargo = useCallback((cargoStation, e) => { + if (gsxPayloadSyncEnabled === 1 && boardingStarted) { + return; + } + const cargoPercent = Math.min(Math.max(0, e.nativeEvent.offsetX / cargoMap[cargoStation].progressBarWidth), 1); + setCargoDesired[cargoStation](Math.round(cargoMap[cargoStation].weight * cargoPercent)); + }, [cargoMap]); + + const onClickSeat = useCallback((stationIndex: number, seatId: number) => { + if (gsxPayloadSyncEnabled === 1 && boardingStarted) { + return; + } + + // TODO FIXME: This calculation does not work correctly if user clicks on many seats in rapid succession + const oldPaxBag = totalPaxDesired * paxBagWeight; + const freight = Math.max(totalCargoDesired - oldPaxBag, 0); + + const seatFlags: SeatFlags = desiredFlags[stationIndex]; + seatFlags.toggleSeatId(seatId); + setDesiredFlags[stationIndex](seatFlags); + + let newPaxDesired = 0; + desiredFlags.forEach((flag) => { + newPaxDesired += flag.getTotalFilledSeats(); + }); + + setTargetCargo(newPaxDesired, freight); + }, [ + paxBagWeight, + totalCargoDesired, + ...cargoDesired, + ...desiredFlags, + totalPaxDesired, + ]); + + const handleDeboarding = useCallback(() => { + if (!boardingStarted) { + showModal( + { + setTargetPax(0); + setTargetCargo(0, 0); + setTimeout(() => { + setBoardingStarted(true); + }, 500); + }} + />, + ); + return; + } + setBoardingStarted(false); + }, [totalPaxDesired, totalPax, totalCargo, boardingStarted, totalCargoDesired]); + + const calculateBoardingTime = useMemo(() => { + // factors taken from payload.rs TODO: Simvar + let boardingRateMultiplier = 0; + if (boardingRate === 'REAL') { + boardingRateMultiplier = 5; + } else if (boardingRate === 'FAST') { + boardingRateMultiplier = 1; + } + + // factors taken from payload.rs TODO: Simvar + const cargoWeightPerWeightStep = 60; + + const differentialPax = Math.abs(totalPaxDesired - totalPax); + const differentialCargo = Math.abs(totalCargoDesired - totalCargo); + + const estimatedPaxBoardingSeconds = differentialPax * boardingRateMultiplier; + const estimatedCargoLoadingSeconds = (differentialCargo / cargoWeightPerWeightStep) * boardingRateMultiplier; + + return Math.max(estimatedPaxBoardingSeconds, estimatedCargoLoadingSeconds); + }, [totalPaxDesired, totalPax, totalCargoDesired, totalCargo, boardingRate]); + + const boardingStatusClass = useMemo(() => { + if (!boardingStarted) { + return 'text-theme-highlight'; + } + return (totalPaxDesired * paxWeight + totalCargoDesired) >= (totalPax * paxWeight + totalCargo) ? 'text-green-500' : 'text-yellow-500'; + }, [boardingStarted, paxWeight, totalCargoDesired, totalCargo, totalPaxDesired, totalPax]); + + // Init + useEffect(() => { + if (paxWeight === 0) { + setPaxWeight(Math.round(Loadsheet.specs.pax.defaultPaxWeight)); + } + if (paxBagWeight === 0) { + setPaxBagWeight(Math.round(Loadsheet.specs.pax.defaultBagWeight)); + } + }, []); + + // Set Cold and Dark State + useEffect(() => { + if (eng1Running || eng2Running || !isOnGround) { + setColdAndDark(false); + } else { + setColdAndDark(true); + } + }, [eng1Running, eng2Running, isOnGround]); + + useEffect(() => { + if (boardingRate !== 'INSTANT') { + if (!coldAndDark) { + setBoardingRate('INSTANT'); + } + } + }, [coldAndDark, boardingRate]); + + useEffect(() => { + if (gsxPayloadSyncEnabled === 1) { + switch (gsxBoardingState) { + // If boarding has been requested, performed or completed + case gsxStates.REQUESTED: + case gsxStates.PERFORMING: + case gsxStates.COMPLETED: + setBoardingStarted(true); + break; + default: + break; + } + } + }, [gsxBoardingState]); + + useEffect(() => { + if (gsxPayloadSyncEnabled === 1) { + switch (gsxDeBoardingState) { + case gsxStates.REQUESTED: + // If Deboarding has been requested, set target pax to 0 for boarding backend + setTargetPax(0); + setTargetCargo(0, 0); + setBoardingStarted(true); + break; + case gsxStates.PERFORMING: + // If deboarding is being performed + setBoardingStarted(true); + break; + case gsxStates.COMPLETED: + // If deboarding is completed + setBoardingStarted(false); + break; + default: + break; + } + } + }, [gsxDeBoardingState]); + + useEffect(() => { + let simbriefStatus = false; + if (simbriefUnits === 'kgs') { + simbriefStatus = (simbriefDataLoaded + && ( + simbriefPax !== totalPaxDesired + || Math.abs(simbriefFreight + simbriefBag * simbriefBagWeight - totalCargoDesired) > 3.0 + || Math.abs(simbriefPaxWeight - paxWeight) > 3.0 + || Math.abs(simbriefBagWeight - paxBagWeight) > 3.0 + ) + ); + } else { + simbriefStatus = (simbriefDataLoaded + && ( + simbriefPax !== totalPaxDesired + || Math.abs(Units.poundToKilogram(simbriefFreight + simbriefBag * simbriefBagWeight) - totalCargoDesired) > 3.0 + || Math.abs(Units.poundToKilogram(simbriefPaxWeight) - paxWeight) > 3.0 + || Math.abs(Units.poundToKilogram(simbriefBagWeight) - paxBagWeight) > 3.0 + ) + ); + } + + if (gsxPayloadSyncEnabled === 1) { + if (boardingStarted) { + setShowSimbriefButton(false); + return; + } + + setShowSimbriefButton(simbriefStatus); + return; + } + setShowSimbriefButton(simbriefStatus); + }, [ + simbriefUnits, + simbriefFreight, + simbriefBag, + simbriefBagWeight, + paxWeight, paxBagWeight, + totalPaxDesired, totalCargoDesired, + simbriefDataLoaded, + boardingStarted, + gsxPayloadSyncEnabled, + ]); + + const remainingTimeString = () => { + const minutes = Math.round(calculateBoardingTime / 60); + const seconds = calculateBoardingTime % 60; + const padding = seconds < 10 ? '0' : ''; + return `${minutes}:${padding}${seconds.toFixed(0)} ${t('Ground.Payload.EstimatedDurationUnit')}`; + }; + + const [theme] = usePersistentProperty('EFB_UI_THEME', 'blue'); + const getTheme = useCallback((theme: string): [string, string, string] => { + let base = '#fff'; + let primary = '#E37E28'; + let secondary = '#14181F'; + switch (theme) { + case 'dark': + base = '#fff'; + primary = '#3B82F6'; + secondary = '#84CC16'; + break; + case 'light': + base = '#000000'; + primary = '#3B82F6'; + secondary = '#84CC16'; + break; + default: + break; + } + return [base, primary, secondary]; + }, [theme]); + + return ( +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + {gsxPayloadSyncEnabled !== 1 && ( + + )} +
+
+ {showSimbriefButton + && ( + +
+ +
+
+ )} +
+ {(gsxPayloadSyncEnabled !== 1) && ( +
+ +
+
+ {t('Ground.Payload.BoardingTime')} + + ( + {remainingTimeString()} + ) + +
+ + + setBoardingRate('INSTANT')} + > + {t('Settings.Instant')} + + + +
+ setBoardingRate('FAST')} + > + {t('Settings.Fast')} + +
+
+ +
+ setBoardingRate('REAL')} + > + {t('Settings.Real')} + +
+
+
+
+
+ )} + {gsxPayloadSyncEnabled === 1 && ( +
+ {t('Ground.Payload.GSXPayloadSyncEnabled')} +
+ )} +
+
+ +
+
+
+
+ ); +}; diff --git a/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/A330_941/CargoWidget.tsx b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/A330_941/CargoWidget.tsx new file mode 100644 index 000000000..e7aa41c3f --- /dev/null +++ b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/A330_941/CargoWidget.tsx @@ -0,0 +1,31 @@ +/* eslint-disable max-len */ +import React from 'react'; +import { CargoBar } from '../PayloadElements'; +import { CargoStationInfo } from '../Seating/Constants'; + +interface SeatMapProps { + cargo: number[], + cargoDesired: number[], + cargoMap: CargoStationInfo[], + onClickCargo: (cargoStation: number, event: any) => void, +} + +enum CargoStation { + FwdBag, + AftCont, + AftBag, + AftBulk +} + +export const CargoWidget: React.FC = ({ cargo, cargoDesired, cargoMap, onClickCargo }) => ( + <> +
+ +
+
+ + + +
+ +); diff --git a/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/Loadsheet/a339x.json b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/A330_941/a339x.json similarity index 100% rename from hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/Loadsheet/a339x.json rename to hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/A330_941/a339x.json diff --git a/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/ACJ330_941/A330Payload.tsx b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/ACJ330_941/A330Payload.tsx new file mode 100644 index 000000000..25400d1e5 --- /dev/null +++ b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/ACJ330_941/A330Payload.tsx @@ -0,0 +1,614 @@ +/* eslint-disable max-len */ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { CloudArrowDown } from 'react-bootstrap-icons'; +import { SeatFlags, Units, usePersistentNumberProperty, usePersistentProperty, useSeatFlags, useSimVar } from '@flybywiresim/fbw-sdk'; +import { SeatOutlineBg } from 'instruments/src/EFB/Assets/SeatOutlineBg'; +import { BoardingInput, MiscParamsInput, PayloadInputTable } from '../PayloadElements'; +import { CargoWidget } from './CargoWidget'; +import { ChartWidget } from '../Chart/ChartWidget'; +import { CargoStationInfo, PaxStationInfo } from '../Seating/Constants'; +import { t } from '../../../../translation'; +import { TooltipWrapper } from '../../../../UtilComponents/TooltipWrapper'; +import Loadsheet from './acj339x.json'; +import Card from '../../../../UtilComponents/Card/Card'; +import { SelectGroup, SelectItem } from '../../../../UtilComponents/Form/Select'; +import { SeatMapWidget } from '../Seating/SeatMapWidget'; +import { PromptModal, useModals } from '../../../../UtilComponents/Modals/Modals'; + +interface A330Props { + simbriefUnits: string, + simbriefBagWeight: number, + simbriefPaxWeight: number, + simbriefPax: number, + simbriefBag: number, + simbriefFreight: number, + simbriefDataLoaded: boolean, + massUnitForDisplay: string, + isOnGround: boolean, + + boardingStarted: boolean, + boardingRate: string, + setBoardingStarted: (boardingStarted: any) => void, + setBoardingRate: (boardingRate: any) => void, +} +export const ACJ330Payload: React.FC = ({ + simbriefUnits, + simbriefBagWeight, + simbriefPaxWeight, + simbriefPax, + simbriefBag, + simbriefFreight, + simbriefDataLoaded, + massUnitForDisplay, + isOnGround, + boardingStarted, + boardingRate, + setBoardingStarted, + setBoardingRate, +}) => { + const { showModal } = useModals(); + + const [aFlags] = useSeatFlags(`L:${Loadsheet.seatMap[0].simVar}`, Loadsheet.seatMap[0].capacity, 509); + const [bFlags] = useSeatFlags(`L:${Loadsheet.seatMap[1].simVar}`, Loadsheet.seatMap[1].capacity, 541); + const [cFlags] = useSeatFlags(`L:${Loadsheet.seatMap[2].simVar}`, Loadsheet.seatMap[2].capacity, 569); + + const [aFlagsDesired, setAFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[0].simVar}_DESIRED`, Loadsheet.seatMap[0].capacity, 379); + const [bFlagsDesired, setBFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[1].simVar}_DESIRED`, Loadsheet.seatMap[1].capacity, 421); + const [cFlagsDesired, setCFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[2].simVar}_DESIRED`, Loadsheet.seatMap[2].capacity, 457); + + const activeFlags = useMemo(() => [aFlags, bFlags, cFlags], [aFlags, bFlags, cFlags]); + const desiredFlags = useMemo(() => [aFlagsDesired, bFlagsDesired, cFlagsDesired], [aFlagsDesired, bFlagsDesired, cFlagsDesired]); + const setDesiredFlags = useMemo(() => [setAFlagsDesired, setBFlagsDesired, setCFlagsDesired], []); + + const [fwdBag] = useSimVar(`L:${Loadsheet.cargoMap[0].simVar}`, 'Number', 719); + const [aftCont] = useSimVar(`L:${Loadsheet.cargoMap[1].simVar}`, 'Number', 743); + const [aftBag] = useSimVar(`L:${Loadsheet.cargoMap[2].simVar}`, 'Number', 769); + const [aftBulk] = useSimVar(`L:${Loadsheet.cargoMap[3].simVar}`, 'Number', 797); + + const [fwdBagDesired, setFwdBagDesired] = useSimVar(`L:${Loadsheet.cargoMap[0].simVar}_DESIRED`, 'Number', 619); + const [aftContDesired, setAftContDesired] = useSimVar(`L:${Loadsheet.cargoMap[1].simVar}_DESIRED`, 'Number', 631); + const [aftBagDesired, setAftBagDesired] = useSimVar(`L:${Loadsheet.cargoMap[2].simVar}_DESIRED`, 'Number', 641); + const [aftBulkDesired, setAftBulkDesired] = useSimVar(`L:${Loadsheet.cargoMap[3].simVar}_DESIRED`, 'Number', 677); + + const cargo = useMemo(() => [fwdBag, aftCont, aftBag, aftBulk], [fwdBag, aftCont, aftBag, aftBulk]); + const cargoDesired = useMemo(() => [fwdBagDesired, aftContDesired, aftBagDesired, aftBulkDesired], [fwdBagDesired, aftContDesired, aftBagDesired, aftBulkDesired]); + const setCargoDesired = useMemo(() => [setFwdBagDesired, setAftContDesired, setAftBagDesired, setAftBulkDesired], []); + + const [paxWeight, setPaxWeight] = useSimVar('L:A32NX_WB_PER_PAX_WEIGHT', 'Kilograms', 739); + const [paxBagWeight, setPaxBagWeight] = useSimVar('L:A32NX_WB_PER_BAG_WEIGHT', 'Kilograms', 797); + // const [destEfob] = useSimVar('L:A32NX_DESTINATION_FUEL_ON_BOARD', 'Kilograms', 5_000); + + const [emptyWeight] = useState(SimVar.GetSimVarValue('A:EMPTY WEIGHT', 'Kilograms')); + + const [seatMap] = useState(Loadsheet.seatMap); + const [cargoMap] = useState(Loadsheet.cargoMap); + + const maxPax = useMemo(() => seatMap.reduce((a, b) => a + b.capacity, 0), [seatMap]); + const maxCargo = useMemo(() => cargoMap.reduce((a, b) => a + b.weight, 0), [cargoMap]); + + // Calculate Total Pax from Pax Flags + const totalPax = useMemo(() => { + let p = 0; + activeFlags.forEach((flag) => { + p += flag.getTotalFilledSeats(); + }); + return p; + }, [...activeFlags]); + + const totalPaxDesired = useMemo(() => { + let p = 0; + desiredFlags.forEach((flag) => { + p += flag.getTotalFilledSeats(); + }); + return p; + }, [...desiredFlags]); + + const totalCargoDesired = useMemo(() => ((cargoDesired && cargoDesired.length > 0) ? cargoDesired.reduce((a, b) => a + b) : -1), [...cargoDesired]); + const totalCargo = useMemo(() => ((cargo && cargo.length > 0) ? cargo.reduce((a, b) => a + b) : -1), [...cargo]); + + // Units + // Weights + const [zfw] = useSimVar('L:A32NX_AIRFRAME_ZFW', 'number', 1_553); + const [zfwDesired] = useSimVar('L:A32NX_AIRFRAME_ZFW_DESIRED', 'number', 1_621); + const [gw] = useSimVar('L:A32NX_AIRFRAME_GW', 'number', 1_741); + const [gwDesired] = useSimVar('L:A32NX_AIRFRAME_GW_DESIRED', 'number', 1_787); + + // CG MAC + const [zfwCgMac] = useSimVar('L:A32NX_AIRFRAME_ZFW_CG_PERCENT_MAC', 'number', 1_223); + const [desiredZfwCgMac] = useSimVar('L:A32NX_AIRFRAME_ZFW_CG_PERCENT_MAC_DESIRED', 'number', 1_279); + const [gwCgMac] = useSimVar('L:A32NX_AIRFRAME_GW_CG_PERCENT_MAC', 'number', 1_301); + const [desiredGwCgMac] = useSimVar('L:A32NX_AIRFRAME_GW_CG_PERCENT_MAC_DESIRED', 'number', 1_447); + + const [showSimbriefButton, setShowSimbriefButton] = useState(false); + const [displayZfw, setDisplayZfw] = useState(true); + + // GSX + const [gsxPayloadSyncEnabled] = usePersistentNumberProperty('GSX_PAYLOAD_SYNC', 0); + const [_, setGsxNumPassengers] = useSimVar('L:FSDT_GSX_NUMPASSENGERS', 'Number', 223); + const [gsxBoardingState] = useSimVar('L:FSDT_GSX_BOARDING_STATE', 'Number', 227); + const [gsxDeBoardingState] = useSimVar('L:FSDT_GSX_DEBOARDING_STATE', 'Number', 229); + const gsxStates = { + AVAILABLE: 1, + NOT_AVAILABLE: 2, + BYPASSED: 3, + REQUESTED: 4, + PERFORMING: 5, + COMPLETED: 6, + }; + + const setSimBriefValues = () => { + if (simbriefUnits === 'kgs') { + setPaxBagWeight(simbriefBagWeight); + setPaxWeight(simbriefPaxWeight); + setTargetPax(simbriefPax > maxPax ? maxPax : simbriefPax); + setTargetCargo(simbriefBag, simbriefFreight, simbriefBagWeight); + } else { + setPaxBagWeight(Units.poundToKilogram(simbriefBagWeight)); + setPaxWeight(Units.poundToKilogram(simbriefPaxWeight)); + setTargetPax(simbriefPax); + setTargetCargo(simbriefBag, Units.poundToKilogram(simbriefFreight), Units.poundToKilogram(simbriefBagWeight)); + } + }; + + const [eng1Running] = useSimVar('ENG COMBUSTION:1', 'Bool', 6_581); + const [eng2Running] = useSimVar('ENG COMBUSTION:2', 'Bool', 6_397); + const [coldAndDark, setColdAndDark] = useState(true); + + const chooseDesiredSeats = useCallback((stationIndex: number, fillSeats: boolean = true, numChoose: number) => { + const seatFlags: SeatFlags = desiredFlags[stationIndex]; + if (fillSeats) { + seatFlags.fillEmptySeats(numChoose); + } else { + seatFlags.emptyFilledSeats(numChoose); + } + + setDesiredFlags[stationIndex](seatFlags); + }, [...desiredFlags]); + + const setTargetPax = useCallback((numOfPax: number) => { + setGsxNumPassengers(numOfPax); + + if (numOfPax === totalPaxDesired || numOfPax > maxPax || numOfPax < 0) return; + + let paxRemaining = numOfPax; + + const fillStation = (stationIndex: number, percent: number, paxToFill: number) => { + const sFlags: SeatFlags = desiredFlags[stationIndex]; + const toBeFilled = Math.min(Math.trunc(percent * paxToFill), seatMap[stationIndex].capacity); + + paxRemaining -= toBeFilled; + + const planSeatedPax = sFlags.getTotalFilledSeats(); + chooseDesiredSeats( + stationIndex, + (toBeFilled > planSeatedPax), + Math.abs(toBeFilled - planSeatedPax), + ); + }; + + for (let i = seatMap.length - 1; i > 0; i--) { + fillStation(i, seatMap[i].fill, numOfPax); + } + fillStation(0, 1, paxRemaining); + }, [maxPax, ...seatMap, totalPaxDesired]); + + const setTargetCargo = useCallback((numberOfPax: number, freight: number, perBagWeight: number = paxBagWeight) => { + const bagWeight = numberOfPax * perBagWeight; + const loadableCargoWeight = Math.min(bagWeight + Math.round(freight), maxCargo); + + let remainingWeight = loadableCargoWeight; + + async function fillCargo(station: number, percent: number, loadableCargoWeight: number) { + const c = Math.round(percent * loadableCargoWeight); + remainingWeight -= c; + setCargoDesired[station](c); + } + + for (let i = cargoDesired.length - 1; i > 0; i--) { + fillCargo(i, cargoMap[i].weight / maxCargo, loadableCargoWeight); + } + fillCargo(0, 1, remainingWeight); + }, [maxCargo, ...cargoMap, ...cargoDesired, paxBagWeight]); + + const processZfw = useCallback((newZfw) => { + let paxCargoWeight = newZfw - emptyWeight; + + // Load pax first + const pWeight = paxWeight + paxBagWeight; + const newPax = Math.max(Math.min(Math.round(paxCargoWeight / pWeight), maxPax), 0); + + paxCargoWeight -= newPax * pWeight; + const newCargo = Math.max(Math.min(paxCargoWeight, maxCargo), 0); + + setTargetPax(newPax); + setTargetCargo(newPax, newCargo); + }, [emptyWeight, paxWeight, paxBagWeight, maxPax, maxCargo]); + + const processGw = useCallback((newGw) => { + let paxCargoWeight = newGw - emptyWeight - (gw - zfw); // new gw - empty - total fuel + + // Load pax first + const pWeight = paxWeight + paxBagWeight; + const newPax = Math.max(Math.min(Math.round(paxCargoWeight / pWeight), maxPax), 0); + + paxCargoWeight -= newPax * pWeight; + const newCargo = Math.max(Math.min(paxCargoWeight, maxCargo), 0); + + setTargetPax(newPax); + setTargetCargo(newPax, newCargo); + }, [emptyWeight, paxWeight, paxBagWeight, maxPax, maxCargo, gw, zfw]); + + const onClickCargo = useCallback((cargoStation, e) => { + if (gsxPayloadSyncEnabled === 1 && boardingStarted) { + return; + } + const cargoPercent = Math.min(Math.max(0, e.nativeEvent.offsetX / cargoMap[cargoStation].progressBarWidth), 1); + setCargoDesired[cargoStation](Math.round(cargoMap[cargoStation].weight * cargoPercent)); + }, [cargoMap]); + + const onClickSeat = useCallback((stationIndex: number, seatId: number) => { + if (gsxPayloadSyncEnabled === 1 && boardingStarted) { + return; + } + + // TODO FIXME: This calculation does not work correctly if user clicks on many seats in rapid succession + const oldPaxBag = totalPaxDesired * paxBagWeight; + const freight = Math.max(totalCargoDesired - oldPaxBag, 0); + + const seatFlags: SeatFlags = desiredFlags[stationIndex]; + seatFlags.toggleSeatId(seatId); + setDesiredFlags[stationIndex](seatFlags); + + let newPaxDesired = 0; + desiredFlags.forEach((flag) => { + newPaxDesired += flag.getTotalFilledSeats(); + }); + + setTargetCargo(newPaxDesired, freight); + }, [ + paxBagWeight, + totalCargoDesired, + ...cargoDesired, + ...desiredFlags, + totalPaxDesired, + ]); + + const handleDeboarding = useCallback(() => { + if (!boardingStarted) { + showModal( + { + setTargetPax(0); + setTargetCargo(0, 0); + setTimeout(() => { + setBoardingStarted(true); + }, 500); + }} + />, + ); + return; + } + setBoardingStarted(false); + }, [totalPaxDesired, totalPax, totalCargo, boardingStarted, totalCargoDesired]); + + const calculateBoardingTime = useMemo(() => { + // factors taken from payload.rs TODO: Simvar + let boardingRateMultiplier = 0; + if (boardingRate === 'REAL') { + boardingRateMultiplier = 5; + } else if (boardingRate === 'FAST') { + boardingRateMultiplier = 1; + } + + // factors taken from payload.rs TODO: Simvar + const cargoWeightPerWeightStep = 60; + + const differentialPax = Math.abs(totalPaxDesired - totalPax); + const differentialCargo = Math.abs(totalCargoDesired - totalCargo); + + const estimatedPaxBoardingSeconds = differentialPax * boardingRateMultiplier; + const estimatedCargoLoadingSeconds = (differentialCargo / cargoWeightPerWeightStep) * boardingRateMultiplier; + + return Math.max(estimatedPaxBoardingSeconds, estimatedCargoLoadingSeconds); + }, [totalPaxDesired, totalPax, totalCargoDesired, totalCargo, boardingRate]); + + const boardingStatusClass = useMemo(() => { + if (!boardingStarted) { + return 'text-theme-highlight'; + } + return (totalPaxDesired * paxWeight + totalCargoDesired) >= (totalPax * paxWeight + totalCargo) ? 'text-green-500' : 'text-yellow-500'; + }, [boardingStarted, paxWeight, totalCargoDesired, totalCargo, totalPaxDesired, totalPax]); + + // Init + useEffect(() => { + if (paxWeight === 0) { + setPaxWeight(Math.round(Loadsheet.specs.pax.defaultPaxWeight)); + } + if (paxBagWeight === 0) { + setPaxBagWeight(Math.round(Loadsheet.specs.pax.defaultBagWeight)); + } + }, []); + + // Set Cold and Dark State + useEffect(() => { + if (eng1Running || eng2Running || !isOnGround) { + setColdAndDark(false); + } else { + setColdAndDark(true); + } + }, [eng1Running, eng2Running, isOnGround]); + + useEffect(() => { + if (boardingRate !== 'INSTANT') { + if (!coldAndDark) { + setBoardingRate('INSTANT'); + } + } + }, [coldAndDark, boardingRate]); + + useEffect(() => { + if (gsxPayloadSyncEnabled === 1) { + switch (gsxBoardingState) { + // If boarding has been requested, performed or completed + case gsxStates.REQUESTED: + case gsxStates.PERFORMING: + case gsxStates.COMPLETED: + setBoardingStarted(true); + break; + default: + break; + } + } + }, [gsxBoardingState]); + + useEffect(() => { + if (gsxPayloadSyncEnabled === 1) { + switch (gsxDeBoardingState) { + case gsxStates.REQUESTED: + // If Deboarding has been requested, set target pax to 0 for boarding backend + setTargetPax(0); + setTargetCargo(0, 0); + setBoardingStarted(true); + break; + case gsxStates.PERFORMING: + // If deboarding is being performed + setBoardingStarted(true); + break; + case gsxStates.COMPLETED: + // If deboarding is completed + setBoardingStarted(false); + break; + default: + break; + } + } + }, [gsxDeBoardingState]); + + useEffect(() => { + let simbriefStatus = false; + if (simbriefUnits === 'kgs') { + simbriefStatus = (simbriefDataLoaded + && ( + simbriefPax !== totalPaxDesired + || Math.abs(simbriefFreight + simbriefBag * simbriefBagWeight - totalCargoDesired) > 3.0 + || Math.abs(simbriefPaxWeight - paxWeight) > 3.0 + || Math.abs(simbriefBagWeight - paxBagWeight) > 3.0 + ) + ); + } else { + simbriefStatus = (simbriefDataLoaded + && ( + simbriefPax !== totalPaxDesired + || Math.abs(Units.poundToKilogram(simbriefFreight + simbriefBag * simbriefBagWeight) - totalCargoDesired) > 3.0 + || Math.abs(Units.poundToKilogram(simbriefPaxWeight) - paxWeight) > 3.0 + || Math.abs(Units.poundToKilogram(simbriefBagWeight) - paxBagWeight) > 3.0 + ) + ); + } + + if (gsxPayloadSyncEnabled === 1) { + if (boardingStarted) { + setShowSimbriefButton(false); + return; + } + + setShowSimbriefButton(simbriefStatus); + return; + } + setShowSimbriefButton(simbriefStatus); + }, [ + simbriefUnits, + simbriefFreight, + simbriefBag, + simbriefBagWeight, + paxWeight, paxBagWeight, + totalPaxDesired, totalCargoDesired, + simbriefDataLoaded, + boardingStarted, + gsxPayloadSyncEnabled, + ]); + + const remainingTimeString = () => { + const minutes = Math.round(calculateBoardingTime / 60); + const seconds = calculateBoardingTime % 60; + const padding = seconds < 10 ? '0' : ''; + return `${minutes}:${padding}${seconds.toFixed(0)} ${t('Ground.Payload.EstimatedDurationUnit')}`; + }; + + const [theme] = usePersistentProperty('EFB_UI_THEME', 'blue'); + const getTheme = useCallback((theme: string): [string, string, string] => { + let base = '#fff'; + let primary = '#E37E28'; + let secondary = '#14181F'; + switch (theme) { + case 'dark': + base = '#fff'; + primary = '#3B82F6'; + secondary = '#84CC16'; + break; + case 'light': + base = '#000000'; + primary = '#3B82F6'; + secondary = '#84CC16'; + break; + default: + break; + } + return [base, primary, secondary]; + }, [theme]); + + return ( +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + {gsxPayloadSyncEnabled !== 1 && ( + + )} +
+
+ {showSimbriefButton + && ( + +
+ +
+
+ )} +
+ {(gsxPayloadSyncEnabled !== 1) && ( +
+ +
+
+ {t('Ground.Payload.BoardingTime')} + + ( + {remainingTimeString()} + ) + +
+ + + setBoardingRate('INSTANT')} + > + {t('Settings.Instant')} + + + +
+ setBoardingRate('FAST')} + > + {t('Settings.Fast')} + +
+
+ +
+ setBoardingRate('REAL')} + > + {t('Settings.Real')} + +
+
+
+
+
+ )} + {gsxPayloadSyncEnabled === 1 && ( +
+ {t('Ground.Payload.GSXPayloadSyncEnabled')} +
+ )} +
+
+ +
+
+
+
+ ); +}; diff --git a/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/ACJ330_941/CargoWidget.tsx b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/ACJ330_941/CargoWidget.tsx new file mode 100644 index 000000000..e7aa41c3f --- /dev/null +++ b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/ACJ330_941/CargoWidget.tsx @@ -0,0 +1,31 @@ +/* eslint-disable max-len */ +import React from 'react'; +import { CargoBar } from '../PayloadElements'; +import { CargoStationInfo } from '../Seating/Constants'; + +interface SeatMapProps { + cargo: number[], + cargoDesired: number[], + cargoMap: CargoStationInfo[], + onClickCargo: (cargoStation: number, event: any) => void, +} + +enum CargoStation { + FwdBag, + AftCont, + AftBag, + AftBulk +} + +export const CargoWidget: React.FC = ({ cargo, cargoDesired, cargoMap, onClickCargo }) => ( + <> +
+ +
+
+ + + +
+ +); diff --git a/hdw-a339x-acj/src/systems/instruments/src/EFB/Ground/Pages/Payload/Loadsheet/acj339x.json b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/ACJ330_941/acj339x.json similarity index 100% rename from hdw-a339x-acj/src/systems/instruments/src/EFB/Ground/Pages/Payload/Loadsheet/acj339x.json rename to hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/ACJ330_941/acj339x.json diff --git a/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/Loadsheet/a339v900.json b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/Loadsheet/a339v900.json deleted file mode 100644 index 86b871d9b..000000000 --- a/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/Loadsheet/a339v900.json +++ /dev/null @@ -1,2139 +0,0 @@ -{ - "specs": { - "prefix": "A339X", - "emptyPosition": -28.31, - "macSize": 25.49, - "leMacZ": -21.63, - "weights": { - "maxGw": 251000, - "maxZfw": 181000, - "minZfw": 127000, - "maxGwCg": 41, - "maxZfwCg": 41 - }, - "pax": { - "defaultPaxWeight": 84, - "defaultBagWeight": 20, - "minPaxWeight": 10, - "maxPaxWeight": 250, - "minBagWeight": 1, - "maxBagWeight": 250 - } - }, - "seatMap": [ - { - "name": "ROWS [1-6]", - "capacity": 54, - "rows": [ - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 10, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - } - ], - "stationIndex": 1, - "position": 40, - "fill": 0.124, - "simVar": "A32NX_PAX_A" - }, - { - "name": "ROWS [7-15]", - "capacity": 57, - "rows": [ - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0, - "yOffset": 49 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - } - ], - "stationIndex": 2, - "position": 20, - "fill": 0.131, - "simVar": "A32NX_PAX_B" - }, - { - "name": "ROWS [16-22]", - "capacity": 57, - "rows": [ - { - "seats": [ - { - "type": 0, - "yOffset": 49 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 10, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - } - ], - "stationIndex": 3, - "position": 10, - "fill": 0.131, - "simVar": "A32NX_PAX_C" - }, - { - "name": "ROWS [23-29]", - "capacity": 63, - "rows": [ - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - } - ], - "stationIndex": 4, - "position": -30, - "fill": 0.145, - "simVar": "A32NX_PAX_D" - }, - { - "name": "ROWS [30-36]", - "capacity": 63, - "rows": [ - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - } - ], - "stationIndex": 5, - "position": -50, - "fill": 0.145, - "simVar": "A32NX_PAX_E" - }, - { - "name": "ROWS [37-42]", - "capacity": 54, - "rows": [ - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 70, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - } - ], - "stationIndex": 6, - "position": -60, - "fill": 0.124, - "simVar": "A32NX_PAX_F" - }, - { - "name": "ROWS [43-46]", - "capacity": 36, - "rows": [ - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - "yOffset": 4 - }, - { - "type": 0 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - } - ], - "stationIndex": 7, - "position": -70, - "fill": 0.083, - "simVar": "A32NX_PAX_G" - }, - { - "name": "ROWS [47-54]", - "capacity": 52, - "rows": [ - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - - "yOffset": 19 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - - "yOffset": 19 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - - "yOffset": 19 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - - "yOffset": 19 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - - "yOffset": 19 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - - "yOffset": 19 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - - "yOffset": 19 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - - "yOffset": 19 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - - "yOffset": 19 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - - "yOffset": 19 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0, - "yOffset": 49 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - - "yOffset": 19 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - - "yOffset": 19 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - - "yOffset": 19 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - }, - { - "seats": [ - { - "type": 0, - "yOffset": 49 - }, - { - "type": 0 - }, - { - "type": 0 - }, - { - "type": 0, - - "yOffset": 19 - }, - { - "type": 0 - } - ], - "x": 0, - "y": 0, - "xOffset": 0, - "yOffset": 0 - } - ], - "fill": 0.119, - "stationIndex": 8, - "position": -80.00, - "simVar": "A32NX_PAX_H" - } - ], - "cargoMap": [ - { - "name": "FWD BAGGAGE/CONTAINER", - "weight": 5800, - "stationIndex": 9, - "position": 40.00, - "progressBarWidth": 150, - "simVar": "A32NX_CARGO_FWD_BAGGAGE_CONTAINER" - }, - { - "name": "FWD CONTAINER CPT 1/2", - "weight": 17061, - "stationIndex": 10, - "position": 0.00, - "progressBarWidth": 100, - "simVar": "A32NX_CARGO_AFT_CONTAINER" - }, - { - "name": "AFT CONTAINER CPT 3/4", - "weight": 18507, - "stationIndex": 11, - "position": -55.00, - "progressBarWidth": 100, - "simVar": "A32NX_CARGO_AFT_BAGGAGE" - }, - { - "name": "AFT BULK/LOOSE CPT 5", - "weight": 3468, - "stationIndex": 12, - "position": -80, - "progressBarWidth": 100, - "simVar": "A32NX_CARGO_AFT_BULK_LOOSE" - } - ], - "chart": { - "performanceEnvelope": { - "mlw": [ - [ - 15, - 127000 - ], - [ - 15, - 191000 - ], - [ - 40.12, - 191000 - ], - [ - 41, - 173000 - ], - [ - 41, - 127000 - ], - [ - 15, - 127000 - ] - ], - "mzfw": [ - [ - 15, - 181000 - ], - [ - 40.5, - 181000 - ] - ], - "mtow": [ - [ - 15, - 127000 - ], - [ - 15, - 210000 - ], - [ - 20.61, - 251000 - ], - [ - 33.00, - 251000 - ], - [ - 38.60, - 233000 - ], - [ - 39.90, - 197200 - ], - [ - 32.45, - 127000 - ], - [ - 15, - 127000 - ] - ], - "flight": [ - [ - 14, - 127000 - ], - [ - 14, - 210000 - ], - [ - 19.61, - 251000 - ], - [ - 34.00, - 251000 - ], - [ - 39.60, - 233000 - ], - [ - 42, - 173000 - ], - [ - 42, - 127000 - ], - [ - 14, - 127000 - ] - ] - }, - "chartLimits": { - "weight": { - "min": 120000, - "max": 260000, - "lines": 8, - "scale": 20000, - "values": [ - 260, - 240, - 220, - 200, - 180, - 160, - 140, - 120 - ] - }, - "cg": { - "angleRad": 0.014025, - "min": 12, - "max": 47, - "overlap": 32, - "highlight": 5, - "lines": 35, - "scale": 1, - "values": [ - 15, - 20, - 25, - 30, - 35, - 40 - ] - }, - "labels": { - "mtow": { - "x1": 0.65, - "x2": 0.27, - "y": 0.05 - }, - "mlw": { - "x1": 0.65, - "x2": 0.27, - "y": 0.37 - }, - "mzfw": { - "x1": 0.65, - "x2": 0.27, - "y": 0.44 - } - } - } - } -} diff --git a/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/Payload.tsx b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/Payload.tsx index abcb960a6..90aa25163 100644 --- a/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/Payload.tsx +++ b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/Payload.tsx @@ -1,149 +1,19 @@ +// Copyright (c) 2021-2023 FlyByWire Simulations +// +// SPDX-License-Identifier: GPL-3.0 + /* eslint-disable max-len */ -import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { - Shuffle, - ArrowLeftRight, - BoxArrowRight, - BriefcaseFill, - CloudArrowDown, - PersonFill, - StopCircleFill, -} from 'react-bootstrap-icons'; -import { useSimVar, Units, SeatFlags, useSeatFlags, usePersistentNumberProperty, usePersistentProperty } from '@flybywiresim/fbw-sdk'; -import { round } from 'lodash'; -import { CargoWidget } from './Seating/CargoWidget'; -import { ChartWidget } from './Chart/ChartWidget'; -import { CargoStationInfo, PaxStationInfo } from './Seating/Constants'; -import { t } from '../../../translation'; -import { TooltipWrapper } from '../../../UtilComponents/TooltipWrapper'; -import { SimpleInput } from '../../../UtilComponents/Form/SimpleInput/SimpleInput'; -import Loadsheet from './Loadsheet/a339x.json'; -import Card from '../../../UtilComponents/Card/Card'; -import { SelectGroup, SelectItem } from '../../../UtilComponents/Form/Select'; -import { SeatMapWidget } from './Seating/SeatMapWidget'; -import { isSimbriefDataLoaded } from '../../../Store/features/simBrief'; -import { PromptModal, useModals } from '../../../UtilComponents/Modals/Modals'; +import React, { useState } from 'react'; +import { Units, usePersistentProperty, useSimVar } from '@flybywiresim/fbw-sdk'; +import { getAirframeType } from '../../../Efb'; +import { A320Payload } from './A320_251N/A320Payload'; +import { A330Payload } from './A330_941/A330Payload'; +import { ACJ330Payload } from './ACJ330_941/A330Payload'; +import { A380Payload } from './A380_842/A380Payload'; import { useAppSelector } from '../../../Store/store'; +import { isSimbriefDataLoaded } from '../../../Store/features/simBrief'; export const Payload = () => { - const { usingMetric } = Units; - const { showModal } = useModals(); - - const [aFlags] = useSeatFlags(`L:${Loadsheet.seatMap[0].simVar}`, Loadsheet.seatMap[0].capacity); - const [bFlags] = useSeatFlags(`L:${Loadsheet.seatMap[1].simVar}`, Loadsheet.seatMap[1].capacity); - const [cFlags] = useSeatFlags(`L:${Loadsheet.seatMap[2].simVar}`, Loadsheet.seatMap[2].capacity); - const [dFlags] = useSeatFlags(`L:${Loadsheet.seatMap[3].simVar}`, Loadsheet.seatMap[3].capacity); - const [eFlags] = useSeatFlags(`L:${Loadsheet.seatMap[4].simVar}`, Loadsheet.seatMap[4].capacity); - const [fFlags] = useSeatFlags(`L:${Loadsheet.seatMap[5].simVar}`, Loadsheet.seatMap[5].capacity); - const [gFlags] = useSeatFlags(`L:${Loadsheet.seatMap[6].simVar}`, Loadsheet.seatMap[6].capacity); - const [hFlags] = useSeatFlags(`L:${Loadsheet.seatMap[7].simVar}`, Loadsheet.seatMap[7].capacity); - const [iFlags] = useSeatFlags(`L:${Loadsheet.seatMap[8].simVar}`, Loadsheet.seatMap[8].capacity); - const [jFlags] = useSeatFlags(`L:${Loadsheet.seatMap[9].simVar}`, Loadsheet.seatMap[9].capacity); - - const [aFlagsDesired, setAFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[0].simVar}_DESIRED`, Loadsheet.seatMap[0].capacity); - const [bFlagsDesired, setBFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[1].simVar}_DESIRED`, Loadsheet.seatMap[1].capacity); - const [cFlagsDesired, setCFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[2].simVar}_DESIRED`, Loadsheet.seatMap[2].capacity); - const [dFlagsDesired, setDFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[3].simVar}_DESIRED`, Loadsheet.seatMap[3].capacity); - const [eFlagsDesired, setEFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[4].simVar}_DESIRED`, Loadsheet.seatMap[4].capacity); - const [fFlagsDesired, setFFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[5].simVar}_DESIRED`, Loadsheet.seatMap[5].capacity); - const [gFlagsDesired, setGFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[6].simVar}_DESIRED`, Loadsheet.seatMap[6].capacity); - const [hFlagsDesired, setHFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[7].simVar}_DESIRED`, Loadsheet.seatMap[7].capacity); - const [iFlagsDesired, setIFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[8].simVar}_DESIRED`, Loadsheet.seatMap[8].capacity); - const [jFlagsDesired, setJFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[9].simVar}_DESIRED`, Loadsheet.seatMap[9].capacity); - - const activeFlags = useMemo(() => [aFlags, bFlags, cFlags, dFlags, eFlags, fFlags, gFlags, hFlags, iFlags, jFlags], [aFlags, bFlags, cFlags, dFlags, eFlags, fFlags, gFlags, hFlags, iFlags, jFlags]); - const desiredFlags = useMemo(() => [aFlagsDesired, bFlagsDesired, cFlagsDesired, dFlagsDesired, eFlagsDesired, fFlagsDesired, gFlagsDesired, hFlagsDesired, iFlagsDesired, jFlagsDesired], [aFlagsDesired, bFlagsDesired, cFlagsDesired, dFlagsDesired, eFlagsDesired, fFlagsDesired, gFlagsDesired, hFlagsDesired, iFlagsDesired, jFlagsDesired]); - const setDesiredFlags = useMemo(() => [setAFlagsDesired, setBFlagsDesired, setCFlagsDesired, setDFlagsDesired, setEFlagsDesired, setFFlagsDesired, setGFlagsDesired, setHFlagsDesired, setIFlagsDesired, setJFlagsDesired], []); - - const [fwdBag] = useSimVar(`L:${Loadsheet.cargoMap[0].simVar}`, 'Number', 200); - const [aftCont] = useSimVar(`L:${Loadsheet.cargoMap[1].simVar}`, 'Number', 200); - const [aftBag] = useSimVar(`L:${Loadsheet.cargoMap[2].simVar}`, 'Number', 200); - const [aftBulk] = useSimVar(`L:${Loadsheet.cargoMap[3].simVar}`, 'Number', 200); - - const [fwdBagDesired, setFwdBagDesired] = useSimVar(`L:${Loadsheet.cargoMap[0].simVar}_DESIRED`, 'Number', 200); - const [aftContDesired, setAftContDesired] = useSimVar(`L:${Loadsheet.cargoMap[1].simVar}_DESIRED`, 'Number', 200); - const [aftBagDesired, setAftBagDesired] = useSimVar(`L:${Loadsheet.cargoMap[2].simVar}_DESIRED`, 'Number', 200); - const [aftBulkDesired, setAftBulkDesired] = useSimVar(`L:${Loadsheet.cargoMap[3].simVar}_DESIRED`, 'Number', 200); - - const cargo = useMemo(() => [fwdBag, aftCont, aftBag, aftBulk], [fwdBag, aftCont, aftBag, aftBulk]); - const cargoDesired = useMemo(() => [fwdBagDesired, aftContDesired, aftBagDesired, aftBulkDesired], [fwdBagDesired, aftContDesired, aftBagDesired, aftBulkDesired]); - const setCargoDesired = useMemo(() => [setFwdBagDesired, setAftContDesired, setAftBagDesired, setAftBulkDesired], []); - - const massUnitForDisplay = usingMetric ? 'KGS' : 'LBS'; - - const simbriefDataLoaded = isSimbriefDataLoaded(); - const [boardingStarted, setBoardingStarted] = useSimVar('L:A32NX_BOARDING_STARTED_BY_USR', 'Bool', 200); - const [boardingRate, setBoardingRate] = usePersistentProperty('CONFIG_BOARDING_RATE', 'REAL'); - const [paxWeight, setPaxWeight] = useSimVar('L:A32NX_WB_PER_PAX_WEIGHT', 'Kilograms', 200); - const [paxBagWeight, setPaxBagWeight] = useSimVar('L:A32NX_WB_PER_BAG_WEIGHT', 'Kilograms', 200); - const [galToKg] = useSimVar('FUEL WEIGHT PER GALLON', 'Kilograms', 2_000); - // const [destEfob] = useSimVar('L:A32NX_DESTINATION_FUEL_ON_BOARD', 'Kilograms', 5_000); - - const [emptyWeight] = useSimVar('A:EMPTY WEIGHT', 'Kilograms', 2_000); - - const [stationSize, setStationLen] = useState([]); - const maxPax = useMemo(() => ((stationSize && stationSize.length > 0) ? stationSize.reduce((a, b) => a + b) : -1), [stationSize]); - - // Calculate Total Pax from Pax Flags - const totalPax = useMemo(() => { - let p = 0; - activeFlags.forEach((flag) => { - p += flag.getTotalFilledSeats(); - }); - return p; - }, [...activeFlags]); - - const totalPaxDesired = useMemo(() => { - let p = 0; - desiredFlags.forEach((flag) => { - p += flag.getTotalFilledSeats(); - }); - return p; - }, [...desiredFlags]); - - const totalCargoDesired = useMemo(() => ((cargoDesired && cargoDesired.length > 0) ? cargoDesired.reduce((a, b) => a + b) : -1), [...cargoDesired]); - - const [cargoStationWeights, setCargoStationWeight] = useState([]); - - const totalCargo = useMemo(() => ((cargo && cargo.length > 0) ? cargo.reduce((a, b) => a + b) : -1), [...cargo]); - const maxCargo = useMemo(() => ((cargoStationWeights && cargoStationWeights.length > 0) ? cargoStationWeights.reduce((a, b) => a + b) : -1), [cargoStationWeights]); - - const [centerCurrent] = useSimVar('FUEL TANK CENTER QUANTITY', 'Gallons', 7_000); - const [LInnCurrent] = useSimVar('FUEL TANK LEFT MAIN QUANTITY', 'Gallons', 7_000); - const [LOutCurrent] = useSimVar('FUEL TANK LEFT AUX QUANTITY', 'Gallons', 7_000); - const [RInnCurrent] = useSimVar('FUEL TANK RIGHT MAIN QUANTITY', 'Gallons', 7_000); - const [ROutCurrent] = useSimVar('FUEL TANK RIGHT AUX QUANTITY', 'Gallons', 7_000); - - const [LInnCapacity] = useSimVar('FUEL TANK LEFT MAIN CAPACITY', 'Gallons', 7_000); - const [LOutCapacity] = useSimVar('FUEL TANK LEFT AUX CAPACITY', 'Gallons', 7_000); - - const fuel = [centerCurrent, LInnCurrent, LOutCurrent, RInnCurrent, ROutCurrent]; - - // Units - // Weight/CG - // TODO FIXME: Move all ZFW and GW calculations to rust - Will be refactored in phase 2 - const [zfw, setZfw] = useState(0); - const [zfwCg, setZfwCg] = useState(0); - // FIXME boarding refactor phase 2 - const [zfwDesired, setZfwDesired] = useSimVar('L:A32NX_AIRFRAME_ZFW_DESIRED', 'number', 200); - // FIXME boarding refactor phase 2 - const [zfwDesiredCg, setZfwDesiredCg] = useSimVar('L:A32NX_AIRFRAME_ZFW_CG_PERCENT_MAC_DESIRED', 'number', 200); - const [gw, setGw] = useState(emptyWeight); - const [gwDesired, setGwDesired] = useState(emptyWeight); - const [cg, setCg] = useState(25); - const [totalDesiredWeight, setTotalDesiredWeight] = useState(0); - const [desiredCg, setDesiredCg] = useState(0); - const [mlw, setMlw] = useState(0); - const [mlwCg, setMlwCg] = useState(0); - const [mlwDesired, setMlwDesired] = useState(0); - const [mlwDesiredCg, setMlwDesiredCg] = useState(0); - - const [seatMap] = useState(Loadsheet.seatMap); - const [cargoMap] = useState(Loadsheet.cargoMap); - - const totalCurrentGallon = useMemo(() => round(Math.max(LInnCurrent + LOutCurrent + RInnCurrent + ROutCurrent + centerCurrent, 0)), [fuel]); - - const [showSimbriefButton, setShowSimbriefButton] = useState(false); const simbriefUnits = useAppSelector((state) => state.simbrief.data.units); const simbriefBagWeight = parseInt(useAppSelector((state) => state.simbrief.data.weights.bagWeight)); const simbriefPaxWeight = parseInt(useAppSelector((state) => state.simbrief.data.weights.passengerWeight)); @@ -151,841 +21,104 @@ export const Payload = () => { const simbriefBag = parseInt(useAppSelector((state) => state.simbrief.data.weights.bagCount)); const simbriefFreight = parseInt(useAppSelector((state) => state.simbrief.data.weights.freight)); - const [displayZfw, setDisplayZfw] = useState(true); - - // GSX - const [gsxPayloadSyncEnabled] = usePersistentNumberProperty('GSX_PAYLOAD_SYNC', 0); - const [_, setGsxNumPassengers] = useSimVar('L:FSDT_GSX_NUMPASSENGERS', 'Number'); - const [gsxBoardingState] = useSimVar('L:FSDT_GSX_BOARDING_STATE', 'Number'); - const [gsxDeBoardingState] = useSimVar('L:FSDT_GSX_DEBOARDING_STATE', 'Number'); - const gsxStates = { - AVAILABLE: 1, - NOT_AVAILABLE: 2, - BYPASSED: 3, - REQUESTED: 4, - PERFORMING: 5, - COMPLETED: 6, - }; - - const setSimBriefValues = () => { - if (simbriefUnits === 'kgs') { - setPaxBagWeight(simbriefBagWeight); - setPaxWeight(simbriefPaxWeight); - setTargetPax(simbriefPax > maxPax ? maxPax : simbriefPax); - setTargetCargo(simbriefBag, simbriefFreight, simbriefBagWeight); - } else { - setPaxBagWeight(Units.poundToKilogram(simbriefBagWeight)); - setPaxWeight(Units.poundToKilogram(simbriefPaxWeight)); - setTargetPax(simbriefPax); - setTargetCargo(simbriefBag, Units.poundToKilogram(simbriefFreight), Units.poundToKilogram(simbriefBagWeight)); - } - }; - - const [busDC2] = useSimVar('L:A32NX_ELEC_DC_2_BUS_IS_POWERED', 'Bool', 2_000); - const [busDCHot1] = useSimVar('L:A32NX_ELEC_DC_HOT_1_BUS_IS_POWERED', 'Bool', 2_000); - const [simGroundSpeed] = useSimVar('GPS GROUND SPEED', 'knots', 2_000); - const [isOnGround] = useSimVar('SIM ON GROUND', 'Bool', 2_000); - const [eng1Running] = useSimVar('ENG COMBUSTION:1', 'Bool', 2_000); - const [eng2Running] = useSimVar('ENG COMBUSTION:2', 'Bool', 2_000); - const [coldAndDark, setColdAndDark] = useState(true); - - const chooseDesiredSeats = useCallback((stationIndex: number, fillSeats: boolean = true, numChoose: number) => { - const seatFlags: SeatFlags = desiredFlags[stationIndex]; - if (fillSeats) { - seatFlags.fillEmptySeats(numChoose); - } else { - seatFlags.emptyFilledSeats(numChoose); - } - - setDesiredFlags[stationIndex](seatFlags); - }, [...desiredFlags]); - - const setTargetPax = useCallback((numOfPax: number) => { - setGsxNumPassengers(numOfPax); - - if (!stationSize || numOfPax === totalPaxDesired || numOfPax > maxPax || numOfPax < 0) return; - - let paxRemaining = numOfPax; - - const fillStation = (stationIndex: number, percent: number, paxToFill: number) => { - const sFlags: SeatFlags = desiredFlags[stationIndex]; - const toBeFilled = Math.min(Math.trunc(percent * paxToFill), stationSize[stationIndex]); - - paxRemaining -= toBeFilled; - - const planSeatedPax = sFlags.getTotalFilledSeats(); - chooseDesiredSeats( - stationIndex, - (toBeFilled > planSeatedPax), - Math.abs(toBeFilled - planSeatedPax), - ); - }; - - for (let i = seatMap.length - 1; i > 0; i--) { - fillStation(i, seatMap[i].fill, numOfPax); - } - fillStation(0, 1, paxRemaining); - }, [maxPax, ...stationSize, ...seatMap, totalPaxDesired]); - - const setTargetCargo = useCallback((numberOfPax: number, freight: number, perBagWeight: number = paxBagWeight) => { - const bagWeight = numberOfPax * perBagWeight; - const loadableCargoWeight = Math.min(bagWeight + Math.round(freight), maxCargo); - - let remainingWeight = loadableCargoWeight; - - async function fillCargo(station: number, percent: number, loadableCargoWeight: number) { - const c = Math.round(percent * loadableCargoWeight); - remainingWeight -= c; - setCargoDesired[station](c); - } - - for (let i = cargoDesired.length - 1; i > 0; i--) { - fillCargo(i, cargoStationWeights[i] / maxCargo, loadableCargoWeight); - } - fillCargo(0, 1, remainingWeight); - }, [maxCargo, ...cargoStationWeights, ...cargoMap, ...cargoDesired, paxBagWeight]); - - const calculatePaxMoment = useCallback(() => { - let paxMoment = 0; - activeFlags.forEach((stationFlag, i) => { - paxMoment += stationFlag.getTotalFilledSeats() * paxWeight * seatMap[i].position; - }); - return paxMoment; - }, [paxWeight, seatMap, ...activeFlags]); - - const calculatePaxDesiredMoment = useCallback(() => { - let paxMoment = 0; - desiredFlags.forEach((stationFlag, i) => { - paxMoment += stationFlag.getTotalFilledSeats() * paxWeight * seatMap[i].position; - }); - - return paxMoment; - }, [paxWeight, seatMap, ...desiredFlags]); - - const calculateCargoMoment = useCallback(() => { - let cargoMoment = 0; - cargo.forEach((station, i) => { - cargoMoment += station * cargoMap[i].position; - }); - return cargoMoment; - }, [...cargo, cargoMap]); - - const calculateCargoDesiredMoment = useCallback(() => { - let cargoMoment = 0; - cargoDesired.forEach((station, i) => { - cargoMoment += station * cargoMap[i].position; - }); - return cargoMoment; - }, [...cargoDesired, cargoMap]); - - const calculateCg = useCallback((mass: number, moment: number) => -100 * ((moment / mass - Loadsheet.specs.leMacZ) / Loadsheet.specs.macSize), []); - - const processZfw = useCallback((newZfw) => { - let paxCargoWeight = newZfw - emptyWeight; - - // Load pax first - const pWeight = paxWeight + paxBagWeight; - const newPax = Math.min(Math.round(paxCargoWeight / pWeight), maxPax); - - paxCargoWeight -= newPax * pWeight; - const newCargo = Math.min(paxCargoWeight, maxCargo); - - setTargetPax(newPax); - setTargetCargo(newPax, newCargo); - }, [emptyWeight, paxWeight, paxBagWeight, maxPax, maxCargo]); - - const processGw = useCallback((newGw) => { - const totalFuel = round(totalCurrentGallon * galToKg); - let paxCargoWeight = newGw - emptyWeight - totalFuel; - - // Load pax first - const pWeight = paxWeight + paxBagWeight; - const newPax = Math.min(Math.round(paxCargoWeight / pWeight), maxPax); - - paxCargoWeight -= newPax * pWeight; - const newCargo = Math.min(paxCargoWeight, maxCargo); - - setTargetPax(newPax); - setTargetCargo(newPax, newCargo); - }, [emptyWeight, paxWeight, paxBagWeight, maxPax, maxCargo, totalCurrentGallon]); - - const onClickCargo = useCallback((cargoStation, e) => { - if (gsxPayloadSyncEnabled === 1 && boardingStarted) { - return; - } - const cargoPercent = Math.min(Math.max(0, e.nativeEvent.offsetX / cargoMap[cargoStation].progressBarWidth), 1); - setCargoDesired[cargoStation](Math.round(cargoMap[cargoStation].weight * cargoPercent)); - }, [cargoMap]); - - const onClickSeat = useCallback((stationIndex: number, seatId: number) => { - if (gsxPayloadSyncEnabled === 1 && boardingStarted) { - return; - } - - // TODO FIXME: This calculation does not work correctly if user clicks on many seats in rapid succession - const oldPaxBag = totalPaxDesired * paxBagWeight; - const freight = Math.max(totalCargoDesired - oldPaxBag, 0); - - const seatFlags: SeatFlags = desiredFlags[stationIndex]; - seatFlags.toggleSeatId(seatId); - setDesiredFlags[stationIndex](seatFlags); - - let newPaxDesired = 0; - desiredFlags.forEach((flag) => { - newPaxDesired += flag.getTotalFilledSeats(); - }); - - setTargetCargo(newPaxDesired, freight); - }, [ - paxBagWeight, - totalCargoDesired, - ...cargoDesired, - ...desiredFlags, ...stationSize, - totalPaxDesired, - ]); - - const handleDeboarding = useCallback(() => { - if (!boardingStarted) { - showModal( - { - setTargetPax(0); - setTargetCargo(0, 0); - setBoardingStarted(true); - }} - />, - ); - return; - } - setBoardingStarted(false); - }, [totalPaxDesired, totalPax, totalCargo, boardingStarted, totalCargoDesired]); - - const calculateBoardingTime = useMemo(() => { - // factors taken from payload.rs TODO: Simvar - let boardingRateMultiplier = 0; - if (boardingRate === 'REAL') { - boardingRateMultiplier = 5; - } else if (boardingRate === 'FAST') { - boardingRateMultiplier = 1; - } - - // factors taken from payload.rs TODO: Simvar - const cargoWeightPerWeightStep = 60; - - const differentialPax = Math.abs(totalPaxDesired - totalPax); - const differentialCargo = Math.abs(totalCargoDesired - totalCargo); - - const estimatedPaxBoardingSeconds = differentialPax * boardingRateMultiplier; - const estimatedCargoLoadingSeconds = (differentialCargo / cargoWeightPerWeightStep) * boardingRateMultiplier; - - return Math.max(estimatedPaxBoardingSeconds, estimatedCargoLoadingSeconds); - }, [totalPaxDesired, totalPax, totalCargoDesired, totalCargo, boardingRate]); - - const boardingStatusClass = useMemo(() => { - if (!boardingStarted) { - return 'text-theme-highlight'; - } - return (totalPaxDesired * paxWeight + totalCargoDesired) >= (totalPax * paxWeight + totalCargo) ? 'text-green-500' : 'text-yellow-500'; - }, [boardingStarted, paxWeight, totalCargoDesired, totalCargo, totalPaxDesired, totalPax]); - - // Init - useEffect(() => { - if (paxWeight === 0) { - setPaxWeight(Math.round(Loadsheet.specs.pax.defaultPaxWeight)); - } - if (paxBagWeight === 0) { - setPaxBagWeight(Math.round(Loadsheet.specs.pax.defaultBagWeight)); - } - }, []); - - // Set Cold and Dark State - useEffect(() => { - if (simGroundSpeed > 0.1 || eng1Running || eng2Running || !isOnGround || (!busDC2 && !busDCHot1)) { - setColdAndDark(false); - } else { - setColdAndDark(true); - } - }, [simGroundSpeed, eng1Running, eng2Running, isOnGround, busDC2, busDCHot1]); - - useEffect(() => { - if (boardingRate !== 'INSTANT') { - if (!coldAndDark) { - setBoardingRate('INSTANT'); - } - } - }, [coldAndDark, boardingRate]); - - // Init the seating map - useEffect(() => { - const stationSize: number[] = []; - for (let i = 0; i < seatMap.length; i++) { - stationSize.push(0); - } - seatMap.forEach((station, i) => { - stationSize[i] = station.capacity; - }); - setStationLen(stationSize); - }, [seatMap]); - - // Init the cargo map - useEffect(() => { - const cargoSize: number[] = []; - for (let i = 0; i < cargoMap.length; i++) { - cargoSize.push(0); - } - cargoMap.forEach((station, index) => { - cargoSize[index] = station.weight; - }); - setCargoStationWeight(cargoSize); - }, [cargoMap]); - - useEffect(() => { - if (gsxPayloadSyncEnabled === 1) { - switch (gsxBoardingState) { - // If boarding has been requested, performed or completed - case gsxStates.REQUESTED: - case gsxStates.PERFORMING: - case gsxStates.COMPLETED: - setBoardingStarted(true); - break; - default: - break; - } - } - }, [gsxBoardingState]); - - useEffect(() => { - if (gsxPayloadSyncEnabled === 1) { - switch (gsxDeBoardingState) { - case gsxStates.REQUESTED: - // If Deboarding has been requested, set target pax to 0 for boarding backend - setTargetPax(0); - setTargetCargo(0, 0); - setBoardingStarted(true); - break; - case gsxStates.PERFORMING: - // If deboarding is being performed - setBoardingStarted(true); - break; - case gsxStates.COMPLETED: - // If deboarding is completed - setBoardingStarted(false); - break; - default: - break; - } - } - }, [gsxDeBoardingState]); - - useEffect(() => { - let simbriefStatus = false; - if (simbriefUnits === 'kgs') { - simbriefStatus = (simbriefDataLoaded - && ( - simbriefPax !== totalPaxDesired - || Math.abs(simbriefFreight + simbriefBag * simbriefBagWeight - totalCargoDesired) > 3.0 - || Math.abs(simbriefPaxWeight - paxWeight) > 3.0 - || Math.abs(simbriefBagWeight - paxBagWeight) > 3.0 - ) - ); - } else { - simbriefStatus = (simbriefDataLoaded - && ( - simbriefPax !== totalPaxDesired - || Math.abs(Units.poundToKilogram(simbriefFreight + simbriefBag * simbriefBagWeight) - totalCargoDesired) > 3.0 - || Math.abs(Units.poundToKilogram(simbriefPaxWeight) - paxWeight) > 3.0 - || Math.abs(Units.poundToKilogram(simbriefBagWeight) - paxBagWeight) > 3.0 - ) - ); - } - - if (gsxPayloadSyncEnabled === 1) { - if (boardingStarted) { - setShowSimbriefButton(false); - return; - } - - setShowSimbriefButton(simbriefStatus); - return; - } - setShowSimbriefButton(simbriefStatus); - }, [ - simbriefUnits, - simbriefFreight, - simbriefBag, - simbriefBagWeight, - paxWeight, paxBagWeight, - totalPaxDesired, totalCargoDesired, - simbriefDataLoaded, - boardingStarted, - gsxPayloadSyncEnabled, - ]); - - useEffect(() => { - // TODO FIXME: Move all this logic into rust - // Note: Looks messy after phase 1 refactor, will be fixed by deprecating this and moving all calculations into rust - const centerTankMoment = -20.3; - const innerTankMoment = -25.5 - const outerTankMoment = -41.085 - // Adjust ZFW CG Values based on payload - const newZfw = emptyWeight + totalPax * paxWeight + totalCargo; - const newZfwDesired = emptyWeight + totalPaxDesired * paxWeight + totalCargoDesired; - const newZfwMoment = Loadsheet.specs.emptyPosition * emptyWeight + calculatePaxMoment() + calculateCargoMoment(); - const newZfwDesiredMoment = Loadsheet.specs.emptyPosition * emptyWeight + calculatePaxDesiredMoment() + calculateCargoDesiredMoment(); - const newZfwCg = calculateCg(newZfw, newZfwMoment); - const newZfwDesiredCg = calculateCg(newZfwDesired, newZfwDesiredMoment); - const totalFuel = round(totalCurrentGallon * galToKg); - - const totalFuelMoment = centerCurrent * galToKg * centerTankMoment + (LOutCurrent + ROutCurrent) * galToKg * outerTankMoment + (LInnCurrent + RInnCurrent) * galToKg * innerTankMoment; - const newGw = newZfw + totalFuel; - const newGwDesired = newZfwDesired + totalFuel; - const newTotalMoment = newZfwMoment + totalFuelMoment; - const newCg = calculateCg(newGw, newTotalMoment); - - const newTotalWeightDesired = newZfwDesired + totalFuel; - const newTotalDesiredMoment = newZfwDesiredMoment + totalFuelMoment; - const newDesiredCg = calculateCg(newTotalWeightDesired, newTotalDesiredMoment); - - setZfw(newZfw); - setZfwCg(newZfwCg); - setZfwDesired(newZfwDesired); - setZfwDesiredCg(newZfwDesiredCg); - setGw(newGw); - setGwDesired(newGwDesired); - setCg(newCg); - setTotalDesiredWeight(newTotalWeightDesired); - setDesiredCg(newDesiredCg); - // TODO: Predicted MLDW - setMlw(newGw); - setMlwCg(newCg); - setMlwDesired(newTotalWeightDesired); - setMlwDesiredCg(newDesiredCg); - }, [ - ...desiredFlags, ...activeFlags, - ...cargo, ...cargoDesired, - ...fuel, - paxWeight, paxBagWeight, - emptyWeight, - ]); - - const remainingTimeString = () => { - const minutes = Math.round(calculateBoardingTime / 60); - const seconds = calculateBoardingTime % 60; - const padding = seconds < 10 ? '0' : ''; - return `${minutes}:${padding}${seconds.toFixed(0)} ${t('Ground.Payload.EstimatedDurationUnit')}`; - }; - - return ( -
-
-
- -
- - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {' '} - - {t('Ground.Payload.Planned')} - - {t('Ground.Payload.Current')} -
- {t('Ground.Payload.Passengers')} - - -
- 0 ? maxPax : 999} - value={totalPaxDesired} - onBlur={(x) => { - if (!Number.isNaN(parseInt(x) || parseInt(x) === 0)) { - setTargetPax(parseInt(x)); - setTargetCargo(parseInt(x), 0); - } - }} - unit="PAX" - disabled={gsxPayloadSyncEnabled === 1 && boardingStarted} - /> -
-
-
- -
- {t('Ground.Payload.Cargo')} - - -
- 0 ? Math.round(Units.kilogramToUser(maxCargo)) : 99999} - value={Units.kilogramToUser(totalCargoDesired)} - onBlur={(x) => { - if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) { - setTargetCargo(0, Units.userToKilogram(parseInt(x))); - } - }} - unit={massUnitForDisplay} - disabled={gsxPayloadSyncEnabled === 1 && boardingStarted} - /> -
-
-
- -
- {displayZfw ? t('Ground.Payload.ZFW') : t('Ground.Payload.GW')} - - {(displayZfw - ? ( - -
- { - if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) processZfw(Units.userToKilogram(parseInt(x))); - }} - unit={massUnitForDisplay} - disabled={gsxPayloadSyncEnabled === 1 && boardingStarted} - /> -
-
- ) - : ( - -
- { - if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) processGw(Units.userToKilogram(parseInt(x))); - }} - unit={massUnitForDisplay} - disabled={gsxPayloadSyncEnabled === 1 && boardingStarted} - /> -
-
- ) - )} -
- -
-
-
- {t(displayZfw ? 'Ground.Payload.ZFWCG' : 'Ground.Payload.GWCG')} -
-
- -
-
-
- -
- {/* TODO FIXME: Setting pax/cargo given desired ZFWCG, ZFW, total pax, total cargo */} -
- -
- {/* - 0 ? maxPax : 999} - value={zfwCg.toFixed(2)} - onBlur={{(x) => processZfwCg(x)} - /> - */} -
-
-
- -
- -
- -
- -
- - { - if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) setPaxWeight(Units.userToKilogram(parseInt(x))); - }} - /> -
{massUnitForDisplay}
-
-
- - -
- - { - if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) setPaxBagWeight(Units.userToKilogram(parseInt(x))); - }} - /> -
{massUnitForDisplay}
-
-
- {gsxPayloadSyncEnabled !== 1 && ( - <> - - - - - - - - - )} -
-
- - {showSimbriefButton - && ( - -
- -
-
- )} -
- {(gsxPayloadSyncEnabled !== 1) && ( -
- -
-
- {t('Ground.Payload.BoardingTime')} - - ( - {remainingTimeString()} - ) - -
- - - setBoardingRate('INSTANT')} - > - {t('Settings.Instant')} - - - -
- setBoardingRate('FAST')} - > - {t('Settings.Fast')} - -
-
- -
- setBoardingRate('REAL')} - > - {t('Settings.Real')} - -
-
-
-
- - {/* */} - {/* */} - {/* */} -
- )} -
-
- -
-
-
-
- ); -}; - -interface PayloadValueInputProps { - min: number, - max: number, - value: number - onBlur: (v: string) => void, - unit: string, - disabled?: boolean -} - -const PayloadValueInput: FC = ({ min, max, value, onBlur, unit, disabled }) => ( -
- -
{unit}
-
-); - -interface NumberUnitDisplayProps { - /** - * The value to show - */ - value: number, - - /** - * The amount of leading zeroes to pad with - */ - padTo: number, - - /** - * The unit to show at the end - */ - unit: string, -} - -const PayloadValueUnitDisplay: FC = ({ value, padTo, unit }) => { - const fixedValue = value.toFixed(0); - const leadingZeroCount = Math.max(0, padTo - fixedValue.length); - - return ( - - - {'0'.repeat(leadingZeroCount)} - {fixedValue} - - {' '} - {unit} - - ); -}; + const [isOnGround] = useSimVar('SIM ON GROUND', 'Bool', 8_059); + const [boardingStarted, setBoardingStarted] = useSimVar('L:A32NX_BOARDING_STARTED_BY_USR', 'Bool', 509); + const [boardingRate, setBoardingRate] = usePersistentProperty('CONFIG_BOARDING_RATE', 'REAL'); -const PayloadPercentUnitDisplay: FC<{value: number}> = ({ value }) => { - const fixedValue = value.toFixed(2); + const simbriefDataLoaded = isSimbriefDataLoaded(); - return ( - - - {fixedValue} - - {' '} - % - - ); + const [massUnitForDisplay] = useState(Units.usingMetric ? 'KGS' : 'LBS'); + + switch (getAirframeType()) { + case 'A380_842': + return ( + + ); + case 'A320_251N': + return ( + + ); + case 'A330_941': + return ( + + ); + case 'ACJ330_941': + return ( + + ); + default: + return ( + + ); + } }; diff --git a/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/PayloadElements.tsx b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/PayloadElements.tsx new file mode 100644 index 000000000..89540b559 --- /dev/null +++ b/hdw-a339x/src/systems/instruments/src/EFB/Ground/Pages/Payload/PayloadElements.tsx @@ -0,0 +1,453 @@ +/* eslint-disable max-len */ +import React from 'react'; +import { ArrowLeftRight, BoxArrowRight, BriefcaseFill, CaretDownFill, PersonFill, Shuffle, StopCircleFill } from 'react-bootstrap-icons'; +import { ProgressBar } from '../../../UtilComponents/Progress/Progress'; +import { t } from '../../../translation'; +import { TooltipWrapper } from '../../../UtilComponents/TooltipWrapper'; +import { SimpleInput } from '../../../UtilComponents/Form/SimpleInput/SimpleInput'; +import { Units } from '../../../../../../../../../build-common/src/systems/shared/src'; +import { CargoStationInfo, PaxStationInfo } from './Seating/Constants'; + +export type Loadsheet = { + specs: AirframeSpec, + seatMap: PaxStationInfo[] +} + +export type AirframeSpec = { + prefix: string, + weights: AirframeWeights, + pax: PaxWeights, +} + +export type AirframeWeights = { + maxGw: number, + maxZfw: number, + minZfw: number, + maxGwCg: number, + maxZfwCg: number, +} + +export type PaxWeights = { + defaultPaxWeight: number, + defaultBagWeight: number, + minPaxWeight: number, + maxPaxWeight: number, + minBagWeight: number, + maxBagWeight: number, +} + +interface PayloadValueInputProps { + min: number, + max: number, + value: number + onBlur: (v: string) => void, + unit: string, + disabled?: boolean +} + +export const PayloadValueInput: React.FC = ({ min, max, value, onBlur, unit, disabled }) => ( +
+ +
{unit}
+
+); + +interface CargoBarProps { + cargoId: number, + cargo: number[], + cargoDesired: number[], + cargoMap: CargoStationInfo[], + onClickCargo: (cargoStation: number, event: any) => void, +} + +export const CargoBar: React.FC = ({ cargoId, cargo, cargoDesired, cargoMap, onClickCargo }) => ( + <> +
+
onClickCargo(cargoId, e)}> + + +
+ +); + +interface MiscParamsProps { + disable: boolean, + minPaxWeight: number, + maxPaxWeight: number, + defaultPaxWeight: number, + minBagWeight: number, + maxBagWeight: number, + defaultBagWeight: number, + paxWeight: number, + bagWeight: number, + massUnitForDisplay: string, + setPaxWeight: (w: number) => void, + setBagWeight: (w: number) => void, + +} + +export const MiscParamsInput: React.FC = ({ + disable, + minPaxWeight, maxPaxWeight, defaultPaxWeight, + minBagWeight, maxBagWeight, defaultBagWeight, + paxWeight, + bagWeight, + massUnitForDisplay, + setPaxWeight, setBagWeight, +}) => ( + <> + +
+ + { + if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) setPaxWeight(Units.userToKilogram(parseInt(x))); + }} + /> +
{massUnitForDisplay}
+
+
+ + +
+ + { + if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) setBagWeight(Units.userToKilogram(parseInt(x))); + }} + /> +
{massUnitForDisplay}
+
+
+ +); + +interface BoardingInputProps { + boardingStatusClass: string, + boardingStarted: boolean, + totalPax: number, + totalCargo: number, + setBoardingStarted: (boardingStarted: boolean) => void, + handleDeboarding: () => void, +} + +export const BoardingInput: React.FC = ({ boardingStatusClass, boardingStarted, totalPax, totalCargo, setBoardingStarted, handleDeboarding }) => ( + <> + + + + + + + + +); + +interface NumberUnitDisplayProps { + /** + * The value to show + */ + value: number, + + /** + * The amount of leading zeroes to pad with + */ + padTo: number, + + /** + * The unit to show at the end + */ + unit: string, +} + +export const PayloadValueUnitDisplay: React.FC = ({ value, padTo, unit }) => { + const fixedValue = value.toFixed(0); + const leadingZeroCount = Math.max(0, padTo - fixedValue.length); + + return ( + + + {'0'.repeat(leadingZeroCount)} + {fixedValue} + + {' '} + {unit} + + ); +}; + +export const PayloadPercentUnitDisplay: React.FC<{value: number}> = ({ value }) => { + const fixedValue = value.toFixed(2); + + return ( + + + {fixedValue} + + {' '} + % + + ); +}; + +interface PayloadInputTableProps { + loadsheet: Loadsheet, + emptyWeight: number, + massUnitForDisplay: string, + gsxPayloadSyncEnabled: boolean, + displayZfw: boolean, + boardingStarted: boolean, + totalPax: number, + totalPaxDesired: number, + maxPax: number, + totalCargo: number, + totalCargoDesired: number, + maxCargo: number, + zfw: number, + zfwDesired: number, + zfwCgMac: number, + desiredZfwCgMac: number, + gw: number, + gwDesired: number, + gwCgMac: number, + desiredGwCgMac: number, + setTargetPax: (targetPax: number) => void, + setTargetCargo: (targetCargo: number, cargoStation: number) => void, + processZfw: (zfw: number) => void, + processGw: (zfw: number) => void, + setDisplayZfw: (displayZfw: boolean) => void, + +} + +export const PayloadInputTable: React.FC = ( + { + loadsheet, emptyWeight, massUnitForDisplay, + gsxPayloadSyncEnabled, + displayZfw, boardingStarted, + totalPax, totalPaxDesired, maxPax, + totalCargo, totalCargoDesired, maxCargo, + zfw, zfwDesired, zfwCgMac, desiredZfwCgMac, + gw, gwDesired, gwCgMac, desiredGwCgMac, + setTargetPax, setTargetCargo, + processZfw, processGw, + setDisplayZfw, + }, +) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {' '} + + {t('Ground.Payload.Planned')} + + {t('Ground.Payload.Current')} +
+ {t('Ground.Payload.Passengers')} + + +
+ 0 ? maxPax : 999} + value={totalPaxDesired} + onBlur={(x) => { + if (!Number.isNaN(parseInt(x) || parseInt(x) === 0)) { + setTargetPax(parseInt(x)); + setTargetCargo(parseInt(x), 0); + } + }} + unit="PAX" + disabled={gsxPayloadSyncEnabled && boardingStarted} + /> +
+
+
+ +
+ {t('Ground.Payload.Cargo')} + + +
+ 0 ? Math.round(Units.kilogramToUser(maxCargo)) : 99999} + value={Units.kilogramToUser(totalCargoDesired)} + onBlur={(x) => { + if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) { + setTargetCargo(0, Units.userToKilogram(parseInt(x))); + } + }} + unit={massUnitForDisplay} + disabled={gsxPayloadSyncEnabled && boardingStarted} + /> +
+
+
+ +
+ {displayZfw ? t('Ground.Payload.ZFW') : t('Ground.Payload.GW')} + + {(displayZfw + ? ( + +
+ { + if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) processZfw(Units.userToKilogram(parseInt(x))); + }} + unit={massUnitForDisplay} + disabled={gsxPayloadSyncEnabled && boardingStarted} + /> +
+
+ ) + : ( + +
+ { + if (!Number.isNaN(parseInt(x)) || parseInt(x) === 0) processGw(Units.userToKilogram(parseInt(x))); + }} + unit={massUnitForDisplay} + disabled={gsxPayloadSyncEnabled && boardingStarted} + /> +
+
+ ) + )} +
+ +
+
+
+ {t(displayZfw ? 'Ground.Payload.ZFWCG' : 'Ground.Payload.GWCG')} +
+
+ +
+
+
+ +
+ {/* TODO FIXME: Setting pax/cargo given desired ZFWCG, ZFW, total pax, total cargo */} +
+ +
+ {/* + 0 ? maxPax : 999} + value={zfwCgMac.toFixed(2)} + onBlur={{(x) => processZfwCg(x)} + /> + */} +
+
+
+ +
+); diff --git a/hdw-a339x/src/systems/instruments/src/EWD/N3.tsx b/hdw-a339x/src/systems/instruments/src/EWD/N3.tsx index e4587afc7..035415b6c 100644 --- a/hdw-a339x/src/systems/instruments/src/EWD/N3.tsx +++ b/hdw-a339x/src/systems/instruments/src/EWD/N3.tsx @@ -35,7 +35,7 @@ export class N3 extends DisplayComponent { this.activeVisibility.set(f ? 'visible' : 'hidden'); }); - sub.on(`engine${this.props.engine}N2`).whenChanged().handle((n3) => { + sub.on(`engine${this.props.engine}N3`).whenChanged().handle((n3) => { const n3Parts = n3.toFixed(1).split('.', 2); this.n3 = n3; this.n3Int.set(n3Parts[0]); diff --git a/hdw-a339x/src/systems/instruments/src/EWD/shared/EwdSimvarPublisher.tsx b/hdw-a339x/src/systems/instruments/src/EWD/shared/EwdSimvarPublisher.tsx new file mode 100644 index 000000000..a1612c5ee --- /dev/null +++ b/hdw-a339x/src/systems/instruments/src/EWD/shared/EwdSimvarPublisher.tsx @@ -0,0 +1,202 @@ +import { EventBus, SimVarDefinition, SimVarValueType, SimVarPublisher } from '@microsoft/msfs-sdk'; + +export type EwdSimvars = { + acEssBus: boolean; + ewdPotentiometer: number; + autoThrustCommand1: number; + autoThrustCommand2: number; + autoThrustLimit: number; + autoThrustLimitToga: number; + thrustLimitType: number; + autoThrustMode: number; + autoThrustStatus: number; + autoThrustTLA1: number; + autoThrustTLA2: number; + autoThrustWarningToga: boolean; + packs1Supplying: boolean; + packs2Supplying: boolean; + engine1AntiIce: boolean; + engine1EGT: number; + engine1Fadec: boolean; + engine1FF: number; + engine1N1: number; + engine1N2: number; + engine1N3: number; + engine1ReverserTransit: boolean; + engine1ReverserDeployed: boolean; + engine1State: number; + engine2AntiIce: boolean; + engine2EGT: number; + engine2Fadec: boolean; + engine2FF: number; + engine2N1: number; + engine2N2: number; + engine2N3: number; + engine2ReverserTransit: boolean; + engine2ReverserDeployed: boolean; + engine2State: number; + wingAntiIce: boolean; + apuBleedPressure: number; + left1LandingGear: boolean; + right1LandingGear: boolean; + throttle1Position: number; + throttle2Position: number; + fwcFlightPhase: number; + idleN1: number; + flexTemp: number; + satRaw: number; + totalFuel: number; + slatsFlapsStatusRaw: number; + slatsPositionRaw: number; + flapsPositionRaw: number; + ewdLowerLeft1: number; + ewdLowerLeft2: number; + ewdLowerLeft3: number; + ewdLowerLeft4: number; + ewdLowerLeft5: number; + ewdLowerLeft6: number; + ewdLowerLeft7: number; + ewdLowerRight1: number; + ewdLowerRight2: number; + ewdLowerRight3: number; + ewdLowerRight4: number; + ewdLowerRight5: number; + ewdLowerRight6: number; + ewdLowerRight7: number; +} + +export enum EwdVars { + acEssBus = 'L:A32NX_ELEC_AC_ESS_BUS_IS_POWERED', + ewdPotentiometer = 'LIGHT POTENTIOMETER:92', + autoThrustCommand1 = 'L:A32NX_AUTOTHRUST_N1_COMMANDED:1', + autoThrustCommand2 = 'L:A32NX_AUTOTHRUST_N1_COMMANDED:2', + autoThrustLimit = 'L:A32NX_AUTOTHRUST_THRUST_LIMIT', + autoThrustLimitToga = 'L:A32NX_AUTOTHRUST_THRUST_LIMIT_TOGA', + thrustLimitType = 'L:A32NX_AUTOTHRUST_THRUST_LIMIT_TYPE', + autoThrustMode = 'L:A32NX_AUTOTHRUST_MODE', + autoThrustStatus = 'L:A32NX_AUTOTHRUST_STATUS', + autoThrustTLA1 = 'L:A32NX_AUTOTHRUST_TLA_N1:1', + autoThrustTLA2 = 'L:A32NX_AUTOTHRUST_TLA_N1:2', + autoThrustWarningToga = 'L:A32NX_AUTOTHRUST_THRUST_LEVER_WARNING_TOGA', + packs1Supplying = 'L:A32NX_COND_PACK_FLOW_VALVE_1_IS_OPEN', + packs2Supplying = 'L:A32NX_COND_PACK_FLOW_VALVE_2_IS_OPEN', + engine1AntiIce = 'L:XMLVAR_Momentary_PUSH_OVHD_ANTIICE_ENG1_Pressed', + engine1EGT = 'L:A32NX_ENGINE_EGT:1', + engine1Fadec = 'L:A32NX_FADEC_POWERED_ENG1', + engine1FF = 'L:A32NX_ENGINE_FF:1', + engine1N1 = 'L:A32NX_ENGINE_N1:1', + engine1N2 = 'L:A32NX_ENGINE_N2:1', + engine1N3 = 'L:A32NX_ENGINE_N3:1', + engine1ReverserTransit = 'L:A32NX_REVERSER_1_DEPLOYING', + engine1ReverserDeployed = 'L:A32NX_REVERSER_1_DEPLOYED', + engine1State = 'L:A32NX_ENGINE_STATE:1', + engine2AntiIce = 'L:XMLVAR_Momentary_PUSH_OVHD_ANTIICE_ENG2_Pressed', + engine2EGT = 'L:A32NX_ENGINE_EGT:2', + engine2Fadec = 'L:A32NX_FADEC_POWERED_ENG2', + engine2FF = 'L:A32NX_ENGINE_FF:2', + engine2N1 = 'L:A32NX_ENGINE_N1:2', + engine2N2 = 'L:A32NX_ENGINE_N2:2', + engine2N3 = 'L:A32NX_ENGINE_N3:2', + engine2ReverserTransit = 'L:A32NX_REVERSER_2_DEPLOYING', + engine2ReverserDeployed = 'L:A32NX_REVERSER_2_DEPLOYED', + engine2State = 'L:A32NX_ENGINE_STATE:2', + wingAntiIce = 'L:A32NX_PNEU_WING_ANTI_ICE_SYSTEM_SELECTED', + apuBleedPressure = 'L:APU_BLEED_PRESSURE', + left1LandingGear = 'L:A32NX_LGCIU_1_LEFT_GEAR_COMPRESSED', + right1LandingGear = 'L:A32NX_LGCIU_1_RIGHT_GEAR_COMPRESSED', + throttle1Position = 'L:XMLVAR_Throttle1Position', + throttle2Position = 'L:XMLVAR_Throttle2Position', + fwcFlightPhase = 'L:A32NX_FWC_FLIGHT_PHASE', + idleN1 = 'L:A32NX_ENGINE_IDLE_N1', + flexTemp = 'L:AIRLINER_TO_FLEX_TEMP', + satRaw = 'L:A32NX_ADIRS_ADR_1_STATIC_AIR_TEMPERATURE', + totalFuel = 'FUEL TOTAL QUANTITY WEIGHT', + slatsFlapsStatusRaw = 'L:A32NX_SFCC_SLAT_FLAP_SYSTEM_STATUS_WORD', + slatsPositionRaw = 'L:A32NX_SFCC_SLAT_ACTUAL_POSITION_WORD', + flapsPositionRaw = 'L:A32NX_SFCC_FLAP_ACTUAL_POSITION_WORD', + ewdLowerLeft1 = 'L:A32NX_Ewd_LOWER_LEFT_LINE_1', + ewdLowerLeft2 = 'L:A32NX_Ewd_LOWER_LEFT_LINE_2', + ewdLowerLeft3 = 'L:A32NX_Ewd_LOWER_LEFT_LINE_3', + ewdLowerLeft4 = 'L:A32NX_Ewd_LOWER_LEFT_LINE_4', + ewdLowerLeft5 = 'L:A32NX_Ewd_LOWER_LEFT_LINE_5', + ewdLowerLeft6 = 'L:A32NX_Ewd_LOWER_LEFT_LINE_6', + ewdLowerLeft7 = 'L:A32NX_Ewd_LOWER_LEFT_LINE_7', + ewdLowerRight1 = 'L:A32NX_Ewd_LOWER_RIGHT_LINE_1', + ewdLowerRight2 = 'L:A32NX_Ewd_LOWER_RIGHT_LINE_2', + ewdLowerRight3 = 'L:A32NX_Ewd_LOWER_RIGHT_LINE_3', + ewdLowerRight4 = 'L:A32NX_Ewd_LOWER_RIGHT_LINE_4', + ewdLowerRight5 = 'L:A32NX_Ewd_LOWER_RIGHT_LINE_5', + ewdLowerRight6 = 'L:A32NX_Ewd_LOWER_RIGHT_LINE_6', + ewdLowerRight7 = 'L:A32NX_Ewd_LOWER_RIGHT_LINE_7', +} + +export class EwdSimvarPublisher extends SimVarPublisher { + private static simvars = new Map([ + ['acEssBus', { name: EwdVars.acEssBus, type: SimVarValueType.Bool }], + ['ewdPotentiometer', { name: EwdVars.ewdPotentiometer, type: SimVarValueType.Number }], + ['autoThrustCommand1', { name: EwdVars.autoThrustCommand1, type: SimVarValueType.Number }], + ['autoThrustCommand2', { name: EwdVars.autoThrustCommand2, type: SimVarValueType.Number }], + ['autoThrustLimit', { name: EwdVars.autoThrustLimit, type: SimVarValueType.Number }], + ['autoThrustLimitToga', { name: EwdVars.autoThrustLimitToga, type: SimVarValueType.Number }], + ['thrustLimitType', { name: EwdVars.thrustLimitType, type: SimVarValueType.Enum }], + ['autoThrustMode', { name: EwdVars.autoThrustMode, type: SimVarValueType.Enum }], + ['autoThrustStatus', { name: EwdVars.autoThrustStatus, type: SimVarValueType.Enum }], + ['autoThrustTLA1', { name: EwdVars.autoThrustTLA1, type: SimVarValueType.Number }], + ['autoThrustTLA2', { name: EwdVars.autoThrustTLA2, type: SimVarValueType.Number }], + ['autoThrustWarningToga', { name: EwdVars.autoThrustWarningToga, type: SimVarValueType.Bool }], + ['packs1Supplying', { name: EwdVars.packs1Supplying, type: SimVarValueType.Bool }], + ['packs2Supplying', { name: EwdVars.packs2Supplying, type: SimVarValueType.Bool }], + ['engine1AntiIce', { name: EwdVars.engine1AntiIce, type: SimVarValueType.Bool }], + ['engine1EGT', { name: EwdVars.engine1EGT, type: SimVarValueType.Number }], + ['engine1Fadec', { name: EwdVars.engine1Fadec, type: SimVarValueType.Bool }], + ['engine1FF', { name: EwdVars.engine1FF, type: SimVarValueType.Number }], + ['engine1N1', { name: EwdVars.engine1N1, type: SimVarValueType.Number }], + ['engine1N2', { name: EwdVars.engine1N2, type: SimVarValueType.Number }], + ['engine1N3', { name: EwdVars.engine1N2, type: SimVarValueType.Number }], + ['engine1ReverserTransit', { name: EwdVars.engine1ReverserTransit, type: SimVarValueType.Bool }], + ['engine1ReverserDeployed', { name: EwdVars.engine1ReverserDeployed, type: SimVarValueType.Bool }], + ['engine1State', { name: EwdVars.engine1State, type: SimVarValueType.Enum }], + ['engine2AntiIce', { name: EwdVars.engine2AntiIce, type: SimVarValueType.Bool }], + ['engine2EGT', { name: EwdVars.engine2EGT, type: SimVarValueType.Number }], + ['engine2Fadec', { name: EwdVars.engine2Fadec, type: SimVarValueType.Bool }], + ['engine2FF', { name: EwdVars.engine2FF, type: SimVarValueType.Number }], + ['engine2N1', { name: EwdVars.engine2N1, type: SimVarValueType.Number }], + ['engine2N2', { name: EwdVars.engine2N2, type: SimVarValueType.Number }], + ['engine2N3', { name: EwdVars.engine2N2, type: SimVarValueType.Number }], + ['engine2ReverserTransit', { name: EwdVars.engine2ReverserTransit, type: SimVarValueType.Bool }], + ['engine2ReverserDeployed', { name: EwdVars.engine2ReverserDeployed, type: SimVarValueType.Bool }], + ['engine2State', { name: EwdVars.engine2State, type: SimVarValueType.Enum }], + ['wingAntiIce', { name: EwdVars.wingAntiIce, type: SimVarValueType.Bool }], + ['apuBleedPressure', { name: EwdVars.apuBleedPressure, type: SimVarValueType.PSI }], + ['left1LandingGear', { name: EwdVars.left1LandingGear, type: SimVarValueType.Bool }], + ['right1LandingGear', { name: EwdVars.right1LandingGear, type: SimVarValueType.Bool }], + ['throttle1Position', { name: EwdVars.throttle1Position, type: SimVarValueType.Number }], + ['throttle2Position', { name: EwdVars.throttle2Position, type: SimVarValueType.Number }], + ['fwcFlightPhase', { name: EwdVars.fwcFlightPhase, type: SimVarValueType.Enum }], + ['idleN1', { name: EwdVars.idleN1, type: SimVarValueType.Number }], + ['flexTemp', { name: EwdVars.flexTemp, type: SimVarValueType.Number }], + ['satRaw', { name: EwdVars.satRaw, type: SimVarValueType.Number }], + ['totalFuel', { name: EwdVars.totalFuel, type: SimVarValueType.Number }], + ['slatsFlapsStatusRaw', { name: EwdVars.slatsFlapsStatusRaw, type: SimVarValueType.Number }], + ['slatsPositionRaw', { name: EwdVars.slatsPositionRaw, type: SimVarValueType.Number }], + ['flapsPositionRaw', { name: EwdVars.flapsPositionRaw, type: SimVarValueType.Number }], + ['ewdLowerLeft1', { name: EwdVars.ewdLowerLeft1, type: SimVarValueType.Number }], + ['ewdLowerLeft2', { name: EwdVars.ewdLowerLeft2, type: SimVarValueType.Number }], + ['ewdLowerLeft3', { name: EwdVars.ewdLowerLeft3, type: SimVarValueType.Number }], + ['ewdLowerLeft4', { name: EwdVars.ewdLowerLeft4, type: SimVarValueType.Number }], + ['ewdLowerLeft5', { name: EwdVars.ewdLowerLeft5, type: SimVarValueType.Number }], + ['ewdLowerLeft6', { name: EwdVars.ewdLowerLeft6, type: SimVarValueType.Number }], + ['ewdLowerLeft7', { name: EwdVars.ewdLowerLeft7, type: SimVarValueType.Number }], + ['ewdLowerRight1', { name: EwdVars.ewdLowerRight1, type: SimVarValueType.Number }], + ['ewdLowerRight2', { name: EwdVars.ewdLowerRight2, type: SimVarValueType.Number }], + ['ewdLowerRight3', { name: EwdVars.ewdLowerRight3, type: SimVarValueType.Number }], + ['ewdLowerRight4', { name: EwdVars.ewdLowerRight4, type: SimVarValueType.Number }], + ['ewdLowerRight5', { name: EwdVars.ewdLowerRight5, type: SimVarValueType.Number }], + ['ewdLowerRight6', { name: EwdVars.ewdLowerRight6, type: SimVarValueType.Number }], + ['ewdLowerRight7', { name: EwdVars.ewdLowerRight7, type: SimVarValueType.Number }], + ]) + + public constructor(bus: EventBus) { + super(EwdSimvarPublisher.simvars, bus); + } +} diff --git a/hdw-a339x/src/wasm/fadec_a320/CMakeList.txt b/hdw-a339x/src/wasm/fadec_a330/CMakeList.txt similarity index 100% rename from hdw-a339x/src/wasm/fadec_a320/CMakeList.txt rename to hdw-a339x/src/wasm/fadec_a330/CMakeList.txt diff --git a/hdw-a339x/src/wasm/fadec_a320/build.sh b/hdw-a339x/src/wasm/fadec_a330/build.sh similarity index 100% rename from hdw-a339x/src/wasm/fadec_a320/build.sh rename to hdw-a339x/src/wasm/fadec_a330/build.sh diff --git a/hdw-a339x/src/wasm/fadec_a320/src/EngineControl.h b/hdw-a339x/src/wasm/fadec_a330/src/EngineControl.h similarity index 82% rename from hdw-a339x/src/wasm/fadec_a320/src/EngineControl.h rename to hdw-a339x/src/wasm/fadec_a330/src/EngineControl.h index add567ed1..b34bfb8bc 100644 --- a/hdw-a339x/src/wasm/fadec_a320/src/EngineControl.h +++ b/hdw-a339x/src/wasm/fadec_a330/src/EngineControl.h @@ -17,14 +17,16 @@ #define CONFIGURATION_SECTION_FUEL_RIGHT_QUANTITY "FUEL_RIGHT_QUANTITY" #define CONFIGURATION_SECTION_FUEL_LEFT_AUX_QUANTITY "FUEL_LEFT_AUX_QUANTITY" #define CONFIGURATION_SECTION_FUEL_RIGHT_AUX_QUANTITY "FUEL_RIGHT_AUX_QUANTITY" +// #define CONFIGURATION_SECTION_FUEL_TRIM_QTY "FUEL_TRIM_QTY" /* Values in gallons */ struct Configuration { double fuelCenter = 0; - double fuelLeft = 1645; + double fuelLeft = 1645.0; double fuelRight = fuelLeft; - double fuelLeftAux = 228; + double fuelLeftAux = 228.0; double fuelRightAux = fuelLeftAux; + // double fuelTrim = 1617.0; }; class EngineControl { @@ -32,8 +34,8 @@ class EngineControl { SimVars* simVars; EngineRatios* ratios; Polynomial* poly; - Timer timerLeft; - Timer timerRight; + Timer timerEngine1; + Timer timerEngine2; Timer timerFuel; std::string confFilename = FILENAME_FADEC_CONF_DIRECTORY; @@ -50,7 +52,7 @@ class EngineControl { int engine; int egtImbalance; int ffImbalance; - int n2Imbalance; + int n3Imbalance; double engineState; double engineStarter; double engineIgniter; @@ -61,19 +63,19 @@ class EngineControl { double simCN1; double simN1; - double simN2; + double simN3; double thrust; - double simN2LeftPre; - double simN2RightPre; - double deltaN2; + double simN3Engine1Pre; + double simN3Engine2Pre; + double deltaN3; double thermalEnergy1; double thermalEnergy2; double oilTemperature; - double oilTemperatureLeftPre; - double oilTemperatureRightPre; + double oilTemperatureEngine1Pre; + double oilTemperatureEngine2Pre; double oilTemperatureMax; double idleN1; - double idleN2; + double idleN3; double idleFF; double idleEGT; double idleOil; @@ -110,13 +112,13 @@ class EngineControl { idleCN1 = iCN1(pressAltitude, mach, ambientTemp); idleN1 = idleCN1 * sqrt(ratios->theta2(0, ambientTemp)); - idleN2 = iCN2(pressAltitude, mach) * sqrt(ratios->theta(ambientTemp)); + idleN3 = iCN3(pressAltitude, mach) * sqrt(ratios->theta(ambientTemp)); idleCFF = poly->correctedFuelFlow(idleCN1, 0, pressAltitude); // lbs/hr idleFF = idleCFF * LBS_TO_KGS * ratios->delta2(0, ambientPressure) * sqrt(ratios->theta2(0, ambientTemp)); // Kg/hr idleEGT = poly->correctedEGT(idleCN1, idleCFF, 0, pressAltitude) * ratios->theta2(0, ambientTemp); simVars->setEngineIdleN1(idleN1); - simVars->setEngineIdleN2(idleN2); + simVars->setEngineIdleN3(idleN3); simVars->setEngineIdleFF(idleFF); simVars->setEngineIdleEGT(idleEGT); } @@ -151,8 +153,8 @@ class EngineControl { // Obtain FF imbalance (Max 36 Kg/h) ffImbalance = (rand() % 36) + 1; - // Obtain N2 imbalance (Max 0.3%) - n2Imbalance = (rand() % 30) + 1; + // Obtain N3 imbalance (Max 0.3%) + n3Imbalance = (rand() % 30) + 1; // Obtain Oil Qty imbalance (Max 2.0 qt) oilQtyImbalance = (rand() % 20) + 1; @@ -168,7 +170,7 @@ class EngineControl { // Zero Padding and Merging imbalanceCode = to_string_with_zero_padding(engine, 2) + to_string_with_zero_padding(egtImbalance, 2) + - to_string_with_zero_padding(ffImbalance, 2) + to_string_with_zero_padding(n2Imbalance, 2) + + to_string_with_zero_padding(ffImbalance, 2) + to_string_with_zero_padding(n3Imbalance, 2) + to_string_with_zero_padding(oilQtyImbalance, 2) + to_string_with_zero_padding(oilPressureImbalance, 2) + to_string_with_zero_padding(oilPressureIdle, 2) + to_string_with_zero_padding(oilTemperatureMax, 2); @@ -183,8 +185,8 @@ class EngineControl { void engineStateMachine(int engine, double engineIgniter, double engineStarter, - double simN2, - double idleN2, + double simN3, + double idleN3, double pressAltitude, double ambientTemp, double deltaTimeDiff) { @@ -212,7 +214,7 @@ class EngineControl { // Present State OFF if (engineState == 0 || engineState == 10) { - if (engineIgniter == 1 && engineStarter == 1 && simN2 > 20) { + if (engineIgniter == 1 && engineStarter == 1 && simN3 > 20) { engineState = 1; } else if (engineIgniter == 2 && engineStarter == 1) { engineState = 2; @@ -232,7 +234,7 @@ class EngineControl { // Present State Starting. if (engineState == 2 || engineState == 12) { - if (engineStarter == 1 && simN2 >= (idleN2 - 0.1)) { + if (engineStarter == 1 && simN3 >= (idleN3 - 0.1)) { engineState = 1; resetTimer = 1; } else if (engineStarter == 0) { @@ -245,7 +247,7 @@ class EngineControl { // Present State Re-Starting. if (engineState == 3 || engineState == 13) { - if (engineStarter == 1 && simN2 >= (idleN2 - 0.1)) { + if (engineStarter == 1 && simN3 >= (idleN3 - 0.1)) { engineState = 1; resetTimer = 1; } else if (engineStarter == 0) { @@ -261,10 +263,10 @@ class EngineControl { if (engineIgniter == 2 && engineStarter == 1) { engineState = 3; resetTimer = 1; - } else if (engineStarter == 0 && simN2 < 0.05 && egtFbw <= ambientTemp) { + } else if (engineStarter == 0 && simN3 < 0.05 && egtFbw <= ambientTemp) { engineState = 0; resetTimer = 1; - } else if (engineStarter == 1 && simN2 > 50) { + } else if (engineStarter == 1 && simN3 > 50) { engineState = 3; resetTimer = 1; } else { @@ -297,21 +299,21 @@ class EngineControl { double imbalance, double deltaTime, double timer, - double simN2, + double simN3, double pressAltitude, double ambientTemp) { - double startCN2Left; - double startCN2Right; - double preN2Fbw; - double newN2Fbw; + double startCN3Engine1; + double startCN3Engine2; + double preN3Fbw; + double newN3Fbw; double preEgtFbw; double startEgtFbw; double shutdownEgtFbw; - n2Imbalance = 0; + n3Imbalance = 0; ffImbalance = 0; egtImbalance = 0; - idleN2 = simVars->getEngineIdleN2(); + idleN3 = simVars->getEngineIdleN3(); idleN1 = simVars->getEngineIdleN1(); idleFF = simVars->getEngineIdleFF(); idleEGT = simVars->getEngineIdleEGT(); @@ -323,36 +325,37 @@ class EngineControl { if (engineImbalanced == engine) { ffImbalance = imbalanceExtractor(imbalance, 3); egtImbalance = imbalanceExtractor(imbalance, 2); - n2Imbalance = imbalanceExtractor(imbalance, 4) / 100; + n3Imbalance = imbalanceExtractor(imbalance, 4) / 100; } if (engine == 1) { // Delay between Engine Master ON and Start Valve Open if (timer < 1.7) { if (simOnGround == 1) { - simVars->setFuelUsedLeft(0); + simVars->setFuelUsedEngine1(0); } simVars->setEngine1Timer(timer + deltaTime); - startCN2Left = 0; - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::StartCN2Left, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), - &startCN2Left); + startCN3Engine1 = 0; + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::StartCN3Engine1, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + &startCN3Engine1); } else { - preN2Fbw = simVars->getEngine1N2(); + preN3Fbw = simVars->getEngine1N3(); preEgtFbw = simVars->getEngine1EGT(); - newN2Fbw = poly->startN2(simN2, preN2Fbw, idleN2 - n2Imbalance); - startEgtFbw = poly->startEGT(newN2Fbw, idleN2 - n2Imbalance, ambientTemp, idleEGT - egtImbalance); + newN3Fbw = poly->startN3(simN3, preN3Fbw, idleN3); + startEgtFbw = poly->startEGT(newN3Fbw, idleN3, ambientTemp, idleEGT); shutdownEgtFbw = poly->shutdownEGT(preEgtFbw, ambientTemp, deltaTime); - simVars->setEngine1N2(newN2Fbw); - simVars->setEngine1N1(poly->startN1(newN2Fbw, idleN2 - n2Imbalance, idleN1)); - simVars->setEngine1FF(poly->startFF(newN2Fbw, idleN2 - n2Imbalance, idleFF - ffImbalance)); + simVars->setEngine1N3(newN3Fbw); + simVars->setEngine1N2(newN3Fbw + 0.7); + simVars->setEngine1N1(poly->startN1(newN3Fbw, idleN3, idleN1)); + simVars->setEngine1FF(poly->startFF(newN3Fbw, idleN3, idleFF)); if (engineState == 3) { if (abs(startEgtFbw - preEgtFbw) <= 1.5) { simVars->setEngine1EGT(startEgtFbw); simVars->setEngine1State(2); } else if (startEgtFbw > preEgtFbw) { - simVars->setEngine1EGT(preEgtFbw + (0.75 * deltaTime * (idleN2 - newN2Fbw))); + simVars->setEngine1EGT(preEgtFbw + (0.75 * deltaTime * (idleN3 - newN3Fbw))); } else { simVars->setEngine1EGT(shutdownEgtFbw); } @@ -360,37 +363,38 @@ class EngineControl { simVars->setEngine1EGT(startEgtFbw); } - oilTemperature = poly->startOilTemp(newN2Fbw, idleN2, ambientTemp); - oilTemperatureLeftPre = oilTemperature; - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempLeft, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + oilTemperature = poly->startOilTemp(newN3Fbw, idleN3, ambientTemp); + oilTemperatureEngine1Pre = oilTemperature; + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempEngine1, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilTemperature); } } else { if (timer < 1.7) { if (simOnGround == 1) { - simVars->setFuelUsedRight(0); + simVars->setFuelUsedEngine2(0); } simVars->setEngine2Timer(timer + deltaTime); - startCN2Right = 0; - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::StartCN2Right, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), - &startCN2Right); + startCN3Engine2 = 0; + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::StartCN3Engine2, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + &startCN3Engine2); } else { - preN2Fbw = simVars->getEngine2N2(); + preN3Fbw = simVars->getEngine2N3(); preEgtFbw = simVars->getEngine2EGT(); - newN2Fbw = poly->startN2(simN2, preN2Fbw, idleN2 - n2Imbalance); - startEgtFbw = poly->startEGT(newN2Fbw, idleN2 - n2Imbalance, ambientTemp, idleEGT - egtImbalance); + newN3Fbw = poly->startN3(simN3, preN3Fbw, idleN3); + startEgtFbw = poly->startEGT(newN3Fbw, idleN3, ambientTemp, idleEGT); shutdownEgtFbw = poly->shutdownEGT(preEgtFbw, ambientTemp, deltaTime); - simVars->setEngine2N2(newN2Fbw); - simVars->setEngine2N1(poly->startN1(newN2Fbw, idleN2 - n2Imbalance, idleN1)); - simVars->setEngine2FF(poly->startFF(newN2Fbw, idleN2 - n2Imbalance, idleFF - ffImbalance)); + simVars->setEngine2N3(newN3Fbw); + simVars->setEngine2N2(newN3Fbw + 0.7); + simVars->setEngine2N1(poly->startN1(newN3Fbw, idleN3, idleN1)); + simVars->setEngine2FF(poly->startFF(newN3Fbw, idleN3, idleFF)); if (engineState == 3) { if (abs(startEgtFbw - preEgtFbw) <= 1.5) { simVars->setEngine2EGT(startEgtFbw); simVars->setEngine2State(2); } else if (startEgtFbw > preEgtFbw) { - simVars->setEngine2EGT(preEgtFbw + (0.75 * deltaTime * (idleN2 - newN2Fbw))); + simVars->setEngine2EGT(preEgtFbw + (0.75 * deltaTime * (idleN3 - newN3Fbw))); } else { simVars->setEngine2EGT(shutdownEgtFbw); } @@ -398,9 +402,9 @@ class EngineControl { simVars->setEngine2EGT(startEgtFbw); } - oilTemperature = poly->startOilTemp(newN2Fbw, idleN2, ambientTemp); - oilTemperatureRightPre = oilTemperature; - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempRight, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + oilTemperature = poly->startOilTemp(newN3Fbw, idleN3, ambientTemp); + oilTemperatureEngine2Pre = oilTemperature; + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempEngine2, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilTemperature); } } @@ -411,10 +415,10 @@ class EngineControl { /// void engineShutdownProcedure(int engine, double ambientTemp, double simN1, double deltaTime, double timer) { double preN1Fbw; - double preN2Fbw; + double preN3Fbw; double preEgtFbw; double newN1Fbw; - double newN2Fbw; + double newN3Fbw; double newEgtFbw; if (engine == 1) { @@ -422,41 +426,42 @@ class EngineControl { simVars->setEngine1Timer(timer + deltaTime); } else { preN1Fbw = simVars->getEngine1N1(); - preN2Fbw = simVars->getEngine1N2(); + preN3Fbw = simVars->getEngine1N3(); preEgtFbw = simVars->getEngine1EGT(); newN1Fbw = poly->shutdownN1(preN1Fbw, deltaTime); if (simN1 < 5 && simN1 > newN1Fbw) { // Takes care of windmilling newN1Fbw = simN1; } - newN2Fbw = poly->shutdownN2(preN2Fbw, deltaTime); + newN3Fbw = poly->shutdownN3(preN3Fbw, deltaTime); newEgtFbw = poly->shutdownEGT(preEgtFbw, ambientTemp, deltaTime); simVars->setEngine1N1(newN1Fbw); - simVars->setEngine1N2(newN2Fbw); + simVars->setEngine1N2(newN3Fbw + 0.7); + simVars->setEngine1N3(newN3Fbw); simVars->setEngine1EGT(newEgtFbw); } - - } else { + } else if (engine == 2) { if (timer < 1.8) { simVars->setEngine2Timer(timer + deltaTime); } else { preN1Fbw = simVars->getEngine2N1(); - preN2Fbw = simVars->getEngine2N2(); + preN3Fbw = simVars->getEngine2N3(); preEgtFbw = simVars->getEngine2EGT(); newN1Fbw = poly->shutdownN1(preN1Fbw, deltaTime); if (simN1 < 5 && simN1 > newN1Fbw) { // Takes care of windmilling newN1Fbw = simN1; } - newN2Fbw = poly->shutdownN2(preN2Fbw, deltaTime); + newN3Fbw = poly->shutdownN3(preN3Fbw, deltaTime); newEgtFbw = poly->shutdownEGT(preEgtFbw, ambientTemp, deltaTime); simVars->setEngine2N1(newN1Fbw); - simVars->setEngine2N2(newN2Fbw); + simVars->setEngine2N2(newN3Fbw + 0.7); + simVars->setEngine2N3(newN3Fbw); simVars->setEngine2EGT(newEgtFbw); } } } /// - /// FBW Engine RPM (N1 and N2) - /// Updates Engine N1 and N2 with our own algorithm for start-up and shutdown + /// FBW Engine RPM (N1, N2 and N3) + /// Updates Engine N1, N2 and N3 with our own algorithm for start-up and shutdown /// void updatePrimaryParameters(int engine, double imbalance, double simN1, double simN2) { // Engine imbalance @@ -471,9 +476,11 @@ class EngineControl { if (engine == 1) { simVars->setEngine1N1(simN1); simVars->setEngine1N2(max(0, simN2 - paramImbalance)); + simVars->setEngine1N3(simN3); } else { simVars->setEngine2N1(simN1); simVars->setEngine2N2(max(0, simN2 - paramImbalance)); + simVars->setEngine2N3(simN3); } } @@ -568,7 +575,7 @@ class EngineControl { /// FBW Oil Qty, Pressure and Temperature (in Quarts, PSI and degree Celsius) /// Updates Oil with realistic values visualized in the SD /// - void updateOil(int engine, double imbalance, double thrust, double simN2, double deltaN2, double deltaTime, double ambientTemp) { + void updateOil(int engine, double thrust, double simN3, double deltaN3, double deltaTime, double ambientTemp) { double steadyTemperature; double thermalEnergy; double oilTemperaturePre; @@ -585,15 +592,15 @@ class EngineControl { if (engine == 1) { steadyTemperature = simVars->getEngine1EGT(); thermalEnergy = thermalEnergy1; - oilTemperaturePre = oilTemperatureLeftPre; + oilTemperaturePre = oilTemperatureEngine1Pre; oilQtyActual = simVars->getEngine1Oil(); - oilTotalActual = simVars->getEngine1OilTotal(); + oilTotalActual = simVars->getEngine1TotalOil(); } else { steadyTemperature = simVars->getEngine2EGT(); thermalEnergy = thermalEnergy2; - oilTemperaturePre = oilTemperatureRightPre; + oilTemperaturePre = oilTemperatureEngine2Pre; oilQtyActual = simVars->getEngine2Oil(); - oilTotalActual = simVars->getEngine2OilTotal(); + oilTotalActual = simVars->getEngine2TotalOil(); } //-------------------------------------------- @@ -605,7 +612,7 @@ class EngineControl { if (steadyTemperature > oilTemperatureMax) { steadyTemperature = oilTemperatureMax; } - thermalEnergy = (0.995 * thermalEnergy) + (deltaN2 / deltaTime); + thermalEnergy = (0.995 * thermalEnergy) + (deltaN3 / deltaTime); oilTemperature = poly->oilTemperature(thermalEnergy, oilTemperaturePre, steadyTemperature, deltaTime); } @@ -634,27 +641,27 @@ class EngineControl { paramImbalance = 0; } - oilPressure = poly->oilPressure(simN2) - paramImbalance + oilIdleRandom; + oilPressure = poly->oilPressure(simN3) - paramImbalance + oilIdleRandom; //-------------------------------------------- // Engine Writing //-------------------------------------------- if (engine == 1) { thermalEnergy1 = thermalEnergy; - oilTemperatureLeftPre = oilTemperature; + oilTemperatureEngine1Pre = oilTemperature; simVars->setEngine1Oil(oilQtyActual); - simVars->setEngine1OilTotal(oilTotalActual); - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempLeft, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + simVars->setEngine1TotalOil(oilTotalActual); + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempEngine1, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilTemperature); - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilPsiLeft, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilPressure); + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilPsiEngine1, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilPressure); } else { thermalEnergy2 = thermalEnergy; - oilTemperatureRightPre = oilTemperature; + oilTemperatureEngine2Pre = oilTemperature; simVars->setEngine2Oil(oilQtyActual); - simVars->setEngine2OilTotal(oilTotalActual); - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempRight, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + simVars->setEngine2TotalOil(oilTotalActual); + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempEngine2, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilTemperature); - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilPsiRight, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilPressure); + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilPsiEngine2, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &oilPressure); } } @@ -670,8 +677,9 @@ class EngineControl { double refuelRate = simVars->getRefuelRate(); double refuelStartedByUser = simVars->getRefuelStartedByUser(); bool uiFuelTamper = false; - double pumpStateLeft = simVars->getPumpStateLeft(); - double pumpStateRight = simVars->getPumpStateRight(); + + double pumpStateEngine1 = simVars->getPumpStateEngine1(); + double pumpStateEngine2 = simVars->getPumpStateEngine2(); bool xfrCenterLeftManual = simVars->getJunctionSetting(4) > 1.5; bool xfrCenterRightManual = simVars->getJunctionSetting(5) > 1.5; bool xfrCenterLeftAuto = simVars->getValve(11) > 0.0 && !xfrCenterLeftManual; @@ -693,34 +701,35 @@ class EngineControl { /// weight of one gallon of fuel in pounds double fuelWeightGallon = simVars->getFuelWeightGallon(); - double fuelUsedLeft = simVars->getFuelUsedLeft(); // Kg - double fuelUsedRight = simVars->getFuelUsedRight(); // Kg - - double fuelLeftPre = simVars->getFuelLeftPre(); // LBS - double fuelRightPre = simVars->getFuelRightPre(); // LBS - double fuelAuxLeftPre = simVars->getFuelAuxLeftPre(); // LBS - double fuelAuxRightPre = simVars->getFuelAuxRightPre(); // LBS - double fuelCenterPre = simVars->getFuelCenterPre(); // LBS + double fuelUsedEngine1 = simVars->getFuelUsedEngine1(); // Kg + double fuelUsedEngine2 = simVars->getFuelUsedEngine2(); // Kg + + double fuelLeftPre = simVars->getFuelLeftPre(); // LBS + double fuelRightPre = simVars->getFuelRightPre(); // LBS + double fuelAuxLeftPre = simVars->getFuelAuxLeftPre(); // LBS + double fuelAuxRightPre = simVars->getFuelAuxRightPre(); // LBS + double fuelCenterPre = simVars->getFuelCenterPre(); // LBS + // double fuelTrimPre = simVars->getFuelTrimPre(); // LBS double leftQuantity = simVars->getFuelTankQuantity(2) * fuelWeightGallon; // LBS double rightQuantity = simVars->getFuelTankQuantity(3) * fuelWeightGallon; // LBS double leftAuxQuantity = simVars->getFuelTankQuantity(4) * fuelWeightGallon; // LBS double rightAuxQuantity = simVars->getFuelTankQuantity(5) * fuelWeightGallon; // LBS double centerQuantity = simVars->getFuelTankQuantity(1) * fuelWeightGallon; // LBS - /// Left inner tank fuel quantity in pounds + // double trimQuantity = simVars->getTankFuelQuantity(6) * fuelWeightGallon; // LBS + double fuelLeft = 0; - /// Right inner tank fuel quantity in pounds double fuelRight = 0; double fuelLeftAux = 0; double fuelRightAux = 0; double fuelCenter = 0; + // double fuelTrim = 0; double xfrCenterToLeft = 0; double xfrCenterToRight = 0; double xfrAuxLeft = 0; double xfrAuxRight = 0; - double fuelTotalActual = leftQuantity + rightQuantity + leftAuxQuantity + rightAuxQuantity + centerQuantity; // LBS - double fuelTotalPre = fuelLeftPre + fuelRightPre + fuelAuxLeftPre + fuelAuxRightPre + fuelCenterPre; // LBS - double deltaFuelRate = abs(fuelTotalActual - fuelTotalPre) / (fuelWeightGallon * deltaTimeSeconds); // LBS/ sec - + double fuelTotalActual = leftQuantity + rightQuantity + leftAuxQuantity + rightAuxQuantity + centerQuantity; // + trimQuantity; // LBS + double fuelTotalPre = fuelLeftPre + fuelRightPre + fuelAuxLeftPre + fuelAuxRightPre + fuelCenterPre; // + fuelTrimPre; // LBS + double deltaFuelRate = abs(fuelTotalActual - fuelTotalPre) / (fuelWeightGallon * deltaTimeSeconds); // LBS/ sec double engine1State = simVars->getEngine1State(); double engine2State = simVars->getEngine2State(); @@ -740,41 +749,41 @@ class EngineControl { double deltaTime = deltaTimeSeconds / 3600; // Pump State Logic for Left Wing - if (pumpStateLeft == 0 && (timerLeft.elapsed() == 0 || timerLeft.elapsed() >= 1000)) { + if (pumpStateEngine1 == 0 && (timerEngine1.elapsed() == 0 || timerEngine1.elapsed() >= 1000)) { if (fuelLeftPre - leftQuantity > 0 && leftQuantity == 0) { - timerLeft.reset(); - simVars->setPumpStateLeft(1); + timerEngine1.reset(); + simVars->setPumpStateEngine1(1); } else if (fuelLeftPre == 0 && leftQuantity - fuelLeftPre > 0) { - timerLeft.reset(); - simVars->setPumpStateLeft(2); + timerEngine1.reset(); + simVars->setPumpStateEngine1(2); } else { - simVars->setPumpStateLeft(0); + simVars->setPumpStateEngine1(0); } - } else if (pumpStateLeft == 1 && timerLeft.elapsed() >= 2100) { - simVars->setPumpStateLeft(0); - timerLeft.reset(); - } else if (pumpStateLeft == 2 && timerLeft.elapsed() >= 2700) { - simVars->setPumpStateLeft(0); - timerLeft.reset(); + } else if (pumpStateEngine1 == 1 && timerEngine1.elapsed() >= 2100) { + simVars->setPumpStateEngine1(0); + timerEngine1.reset(); + } else if (pumpStateEngine1 == 2 && timerEngine1.elapsed() >= 2700) { + simVars->setPumpStateEngine1(0); + timerEngine1.reset(); } // Pump State Logic for Right Wing - if (pumpStateRight == 0 && (timerRight.elapsed() == 0 || timerRight.elapsed() >= 1000)) { + if (pumpStateEngine2 == 0 && (timerEngine2.elapsed() == 0 || timerEngine2.elapsed() >= 1000)) { if (fuelRightPre - rightQuantity > 0 && rightQuantity == 0) { - timerRight.reset(); - simVars->setPumpStateRight(1); + timerEngine2.reset(); + simVars->setPumpStateEngine2(1); } else if (fuelRightPre == 0 && rightQuantity - fuelRightPre > 0) { - timerRight.reset(); - simVars->setPumpStateRight(2); + timerEngine2.reset(); + simVars->setPumpStateEngine2(2); } else { - simVars->setPumpStateRight(0); + simVars->setPumpStateEngine2(0); } - } else if (pumpStateRight == 1 && timerRight.elapsed() >= 2100) { - simVars->setPumpStateRight(0); - timerRight.reset(); - } else if (pumpStateRight == 2 && timerRight.elapsed() >= 2700) { - simVars->setPumpStateRight(0); - timerRight.reset(); + } else if (pumpStateEngine2 == 1 && timerEngine2.elapsed() >= 2100) { + simVars->setPumpStateEngine2(0); + timerEngine2.reset(); + } else if (pumpStateEngine2 == 2 && timerEngine2.elapsed() >= 2700) { + simVars->setPumpStateEngine2(0); + timerEngine2.reset(); } // Checking for in-game UI Fuel tampering @@ -789,24 +798,29 @@ class EngineControl { simVars->setFuelAuxLeftPre(fuelAuxLeftPre); // in LBS simVars->setFuelAuxRightPre(fuelAuxRightPre); // in LBS simVars->setFuelCenterPre(fuelCenterPre); // in LBS + // simVars->setFuelTrimPre(fuelTrimPre); // in LBS fuelLeft = (fuelLeftPre / fuelWeightGallon); // USG fuelRight = (fuelRightPre / fuelWeightGallon); // USG fuelCenter = (fuelCenterPre / fuelWeightGallon); // USG fuelLeftAux = (fuelAuxLeftPre / fuelWeightGallon); // USG fuelRightAux = (fuelAuxRightPre / fuelWeightGallon); // USG + // fuelTrim = (fuelTrimPre / fuelWeightGallon); // USG SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::FuelCenterMain, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &fuelCenter); SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::FuelLeftMain, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &fuelLeft); SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::FuelRightMain, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &fuelRight); SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::FuelLeftAux, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &fuelLeftAux); SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::FuelRightAux, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), &fuelRightAux); + // SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::FuelSystemTrim, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + // &fuelTrim); } else if (!uiFuelTamper && refuelStartedByUser == 1) { // Detects refueling from the EFB simVars->setFuelLeftPre(leftQuantity); // in LBS simVars->setFuelRightPre(rightQuantity); // in LBS simVars->setFuelAuxLeftPre(leftAuxQuantity); // in LBS simVars->setFuelAuxRightPre(rightAuxQuantity); // in LBS simVars->setFuelCenterPre(centerQuantity); // in LBS + // simVars->setFuelTrimPre(trimQty); // in LBS } else { if (uiFuelTamper == 1) { fuelLeftPre = leftQuantity; // LBS @@ -814,6 +828,7 @@ class EngineControl { fuelAuxLeftPre = leftAuxQuantity; // LBS fuelAuxRightPre = rightAuxQuantity; // LBS fuelCenterPre = centerQuantity; // LBS + // fuelTrimPre = trimQuantity; // LBS } //----------------------------------------------------------- // Cross-feed Logic @@ -867,8 +882,8 @@ class EngineControl { //-------------------------------------------- // Fuel used accumulators - fuelUsedLeft += fuelBurn1; - fuelUsedRight += fuelBurn2; + fuelUsedEngine1 += fuelBurn1; + fuelUsedEngine2 += fuelBurn2; //-------------------------------------------- // Cross-feed fuel burn routine @@ -918,8 +933,8 @@ class EngineControl { // Setting new pre-cycle conditions simVars->setEngine1PreFF(engine1FF); simVars->setEngine2PreFF(engine2FF); - simVars->setFuelUsedLeft(fuelUsedLeft); // in KG - simVars->setFuelUsedRight(fuelUsedRight); // in KG + simVars->setFuelUsedEngine1(fuelUsedEngine1); // in KG + simVars->setFuelUsedEngine2(fuelUsedEngine2); // in KG simVars->setFuelAuxLeftPre(leftAuxQuantity); // in LBS simVars->setFuelAuxRightPre(rightAuxQuantity); // in LBS simVars->setFuelCenterPre(centerQuantity); // in LBS @@ -947,6 +962,7 @@ class EngineControl { configuration.fuelCenter = simVars->getFuelCenterPre() / simVars->getFuelWeightGallon(); configuration.fuelLeftAux = simVars->getFuelAuxLeftPre() / simVars->getFuelWeightGallon(); configuration.fuelRightAux = simVars->getFuelAuxRightPre() / simVars->getFuelWeightGallon(); + // configuration.fuelTrim = simVars->getFuelTrimPre() / simVars->getFuelWeightGallon(); saveFuelInConfiguration(configuration); timerFuel.reset(); @@ -1070,8 +1086,8 @@ class EngineControl { simVars = new SimVars(); double engTime = 0; ambientTemp = simVars->getAmbientTemperature(); - simN2LeftPre = simVars->getN2(1); - simN2RightPre = simVars->getN2(2); + simN3Engine1Pre = simVars->getN2(1); + simN3Engine2Pre = simVars->getN2(2); confFilename += acftRegistration; confFilename += FILENAME_FADEC_CONF_FILE_EXTENSION; @@ -1104,9 +1120,9 @@ class EngineControl { // Setting initial Oil if (engine == 1) { - simVars->setEngine1OilTotal(idleOil - paramImbalance); + simVars->setEngine1TotalOil(idleOil - paramImbalance); } else { - simVars->setEngine2OilTotal(idleOil - paramImbalance); + simVars->setEngine2TotalOil(idleOil - paramImbalance); } } @@ -1119,21 +1135,21 @@ class EngineControl { double engine2Combustion = simVars->getEngineCombustion(2); if (simOnGround == 1 && engine1Combustion == 1 && engine2Combustion == 1) { - oilTemperatureLeftPre = 75; - oilTemperatureRightPre = 75; + oilTemperatureEngine1Pre = 75; + oilTemperatureEngine2Pre = 75; } else if (simOnGround == 0 && engine1Combustion == 1 && engine2Combustion == 1) { - oilTemperatureLeftPre = 85; - oilTemperatureRightPre = 85; + oilTemperatureEngine1Pre = 85; + oilTemperatureEngine2Pre = 85; } else { - oilTemperatureLeftPre = ambientTemp; - oilTemperatureRightPre = ambientTemp; + oilTemperatureEngine1Pre = ambientTemp; + oilTemperatureEngine2Pre = ambientTemp; } - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempLeft, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), - &oilTemperatureLeftPre); - SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempRight, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), - &oilTemperatureRightPre); + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempEngine1, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + &oilTemperatureEngine1Pre); + SimConnect_SetDataOnSimObject(hSimConnect, DataTypesID::OilTempEngine2, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(double), + &oilTemperatureEngine2Pre); // Initialize Engine State simVars->setEngine1State(10); @@ -1149,10 +1165,11 @@ class EngineControl { simVars->setFuelAuxLeftPre(configuration.fuelLeftAux * simVars->getFuelWeightGallon()); // in LBS simVars->setFuelAuxRightPre(configuration.fuelRightAux * simVars->getFuelWeightGallon()); // in LBS simVars->setFuelCenterPre(configuration.fuelCenter * simVars->getFuelWeightGallon()); // in LBS + // simVars->setFuelTrimPre(configuration.fuelTrim * simVars->getFuelWeightGallon()); // Initialize Pump State - simVars->setPumpStateLeft(0); - simVars->setPumpStateRight(0); + simVars->setPumpStateEngine1(0); + simVars->setPumpStateEngine2(0); // Initialize Thrust Limits simVars->setThrustLimitIdle(0); @@ -1200,38 +1217,38 @@ class EngineControl { engineIgniter = simVars->getEngineIgniter(engine); simCN1 = simVars->getCN1(engine); simN1 = simVars->getN1(engine); - simN2 = simVars->getN2(engine); + simN3 = simVars->getN2(engine); thrust = simVars->getThrust(engine); // Set & Check Engine Status for this Cycle - engineStateMachine(engine, engineIgniter, engineStarter, simN2, idleN2, pressAltitude, ambientTemp, + engineStateMachine(engine, engineIgniter, engineStarter, simN3, idleN3, pressAltitude, ambientTemp, animationDeltaTime - prevAnimationDeltaTime); if (engine == 1) { engineState = simVars->getEngine1State(); - deltaN2 = simN2 - simN2LeftPre; - simN2LeftPre = simN2; + deltaN3 = simN3 - simN3Engine1Pre; + simN3Engine1Pre = simN3; timer = simVars->getEngine1Timer(); } else { engineState = simVars->getEngine2State(); - deltaN2 = simN2 - simN2RightPre; - simN2RightPre = simN2; + deltaN3 = simN3 - simN3Engine2Pre; + simN3Engine2Pre = simN3; timer = simVars->getEngine2Timer(); } switch (int(engineState)) { case 2: case 3: - engineStartProcedure(engine, engineState, imbalance, deltaTime, timer, simN2, pressAltitude, ambientTemp); + engineStartProcedure(engine, engineState, imbalance, deltaTime, timer, simN3, pressAltitude, ambientTemp); break; case 4: engineShutdownProcedure(engine, ambientTemp, simN1, deltaTime, timer); cFbwFF = updateFF(engine, imbalance, simCN1, mach, pressAltitude, ambientTemp, ambientPressure); break; default: - updatePrimaryParameters(engine, imbalance, simN1, simN2); + updatePrimaryParameters(engine, imbalance, simN1, simN3); cFbwFF = updateFF(engine, imbalance, simCN1, mach, pressAltitude, ambientTemp, ambientPressure); updateEGT(engine, imbalance, deltaTime, simOnGround, engineState, simCN1, cFbwFF, mach, pressAltitude, ambientTemp); - // updateOil(engine, imbalance, thrust, simN2, deltaN2, deltaTime, ambientTemp); + // updateOil(engine, imbalance, thrust, simN3, deltaN3, deltaTime, ambientTemp); } // set highest N1 from either engine @@ -1270,6 +1287,7 @@ class EngineControl { mINI::INITypeConversion::getDouble(structure, CONFIGURATION_SECTION_FUEL, CONFIGURATION_SECTION_FUEL_RIGHT_QUANTITY, 1645.0), mINI::INITypeConversion::getDouble(structure, CONFIGURATION_SECTION_FUEL, CONFIGURATION_SECTION_FUEL_LEFT_AUX_QUANTITY, 228.0), mINI::INITypeConversion::getDouble(structure, CONFIGURATION_SECTION_FUEL, CONFIGURATION_SECTION_FUEL_RIGHT_AUX_QUANTITY, 228.0), + // mINI::INITypeConversion::getDouble(structure, CONFIGURATION_SECTION_FUEL, CONFIGURATION_SECTION_FUEL_TRIM_QTY, 1617.0), }; } @@ -1285,6 +1303,7 @@ class EngineControl { stInitStructure[CONFIGURATION_SECTION_FUEL][CONFIGURATION_SECTION_FUEL_RIGHT_QUANTITY] = std::to_string(configuration.fuelRight); stInitStructure[CONFIGURATION_SECTION_FUEL][CONFIGURATION_SECTION_FUEL_LEFT_AUX_QUANTITY] = std::to_string(configuration.fuelLeftAux); stInitStructure[CONFIGURATION_SECTION_FUEL][CONFIGURATION_SECTION_FUEL_RIGHT_AUX_QUANTITY] = std::to_string(configuration.fuelRightAux); + // stInitStructure[CONFIGURATION_SECTION_FUEL][CONFIGURATION_SECTION_FUEL_TRIM_QTY] = std::to_string(configuration.fuelTrim); if (!iniFile.write(stInitStructure, true)) { std::cout << "EngineControl: failed to write engine conf " << confFilename << " due to error \"" << strerror(errno) << "\"" diff --git a/hdw-a339x/src/wasm/fadec_a330/src/FadecGauge.cpp b/hdw-a339x/src/wasm/fadec_a330/src/FadecGauge.cpp new file mode 100644 index 000000000..f502997ce --- /dev/null +++ b/hdw-a339x/src/wasm/fadec_a330/src/FadecGauge.cpp @@ -0,0 +1,32 @@ +#include "FadecGauge.h" + +FadecGauge FADEC_GAUGE; + +__attribute__((export_name("FadecGauge_gauge_callback"))) extern "C" bool FadecGauge_gauge_callback(FsContext ctx, + int service_id, + void* pData) { + switch (service_id) { + case PANEL_SERVICE_PRE_INSTALL: { + return true; + } break; + case PANEL_SERVICE_POST_INSTALL: { + return FADEC_GAUGE.initializeFADEC(); + } break; + case PANEL_SERVICE_PRE_DRAW: { + /* Start updating the engines only if all needed data was fetched */ + /* Due to the data fetching feature from the sim being available at this stage only (from what I have observed) */ + /* Event SIMCONNECT_RECV_ID_OPEN received after the first PANEL_SERVICE_POST_INSTALL */ + if (FADEC_GAUGE.isReady()) { + sGaugeDrawData* drawData = static_cast(pData); + return FADEC_GAUGE.onUpdate(drawData->dt); + } else { + return FADEC_GAUGE.fetchNeededData(); + } + } break; + case PANEL_SERVICE_PRE_KILL: { + FADEC_GAUGE.killFADEC(); + return true; + } break; + } + return false; +} diff --git a/hdw-a339x/src/wasm/fadec_a330/src/FadecGauge.h b/hdw-a339x/src/wasm/fadec_a330/src/FadecGauge.h new file mode 100644 index 000000000..55f7c1968 --- /dev/null +++ b/hdw-a339x/src/wasm/fadec_a330/src/FadecGauge.h @@ -0,0 +1,387 @@ +#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 "EngineControl.h" +// #include "ThrustLimits.h" +#include "RegPolynomials.h" +#include "SimVars.h" +#include "Tables.h" +#include "common.h" + +#define DEFAULT_AIRCRAFT_REGISTRATION "ASX339" + +class FadecGauge { + private: + bool isConnected = false; + bool _isReady = false; + double previousSimulationTime = 0; + SimulationData simulationData = {}; + SimulationDataLivery simulationDataLivery = {}; + + /// + /// Initializes the connection to SimConnect + /// + /// True if successful, false otherwise. + bool initializeSimConnect() { + std::cout << "FADEC: Connecting to SimConnect..." << std::endl; + if (SUCCEEDED(SimConnect_Open(&hSimConnect, "FadecGauge", nullptr, 0, 0, 0))) { + std::cout << "FADEC: SimConnect connected." << std::endl; + + // SimConnect Tanker Definitions + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::FuelLeftMain, "FUEL TANK LEFT MAIN QUANTITY", "Gallons"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::FuelRightMain, "FUEL TANK RIGHT MAIN QUANTITY", "Gallons"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::FuelCenterMain, "FUEL TANK CENTER QUANTITY", "Gallons"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::FuelLeftAux, "FUEL TANK LEFT AUX QUANTITY", "Gallons"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::FuelRightAux, "FUEL TANK RIGHT AUX QUANTITY", "Gallons"); + + // SimConnect Oil Temperature Definitions + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::OilTempEngine1, "GENERAL ENG OIL TEMPERATURE:1", "Celsius"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::OilTempEngine2, "GENERAL ENG OIL TEMPERATURE:2", "Celsius"); + + // SimConnect Oil Pressure Definitions + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::OilPsiEngine1, "GENERAL ENG OIL PRESSURE:1", "Psi"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::OilPsiEngine2, "GENERAL ENG OIL PRESSURE:2", "Psi"); + + // SimConnect Engine Start Definitions + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::StartCN3Engine1, "TURB ENG CORRECTED N2:1", "Percent"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::StartCN3Engine2, "TURB ENG CORRECTED N2:2", "Percent"); + // Simulation Data + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::SimulationDataTypeId, "SIMULATION TIME", "NUMBER"); + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::SimulationDataTypeId, "SIMULATION RATE", "NUMBER"); + + SimConnect_AddToDataDefinition(hSimConnect, DataTypesID::AcftInfo, "ATC ID", NULL, SIMCONNECT_DATATYPE_STRING32); + + std::cout << "FADEC: SimConnect registrations complete." << std::endl; + return true; + } + + std::cout << "FADEC: SimConnect failed." << std::endl; + + return false; + } + + bool isRegistrationFound() { return simulationDataLivery.atc_id[0] != 0; } + + public: + /// + /// Initializes the FADEC control + /// + /// True if successful, false otherwise. + bool initializeFADEC() { + if (!this->initializeSimConnect()) { + std::cout << "FADEC: Init SimConnect failed." << std::endl; + return false; + } + + isConnected = true; + simConnectRequestData(); + + return true; + } + + /// + /// Callback used to update the FADEC at each tick (dt) + /// + /// True if successful, false otherwise. + bool onUpdate(double deltaTime) { + if (isConnected == true) { + // read simulation data from simconnect + simConnectReadData(); + // detect pause + if ((simulationData.simulationTime == previousSimulationTime) || (simulationData.simulationTime < 0.2)) { + // pause detected -> return + return true; + } + // calculate delta time + double calculatedSampleTime = max(0.002, simulationData.simulationTime - previousSimulationTime); + // store previous simulation time + previousSimulationTime = simulationData.simulationTime; + // update engines + EngineControlInstance.update(calculatedSampleTime, simulationData.simulationTime); + } + + return true; + } + + bool simConnectRequestDataAcftInfo() { + // check if we are connected + if (!isConnected) { + return false; + } + + // request data + return S_OK == + SimConnect_RequestDataOnSimObject(hSimConnect, 8, DataTypesID::AcftInfo, SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_ONCE); + } + + bool simConnectRequestData() { + // check if we are connected + if (!isConnected) { + return false; + } + + // request data + HRESULT result = SimConnect_RequestDataOnSimObject(hSimConnect, 0, DataTypesID::SimulationDataTypeId, SIMCONNECT_OBJECT_ID_USER, + SIMCONNECT_PERIOD_VISUAL_FRAME); + + // check result of data request + if (result != S_OK) { + // request failed + return false; + } + + // success + return true; + } + + bool simConnectReadData() { + // check if we are connected + if (!isConnected) { + return false; + } + + // get next dispatch message(s) and process them + DWORD cbData; + SIMCONNECT_RECV* pData; + while (SUCCEEDED(SimConnect_GetNextDispatch(hSimConnect, &pData, &cbData))) { + simConnectProcessDispatchMessage(pData, &cbData); + } + + // success + return true; + } + + void simConnectProcessDispatchMessage(SIMCONNECT_RECV* pData, DWORD* cbData) { + switch (pData->dwID) { + case SIMCONNECT_RECV_ID_OPEN: + // connection established + std::cout << "FADEC: SimConnect connection established" << std::endl; + break; + + case SIMCONNECT_RECV_ID_QUIT: + // connection lost + std::cout << "FADEC: Received SimConnect connection quit message" << std::endl; + break; + + case SIMCONNECT_RECV_ID_SIMOBJECT_DATA: + // process data + simConnectProcessSimObjectData(static_cast(pData)); + break; + + case SIMCONNECT_RECV_ID_EXCEPTION: + // exception + std::cout << "FADEC: Exception in SimConnect connection: "; + std::cout << getSimConnectExceptionString( + static_cast(static_cast(pData)->dwException)); + std::cout << std::endl; + break; + + default: + break; + } + } + + void simConnectProcessSimObjectData(const SIMCONNECT_RECV_SIMOBJECT_DATA* data) { + // process depending on request id + switch (data->dwRequestID) { + case 0: + // store aircraft data + simulationData = *((SimulationData*)&data->dwData); + return; + case 8: + simulationDataLivery = *((SimulationDataLivery*)&data->dwData); + if (simulationDataLivery.atc_id[0] == '\0') { + std::cout << "FADEC: Use default aircraft registration " << DEFAULT_AIRCRAFT_REGISTRATION << std::endl; + strncpy(simulationDataLivery.atc_id, DEFAULT_AIRCRAFT_REGISTRATION, sizeof(simulationDataLivery.atc_id)); + } + return; + + default: + // print unknown request id + std::cout << "FADEC: Unknown request id in SimConnect connection: "; + std::cout << data->dwRequestID << std::endl; + return; + } + } + + /// + /// Kills the FADEC and unregisters all LVars + /// + /// True if successful, false otherwise. + bool killFADEC() { + std::cout << "FADEC: Disconnecting ..." << std::endl; + EngineControlInstance.terminate(); + + isConnected = false; + unregister_all_named_vars(); + + std::cout << "FADEC: Disconnected." << std::endl; + return SUCCEEDED(SimConnect_Close(hSimConnect)); + } + + 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"; + } + } + + /* + * This function is to call if some data are needed before initialization of the engine (such as aircraft registration). + * This has to be done here due to data fetch feature being available after the first PANEL_SERVICE_PRE_DRAW (from what I have observed) + * This problem was observed when engine configuration file was under development + * Reach Julian Sebline on Discord if information needed + * + * Modify this function as needed + * + * @return true if all the requirements to initialize the engine are fulfilled + */ + bool fetchNeededData() { + // This two lines request aircraft registration + simConnectRequestDataAcftInfo(); + simConnectReadData(); + + _isReady = isRegistrationFound(); + + if (_isReady) { + EngineControlInstance.initialize(simulationDataLivery.atc_id); + } + + return _isReady; + } + + bool isReady() { return _isReady; } +}; diff --git a/hdw-a339x-acj/src/wasm/fadec_a320/src/RegPolynomials.h b/hdw-a339x/src/wasm/fadec_a330/src/RegPolynomials.h similarity index 56% rename from hdw-a339x-acj/src/wasm/fadec_a320/src/RegPolynomials.h rename to hdw-a339x/src/wasm/fadec_a330/src/RegPolynomials.h index 530dd1ea4..64fc4cff2 100644 --- a/hdw-a339x-acj/src/wasm/fadec_a320/src/RegPolynomials.h +++ b/hdw-a339x/src/wasm/fadec_a330/src/RegPolynomials.h @@ -9,18 +9,18 @@ class Polynomial { public: /// - /// Shutdown polynomials - N2 (%) + /// Shutdown polynomials - N3 (%) /// - double shutdownN2(double preN2, double deltaTime) { - double outN2 = 0; + double shutdownN3(double preN3, double deltaTime) { + double outN3 = 0; double k = -0.08183; - if (preN2 < 30) + if (preN3 < 30) k = -0.0515; - outN2 = preN2 * expFBW(k * deltaTime); + outN3 = preN3 * expFBW(k * deltaTime); - return outN2; + return outN3; } /// @@ -61,51 +61,51 @@ class Polynomial { } /// - /// Start-up polynomials - N2 (%) + /// Start-up polynomials - N3 (%) /// - double startN2(double n2, double preN2, double idleN2) { - double outN2 = 0; - double normalN2 = 0; + double startN3(double n3, double preN3, double idleN3) { + double outN3 = 0; + double normalN3 = 0; - normalN2 = n2 * 68.2 / idleN2; + normalN3 = n3 * 60.0 / idleN3; - double c_N2[16] = {4.03649879e+00, -9.41981960e-01, 1.98426614e-01, -2.11907840e-02, 1.00777507e-03, -1.57319166e-06, + double c_N3[16] = {4.03649879e+00, -9.41981960e-01, 1.98426614e-01, -2.11907840e-02, 1.00777507e-03, -1.57319166e-06, -2.15034888e-06, 1.08288379e-07, -2.48504632e-09, 2.52307089e-11, -2.06869243e-14, 8.99045761e-16, -9.94853959e-17, 1.85366499e-18, -1.44869928e-20, 4.31033031e-23}; - outN2 = c_N2[0] + (c_N2[1] * normalN2) + (c_N2[2] * powFBW(normalN2, 2)) + (c_N2[3] * powFBW(normalN2, 3)) + - (c_N2[4] * powFBW(normalN2, 4)) + (c_N2[5] * powFBW(normalN2, 5)) + (c_N2[6] * powFBW(normalN2, 6)) + - (c_N2[7] * powFBW(normalN2, 7)) + (c_N2[8] * powFBW(normalN2, 8)) + (c_N2[9] * powFBW(normalN2, 9)) + - (c_N2[10] * powFBW(normalN2, 10)) + (c_N2[11] * powFBW(normalN2, 11)) + (c_N2[12] * powFBW(normalN2, 12)) + - (c_N2[13] * powFBW(normalN2, 13)) + (c_N2[14] * powFBW(normalN2, 14)) + (c_N2[15] * powFBW(normalN2, 15)); + outN3 = c_N3[0] + (c_N3[1] * normalN3) + (c_N3[2] * powFBW(normalN3, 2)) + (c_N3[3] * powFBW(normalN3, 3)) + + (c_N3[4] * powFBW(normalN3, 4)) + (c_N3[5] * powFBW(normalN3, 5)) + (c_N3[6] * powFBW(normalN3, 6)) + + (c_N3[7] * powFBW(normalN3, 7)) + (c_N3[8] * powFBW(normalN3, 8)) + (c_N3[9] * powFBW(normalN3, 9)) + + (c_N3[10] * powFBW(normalN3, 10)) + (c_N3[11] * powFBW(normalN3, 11)) + (c_N3[12] * powFBW(normalN3, 12)) + + (c_N3[13] * powFBW(normalN3, 13)) + (c_N3[14] * powFBW(normalN3, 14)) + (c_N3[15] * powFBW(normalN3, 15)); - outN2 = outN2 * n2; + outN3 = outN3 * n3; - if (outN2 < preN2) { - outN2 = preN2 + 0.002; + if (outN3 < preN3) { + outN3 = preN3 + 0.002; } - if (outN2 >= idleN2 + 0.1) { - outN2 = idleN2 + 0.05; + if (outN3 >= idleN3 + 0.1) { + outN3 = idleN3 + 0.05; } - return outN2; + return outN3; } /// /// Start-up polynomials - N1 (%) /// - double startN1(double fbwN2, double idleN2, double idleN1) { + double startN1(double fbwN3, double idleN3, double idleN1) { double normalN1pre = 0; double normalN1post = 0; - double normalN2 = fbwN2 / idleN2; + double normalN3 = fbwN3 / idleN3; double c_N1[9] = {-2.2812156e-12, -5.9830374e+01, 7.0629094e+02, -3.4580361e+03, 9.1428923e+03, -1.4097740e+04, 1.2704110e+04, -6.2099935e+03, 1.2733071e+03}; - normalN1pre = (-2.4698087 * powFBW(normalN2, 3)) + (0.9662026 * powFBW(normalN2, 2)) + (0.0701367 * normalN2); + normalN1pre = (-2.4698087 * powFBW(normalN3, 3)) + (0.9662026 * powFBW(normalN3, 2)) + (0.0701367 * normalN3); - normalN1post = c_N1[0] + (c_N1[1] * normalN2) + (c_N1[2] * powFBW(normalN2, 2)) + (c_N1[3] * powFBW(normalN2, 3)) + - (c_N1[4] * powFBW(normalN2, 4)) + (c_N1[5] * powFBW(normalN2, 5)) + (c_N1[6] * powFBW(normalN2, 6)) + - (c_N1[7] * powFBW(normalN2, 7)) + (c_N1[8] * powFBW(normalN2, 8)); + normalN1post = c_N1[0] + (c_N1[1] * normalN3) + (c_N1[2] * powFBW(normalN3, 2)) + (c_N1[3] * powFBW(normalN3, 3)) + + (c_N1[4] * powFBW(normalN3, 4)) + (c_N1[5] * powFBW(normalN3, 5)) + (c_N1[6] * powFBW(normalN3, 6)) + + (c_N1[7] * powFBW(normalN3, 7)) + (c_N1[8] * powFBW(normalN3, 8)); if (normalN1post >= normalN1pre) return normalN1post * idleN1; @@ -116,20 +116,20 @@ class Polynomial { /// /// Start-up polynomials - Fuel Flow (Kg/hr) /// - double startFF(double fbwN2, double idleN2, double idleFF) { + double startFF(double fbwN3, double idleN3, double idleFF) { double normalFF = 0; double outFF = 0; - double normalN2 = fbwN2 / idleN2; + double normalN3 = fbwN3 / idleN3; - if (normalN2 <= 0.37) { + if (normalN3 <= 0.37) { normalFF = 0; } else { double c_FF[9] = {3.1110282e-12, 1.0804331e+02, -1.3972629e+03, 7.4874131e+03, -2.1511983e+04, 3.5957757e+04, -3.5093994e+04, 1.8573033e+04, -4.1220062e+03}; - normalFF = c_FF[0] + (c_FF[1] * normalN2) + (c_FF[2] * powFBW(normalN2, 2)) + (c_FF[3] * powFBW(normalN2, 3)) + - (c_FF[4] * powFBW(normalN2, 4)) + (c_FF[5] * powFBW(normalN2, 5)) + (c_FF[6] * powFBW(normalN2, 6)) + - (c_FF[7] * powFBW(normalN2, 7)) + (c_FF[8] * powFBW(normalN2, 8)); + normalFF = c_FF[0] + (c_FF[1] * normalN3) + (c_FF[2] * powFBW(normalN3, 2)) + (c_FF[3] * powFBW(normalN3, 3)) + + (c_FF[4] * powFBW(normalN3, 4)) + (c_FF[5] * powFBW(normalN3, 5)) + (c_FF[6] * powFBW(normalN3, 6)) + + (c_FF[7] * powFBW(normalN3, 7)) + (c_FF[8] * powFBW(normalN3, 8)); } if (normalFF < 0) { @@ -142,22 +142,22 @@ class Polynomial { /// /// Start-up polynomials - EGT (Celsius) /// - double startEGT(double fbwN2, double idleN2, double ambientTemp, double idleEGT) { + double startEGT(double fbwN3, double idleN3, double ambientTemp, double idleEGT) { double normalEGT = 0; double outEGT = 0; - double normalN2 = fbwN2 / idleN2; + double normalN3 = fbwN3 / idleN3; - if (normalN2 < 0.17) { + if (normalN3 < 0.17) { normalEGT = 0; - } else if (normalN2 <= 0.4) { - normalEGT = (0.04783 * normalN2) - 0.00813; + } else if (normalN3 <= 0.4) { + normalEGT = (0.04783 * normalN3) - 0.00813; } else { double c_EGT[9] = {-6.8725167e+02, 7.7548864e+03, -3.7507098e+04, 1.0147016e+05, -1.6779273e+05, 1.7357157e+05, -1.0960924e+05, 3.8591956e+04, -5.7912600e+03}; - normalEGT = c_EGT[0] + (c_EGT[1] * normalN2) + (c_EGT[2] * powFBW(normalN2, 2)) + (c_EGT[3] * powFBW(normalN2, 3)) + - (c_EGT[4] * powFBW(normalN2, 4)) + (c_EGT[5] * powFBW(normalN2, 5)) + (c_EGT[6] * powFBW(normalN2, 6)) + - (c_EGT[7] * powFBW(normalN2, 7)) + (c_EGT[8] * powFBW(normalN2, 8)); + normalEGT = c_EGT[0] + (c_EGT[1] * normalN3) + (c_EGT[2] * powFBW(normalN3, 2)) + (c_EGT[3] * powFBW(normalN3, 3)) + + (c_EGT[4] * powFBW(normalN3, 4)) + (c_EGT[5] * powFBW(normalN3, 5)) + (c_EGT[6] * powFBW(normalN3, 6)) + + (c_EGT[7] * powFBW(normalN3, 7)) + (c_EGT[8] * powFBW(normalN3, 8)); } outEGT = (normalEGT * (idleEGT - (ambientTemp))) + (ambientTemp); @@ -168,12 +168,12 @@ class Polynomial { /// /// Start-up polynomials - Oil Temperature (Celsius) /// - double startOilTemp(double fbwN2, double idleN2, double ambientTemp) { + double startOilTemp(double fbwN3, double idleN3, double ambientTemp) { double outOilTemp = 0; - if (fbwN2 < 0.79 * idleN2) { + if (fbwN3 < 0.79 * idleN3) { outOilTemp = ambientTemp; - } else if (fbwN2 < 0.98 * idleN2) { + } else if (fbwN3 < 0.98 * idleN3) { outOilTemp = ambientTemp + 5; } else { outOilTemp = ambientTemp + 10; @@ -187,15 +187,14 @@ class Polynomial { /// double correctedEGT(double cn1, double cff, double mach, double alt) { double outCEGT = 0; - double cff_a330 = 2.7; + cff = cff / 2; // to account for the A380 double fuel flow. Will have to be taken care of - double c_EGT[16] = {443.3145034, 0.0000000e+00, 3.0141710e+00, 3.9132758e-02, -4.8488279e+02, -1.2890964e-03, - -2.2332050e-02, 8.3849683e-05, 6.0478647e+00, 6.9171710e-05, -6.5369271e-07, -8.1438322e-03, - -5.1229403e-07, 7.4657497e+01, -4.6016728e-03, 2.8637860e-08}; + double c_EGT[16] = {3.2636e+02, 0.0000e+00, 9.2893e-01, 3.9505e-02, 3.9070e+02, -4.7911e-04, 7.7679e-03, 5.8361e-05, + -2.5566e+00, 5.1227e-06, 1.0178e-07, -7.4602e-03, 1.2106e-07, -5.1639e+01, -2.7356e-03, 1.9312e-08}; - outCEGT = c_EGT[0] + c_EGT[1] + (c_EGT[2] * cn1) + (c_EGT[3] * cff_a330) + (c_EGT[4] * mach) + (c_EGT[5] * alt) + - (c_EGT[6] * powFBW(cn1, 2)) + (c_EGT[7] * cn1 * cff_a330) + (c_EGT[8] * cn1 * mach) + (c_EGT[9] * cn1 * alt) + - (c_EGT[10] * powFBW(cff_a330, 2)) + (c_EGT[11] * mach * cff_a330) + (c_EGT[12] * cff_a330 * alt) + (c_EGT[13] * powFBW(mach, 2)) + + outCEGT = c_EGT[0] + c_EGT[1] + (c_EGT[2] * cn1) + (c_EGT[3] * cff) + (c_EGT[4] * mach) + (c_EGT[5] * alt) + + (c_EGT[6] * powFBW(cn1, 2)) + (c_EGT[7] * cn1 * cff) + (c_EGT[8] * cn1 * mach) + (c_EGT[9] * cn1 * alt) + + (c_EGT[10] * powFBW(cff, 2)) + (c_EGT[11] * mach * cff) + (c_EGT[12] * cff * alt) + (c_EGT[13] * powFBW(mach, 2)) + (c_EGT[14] * mach * alt) + (c_EGT[15] * powFBW(alt, 2)); return outCEGT; @@ -206,11 +205,10 @@ class Polynomial { /// double correctedFuelFlow(double cn1, double mach, double alt) { double outCFF = 0; - double a330_f = 2.7; - double c_Flow[21] = {-639.6602981, 0.00000e+00, 1.03705e+02, -2.23264e+03, 5.70316e-03, -2.29404e+00, 1.08230e+02, - 2.77667e-04, -6.17180e+02, -7.20713e-02, 2.19013e-07, 2.49418e-02, -7.31662e-01, -1.00003e-05, - -3.79466e+01, 1.34552e-03, 5.72612e-09, -2.71950e+02, 8.58469e-02, -2.72912e-06, 2.02928e-11}; + double c_Flow[21] = {-1.7630e+02, -2.1542e-01, 4.7119e+01, 6.1519e+02, 1.8047e-03, -4.4554e-01, -4.3940e+01, + 4.0459e-05, -3.2912e+01, -6.2894e-03, -1.2544e-07, 1.0938e-02, 4.0936e-01, -5.5841e-06, + -2.3829e+01, 9.3269e-04, 2.0273e-11, -2.4100e+02, 1.4171e-02, -9.5581e-07, 1.2728e-11}; outCFF = c_Flow[0] + c_Flow[1] + (c_Flow[2] * cn1) + (c_Flow[3] * mach) + (c_Flow[4] * alt) + (c_Flow[5] * powFBW(cn1, 2)) + (c_Flow[6] * cn1 * mach) + (c_Flow[7] * cn1 * alt) + (c_Flow[8] * powFBW(mach, 2)) + (c_Flow[9] * mach * alt) + @@ -219,7 +217,7 @@ class Polynomial { (c_Flow[16] * cn1 * powFBW(alt, 2)) + (c_Flow[17] * powFBW(mach, 3)) + (c_Flow[18] * powFBW(mach, 2) * alt) + (c_Flow[19] * mach * powFBW(alt, 2)) + (c_Flow[20] * powFBW(alt, 3)); - return outCFF * a330_f; + return 2 * outCFF; } double oilTemperature(double energy, double preOilTemp, double maxOilTemp, double deltaTime) { @@ -240,6 +238,9 @@ class Polynomial { oilTemp_out = (t_steady - dt); } + // std::cout << "FADEC: Max= " << maxOilTemp << " Energy = " << energy << " dt = " << dt << " preT= " << preOilTemp + // << " Tss = " << t_steady << " To = " << oilTemp_out << std::flush; + return oilTemp_out; } @@ -248,12 +249,10 @@ class Polynomial { /// double oilGulpPct(double thrust) { double outOilGulpPct = 0; - double thrust_a330 = thrust/2.53; - double c_OilGulp[3] = {20.1968848, -1.2270302e-4, 1.78442e-8}; - outOilGulpPct = c_OilGulp[0] + (c_OilGulp[1] * thrust) + (c_OilGulp[2] * powFBW(thrust_a330, 2)); + outOilGulpPct = c_OilGulp[0] + (c_OilGulp[1] * thrust) + (c_OilGulp[2] * powFBW(thrust, 2)); return outOilGulpPct / 100; } @@ -261,12 +260,12 @@ class Polynomial { /// /// Real-life modeled polynomials - Oil Pressure (PSI) /// - double oilPressure(double simN2) { + double oilPressure(double simN3) { double outOilPressure = 0; double c_OilPress[3] = {-0.88921, 0.23711, 0.00682}; - outOilPressure = c_OilPress[0] + (c_OilPress[1] * simN2) + (c_OilPress[2] * powFBW(simN2, 2)); + outOilPressure = c_OilPress[0] + (c_OilPress[1] * simN3) + (c_OilPress[2] * powFBW(simN3, 2)); return outOilPressure; } diff --git a/hdw-a339x/src/wasm/fadec_a330/src/SimVars.h b/hdw-a339x/src/wasm/fadec_a330/src/SimVars.h new file mode 100644 index 000000000..32767533b --- /dev/null +++ b/hdw-a339x/src/wasm/fadec_a330/src/SimVars.h @@ -0,0 +1,388 @@ +#pragma once + +/// +/// SimConnect data types to send to Sim Updated +/// +enum DataTypesID { + FuelLeftMain, + FuelRightMain, + FuelCenterMain, + FuelLeftAux, + FuelRightAux, + // FuelTrim, + OilTempEngine1, + OilTempEngine2, + OilPsiEngine1, + OilPsiEngine2, + StartCN3Engine1, + StartCN3Engine2, + SimulationDataTypeId, + AcftInfo, +}; + +struct SimulationData { + double simulationTime; + double simulationRate; +}; + +struct SimulationDataLivery { + char atc_id[32] = ""; +}; + +/// +/// A collection of SimVar unit enums. +/// +class Units { + public: + ENUM Percent = get_units_enum("Percent"); + 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 Gph = get_units_enum("Gallons per hour"); + ENUM Feet = get_units_enum("Feet"); + ENUM FootPounds = get_units_enum("Foot pounds"); + ENUM FeetMin = get_units_enum("Feet per minute"); + ENUM Number = get_units_enum("Number"); + ENUM Mach = get_units_enum("Mach"); + ENUM Millibars = get_units_enum("Millibars"); + ENUM SluggerSlugs = get_units_enum("Slug per cubic feet"); + ENUM Celsius = get_units_enum("Celsius"); + ENUM Bool = get_units_enum("Bool"); + ENUM Hours = get_units_enum("Hours"); + ENUM Seconds = get_units_enum("Seconds"); +}; + +/// +/// A collection of SimVars and LVars for the A32NX +/// +class SimVars { + public: + Units* m_Units; + + /// + /// Collection of SimVars for the A32NX + /// + ENUM CorrectedN1 = get_aircraft_var_enum("TURB ENG CORRECTED N1"); + ENUM CorrectedN2 = get_aircraft_var_enum("TURB ENG CORRECTED N2"); + ENUM N1 = get_aircraft_var_enum("TURB ENG N1"); + ENUM N2 = get_aircraft_var_enum("TURB ENG N2"); + ENUM OilPSI = get_aircraft_var_enum("GENERAL ENG OIL PRESSURE"); + ENUM OilTemp = get_aircraft_var_enum("GENERAL ENG OIL TEMPERATURE"); + ENUM Thrust = get_aircraft_var_enum("TURB ENG JET THRUST"); + ENUM correctedFF = get_aircraft_var_enum("TURB ENG CORRECTED FF"); + ENUM PlaneAltitude = get_aircraft_var_enum("PLANE ALTITUDE"); + ENUM PlaneAltitudeAGL = get_aircraft_var_enum("PLANE ALT ABOVE GROUND"); + ENUM PressureAltitude = get_aircraft_var_enum("PRESSURE ALTITUDE"); + ENUM AirSpeedMach = get_aircraft_var_enum("AIRSPEED MACH"); + ENUM AmbientTemp = get_aircraft_var_enum("AMBIENT TEMPERATURE"); + ENUM AmbientPressure = get_aircraft_var_enum("AMBIENT PRESSURE"); + ENUM VerticalSpeed = get_aircraft_var_enum("VERTICAL SPEED"); + ENUM StdTemp = get_aircraft_var_enum("STANDARD ATM TEMPERATURE"); + ENUM SimOnGround = get_aircraft_var_enum("SIM ON GROUND"); + ENUM EngineTime = get_aircraft_var_enum("GENERAL ENG ELAPSED TIME"); + ENUM EngineStarter = get_aircraft_var_enum("GENERAL ENG STARTER"); + ENUM EngineIgniter = get_aircraft_var_enum("TURB ENG IGNITION SWITCH EX1"); + ENUM EngineCombustion = get_aircraft_var_enum("GENERAL ENG COMBUSTION"); + ENUM animDeltaTime = get_aircraft_var_enum("ANIMATION DELTA TIME"); + + ENUM FuelTankQuantity = get_aircraft_var_enum("FUELSYSTEM TANK QUANTITY"); + ENUM EmptyWeight = get_aircraft_var_enum("EMPTY WEIGHT"); + ENUM TotalWeight = get_aircraft_var_enum("TOTAL WEIGHT"); + ENUM FuelTotalQuantity = get_aircraft_var_enum("FUEL TOTAL QUANTITY"); + ENUM FuelWeightGallon = get_aircraft_var_enum("FUEL WEIGHT PER GALLON"); + + ENUM FuelPump = get_aircraft_var_enum("FUELSYSTEM PUMP ACTIVE"); + ENUM FuelValve = get_aircraft_var_enum("FUELSYSTEM VALVE OPEN"); + ENUM FuelLineFlow = get_aircraft_var_enum("FUELSYSTEM LINE FUEL FLOW"); + ENUM FuelJunctionSetting = get_aircraft_var_enum("FUELSYSTEM JUNCTION SETTING"); + + ENUM NacelleAntiIce = get_aircraft_var_enum("ENG ANTI ICE"); + + /// + /// Collection of LVars for the A32NX + /// + ID DevVar; + ID IsReady; + ID FlexTemp; + ID Engine1N3; + ID Engine2N3; + ID Engine1N2; + ID Engine2N2; + ID Engine1N1; + ID Engine2N1; + ID EngineIdleN1; + ID EngineIdleN3; + ID EngineIdleFF; + ID EngineIdleEGT; + ID Engine1EGT; + ID Engine2EGT; + ID Engine1Oil; + ID Engine2Oil; + ID Engine1TotalOil; + ID Engine2TotalOil; + ID Engine1VibN1; + ID Engine2VibN1; + ID Engine1VibN2; + ID Engine2VibN2; + ID Engine1FF; + ID Engine2FF; + ID Engine1PreFF; + ID Engine2PreFF; + ID EngineCycleTime; + ID EngineImbalance; + ID WingAntiIce; + ID FuelUsedEngine1; + ID FuelUsedEngine2; + ID FuelLeftPre; + ID FuelRightPre; + ID FuelAuxLeftPre; + ID FuelAuxRightPre; + ID FuelCenterPre; + // ID FuelTrimPre; + ID RefuelRate; + ID RefuelStartedByUser; + ID FuelOverflowLeft; + ID FuelOverflowRight; + ID Engine1State; + ID Engine2State; + ID Engine1Timer; + ID Engine2Timer; + ID PumpStateEngine1; + ID PumpStateEngine2; + ID ThrustLimitType; + ID ThrustLimitIdle; + ID ThrustLimitToga; + ID ThrustLimitFlex; + ID ThrustLimitClimb; + ID ThrustLimitMct; + ID PacksState1; + ID PacksState2; + + SimVars() { this->initializeVars(); } + + void initializeVars() { + DevVar = register_named_variable("A32NX_DEVELOPER_STATE"); + IsReady = register_named_variable("A32NX_IS_READY"); + FlexTemp = register_named_variable("AIRLINER_TO_FLEX_TEMP"); + Engine1N3 = register_named_variable("A32NX_ENGINE_N3:1"); + Engine2N3 = register_named_variable("A32NX_ENGINE_N3:2"); + Engine1N2 = register_named_variable("A32NX_ENGINE_N2:1"); + Engine2N2 = register_named_variable("A32NX_ENGINE_N2:2"); + Engine1N1 = register_named_variable("A32NX_ENGINE_N1:1"); + Engine2N1 = register_named_variable("A32NX_ENGINE_N1:2"); + EngineIdleN1 = register_named_variable("A32NX_ENGINE_IDLE_N1"); + EngineIdleN3 = register_named_variable("A32NX_ENGINE_IDLE_N3"); + EngineIdleFF = register_named_variable("A32NX_ENGINE_IDLE_FF"); + EngineIdleEGT = register_named_variable("A32NX_ENGINE_IDLE_EGT"); + Engine1EGT = register_named_variable("A32NX_ENGINE_EGT:1"); + Engine2EGT = register_named_variable("A32NX_ENGINE_EGT:2"); + Engine1Oil = register_named_variable("A32NX_ENGINE_OIL_QTY:1"); + Engine2Oil = register_named_variable("A32NX_ENGINE_OIL_QTY:2"); + Engine1TotalOil = register_named_variable("A32NX_ENGINE_OIL_TOTAL:1"); + Engine2TotalOil = register_named_variable("A32NX_ENGINE_OIL_TOTAL:2"); + Engine1VibN1 = register_named_variable("A32NX_ENGINE_VIB_N1:1"); + Engine2VibN1 = register_named_variable("A32NX_ENGINE_VIB_N1:2"); + Engine1FF = register_named_variable("A32NX_ENGINE_FF:1"); + Engine2FF = register_named_variable("A32NX_ENGINE_FF:2"); + Engine1PreFF = register_named_variable("A32NX_ENGINE_PRE_FF:1"); + Engine2PreFF = register_named_variable("A32NX_ENGINE_PRE_FF:2"); + EngineImbalance = register_named_variable("A32NX_ENGINE_IMBALANCE"); + WingAntiIce = register_named_variable("A32NX_PNEU_WING_ANTI_ICE_SYSTEM_ON"); + FuelUsedEngine1 = register_named_variable("A32NX_FUEL_USED:1"); + FuelUsedEngine2 = register_named_variable("A32NX_FUEL_USED:2"); + FuelLeftPre = register_named_variable("A32NX_FUEL_LEFT_PRE"); + FuelRightPre = register_named_variable("A32NX_FUEL_RIGHT_PRE"); + FuelAuxLeftPre = register_named_variable("A32NX_FUEL_AUX_LEFT_PRE"); + FuelAuxRightPre = register_named_variable("A32NX_FUEL_AUX_RIGHT_PRE"); + FuelCenterPre = register_named_variable("A32NX_FUEL_CENTER_PRE"); + // FuelTrimPre = register_named_variable("A32NX_FUEL_TRIM_PRE"); + RefuelRate = register_named_variable("A32NX_EFB_REFUEL_RATE_SETTING"); + RefuelStartedByUser = register_named_variable("A32NX_REFUEL_STARTED_BY_USR"); + Engine1State = register_named_variable("A32NX_ENGINE_STATE:1"); + Engine2State = register_named_variable("A32NX_ENGINE_STATE:2"); + Engine1Timer = register_named_variable("A32NX_ENGINE_TIMER:1"); + Engine2Timer = register_named_variable("A32NX_ENGINE_TIMER:2"); + PumpStateEngine1 = register_named_variable("A32NX_PUMP_STATE:1"); + PumpStateEngine2 = register_named_variable("A32NX_PUMP_STATE:2"); + + ThrustLimitType = register_named_variable("A32NX_AUTOTHRUST_THRUST_LIMIT_TYPE"); + ThrustLimitIdle = register_named_variable("A32NX_AUTOTHRUST_THRUST_LIMIT_IDLE"); + ThrustLimitToga = register_named_variable("A32NX_AUTOTHRUST_THRUST_LIMIT_TOGA"); + ThrustLimitFlex = register_named_variable("A32NX_AUTOTHRUST_THRUST_LIMIT_FLX"); + ThrustLimitClimb = register_named_variable("A32NX_AUTOTHRUST_THRUST_LIMIT_CLB"); + ThrustLimitMct = register_named_variable("A32NX_AUTOTHRUST_THRUST_LIMIT_MCT"); + + PacksState1 = register_named_variable("A32NX_COND_PACK_FLOW_VALVE_1_IS_OPEN"); + PacksState2 = register_named_variable("A32NX_COND_PACK_FLOW_VALVE_2_IS_OPEN"); + + this->setDeveloperState(0); + this->setEngine1N3(0); + this->setEngine2N3(0); + this->setEngine1N2(0); + this->setEngine2N2(0); + this->setEngine1N1(0); + this->setEngine2N1(0); + this->setEngineIdleN1(0); + this->setEngineIdleN3(0); + this->setEngineIdleFF(0); + this->setEngineIdleEGT(0); + this->setEngine1EGT(0); + this->setEngine2EGT(0); + this->setEngine1Oil(0); + this->setEngine2Oil(0); + this->setEngine1TotalOil(0); + this->setEngine2TotalOil(0); + this->setEngine1FF(0); + this->setEngine2FF(0); + this->setEngine1PreFF(0); + this->setEngine2PreFF(0); + this->setEngineImbalance(0); + this->setFuelUsedEngine1(0); + this->setFuelUsedEngine2(0); + this->setFuelLeftPre(0); + this->setFuelRightPre(0); + this->setFuelAuxLeftPre(0); + this->setFuelAuxRightPre(0); + this->setFuelCenterPre(0); + // this->setFuelTrimPre(0); + this->setEngine1State(0); + this->setEngine2State(0); + this->setEngine1Timer(0); + this->setEngine2Timer(0); + this->setPumpStateEngine1(0); + this->setPumpStateEngine2(0); + this->setThrustLimitIdle(0); + this->setThrustLimitToga(0); + this->setThrustLimitFlex(0); + this->setThrustLimitClimb(0); + this->setThrustLimitMct(0); + + m_Units = new Units(); + } + + // Collection of LVar 'set' Functions + void setDeveloperState(FLOAT64 value) { set_named_variable_value(DevVar, value); } + void setEngine1N3(FLOAT64 value) { set_named_variable_value(Engine1N3, value); } + void setEngine2N3(FLOAT64 value) { set_named_variable_value(Engine2N3, value); } + void setEngine1N2(FLOAT64 value) { set_named_variable_value(Engine1N2, value); } + void setEngine2N2(FLOAT64 value) { set_named_variable_value(Engine2N2, value); } + void setEngine1N1(FLOAT64 value) { set_named_variable_value(Engine1N1, value); } + void setEngine2N1(FLOAT64 value) { set_named_variable_value(Engine2N1, value); } + void setEngineIdleN1(FLOAT64 value) { set_named_variable_value(EngineIdleN1, value); } + void setEngineIdleN3(FLOAT64 value) { set_named_variable_value(EngineIdleN3, value); } + void setEngineIdleFF(FLOAT64 value) { set_named_variable_value(EngineIdleFF, value); } + void setEngineIdleEGT(FLOAT64 value) { set_named_variable_value(EngineIdleEGT, value); } + void setEngine1EGT(FLOAT64 value) { set_named_variable_value(Engine1EGT, value); } + void setEngine2EGT(FLOAT64 value) { set_named_variable_value(Engine2EGT, value); } + void setEngine1Oil(FLOAT64 value) { set_named_variable_value(Engine1Oil, value); } + void setEngine2Oil(FLOAT64 value) { set_named_variable_value(Engine2Oil, value); } + void setEngine1TotalOil(FLOAT64 value) { set_named_variable_value(Engine1TotalOil, value); } + void setEngine2TotalOil(FLOAT64 value) { set_named_variable_value(Engine2TotalOil, value); } + void setEngine1FF(FLOAT64 value) { set_named_variable_value(Engine1FF, value); } + void setEngine2FF(FLOAT64 value) { set_named_variable_value(Engine2FF, value); } + void setEngine1PreFF(FLOAT64 value) { set_named_variable_value(Engine1PreFF, value); } + void setEngine2PreFF(FLOAT64 value) { set_named_variable_value(Engine2PreFF, value); } + void setEngineImbalance(FLOAT64 value) { set_named_variable_value(EngineImbalance, value); } + void setFuelUsedEngine1(FLOAT64 value) { set_named_variable_value(FuelUsedEngine1, value); } + void setFuelUsedEngine2(FLOAT64 value) { set_named_variable_value(FuelUsedEngine2, value); } + void setFuelLeftPre(FLOAT64 value) { set_named_variable_value(FuelLeftPre, value); } + void setFuelRightPre(FLOAT64 value) { set_named_variable_value(FuelRightPre, value); } + void setFuelAuxLeftPre(FLOAT64 value) { set_named_variable_value(FuelAuxLeftPre, value); } + void setFuelAuxRightPre(FLOAT64 value) { set_named_variable_value(FuelAuxRightPre, value); } + void setFuelCenterPre(FLOAT64 value) { set_named_variable_value(FuelCenterPre, value); } + // void setFuelTrimPre(FLOAT64 value) { set_named_variable_value(FuelTrimPre, value); }; + void setEngine1State(FLOAT64 value) { set_named_variable_value(Engine1State, value); } + void setEngine2State(FLOAT64 value) { set_named_variable_value(Engine2State, value); } + void setEngine1Timer(FLOAT64 value) { set_named_variable_value(Engine1Timer, value); } + void setEngine2Timer(FLOAT64 value) { set_named_variable_value(Engine2Timer, value); } + void setPumpStateEngine1(FLOAT64 value) { set_named_variable_value(PumpStateEngine1, value); } + void setPumpStateEngine2(FLOAT64 value) { set_named_variable_value(PumpStateEngine2, value); } + void setThrustLimitIdle(FLOAT64 value) { set_named_variable_value(ThrustLimitIdle, value); } + void setThrustLimitToga(FLOAT64 value) { set_named_variable_value(ThrustLimitToga, value); } + void setThrustLimitFlex(FLOAT64 value) { set_named_variable_value(ThrustLimitFlex, value); } + void setThrustLimitClimb(FLOAT64 value) { set_named_variable_value(ThrustLimitClimb, value); } + void setThrustLimitMct(FLOAT64 value) { set_named_variable_value(ThrustLimitMct, value); } + + // Collection of SimVar/LVar 'get' Functions + FLOAT64 getDeveloperState() { return get_named_variable_value(DevVar); } + FLOAT64 getIsReady() { return get_named_variable_value(IsReady); } + FLOAT64 getFlexTemp() { return get_named_variable_value(FlexTemp); } + FLOAT64 getEngine1N3() { return get_named_variable_value(Engine1N3); } + FLOAT64 getEngine2N3() { return get_named_variable_value(Engine2N3); } + FLOAT64 getEngine1N2() { return get_named_variable_value(Engine1N2); } + FLOAT64 getEngine2N2() { return get_named_variable_value(Engine2N2); } + FLOAT64 getEngine1N1() { return get_named_variable_value(Engine1N1); } + FLOAT64 getEngine2N1() { return get_named_variable_value(Engine2N1); } + FLOAT64 getEngineIdleN1() { return get_named_variable_value(EngineIdleN1); } + FLOAT64 getEngineIdleN3() { return get_named_variable_value(EngineIdleN3); } + FLOAT64 getEngineIdleFF() { return get_named_variable_value(EngineIdleFF); } + FLOAT64 getEngineIdleEGT() { return get_named_variable_value(EngineIdleEGT); } + FLOAT64 getEngine1FF() { return get_named_variable_value(Engine1FF); } + FLOAT64 getEngine2FF() { return get_named_variable_value(Engine2FF); } + FLOAT64 getEngine1EGT() { return get_named_variable_value(Engine1EGT); } + FLOAT64 getEngine2EGT() { return get_named_variable_value(Engine2EGT); } + FLOAT64 getEngine1Oil() { return get_named_variable_value(Engine1Oil); } + FLOAT64 getEngine2Oil() { return get_named_variable_value(Engine2Oil); } + FLOAT64 getEngine1TotalOil() { return get_named_variable_value(Engine1TotalOil); } + FLOAT64 getEngine2TotalOil() { return get_named_variable_value(Engine2TotalOil); } + FLOAT64 getEngine1PreFF() { return get_named_variable_value(Engine1PreFF); } + FLOAT64 getEngine2PreFF() { return get_named_variable_value(Engine2PreFF); } + FLOAT64 getEngineImbalance() { return get_named_variable_value(EngineImbalance); } + FLOAT64 getWAI() { return get_named_variable_value(WingAntiIce); } + FLOAT64 getFuelUsedEngine1() { return get_named_variable_value(FuelUsedEngine1); } + FLOAT64 getFuelUsedEngine2() { return get_named_variable_value(FuelUsedEngine2); } + FLOAT64 getFuelLeftPre() { return get_named_variable_value(FuelLeftPre); } + FLOAT64 getFuelRightPre() { return get_named_variable_value(FuelRightPre); } + FLOAT64 getFuelAuxLeftPre() { return get_named_variable_value(FuelAuxLeftPre); } + FLOAT64 getFuelAuxRightPre() { return get_named_variable_value(FuelAuxRightPre); } + FLOAT64 getFuelCenterPre() { return get_named_variable_value(FuelCenterPre); } + // FLOAT64 getFuelTrimPre() { return get_named_variable_value(FuelTrimPre); } + FLOAT64 getRefuelRate() { return get_named_variable_value(RefuelRate); } + FLOAT64 getRefuelStartedByUser() { return get_named_variable_value(RefuelStartedByUser); } + FLOAT64 getPumpStateEngine1() { return get_named_variable_value(PumpStateEngine1); } + FLOAT64 getPumpStateEngine2() { return get_named_variable_value(PumpStateEngine2); } + FLOAT64 getPacksState1() { return get_named_variable_value(PacksState1); } + FLOAT64 getPacksState2() { return get_named_variable_value(PacksState2); } + FLOAT64 getThrustLimitType() { return get_named_variable_value(ThrustLimitType); } + + FLOAT64 getCN1(int index) { return aircraft_varget(CorrectedN1, m_Units->Percent, index); } + FLOAT64 getCN2(int index) { return aircraft_varget(CorrectedN2, m_Units->Percent, index); } + FLOAT64 getN1(int index) { return aircraft_varget(N1, m_Units->Percent, index); } + FLOAT64 getN2(int index) { return aircraft_varget(N2, m_Units->Percent, index); } + FLOAT64 getOilPsi(int index) { return aircraft_varget(OilPSI, m_Units->Psi, index); } + FLOAT64 getOilTemp(int index) { return aircraft_varget(OilTemp, m_Units->Celsius, index); } + FLOAT64 getThrust(int index) { return aircraft_varget(Thrust, m_Units->Pounds, index); } + FLOAT64 getEngine1State() { return get_named_variable_value(Engine1State); } + FLOAT64 getEngine2State() { return get_named_variable_value(Engine2State); } + FLOAT64 getEngine1Timer() { return get_named_variable_value(Engine1Timer); } + FLOAT64 getEngine2Timer() { return get_named_variable_value(Engine2Timer); } + FLOAT64 getFF(int index) { return aircraft_varget(correctedFF, m_Units->Pph, index); } + FLOAT64 getMach() { return aircraft_varget(AirSpeedMach, m_Units->Mach, 0); } + FLOAT64 getPlaneAltitude() { return aircraft_varget(PlaneAltitude, m_Units->Feet, 0); } + FLOAT64 getPlaneAltitudeAGL() { return aircraft_varget(PlaneAltitudeAGL, m_Units->Feet, 0); } + FLOAT64 getPressureAltitude() { return aircraft_varget(PressureAltitude, m_Units->Feet, 0); } + FLOAT64 getVerticalSpeed() { return aircraft_varget(VerticalSpeed, m_Units->FeetMin, 0); } + FLOAT64 getAmbientTemperature() { return aircraft_varget(AmbientTemp, m_Units->Celsius, 0); } + FLOAT64 getAmbientPressure() { return aircraft_varget(AmbientPressure, m_Units->Millibars, 0); } + FLOAT64 getStdTemperature() { return aircraft_varget(StdTemp, m_Units->Celsius, 0); } + FLOAT64 getSimOnGround() { return aircraft_varget(SimOnGround, m_Units->Bool, 0); } + FLOAT64 getFuelTankQuantity(int index) { return aircraft_varget(FuelTankQuantity, m_Units->Gallons, index); } + FLOAT64 getFuelTotalQuantity() { return aircraft_varget(FuelTotalQuantity, m_Units->Gallons, 0); } + FLOAT64 getEmptyWeight() { return aircraft_varget(EmptyWeight, m_Units->Pounds, 0); } + FLOAT64 getTotalWeight() { return aircraft_varget(TotalWeight, m_Units->Pounds, 0); } + FLOAT64 getFuelWeightGallon() { return aircraft_varget(FuelWeightGallon, m_Units->Pounds, 0); } + FLOAT64 getEngineTime(int index) { return aircraft_varget(EngineTime, m_Units->Seconds, index); } + FLOAT64 getEngineStarter(int index) { return aircraft_varget(EngineStarter, m_Units->Bool, index); } + FLOAT64 getEngineIgniter(int index) { return aircraft_varget(EngineIgniter, m_Units->Number, index); } + FLOAT64 getEngineCombustion(int index) { return aircraft_varget(EngineCombustion, m_Units->Bool, index); } + FLOAT64 getAnimDeltaTime() { return aircraft_varget(animDeltaTime, m_Units->Seconds, 0); } + FLOAT64 getNAI(int index) { return aircraft_varget(NacelleAntiIce, m_Units->Bool, index); } + FLOAT64 getPump(int index) { return aircraft_varget(FuelPump, m_Units->Number, index); } + FLOAT64 getValve(int index) { return aircraft_varget(FuelValve, m_Units->Number, index); } + /// @brief Gets a fuel line flow rate in gallons/hour + /// @param index Index of the fuel line + /// @return Fuel line flow rate in gallons/hour + FLOAT64 getLineFlow(int index) { return aircraft_varget(FuelLineFlow, m_Units->Gph, index); } + FLOAT64 getJunctionSetting(int index) { return aircraft_varget(FuelJunctionSetting, m_Units->Number, index); } +}; diff --git a/hdw-a339x/src/wasm/fadec_a330/src/Tables.h b/hdw-a339x/src/wasm/fadec_a330/src/Tables.h new file mode 100644 index 000000000..74c9802c9 --- /dev/null +++ b/hdw-a339x/src/wasm/fadec_a330/src/Tables.h @@ -0,0 +1,69 @@ +#pragma once + +#include "SimVars.h" +#include "common.h" + +EngineRatios* ratios; + +/// +/// Table 1502 (CN3 vs correctedN1) representations with FSX nomenclature +/// +/// Returns CN3 - correctedN1 pair. +double table1502(int i, int j) { + double t[13][4] = {{16.012, 0.000, 0.000, 17.000}, {19.355, 1.845, 1.845, 17.345}, {22.874, 2.427, 2.427, 18.127}, + {50.147, 12.427, 12.427, 26.627}, {60.000, 18.500, 18.500, 33.728}, {67.742, 25.243, 25.243, 40.082}, + {73.021, 30.505, 30.505, 43.854}, {78.299, 39.779, 39.779, 48.899}, {81.642, 49.515, 49.515, 53.557}, + {85.337, 63.107, 63.107, 63.107}, {87.977, 74.757, 74.757, 74.757}, {97.800, 97.200, 97.200, 97.200}, + {118.000, 115.347, 115.347, 115.347}}; + + return t[i][j]; +} + +/// +/// Calculate expected CN3 at Idle +/// +double iCN3(double pressAltitude, double mach) { + double cn3 = 0; + + cn3 = 60.0 / (sqrt((288.15 - (1.98 * pressAltitude / 1000)) / 288.15) * sqrt(1 + (0.2 * powFBW(mach, 2)))); + + return cn3; +} + +/// +/// Calculate expected correctedN1 at Idle +/// +double iCN1(double pressAltitude, double mach, double ambientTemp) { + int i; + double cn1_lo = 0, cn1_hi = 0, cn1 = 0; + double cn3 = iCN3(pressAltitude, mach); + double cell = 0; + double cn3lo = 0, cn3hi = 0; + double cn1lolo = 0, cn1hilo = 0, cn1lohi = 0, cn1hihi = 0; + + for (i = 0; i < 13; i++) { + cell = table1502(i, 0); + if (cell > cn3) { + break; + } + } + + cn3lo = table1502(i - 1, 0); + cn3hi = table1502(i, 0); + + cn1lolo = table1502(i - 1, 1); + cn1hilo = table1502(i, 1); + + if (mach <= 0.2) { + cn1 = interpolate(cn3, cn3lo, cn3hi, cn1lolo, cn1hilo); + } else { + cn1lohi = table1502(i - 1, 3); + cn1hihi = table1502(i, 3); + + cn1_lo = interpolate(cn3, cn3lo, cn3hi, cn1lolo, cn1hilo); + cn1_hi = interpolate(cn3, cn3lo, cn3hi, cn1lohi, cn1hihi); + cn1 = interpolate(mach, 0.2, 0.9, cn1_lo, cn1_hi); + } + + return cn1; +} \ No newline at end of file diff --git a/hdw-a339x/src/wasm/fadec_a330/src/ThrustLimits.h b/hdw-a339x/src/wasm/fadec_a330/src/ThrustLimits.h new file mode 100644 index 000000000..85cbbe6e4 --- /dev/null +++ b/hdw-a339x/src/wasm/fadec_a330/src/ThrustLimits.h @@ -0,0 +1,257 @@ +#pragma once + +#include "SimVars.h" +#include "common.h" + +double cas2mach(double cas, double ambientPressure) { + double k = 2188648.141; + double delta = ambientPressure / 1013; + double mach = sqrt((5 * pow(((pow(((pow(cas, 2) / k) + 1), 3.5) * (1 / delta)) - (1 / delta) + 1), 0.285714286)) - 5); + + return mach; +} + +static constexpr double limits[72][6] = { + {-2000, 48.000, 55.000, 81.351, 79.370, 61.535}, {-1000, 46.000, 55.000, 82.605, 80.120, 62.105}, + {0, 44.000, 55.000, 83.832, 80.776, 62.655}, {500, 42.000, 52.000, 84.210, 81.618, 62.655}, + {1000, 42.000, 52.000, 84.579, 81.712, 62.655}, {2000, 40.000, 50.000, 85.594, 82.720, 62.655}, + {3000, 36.000, 48.000, 86.657, 83.167, 61.960}, {4000, 32.000, 46.000, 87.452, 83.332, 61.206}, + {5000, 29.000, 44.000, 88.833, 84.166, 61.206}, {6000, 25.000, 42.000, 90.232, 84.815, 61.206}, + {7000, 21.000, 40.000, 91.711, 85.565, 61.258}, {8000, 17.000, 38.000, 93.247, 86.225, 61.777}, + {9000, 15.000, 36.000, 94.031, 86.889, 60.968}, {10000, 13.000, 34.000, 94.957, 88.044, 60.935}, + {11000, 12.000, 32.000, 95.295, 88.526, 59.955}, {12000, 11.000, 30.000, 95.568, 88.818, 58.677}, + {13000, 10.000, 28.000, 95.355, 88.819, 59.323}, {14000, 10.000, 26.000, 95.372, 89.311, 59.965}, + {15000, 8.000, 24.000, 95.686, 89.907, 58.723}, {16000, 5.000, 22.000, 96.160, 89.816, 57.189}, + {16600, 5.000, 22.000, 96.560, 89.816, 57.189}, {-2000, 47.751, 54.681, 84.117, 81.901, 63.498}, + {-1000, 45.771, 54.681, 85.255, 82.461, 63.920}, {0, 43.791, 54.681, 86.411, 83.021, 64.397}, + {500, 42.801, 52.701, 86.978, 83.740, 64.401}, {1000, 41.811, 52.701, 87.568, 83.928, 64.525}, + {2000, 38.841, 50.721, 88.753, 84.935, 64.489}, {3000, 36.861, 48.741, 89.930, 85.290, 63.364}, + {4000, 32.901, 46.761, 91.004, 85.836, 62.875}, {5000, 28.941, 44.781, 92.198, 86.293, 62.614}, + {6000, 24.981, 42.801, 93.253, 86.563, 62.290}, {7000, 21.022, 40.821, 94.273, 86.835, 61.952}, + {8000, 17.062, 38.841, 94.919, 87.301, 62.714}, {9000, 15.082, 36.861, 95.365, 87.676, 61.692}, + {10000, 13.102, 34.881, 95.914, 88.150, 60.906}, {11000, 12.112, 32.901, 96.392, 88.627, 59.770}, + {12000, 11.122, 30.921, 96.640, 89.206, 58.933}, {13000, 10.132, 28.941, 96.516, 89.789, 60.503}, + {14000, 9.142, 26.961, 96.516, 90.475, 62.072}, {15000, 9.142, 24.981, 96.623, 90.677, 59.333}, + {16000, 7.162, 23.001, 96.845, 90.783, 58.045}, {16600, 5.182, 21.022, 97.366, 91.384, 58.642}, + {-2000, 30.800, 56.870, 80.280, 72.000, 0.000}, {2000, 20.990, 48.157, 82.580, 74.159, 0.000}, + {5000, 16.139, 43.216, 84.642, 75.737, 0.000}, {8000, 7.342, 38.170, 86.835, 77.338, 0.000}, + {10000, 4.051, 34.518, 88.183, 77.999, 0.000}, {10000.1, 4.051, 34.518, 87.453, 77.353, 0.000}, + {12000, 0.760, 30.865, 88.303, 78.660, 0.000}, {15000, -4.859, 25.039, 89.748, 79.816, 0.000}, + {17000, -9.934, 19.813, 90.668, 80.895, 0.000}, {20000, -15.822, 13.676, 92.106, 81.894, 0.000}, + {24000, -22.750, 6.371, 93.651, 82.716, 0.000}, {27000, -29.105, -0.304, 93.838, 83.260, 0.000}, + {29314, -32.049, -3.377, 93.502, 82.962, 0.000}, {31000, -34.980, -6.452, 95.392, 84.110, 0.000}, + {35000, -45.679, -17.150, 96.104, 85.248, 0.000}, {39000, -45.679, -17.150, 96.205, 84.346, 0.000}, + {41500, -45.679, -17.150, 95.676, 83.745, 0.000}, {-1000, 26.995, 54.356, 82.465, 74.086, 0.000}, + {3000, 18.170, 45.437, 86.271, 77.802, 0.000}, {7000, 9.230, 40.266, 89.128, 79.604, 0.000}, + {11000, 4.019, 31.046, 92.194, 82.712, 0.000}, {15000, -5.226, 21.649, 95.954, 85.622, 0.000}, + {17000, -9.913, 20.702, 97.520, 85.816, 0.000}, {20000, -15.129, 15.321, 99.263, 86.770, 0.000}, + {22000, -19.947, 10.382, 98.977, 86.661, 0.000}, {25000, -25.397, 4.731, 98.440, 85.765, 0.000}, + {27000, -30.369, -0.391, 97.279, 85.556, 0.000}, {31000, -36.806, -7.165, 98.674, 86.650, 0.000}, + {35000, -43.628, -14.384, 98.386, 85.747, 0.000}, {39000, -47.286, -18.508, 97.278, 85.545, 0.000}}; + +/// +/// Finds top-row boundary in an array +/// +int finder(double altitude, int index) { + if (altitude < limits[index][0]) { + return index; + } else { + return finder(altitude, index + 1); + } +} + +/// +/// Calculates Bleed Air situation for engine adaptation +/// +double bleedTotal(int type, double altitude, double oat, double cp, double lp, double flexTemp, double ac, double nacelle, double wing) { + double n1Packs = 0; + double n1Nai = 0; + double n1Wai = 0; + double bleed = 0; + + if (flexTemp > lp && type <= 1) { + n1Packs = -0.6; + n1Nai = -0.7; + n1Wai = -0.7; + } else { + switch (type) { + case 0: + if (altitude < 8000) { + if (oat < cp) { + n1Packs = -0.4; + } else { + n1Packs = -0.5; + n1Nai = -0.6; + n1Wai = -0.7; + } + } else { + if (oat < cp) { + n1Packs = -0.6; + } else { + n1Packs = -0.7; + n1Nai = -0.8; + n1Wai = -0.8; + } + } + break; + case 1: + if (altitude < 8000) { + if (oat < cp) { + n1Packs = -0.4; + } else { + n1Packs = -0.4; + n1Nai = -0.6; + n1Wai = -0.6; + } + } else { + if (oat < cp) { + n1Packs = -0.6; + } else { + n1Packs = -0.6; + n1Nai = -0.7; + n1Wai = -0.8; + } + } + break; + case 2: + if (oat < cp) { + n1Packs = -0.2; + } else { + n1Packs = -0.3; + n1Nai = -0.8; + n1Wai = -0.4; + } + break; + case 3: + if (oat < cp) { + n1Packs = -0.6; + } else { + n1Packs = -0.6; + n1Nai = -0.9; + n1Wai = -1.2; + } + break; + } + } + + if (ac == 0) { + n1Packs = 0; + } + if (nacelle == 0) { + n1Nai = 0; + } + if (wing == 0) { + n1Wai = 0; + } + + bleed = n1Packs + n1Nai + n1Wai; + + return bleed; +} + +/// +/// Main N1 Limit Function +/// +/// 0-TO, 1-GA, 2-CLB, 3-MCT +/// +double +limitN1(int type, double altitude, double ambientTemp, double ambientPressure, double flexTemp, double ac, double nacelle, double wing) { + int rowMin = 0; + int rowMax = 0; + int loAltRow = 0; + int hiAltRow = 0; + double mach = 0; + double cp = 0; + double lp = 0; + double cn1 = 0; + double n1 = 0; + double cn1Flat = 0; + double cn1Last = 0; + double cn1Flex = 0; + double m = 0; + double b = 0; + double bleed = 0; + + // Set main variables per Limit Type + switch (type) { + case 0: + rowMin = 0; + rowMax = 20; + mach = 0; + break; + case 1: + rowMin = 21; + rowMax = 41; + mach = 0.225; + break; + case 2: + rowMin = 42; + rowMax = 58; + if (altitude <= 10000) { + mach = cas2mach(250, ambientPressure); + } else { + mach = cas2mach(300, ambientPressure); + if (mach > 0.78) + mach = 0.78; + } + break; + case 3: + rowMin = 59; + rowMax = 71; + mach = cas2mach(230, ambientPressure); + break; + } + + // Check for over/ underflows. Else, find top row value + if (altitude <= limits[rowMin][0]) { + hiAltRow = rowMin; + loAltRow = rowMin; + } else if (altitude >= limits[rowMax][0]) { + hiAltRow = rowMax; + loAltRow = rowMax; + } else { + hiAltRow = finder(altitude, rowMin); + loAltRow = hiAltRow - 1; + } + + // Define key table variables and interpolation + cp = interpolate(altitude, limits[loAltRow][0], limits[hiAltRow][0], limits[loAltRow][1], limits[hiAltRow][1]); + lp = interpolate(altitude, limits[loAltRow][0], limits[hiAltRow][0], limits[loAltRow][2], limits[hiAltRow][2]); + cn1Flat = interpolate(altitude, limits[loAltRow][0], limits[hiAltRow][0], limits[loAltRow][3], limits[hiAltRow][3]); + cn1Last = interpolate(altitude, limits[loAltRow][0], limits[hiAltRow][0], limits[loAltRow][4], limits[hiAltRow][4]); + cn1Flex = interpolate(altitude, limits[loAltRow][0], limits[hiAltRow][0], limits[loAltRow][5], limits[hiAltRow][5]); + + if (flexTemp > 0 && type <= 1) { // CN1 for Flex Case + if (flexTemp <= cp) { + cn1 = cn1Flat; + } else if (flexTemp > lp) { + m = (cn1Flex - cn1Last) / (100 - lp); + b = cn1Flex - m * 100; + cn1 = (m * flexTemp) + b; + } else { + m = (cn1Last - cn1Flat) / (lp - cp); + b = cn1Last - m * lp; + cn1 = (m * flexTemp) + b; + } + } + else { // CN1 for All other cases + if (ambientTemp <= cp) { + cn1 = cn1Flat; + } else { + m = (cn1Last - cn1Flat) / (lp - cp); + b = cn1Last - m * lp; + cn1 = (m * ambientTemp) + b; + } + } + + // Define bleed rating/ derating + bleed = bleedTotal(type, altitude, ambientTemp, cp, lp, flexTemp, ac, nacelle, wing); + + // Setting N1 + n1 = (cn1 * sqrt(ratios->theta2(mach, ambientTemp))) + bleed; + /*if (type == 3) { + std::cout << "FADEC: bleed= " << bleed << " cn1= " << cn1 << " theta2= " << sqrt(ratios->theta2(mach, ambientTemp)) + << " n1= " << n1 << std::endl; + }*/ + return n1; +} \ No newline at end of file diff --git a/hdw-a339x/src/wasm/systems/a320_systems/src/air_conditioning.rs b/hdw-a339x/src/wasm/systems/a320_systems/src/air_conditioning.rs index 5a5eb45ae..1dadabc76 100644 --- a/hdw-a339x/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/hdw-a339x/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -14,12 +14,12 @@ use systems::{ AutoManFaultPushButton, NormalOnPushButton, OnOffFaultPushButton, OnOffPushButton, SpringLoadedSwitch, ValueKnob, }, + payload::NumberOfPassengers, pneumatic::PneumaticContainer, shared::{ random_number, update_iterator::MaxStepLoop, AverageExt, CabinAltitude, CabinSimulation, - ControllerSignal, ElectricalBusType, EngineBleedPushbutton, EngineCorrectedN1, - EngineFirePushButtons, EngineStartState, LgciuWeightOnWheels, PackFlowValveState, - PneumaticBleed, + ControllerSignal, ElectricalBusType, EngineCorrectedN1, EngineFirePushButtons, + EngineStartState, LgciuWeightOnWheels, PackFlowValveState, PneumaticBleed, }, simulation::{ InitContext, Read, SimulationElement, SimulationElementVisitor, SimulatorReader, @@ -33,7 +33,7 @@ use uom::si::{ velocity::knot, }; -use crate::payload::{A320Pax, NumberOfPassengers}; +use crate::payload::A320Pax; pub(super) struct A320AirConditioning { a320_cabin: A320Cabin, @@ -67,7 +67,6 @@ impl A320AirConditioning { engine_fire_push_buttons: &impl EngineFirePushButtons, number_of_passengers: &impl NumberOfPassengers, pneumatic: &(impl EngineStartState + PackFlowValveState + PneumaticBleed), - pneumatic_overhead: &impl EngineBleedPushbutton<2>, pressurization_overhead: &A320PressurizationOverheadPanel, lgciu: [&impl LgciuWeightOnWheels; 2], ) { @@ -80,7 +79,6 @@ impl A320AirConditioning { engines, engine_fire_push_buttons, pneumatic, - pneumatic_overhead, &self.a320_pressurization_system, pressurization_overhead, lgciu, @@ -200,20 +198,18 @@ impl A320Cabin { } fn update_number_of_passengers(&mut self, number_of_passengers: &impl NumberOfPassengers) { - self.number_of_passengers[1] = (number_of_passengers.number_of_passengers(A320Pax::A) - + number_of_passengers.number_of_passengers(A320Pax::B) - + number_of_passengers.number_of_passengers(A320Pax::C) - + number_of_passengers.number_of_passengers(A320Pax::D) - + number_of_passengers.number_of_passengers(A320Pax::E) - ) - as u8; - self.number_of_passengers[2] = (number_of_passengers.number_of_passengers(A320Pax::F) - + number_of_passengers.number_of_passengers(A320Pax::G) - + number_of_passengers.number_of_passengers(A320Pax::H) - + number_of_passengers.number_of_passengers(A320Pax::I) - + number_of_passengers.number_of_passengers(A320Pax::J) - ) - as u8; + self.number_of_passengers[1] = + (number_of_passengers.number_of_passengers(A320Pax::A.into()) + + number_of_passengers.number_of_passengers(A320Pax::B.into()) + + number_of_passengers.number_of_passengers(A320Pax::C.into()) + + number_of_passengers.number_of_passengers(A320Pax::D.into()) + + number_of_passengers.number_of_passengers(A320Pax::E.into())) as u8; + self.number_of_passengers[2] = + (number_of_passengers.number_of_passengers(A320Pax::F.into()) + + number_of_passengers.number_of_passengers(A320Pax::G.into()) + + number_of_passengers.number_of_passengers(A320Pax::H.into()) + + number_of_passengers.number_of_passengers(A320Pax::I.into()) + + number_of_passengers.number_of_passengers(A320Pax::J.into())) as u8; } } @@ -289,7 +285,6 @@ impl A320AirConditioningSystem { engines: [&impl EngineCorrectedN1; 2], engine_fire_push_buttons: &impl EngineFirePushButtons, pneumatic: &(impl EngineStartState + PackFlowValveState + PneumaticBleed), - pneumatic_overhead: &impl EngineBleedPushbutton<2>, pressurization: &impl CabinAltitude, pressurization_overhead: &A320PressurizationOverheadPanel, lgciu: [&impl LgciuWeightOnWheels; 2], @@ -302,7 +297,6 @@ impl A320AirConditioningSystem { engines, engine_fire_push_buttons, pneumatic, - pneumatic_overhead, pressurization, pressurization_overhead, lgciu, @@ -332,11 +326,11 @@ impl A320AirConditioningSystem { .update(context, &self.mixer_unit, &self.acsc); self.air_conditioning_overhead - .set_pack_pushbutton_fault(self.pack_fault_determination(pneumatic)); + .set_pack_pushbutton_fault(self.pack_fault_determination()); } - pub fn pack_fault_determination(&self, pneumatic: &impl PackFlowValveState) -> [bool; 2] { - self.acsc.pack_fault_determination(pneumatic) + pub fn pack_fault_determination(&self) -> [bool; 2] { + self.acsc.pack_fault_determination() } pub fn mix_packs_air_update(&mut self, pack_container: &mut [impl PneumaticContainer; 2]) { @@ -774,10 +768,10 @@ mod tests { use ntest::assert_about_eq; use systems::{ electrical::{test::TestElectricitySource, ElectricalBus, Electricity}, - overhead::AutoOffFaultPushButton, pneumatic::{ valve::{DefaultValve, PneumaticExhaust}, ControllablePneumaticValve, EngineModeSelector, EngineState, PneumaticPipe, Precooler, + PressureTransducer, }, shared::{ arinc429::{Arinc429Word, SignStatus}, @@ -927,7 +921,7 @@ mod tests { struct TestPayload; impl NumberOfPassengers for TestPayload { - fn number_of_passengers(&self, _ps: A320Pax) -> i8 { + fn number_of_passengers(&self, _ps: usize) -> i8 { 0 } } @@ -995,6 +989,9 @@ mod tests { fn pack_flow_valve_air_flow(&self, pack_id: usize) -> MassRate { self.packs[pack_id - 1].pack_flow_valve_air_flow() } + fn pack_flow_valve_inlet_pressure(&self, pack_id: usize) -> Option { + self.packs[pack_id - 1].pack_flow_valve_inlet_pressure() + } } impl SimulationElement for TestPneumatic { fn accept(&mut self, visitor: &mut V) { @@ -1101,6 +1098,7 @@ mod tests { pack_container: PneumaticPipe, exhaust: PneumaticExhaust, pack_flow_valve: DefaultValve, + pack_inlet_pressure_sensor: PressureTransducer, } impl TestPneumaticPackComplex { fn new(engine_number: usize) -> Self { @@ -1113,6 +1111,9 @@ mod tests { ), exhaust: PneumaticExhaust::new(0.3, 0.3, Pressure::new::(0.)), pack_flow_valve: DefaultValve::new_closed(), + pack_inlet_pressure_sensor: PressureTransducer::new( + ElectricalBusType::DirectCurrentEssentialShed, + ), } } fn update( @@ -1124,6 +1125,7 @@ mod tests { self.pack_flow_valve.update_open_amount( pack_flow_valve_signals.pack_flow_controller(self.engine_number), ); + self.pack_inlet_pressure_sensor.update(context, from); self.pack_flow_valve .update_move_fluid(context, from, &mut self.pack_container); self.exhaust @@ -1135,6 +1137,9 @@ mod tests { fn pack_flow_valve_air_flow(&self) -> MassRate { self.pack_flow_valve.fluid_flow() } + fn pack_flow_valve_inlet_pressure(&self) -> Option { + self.pack_inlet_pressure_sensor.signal() + } } impl PneumaticContainer for TestPneumaticPackComplex { fn pressure(&self) -> Pressure { @@ -1167,22 +1172,11 @@ mod tests { self.pack_container.update_temperature(temperature); } } + impl SimulationElement for TestPneumaticPackComplex { + fn accept(&mut self, visitor: &mut V) { + self.pack_inlet_pressure_sensor.accept(visitor); - struct TestPneumaticOverhead { - engine_1_bleed: AutoOffFaultPushButton, - engine_2_bleed: AutoOffFaultPushButton, - } - impl TestPneumaticOverhead { - fn new(context: &mut InitContext) -> Self { - Self { - engine_1_bleed: AutoOffFaultPushButton::new_auto(context, "PNEU_ENG_1_BLEED"), - engine_2_bleed: AutoOffFaultPushButton::new_auto(context, "PNEU_ENG_2_BLEED"), - } - } - } - impl EngineBleedPushbutton<2> for TestPneumaticOverhead { - fn engine_bleed_pushbuttons_are_auto(&self) -> [bool; 2] { - [self.engine_1_bleed.is_auto(), self.engine_2_bleed.is_auto()] + visitor.visit(self); } } @@ -1233,7 +1227,6 @@ mod tests { engine_fire_push_buttons: TestEngineFirePushButtons, payload: TestPayload, pneumatic: TestPneumatic, - pneumatic_overhead: TestPneumaticOverhead, pressurization_overhead: A320PressurizationOverheadPanel, lgciu1: TestLgciu, lgciu2: TestLgciu, @@ -1266,7 +1259,6 @@ mod tests { engine_fire_push_buttons: TestEngineFirePushButtons::new(), payload: TestPayload {}, pneumatic: TestPneumatic::new(context), - pneumatic_overhead: TestPneumaticOverhead::new(context), pressurization_overhead: A320PressurizationOverheadPanel::new(context), lgciu1: TestLgciu::new(false), lgciu2: TestLgciu::new(false), @@ -1366,7 +1358,6 @@ mod tests { &self.engine_fire_push_buttons, &self.payload, &self.pneumatic, - &self.pneumatic_overhead, &self.pressurization_overhead, [&self.lgciu1, &self.lgciu2], ); diff --git a/hdw-a339x/src/wasm/systems/a320_systems/src/airframe/mod.rs b/hdw-a339x/src/wasm/systems/a320_systems/src/airframe/mod.rs new file mode 100644 index 000000000..23e2a7984 --- /dev/null +++ b/hdw-a339x/src/wasm/systems/a320_systems/src/airframe/mod.rs @@ -0,0 +1,214 @@ +use systems::{ + airframe::{CenterOfGravityData, WeightData}, + fuel::FuelPayload, + payload::{CargoPayload, LoadsheetInfo, PassengerPayload}, + simulation::{InitContext, SimulationElement, SimulationElementVisitor}, +}; +use uom::si::{f64::Mass, mass::kilogram}; + +#[cfg(test)] +mod test; + +pub struct A320Airframe { + center_of_gravity: CenterOfGravityData, + weight: WeightData, + // trim_horizontal_stabilizer: f64, +} +impl A320Airframe { + const LOADSHEET: LoadsheetInfo = LoadsheetInfo { + operating_empty_weight_kg: 279987., + operating_empty_position: (-28.31, 0., 0.), + per_pax_weight_kg: 84., + mean_aerodynamic_chord_size: 25.49, + leading_edge_mean_aerodynamic_chord: -21.63, + }; + + pub fn new(context: &mut InitContext) -> Self { + A320Airframe { + center_of_gravity: CenterOfGravityData::new(context), + weight: WeightData::new(context), + // trim_horizontal_stabilizer: 0.0, + } + } + + #[cfg(test)] + fn zero_fuel_weight_center_of_gravity(&self) -> f64 { + self.center_of_gravity.zero_fuel_weight_center_of_gravity() + } + + #[cfg(test)] + fn gross_weight_center_of_gravity(&self) -> f64 { + self.center_of_gravity.gross_weight_center_of_gravity() + } + + #[cfg(test)] + fn take_off_center_of_gravity(&self) -> f64 { + self.center_of_gravity.take_off_center_of_gravity() + } + + #[cfg(test)] + fn target_zero_fuel_weight_center_of_gravity(&self) -> f64 { + self.center_of_gravity + .target_zero_fuel_weight_center_of_gravity() + } + + #[cfg(test)] + fn target_gross_weight_center_of_gravity(&self) -> f64 { + self.center_of_gravity + .target_gross_weight_center_of_gravity() + } + + #[cfg(test)] + fn target_take_off_center_of_gravity(&self) -> f64 { + self.center_of_gravity.target_take_off_center_of_gravity() + } + + fn convert_cg(cg: f64) -> f64 { + -100. * (cg - Self::LOADSHEET.leading_edge_mean_aerodynamic_chord) + / Self::LOADSHEET.mean_aerodynamic_chord_size + } + + fn set_zero_fuel_weight_center_of_gravity(&mut self, zero_fuel_weight_cg: f64) { + let zero_fuel_weight_center_of_gravity = Self::convert_cg(zero_fuel_weight_cg); + self.center_of_gravity + .set_zero_fuel_weight_center_of_gravity(zero_fuel_weight_center_of_gravity) + } + + fn set_gross_weight_center_of_gravity(&mut self, gross_weight_cg: f64) { + let gross_weight_center_of_gravity = Self::convert_cg(gross_weight_cg); + self.center_of_gravity + .set_gross_weight_center_of_gravity(gross_weight_center_of_gravity); + } + + fn set_take_off_center_of_gravity(&mut self, take_off_cg: f64) { + let take_off_center_of_gravity = Self::convert_cg(take_off_cg); + self.center_of_gravity + .set_take_off_center_of_gravity(take_off_center_of_gravity); + } + + fn set_target_zero_fuel_weight_center_of_gravity(&mut self, tgt_zero_fuel_weight_cg: f64) { + let tgt_zero_fuel_weight_cg_percent_mac = Self::convert_cg(tgt_zero_fuel_weight_cg); + self.center_of_gravity + .set_target_zero_fuel_weight_center_of_gravity(tgt_zero_fuel_weight_cg_percent_mac) + } + + fn set_target_gross_weight_center_of_gravity(&mut self, tgt_gross_weight_cg: f64) { + let tgt_gross_weight_center_of_gravity = Self::convert_cg(tgt_gross_weight_cg); + self.center_of_gravity + .set_target_gross_weight_center_of_gravity(tgt_gross_weight_center_of_gravity); + } + + fn set_target_take_off_center_of_gravity(&mut self, tgt_tow_cg_percent_mac: f64) { + let tgt_tow_cg_percent_mac = Self::convert_cg(tgt_tow_cg_percent_mac); + self.center_of_gravity + .set_target_take_off_center_of_gravity(tgt_tow_cg_percent_mac); + } + + fn set_zero_fuel_weight(&mut self, zero_fuel_weight: Mass) { + self.weight.set_zero_fuel_weight(zero_fuel_weight); + } + + fn set_gross_weight(&mut self, gross_weight: Mass) { + self.weight.set_gross_weight(gross_weight); + } + + fn set_take_off_weight(&mut self, take_off_weight: Mass) { + self.weight.set_take_off_weight(take_off_weight); + } + + fn set_target_zero_fuel_weight(&mut self, tgt_zero_fuel_weight: Mass) { + self.weight + .set_target_zero_fuel_weight(tgt_zero_fuel_weight); + } + + fn set_target_gross_weight(&mut self, tgt_gross_weight: Mass) { + self.weight.set_target_gross_weight(tgt_gross_weight); + } + + fn set_target_take_off_weight(&mut self, tgt_take_off_weight: Mass) { + self.weight.set_target_take_off_weight(tgt_take_off_weight); + } + + pub(crate) fn update( + &mut self, + fuel_payload: &impl FuelPayload, + pax_payload: &impl PassengerPayload, + cargo_payload: &impl CargoPayload, + ) { + let total_pax = pax_payload.total_passenger_load(); + let total_cargo = cargo_payload.total_cargo_load(); + + let operating_empty_weight = + Mass::new::(Self::LOADSHEET.operating_empty_weight_kg); + + let empty_moment = Self::LOADSHEET.operating_empty_position.0 * operating_empty_weight; + let pax_moment = total_pax * pax_payload.fore_aft_center_of_gravity(); + let cargo_moment = total_cargo * cargo_payload.fore_aft_center_of_gravity(); + + let zero_fuel_weight_moment = empty_moment + pax_moment + cargo_moment; + let zero_fuel_weight = operating_empty_weight + total_pax + total_cargo; + let zero_fuel_weight_cg = + zero_fuel_weight_moment.get::() / zero_fuel_weight.get::(); + + self.set_zero_fuel_weight(zero_fuel_weight); + self.set_zero_fuel_weight_center_of_gravity(zero_fuel_weight_cg); + + let total_target_pax = pax_payload.total_target_passenger_load(); + let total_target_cargo = cargo_payload.total_target_cargo_load(); + + let pax_target_moment = total_target_pax * pax_payload.target_fore_aft_center_of_gravity(); + let cargo_target_moment = + total_target_cargo * cargo_payload.target_fore_aft_center_of_gravity(); + + let target_zero_fuel_weight_moment = empty_moment + pax_target_moment + cargo_target_moment; + let target_zero_fuel_weight = + operating_empty_weight + total_target_pax + total_target_cargo; + + let target_zero_fuel_weight_cg = target_zero_fuel_weight_moment.get::() + / target_zero_fuel_weight.get::(); + + self.set_target_zero_fuel_weight(target_zero_fuel_weight); + self.set_target_zero_fuel_weight_center_of_gravity(target_zero_fuel_weight_cg); + + let fuel = fuel_payload.total_load(); + let fuel_moment = fuel * fuel_payload.fore_aft_center_of_gravity(); + + let gross_weight_moment = zero_fuel_weight_moment + fuel_moment; + let gross_weight = zero_fuel_weight + fuel; + let gross_weight_cg = + gross_weight_moment.get::() / gross_weight.get::(); + + self.set_gross_weight(gross_weight); + self.set_gross_weight_center_of_gravity(gross_weight_cg); + + let target_gross_weight_moment = target_zero_fuel_weight_moment + fuel_moment; + let target_gross_weight = target_zero_fuel_weight + fuel; + let target_gross_weight_cg = + target_gross_weight_moment.get::() / target_gross_weight.get::(); + + self.set_target_gross_weight(target_gross_weight); + self.set_target_gross_weight_center_of_gravity(target_gross_weight_cg); + + // TODO: Implement Taxi Fuel Input/Calculation + + let tow = gross_weight; + let to_cg = gross_weight_cg; + + self.set_take_off_weight(tow); + self.set_take_off_center_of_gravity(to_cg); + + let target_tow = target_gross_weight; + let target_to_cg = target_gross_weight_cg; + + self.set_target_take_off_weight(target_tow); + self.set_target_take_off_center_of_gravity(target_to_cg); + } +} +impl SimulationElement for A320Airframe { + fn accept(&mut self, visitor: &mut T) { + self.center_of_gravity.accept(visitor); + self.weight.accept(visitor); + + visitor.visit(self); + } +} diff --git a/hdw-a339x/src/wasm/systems/a320_systems/src/hydraulic/mod.rs b/hdw-a339x/src/wasm/systems/a320_systems/src/hydraulic/mod.rs index 8eb959bc5..d968ba386 100644 --- a/hdw-a339x/src/wasm/systems/a320_systems/src/hydraulic/mod.rs +++ b/hdw-a339x/src/wasm/systems/a320_systems/src/hydraulic/mod.rs @@ -104,10 +104,7 @@ impl A330TiltingGearsFactory { let left_body_gear = Self::new_a330_body_gear(context, true); let right_body_gear = Self::new_a330_body_gear(context, false); - A330TiltingGears::new( - left_body_gear, - right_body_gear, - ) + A330TiltingGears::new(left_body_gear, right_body_gear) } } @@ -6018,10 +6015,7 @@ struct A330TiltingGears { right_body_gear: TiltingGear, } impl A330TiltingGears { - fn new( - left_body_gear: TiltingGear, - right_body_gear: TiltingGear, - ) -> Self { + fn new(left_body_gear: TiltingGear, right_body_gear: TiltingGear) -> Self { Self { left_body_gear, right_body_gear, @@ -6053,7 +6047,7 @@ mod tests { test::TestElectricitySource, ElectricalBus, Electricity, ElectricitySource, ExternalPowerSource, }, - engine::{leap_engine::LeapEngine, EngineFireOverheadPanel}, + engine::{trent_engine::TrentEngine, EngineFireOverheadPanel}, failures::FailureType, hydraulic::{ cargo_doors::{DoorControlState, HydraulicDoorController}, @@ -6206,8 +6200,8 @@ mod tests { } struct A320HydraulicsTestAircraft { pneumatics: A320TestPneumatics, - engine_1: LeapEngine, - engine_2: LeapEngine, + engine_1: TrentEngine, + engine_2: TrentEngine, hydraulics: A320Hydraulic, overhead: A320HydraulicOverheadPanel, autobrake_panel: AutobrakePanel, @@ -6245,8 +6239,8 @@ mod tests { fn new(context: &mut InitContext) -> Self { Self { pneumatics: A320TestPneumatics::new(), - engine_1: LeapEngine::new(context, 1), - engine_2: LeapEngine::new(context, 2), + engine_1: TrentEngine::new(context, 1), + engine_2: TrentEngine::new(context, 2), hydraulics: A320Hydraulic::new(context), overhead: A320HydraulicOverheadPanel::new(context), autobrake_panel: AutobrakePanel::new(context), @@ -6969,6 +6963,7 @@ mod tests { fn start_eng1(mut self, n2: Ratio) -> Self { self.write_by_name("GENERAL ENG STARTER ACTIVE:1", true); self.write_by_name("ENGINE_N2:1", n2); + self.write_by_name("ENGINE_N3:1", n2); self.write_by_name("TURB ENG CORRECTED N2:1", n2); self @@ -6977,6 +6972,7 @@ mod tests { fn start_eng2(mut self, n2: Ratio) -> Self { self.write_by_name("GENERAL ENG STARTER ACTIVE:2", true); self.write_by_name("ENGINE_N2:2", n2); + self.write_by_name("ENGINE_N3:2", n2); self.write_by_name("TURB ENG CORRECTED N2:2", n2); self @@ -6985,6 +6981,7 @@ mod tests { fn stop_eng1(mut self) -> Self { self.write_by_name("GENERAL ENG STARTER ACTIVE:1", false); self.write_by_name("ENGINE_N2:1", 0.); + self.write_by_name("ENGINE_N3:1", 0.); self.write_by_name("TURB ENG CORRECTED N2:1", 0.); self @@ -6993,6 +6990,7 @@ mod tests { fn stopping_eng1(mut self) -> Self { self.write_by_name("GENERAL ENG STARTER ACTIVE:1", false); self.write_by_name("ENGINE_N2:1", 25.); + self.write_by_name("ENGINE_N3:1", 25.); self.write_by_name("TURB ENG CORRECTED N2:1", 25.); self @@ -7001,6 +6999,7 @@ mod tests { fn stop_eng2(mut self) -> Self { self.write_by_name("GENERAL ENG STARTER ACTIVE:2", false); self.write_by_name("ENGINE_N2:2", 0.); + self.write_by_name("ENGINE_N3:2", 0.); self.write_by_name("TURB ENG CORRECTED N2:2", 0.); self @@ -7040,6 +7039,7 @@ mod tests { fn stopping_eng2(mut self) -> Self { self.write_by_name("GENERAL ENG STARTER ACTIVE:2", false); self.write_by_name("ENGINE_N2:2", 25.); + self.write_by_name("ENGINE_N3:2", 25.); self } diff --git a/hdw-a339x/src/wasm/systems/a320_systems/src/lib.rs b/hdw-a339x/src/wasm/systems/a320_systems/src/lib.rs new file mode 100644 index 000000000..0f1ed0239 --- /dev/null +++ b/hdw-a339x/src/wasm/systems/a320_systems/src/lib.rs @@ -0,0 +1,281 @@ +extern crate systems; + +mod air_conditioning; +mod airframe; +mod electrical; +mod fuel; +pub mod hydraulic; +mod navigation; +mod payload; +mod pneumatic; +mod power_consumption; + +use self::{ + air_conditioning::{A320AirConditioning, A320PressurizationOverheadPanel}, + fuel::A320Fuel, + payload::A320Payload, + pneumatic::{A320Pneumatic, A320PneumaticOverheadPanel}, +}; +use airframe::A320Airframe; +use electrical::{ + A320Electrical, A320ElectricalOverheadPanel, A320EmergencyElectricalOverheadPanel, + APU_START_MOTOR_BUS_TYPE, +}; +use hydraulic::{A320Hydraulic, A320HydraulicOverheadPanel}; +use navigation::A320RadioAltimeters; +use power_consumption::A320PowerConsumption; +use systems::enhanced_gpwc::EnhancedGroundProximityWarningComputer; +use systems::simulation::InitContext; +use uom::si::{f64::Length, length::nautical_mile}; + +use systems::{ + apu::{ + Aps3200ApuGenerator, Aps3200StartMotor, AuxiliaryPowerUnit, AuxiliaryPowerUnitFactory, + AuxiliaryPowerUnitFireOverheadPanel, AuxiliaryPowerUnitOverheadPanel, + }, + electrical::{Electricity, ElectricitySource, ExternalPowerSource}, + engine::{reverser_thrust::ReverserForce, trent_engine::TrentEngine, EngineFireOverheadPanel}, + hydraulic::brake_circuit::AutobrakePanel, + landing_gear::{LandingGear, LandingGearControlInterfaceUnitSet}, + navigation::adirs::{ + AirDataInertialReferenceSystem, AirDataInertialReferenceSystemOverheadPanel, + }, + shared::ElectricalBusType, + simulation::{Aircraft, SimulationElement, SimulationElementVisitor, UpdateContext}, +}; + +pub struct A320 { + adirs: AirDataInertialReferenceSystem, + adirs_overhead: AirDataInertialReferenceSystemOverheadPanel, + air_conditioning: A320AirConditioning, + apu: AuxiliaryPowerUnit, + apu_fire_overhead: AuxiliaryPowerUnitFireOverheadPanel, + apu_overhead: AuxiliaryPowerUnitOverheadPanel, + pneumatic_overhead: A320PneumaticOverheadPanel, + pressurization_overhead: A320PressurizationOverheadPanel, + electrical_overhead: A320ElectricalOverheadPanel, + emergency_electrical_overhead: A320EmergencyElectricalOverheadPanel, + payload: A320Payload, + airframe: A320Airframe, + fuel: A320Fuel, + engine_1: TrentEngine, + engine_2: TrentEngine, + engine_fire_overhead: EngineFireOverheadPanel<2>, + electrical: A320Electrical, + power_consumption: A320PowerConsumption, + ext_pwr: ExternalPowerSource, + lgcius: LandingGearControlInterfaceUnitSet, + hydraulic: A320Hydraulic, + hydraulic_overhead: A320HydraulicOverheadPanel, + autobrake_panel: AutobrakePanel, + landing_gear: LandingGear, + pneumatic: A320Pneumatic, + radio_altimeters: A320RadioAltimeters, + egpwc: EnhancedGroundProximityWarningComputer, + reverse_thrust: ReverserForce, +} +impl A320 { + pub fn new(context: &mut InitContext) -> A320 { + A320 { + adirs: AirDataInertialReferenceSystem::new(context), + adirs_overhead: AirDataInertialReferenceSystemOverheadPanel::new(context), + air_conditioning: A320AirConditioning::new(context), + apu: AuxiliaryPowerUnitFactory::new_aps3200( + context, + 1, + APU_START_MOTOR_BUS_TYPE, + ElectricalBusType::DirectCurrentBattery, + ElectricalBusType::DirectCurrentBattery, + ), + apu_fire_overhead: AuxiliaryPowerUnitFireOverheadPanel::new(context), + apu_overhead: AuxiliaryPowerUnitOverheadPanel::new(context), + pneumatic_overhead: A320PneumaticOverheadPanel::new(context), + pressurization_overhead: A320PressurizationOverheadPanel::new(context), + electrical_overhead: A320ElectricalOverheadPanel::new(context), + emergency_electrical_overhead: A320EmergencyElectricalOverheadPanel::new(context), + payload: A320Payload::new(context), + airframe: A320Airframe::new(context), + fuel: A320Fuel::new(context), + engine_1: TrentEngine::new(context, 1), + engine_2: TrentEngine::new(context, 2), + engine_fire_overhead: EngineFireOverheadPanel::new(context), + electrical: A320Electrical::new(context), + power_consumption: A320PowerConsumption::new(context), + ext_pwr: ExternalPowerSource::new(context, 1), + lgcius: LandingGearControlInterfaceUnitSet::new( + context, + ElectricalBusType::DirectCurrentEssential, + ElectricalBusType::DirectCurrentGndFltService, + ), + hydraulic: A320Hydraulic::new(context), + hydraulic_overhead: A320HydraulicOverheadPanel::new(context), + autobrake_panel: AutobrakePanel::new(context), + landing_gear: LandingGear::new(context), + pneumatic: A320Pneumatic::new(context), + radio_altimeters: A320RadioAltimeters::new(context), + egpwc: EnhancedGroundProximityWarningComputer::new( + context, + ElectricalBusType::DirectCurrent(1), + vec![ + Length::new::(10.0), + Length::new::(20.0), + Length::new::(40.0), + Length::new::(80.0), + Length::new::(160.0), + Length::new::(320.0), + ], + 0, + ), + reverse_thrust: ReverserForce::new(context), + } + } +} +impl Aircraft for A320 { + fn update_before_power_distribution( + &mut self, + context: &UpdateContext, + electricity: &mut Electricity, + ) { + self.apu.update_before_electrical( + context, + &self.apu_overhead, + &self.apu_fire_overhead, + self.pneumatic_overhead.apu_bleed_is_on(), + // This will be replaced when integrating the whole electrical system. + // For now we use the same logic as found in the JavaScript code; ignoring whether or not + // the engine generators are supplying electricity. + self.electrical_overhead.apu_generator_is_on() + && !(self.electrical_overhead.external_power_is_on() + && self.electrical_overhead.external_power_is_available()), + self.pneumatic.apu_bleed_air_valve(), + self.fuel.left_inner_tank_has_fuel_remaining(), + ); + + self.electrical.update( + context, + electricity, + &self.ext_pwr, + &self.electrical_overhead, + &self.emergency_electrical_overhead, + &mut self.apu, + &self.apu_overhead, + &self.engine_fire_overhead, + [&self.engine_1, &self.engine_2], + &self.hydraulic, + self.lgcius.lgciu1(), + ); + + self.electrical_overhead + .update_after_electrical(&self.electrical, electricity); + self.emergency_electrical_overhead + .update_after_electrical(context, &self.electrical); + self.payload.update(context); + self.airframe + .update(&self.fuel, &self.payload, &self.payload); + } + + fn update_after_power_distribution(&mut self, context: &UpdateContext) { + self.apu.update_after_power_distribution(); + self.apu_overhead.update_after_apu(&self.apu); + + self.lgcius.update( + context, + &self.landing_gear, + self.hydraulic.gear_system(), + self.ext_pwr.output_potential().is_powered(), + ); + + self.radio_altimeters.update(context); + + self.hydraulic.update( + context, + &self.engine_1, + &self.engine_2, + &self.hydraulic_overhead, + &self.autobrake_panel, + &self.engine_fire_overhead, + &self.lgcius, + &self.emergency_electrical_overhead, + &self.electrical, + &self.pneumatic, + &self.adirs, + ); + + self.reverse_thrust.update( + context, + [&self.engine_1, &self.engine_2], + self.hydraulic.reversers_position(), + ); + + self.pneumatic.update_hydraulic_reservoir_spatial_volumes( + self.hydraulic.green_reservoir(), + self.hydraulic.blue_reservoir(), + self.hydraulic.yellow_reservoir(), + ); + + self.hydraulic_overhead.update(&self.hydraulic); + + self.adirs.update(context, &self.adirs_overhead); + self.adirs_overhead.update(context, &self.adirs); + + self.power_consumption.update(context); + + self.pneumatic.update( + context, + [&self.engine_1, &self.engine_2], + &self.pneumatic_overhead, + &self.engine_fire_overhead, + &self.apu, + &self.air_conditioning, + [self.lgcius.lgciu1(), self.lgcius.lgciu2()], + ); + self.air_conditioning + .mix_packs_air_update(self.pneumatic.packs()); + self.air_conditioning.update( + context, + &self.adirs, + [&self.engine_1, &self.engine_2], + &self.engine_fire_overhead, + &self.payload, + &self.pneumatic, + &self.pressurization_overhead, + [self.lgcius.lgciu1(), self.lgcius.lgciu2()], + ); + + self.egpwc.update(&self.adirs, self.lgcius.lgciu1()); + } +} +impl SimulationElement for A320 { + fn accept(&mut self, visitor: &mut T) { + self.adirs.accept(visitor); + self.adirs_overhead.accept(visitor); + self.air_conditioning.accept(visitor); + self.apu.accept(visitor); + self.apu_fire_overhead.accept(visitor); + self.apu_overhead.accept(visitor); + self.payload.accept(visitor); + self.airframe.accept(visitor); + self.electrical_overhead.accept(visitor); + self.emergency_electrical_overhead.accept(visitor); + self.fuel.accept(visitor); + self.pneumatic_overhead.accept(visitor); + self.pressurization_overhead.accept(visitor); + self.engine_1.accept(visitor); + self.engine_2.accept(visitor); + self.engine_fire_overhead.accept(visitor); + self.electrical.accept(visitor); + self.power_consumption.accept(visitor); + self.ext_pwr.accept(visitor); + self.lgcius.accept(visitor); + self.radio_altimeters.accept(visitor); + self.autobrake_panel.accept(visitor); + self.hydraulic.accept(visitor); + self.hydraulic_overhead.accept(visitor); + self.landing_gear.accept(visitor); + self.pneumatic.accept(visitor); + self.egpwc.accept(visitor); + self.reverse_thrust.accept(visitor); + + visitor.visit(self); + } +} diff --git a/hdw-a339x/src/wasm/systems/a320_systems/src/payload/mod.rs b/hdw-a339x/src/wasm/systems/a320_systems/src/payload/mod.rs index 753e16a89..aa06d9f5c 100644 --- a/hdw-a339x/src/wasm/systems/a320_systems/src/payload/mod.rs +++ b/hdw-a339x/src/wasm/systems/a320_systems/src/payload/mod.rs @@ -1,27 +1,20 @@ -use enum_map::{Enum, EnumMap}; -use lazy_static::lazy_static; +use nalgebra::Vector3; -use std::{cell::Cell, rc::Rc, time::Duration}; +use std::{cell::Cell, rc::Rc}; use uom::si::{f64::Mass, mass::kilogram}; use systems::{ - accept_iterable, - payload::{BoardingRate, Cargo, CargoInfo, GsxState, Pax, PaxInfo}, - simulation::{ - InitContext, Read, SimulationElement, SimulationElementVisitor, SimulatorReader, - SimulatorWriter, UpdateContext, VariableIdentifier, Write, + payload::{ + BoardingAgent, BoardingSounds, Cargo, CargoDeck, CargoInfo, CargoPayload, + NumberOfPassengers, PassengerDeck, PassengerPayload, Pax, PaxInfo, PayloadManager, }, + simulation::{InitContext, SimulationElement, SimulationElementVisitor, UpdateContext}, }; #[cfg(test)] -pub mod test; +mod test; -pub trait NumberOfPassengers { - fn number_of_passengers(&self, ps: A320Pax) -> i8; -} - -#[derive(Debug, Clone, Copy, Enum)] pub enum A320Pax { A, B, @@ -32,672 +25,386 @@ pub enum A320Pax { G, H, I, - J + J, } -impl A320Pax { - pub fn iterator() -> impl Iterator { - [A320Pax::A, A320Pax::B, A320Pax::C, A320Pax::D, A320Pax::E, A320Pax::F, A320Pax::G, A320Pax::H, A320Pax::I, A320Pax::J] - .iter() - .copied() +impl From for usize { + fn from(value: A320Pax) -> Self { + value as usize + } +} +impl From for A320Pax { + fn from(value: usize) -> Self { + match value { + 0 => A320Pax::A, + 1 => A320Pax::B, + 2 => A320Pax::C, + 3 => A320Pax::D, + 4 => A320Pax::E, + 5 => A320Pax::F, + 6 => A320Pax::G, + 7 => A320Pax::H, + 8 => A320Pax::I, + 9 => A320Pax::J, + i => panic!("Cannot convert from {} to A320Pax.", i), + } } } -#[derive(Debug, Clone, Copy, Enum)] +#[cfg(test)] pub enum A320Cargo { FwdBaggage, AftContainer, AftBaggage, AftBulkLoose, } -impl A320Cargo { - pub fn iterator() -> impl Iterator { - [ - A320Cargo::FwdBaggage, - A320Cargo::AftContainer, - A320Cargo::AftBaggage, - A320Cargo::AftBulkLoose, - ] - .iter() - .copied() +#[cfg(test)] +impl From for usize { + fn from(value: A320Cargo) -> Self { + value as usize } } - -lazy_static! { - static ref A320_PAX: EnumMap = EnumMap::from_array([ - PaxInfo::new(36, "PAX_A", "PAYLOAD_STATION_1_REQ",), - PaxInfo::new(36, "PAX_B", "PAYLOAD_STATION_2_REQ",), - PaxInfo::new(39, "PAX_C", "PAYLOAD_STATION_3_REQ",), - PaxInfo::new(48, "PAX_D", "PAYLOAD_STATION_4_REQ",), - PaxInfo::new(45, "PAX_E", "PAYLOAD_STATION_5_REQ",), - PaxInfo::new(45, "PAX_F", "PAYLOAD_STATION_6_REQ",), - PaxInfo::new(45, "PAX_G", "PAYLOAD_STATION_7_REQ",), - PaxInfo::new(45, "PAX_H", "PAYLOAD_STATION_8_REQ",), - PaxInfo::new(45, "PAX_I", "PAYLOAD_STATION_9_REQ",), - PaxInfo::new(52, "PAX_J", "PAYLOAD_STATION_10_REQ",) - ]); - static ref A320_CARGO: EnumMap = EnumMap::from_array([ - CargoInfo::new( - Mass::new::(5800.), - "CARGO_FWD_BAGGAGE_CONTAINER", - "PAYLOAD_STATION_11_REQ", - ), - CargoInfo::new( - Mass::new::(17061.), - "CARGO_AFT_CONTAINER", - "PAYLOAD_STATION_12_REQ", - ), - CargoInfo::new( - Mass::new::(18507.), - "CARGO_AFT_BAGGAGE", - "PAYLOAD_STATION_13_REQ", - ), - CargoInfo::new( - Mass::new::(3468.), - "CARGO_AFT_BULK_LOOSE", - "PAYLOAD_STATION_14_REQ", - ) - ]); -} - -pub struct A320BoardingSounds { - pax_board_id: VariableIdentifier, - pax_deboard_id: VariableIdentifier, - pax_complete_id: VariableIdentifier, - pax_ambience_id: VariableIdentifier, - pax_boarding: bool, - pax_deboarding: bool, - pax_complete: bool, - pax_ambience: bool, -} -impl A320BoardingSounds { - pub fn new( - pax_board_id: VariableIdentifier, - pax_deboard_id: VariableIdentifier, - pax_complete_id: VariableIdentifier, - pax_ambience_id: VariableIdentifier, - ) -> Self { - A320BoardingSounds { - pax_board_id, - pax_deboard_id, - pax_complete_id, - pax_ambience_id, - pax_boarding: false, - pax_deboarding: false, - pax_complete: false, - pax_ambience: false, +#[cfg(test)] +impl From for A320Cargo { + fn from(value: usize) -> Self { + match value { + 0 => A320Cargo::FwdBaggage, + 1 => A320Cargo::AftContainer, + 2 => A320Cargo::AftBaggage, + 3 => A320Cargo::AftBulkLoose, + i => panic!("Cannot convert from {} to A320Cargo.", i), } } - - fn start_pax_boarding(&mut self) { - self.pax_boarding = true; - } - - fn stop_pax_boarding(&mut self) { - self.pax_boarding = false; - } - - fn start_pax_deboarding(&mut self) { - self.pax_deboarding = true; - } - - fn stop_pax_deboarding(&mut self) { - self.pax_deboarding = false; - } - - fn start_pax_complete(&mut self) { - self.pax_complete = true; - } - - fn stop_pax_complete(&mut self) { - self.pax_complete = false; - } - - fn start_pax_ambience(&mut self) { - self.pax_ambience = true; - } - - fn stop_pax_ambience(&mut self) { - self.pax_ambience = false; - } -} -impl SimulationElement for A320BoardingSounds { - fn write(&self, writer: &mut SimulatorWriter) { - writer.write(&self.pax_board_id, self.pax_boarding); - writer.write(&self.pax_deboard_id, self.pax_deboarding); - writer.write(&self.pax_complete_id, self.pax_complete); - writer.write(&self.pax_ambience_id, self.pax_ambience); - } } pub struct A320Payload { - developer_state_id: VariableIdentifier, - is_boarding_id: VariableIdentifier, - board_rate_id: VariableIdentifier, - per_pax_weight_id: VariableIdentifier, - - is_gsx_enabled_id: VariableIdentifier, - gsx_boarding_state_id: VariableIdentifier, - gsx_deboarding_state_id: VariableIdentifier, - gsx_pax_boarding_id: VariableIdentifier, - gsx_pax_deboarding_id: VariableIdentifier, - gsx_cargo_boarding_pct_id: VariableIdentifier, - gsx_cargo_deboarding_pct_id: VariableIdentifier, - - developer_state: i8, - is_boarding: bool, - board_rate: BoardingRate, - per_pax_weight: Rc>, - - is_gsx_enabled: bool, - gsx_boarding_state: GsxState, - gsx_deboarding_state: GsxState, - gsx_pax_boarding: i32, - gsx_pax_deboarding: i32, - gsx_cargo_boarding_pct: f64, - gsx_cargo_deboarding_pct: f64, - - pax: Vec, - cargo: Vec, - boarding_sounds: A320BoardingSounds, - time: Duration, + payload_manager: PayloadManager<10, 2, 4>, } impl A320Payload { - const DEFAULT_PER_PAX_WEIGHT_KG: f64 = 84.; + // Note: These constants reflect flight_model.cfg values and will have to be updated in sync with the configuration + pub const DEFAULT_PER_PAX_WEIGHT_KG: f64 = 84.; + const A320_PAX: [PaxInfo<'_>; 10] = [ + PaxInfo { + max_pax: 36, + position: (40., 0., 0.), + pax_id: "PAX_A", + payload_id: "PAYLOAD_STATION_1_REQ", + }, + PaxInfo { + max_pax: 36, + position: (30., 0., 0.), + pax_id: "PAX_B", + payload_id: "PAYLOAD_STATION_2_REQ", + }, + PaxInfo { + max_pax: 39, + position: (20., 0., 0.), + pax_id: "PAX_C", + payload_id: "PAYLOAD_STATION_3_REQ", + }, + PaxInfo { + max_pax: 48, + position: (10., 0., 0.), + pax_id: "PAX_D", + payload_id: "PAYLOAD_STATION_4_REQ", + }, + PaxInfo { + max_pax: 45, + position: (-10., 0., 0.), + pax_id: "PAX_E", + payload_id: "PAYLOAD_STATION_5_REQ", + }, + PaxInfo { + max_pax: 45, + position: (-30., 0., 0.), + pax_id: "PAX_F", + payload_id: "PAYLOAD_STATION_6_REQ", + }, + PaxInfo { + max_pax: 45, + position: (-50., 0., 0.), + pax_id: "PAX_G", + payload_id: "PAYLOAD_STATION_7_REQ", + }, + PaxInfo { + max_pax: 45, + position: (-60., 0., 0.), + pax_id: "PAX_H", + payload_id: "PAYLOAD_STATION_8_REQ", + }, + PaxInfo { + max_pax: 45, + position: (-70., 0., 0.), + pax_id: "PAX_I", + payload_id: "PAYLOAD_STATION_9_REQ", + }, + PaxInfo { + max_pax: 45, + position: (-80., 0., 0.), + pax_id: "PAX_J", + payload_id: "PAYLOAD_STATION_10_REQ", + }, + ]; + + const A320_CARGO: [CargoInfo<'_>; 4] = [ + CargoInfo { + max_cargo_kg: 5800., + position: (40., 0., 0.), + cargo_id: "CARGO_FWD_BAGGAGE_CONTAINER", + payload_id: "PAYLOAD_STATION_11_REQ", + }, + CargoInfo { + max_cargo_kg: 17061., + position: (0., 0., 0.), + cargo_id: "CARGO_AFT_CONTAINER", + payload_id: "PAYLOAD_STATION_12_REQ", + }, + CargoInfo { + max_cargo_kg: 18507., + position: (-55., 0., 0.), + cargo_id: "CARGO_AFT_BAGGAGE", + payload_id: "PAYLOAD_STATION_13_REQ", + }, + CargoInfo { + max_cargo_kg: 3468., + position: (-80., 0., 0.), + cargo_id: "CARGO_AFT_BULK_LOOSE", + payload_id: "PAYLOAD_STATION_14_REQ", + }, + ]; + pub fn new(context: &mut InitContext) -> Self { let per_pax_weight = Rc::new(Cell::new(Mass::new::( Self::DEFAULT_PER_PAX_WEIGHT_KG, ))); + let developer_state = Rc::new(Cell::new(0)); + let boarding_sounds = BoardingSounds::new(context); + let pax = Self::A320_PAX.map(|p| { + Pax::new( + context.get_identifier(p.pax_id.to_owned()), + context.get_identifier(format!("{}_DESIRED", p.pax_id)), + context.get_identifier(p.payload_id.to_owned()), + developer_state.clone(), + per_pax_weight.clone(), + Vector3::new(p.position.0, p.position.1, p.position.2), + p.max_pax, + ) + }); + + let cargo = Self::A320_CARGO.map(|c| { + Cargo::new( + context.get_identifier(c.cargo_id.to_owned()), + context.get_identifier(format!("{}_DESIRED", c.cargo_id)), + context.get_identifier(c.payload_id.to_owned()), + developer_state.clone(), + Vector3::new(c.position.0, c.position.1, c.position.2), + Mass::new::(c.max_cargo_kg), + ) + }); + let boarding_agents = [ + BoardingAgent::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), + BoardingAgent::new([9, 8, 7, 6, 5, 4, 3, 2, 1, 0]), + ]; + + let passenger_deck = PassengerDeck::new(context, pax, boarding_agents); + let cargo_deck = CargoDeck::new(cargo); - let mut pax = Vec::new(); - - for ps in A320Pax::iterator() { - pax.push(Pax::new( - context.get_identifier(A320_PAX[ps].pax_id.to_owned()), - context.get_identifier(format!("{}_DESIRED", A320_PAX[ps].pax_id).to_owned()), - context.get_identifier(A320_PAX[ps].payload_id.to_owned()), - Rc::clone(&per_pax_weight), - )); - } - - let mut cargo = Vec::new(); - for cs in A320Cargo::iterator() { - cargo.push(Cargo::new( - context.get_identifier(A320_CARGO[cs].cargo_id.to_owned()), - context.get_identifier(format!("{}_DESIRED", A320_CARGO[cs].cargo_id).to_owned()), - context.get_identifier(A320_CARGO[cs].payload_id.to_owned()), - )); - } A320Payload { - developer_state_id: context.get_identifier("DEVELOPER_STATE".to_owned()), - is_boarding_id: context.get_identifier("BOARDING_STARTED_BY_USR".to_owned()), - board_rate_id: context.get_identifier("BOARDING_RATE".to_owned()), - per_pax_weight_id: context.get_identifier("WB_PER_PAX_WEIGHT".to_owned()), - - is_gsx_enabled_id: context.get_identifier("GSX_PAYLOAD_SYNC_ENABLED".to_owned()), - gsx_boarding_state_id: context.get_identifier("FSDT_GSX_BOARDING_STATE".to_owned()), - gsx_deboarding_state_id: context.get_identifier("FSDT_GSX_DEBOARDING_STATE".to_owned()), - gsx_pax_boarding_id: context - .get_identifier("FSDT_GSX_NUMPASSENGERS_BOARDING_TOTAL".to_owned()), - gsx_pax_deboarding_id: context - .get_identifier("FSDT_GSX_NUMPASSENGERS_DEBOARDING_TOTAL".to_owned()), - gsx_cargo_boarding_pct_id: context - .get_identifier("FSDT_GSX_BOARDING_CARGO_PERCENT".to_owned()), - gsx_cargo_deboarding_pct_id: context - .get_identifier("FSDT_GSX_DEBOARDING_CARGO_PERCENT".to_owned()), - - developer_state: 0, - is_boarding: false, - board_rate: BoardingRate::Instant, - per_pax_weight, - is_gsx_enabled: false, - gsx_boarding_state: GsxState::None, - gsx_deboarding_state: GsxState::None, - gsx_pax_boarding: 0, - gsx_pax_deboarding: 0, - gsx_cargo_boarding_pct: 0., - gsx_cargo_deboarding_pct: 0., - boarding_sounds: A320BoardingSounds::new( - context.get_identifier("SOUND_PAX_BOARDING".to_owned()), - context.get_identifier("SOUND_PAX_DEBOARDING".to_owned()), - context.get_identifier("SOUND_BOARDING_COMPLETE".to_owned()), - context.get_identifier("SOUND_PAX_AMBIENCE".to_owned()), + payload_manager: PayloadManager::new( + context, + per_pax_weight, + developer_state, + boarding_sounds, + passenger_deck, + cargo_deck, + 1000, + 5000, ), - pax, - cargo, - time: Duration::from_nanos(0), } } pub(crate) fn update(&mut self, context: &UpdateContext) { - if !self.is_developer_state_active() { - self.ensure_payload_sync() - }; - - if self.is_gsx_enabled() { - self.stop_boarding(); - self.stop_boarding_sounds(); - self.update_extern_gsx(context); - } else { - self.update_intern(context); - } - } - - fn ensure_payload_sync(&mut self) { - for ps in A320Pax::iterator() { - if !self.pax_is_sync(ps) { - self.sync_pax(ps); - } - } - - for cs in A320Cargo::iterator() { - if !self.cargo_is_sync(cs) { - self.sync_cargo(cs); - } - } + self.payload_manager.update(context.delta()); } - fn update_extern_gsx(&mut self, context: &UpdateContext) { - self.update_gsx_deboarding(context); - self.update_gsx_boarding(context); - } - - fn update_gsx_deboarding(&mut self, _context: &UpdateContext) { - self.update_pax_ambience(); - match self.gsx_deboarding_state { - GsxState::None | GsxState::Available | GsxState::NotAvailable | GsxState::Bypassed => {} - GsxState::Requested => { - self.update_cargo_loaded(); - self.reset_all_pax_targets(); - self.reset_all_cargo_targets(); - } - GsxState::Completed => { - self.move_all_payload(); - self.reset_cargo_loaded(); - self.reset_all_cargo_targets(); - } - GsxState::Performing => { - self.move_all_pax_num( - self.total_pax_num() - (self.total_max_pax() - self.gsx_pax_deboarding), - ); - self.load_all_cargo_percent(100. - self.gsx_cargo_deboarding_pct); - } - } + fn pax_num(&self, ps: usize) -> i8 { + self.payload_manager.pax_num(ps) } - fn update_cargo_loaded(&mut self) { - for cs in A320Cargo::iterator() { - self.cargo[cs as usize].update_cargo_loaded() - } + #[cfg(test)] + fn total_pax_num(&self) -> i32 { + self.payload_manager.total_pax_num() } - fn reset_cargo_loaded(&mut self) { - for cs in A320Cargo::iterator() { - self.cargo[cs as usize].reset_cargo_loaded() - } + fn total_passenger_load(&self) -> Mass { + self.payload_manager.total_passenger_load() } - fn update_gsx_boarding(&mut self, _context: &UpdateContext) { - self.update_pax_ambience(); - match self.gsx_boarding_state { - GsxState::None - | GsxState::Available - | GsxState::NotAvailable - | GsxState::Bypassed - | GsxState::Requested => {} - GsxState::Completed => { - for cs in A320Cargo::iterator() { - self.move_all_cargo(cs); - } - } - GsxState::Performing => { - self.move_all_pax_num(self.gsx_pax_boarding - self.total_pax_num()); - self.load_all_cargo_percent(self.gsx_cargo_boarding_pct); - } - } + fn total_target_passenger_load(&self) -> Mass { + self.payload_manager.total_target_passenger_load() } - fn update_intern(&mut self, context: &UpdateContext) { - self.update_pax_ambience(); - - if !self.is_boarding { - self.time = Duration::from_nanos(0); - self.stop_boarding_sounds(); - return; - } - - let ms_delay = if self.board_rate() == BoardingRate::Instant { - 0 - } else if self.board_rate() == BoardingRate::Fast { - 1000 - } else { - 5000 - }; - - let delta_time = context.delta(); - self.time += delta_time; - if self.time.as_millis() > ms_delay { - self.time = Duration::from_nanos(0); - self.update_pax(); - self.update_cargo(); - } - // Check sound before updating boarding status - self.update_boarding_sounds(); - self.update_boarding_status(); + fn total_passenger_moment(&self) -> Vector3 { + self.payload_manager.total_passenger_moment() } - fn update_boarding_status(&mut self) { - if self.is_fully_loaded() { - self.is_boarding = false; - } + fn total_target_passenger_moment(&self) -> Vector3 { + self.payload_manager.total_target_passenger_moment() } - fn update_boarding_sounds(&mut self) { - let pax_board = self.is_pax_boarding(); - self.play_sound_pax_boarding(pax_board); - - let pax_deboard = self.is_pax_deboarding(); - self.play_sound_pax_deboarding(pax_deboard); - - let pax_complete = self.is_pax_loaded() && self.is_boarding(); - self.play_sound_pax_complete(pax_complete); + fn total_cargo_moment(&self) -> Vector3 { + self.payload_manager.total_cargo_moment() } - fn update_pax_ambience(&mut self) { - let pax_ambience = !self.has_no_pax(); - self.play_sound_pax_ambience(pax_ambience); + fn total_target_cargo_moment(&self) -> Vector3 { + self.payload_manager.total_target_cargo_moment() } - fn play_sound_pax_boarding(&mut self, playing: bool) { - if playing { - self.boarding_sounds.start_pax_boarding(); + fn passenger_center_of_gravity(&self) -> Vector3 { + let total_pax_load = self.total_passenger_load().get::(); + if total_pax_load > 0. { + self.total_passenger_moment() / total_pax_load } else { - self.boarding_sounds.stop_pax_boarding(); + Vector3::zeros() } } - fn play_sound_pax_deboarding(&mut self, playing: bool) { - if playing { - self.boarding_sounds.start_pax_deboarding(); + fn target_passenger_center_of_gravity(&self) -> Vector3 { + let total_target_pax_load = self.total_target_passenger_load().get::(); + if total_target_pax_load > 0. { + self.total_target_passenger_moment() / total_target_pax_load } else { - self.boarding_sounds.stop_pax_deboarding(); + Vector3::zeros() } } - fn play_sound_pax_complete(&mut self, playing: bool) { - if playing { - self.boarding_sounds.start_pax_complete(); + fn cargo_center_of_gravity(&self) -> Vector3 { + let total_cargo_load = self.total_cargo_load().get::(); + if total_cargo_load > 0. { + self.total_cargo_moment() / total_cargo_load } else { - self.boarding_sounds.stop_pax_complete(); + Vector3::zeros() } } - fn play_sound_pax_ambience(&mut self, playing: bool) { - if playing { - self.boarding_sounds.start_pax_ambience(); + fn target_cargo_center_of_gravity(&self) -> Vector3 { + let total_target_cargo_load = self.total_target_cargo_load().get::(); + if total_target_cargo_load > 0. { + self.total_target_cargo_moment() / total_target_cargo_load } else { - self.boarding_sounds.stop_pax_ambience(); - } - } - - fn stop_boarding_sounds(&mut self) { - self.boarding_sounds.stop_pax_boarding(); - self.boarding_sounds.stop_pax_deboarding(); - self.boarding_sounds.stop_pax_complete(); - } - - fn reset_all_pax_targets(&mut self) { - for ps in A320Pax::iterator() { - self.reset_pax_target(ps); - } - } - - fn move_all_pax_num(&mut self, pax_diff: i32) { - if pax_diff > 0 { - for _ in 0..pax_diff { - for ps in A320Pax::iterator() { - if self.pax_is_target(ps) { - continue; - } - self.move_one_pax(ps); - break; - } - } - } - } - - fn update_pax(&mut self) { - for ps in A320Pax::iterator() { - if self.pax_is_target(ps) { - continue; - } - if self.board_rate == BoardingRate::Instant { - self.move_all_pax(ps); - } else { - self.move_one_pax(ps); - break; - } - } - } - - fn reset_all_cargo_targets(&mut self) { - for cs in A320Cargo::iterator() { - self.reset_cargo_target(cs); - } - } - - fn update_cargo(&mut self) { - for cs in A320Cargo::iterator() { - if self.cargo_is_target(cs) { - continue; - } - if self.board_rate == BoardingRate::Instant { - self.move_all_cargo(cs); - } else { - self.move_one_cargo(cs); - break; - } - } - } - - fn is_developer_state_active(&self) -> bool { - self.developer_state > 0 - } - - fn is_pax_boarding(&self) -> bool { - for ps in A320Pax::iterator() { - if self.pax_num(ps) < self.pax_target_num(ps) && self.is_boarding() { - return true; - } - } - false - } - - fn is_pax_deboarding(&self) -> bool { - for ps in A320Pax::iterator() { - if self.pax_num(ps) > self.pax_target_num(ps) && self.is_boarding() { - return true; - } - } - false - } - - fn is_pax_loaded(&self) -> bool { - for ps in A320Pax::iterator() { - if !self.pax_is_target(ps) { - return false; - } - } - true - } - - fn is_cargo_loaded(&self) -> bool { - for cs in A320Cargo::iterator() { - if !self.cargo_is_target(cs) { - return false; - } - } - true - } - - fn is_fully_loaded(&self) -> bool { - self.is_pax_loaded() && self.is_cargo_loaded() - } - - fn has_no_pax(&mut self) -> bool { - for ps in A320Pax::iterator() { - let pax_num = 0; - if self.pax_num(ps) == pax_num { - return true; - } + Vector3::zeros() } - false } - fn board_rate(&self) -> BoardingRate { - self.board_rate + #[cfg(test)] + fn max_pax(&self, ps: usize) -> i8 { + self.payload_manager.max_pax(ps) } - fn pax_num(&self, ps: A320Pax) -> i8 { - self.pax[ps as usize].pax_num() as i8 + #[cfg(test)] + fn cargo(&self, cs: usize) -> Mass { + self.payload_manager.cargo(cs) } - fn total_pax_num(&self) -> i32 { - let mut pax_num = 0; - for ps in A320Pax::iterator() { - pax_num += self.pax_num(ps) as i32; - } - pax_num + #[cfg(test)] + fn cargo_payload(&self, cs: usize) -> Mass { + self.payload_manager.cargo_payload(cs) } - fn total_max_pax(&self) -> i32 { - let mut max_pax = 0; - for ps in A320Pax::iterator() { - max_pax += A320_PAX[ps].max_pax as i32; - } - max_pax + #[cfg(test)] + fn pax_payload(&self, ps: usize) -> Mass { + self.payload_manager.pax_payload(ps) } - fn pax_target_num(&self, ps: A320Pax) -> i8 { - self.pax[ps as usize].pax_target_num() as i8 + #[cfg(test)] + fn override_pax_payload(&mut self, ps: usize, payload: Mass) { + self.payload_manager.override_pax_payload(ps, payload) } - fn pax_is_sync(&mut self, ps: A320Pax) -> bool { - self.pax[ps as usize].payload_is_sync() + #[cfg(test)] + fn max_cargo(&self, cs: usize) -> Mass { + self.payload_manager.max_cargo(cs) } - fn pax_is_target(&self, ps: A320Pax) -> bool { - self.pax[ps as usize].pax_is_target() + #[cfg(test)] + fn sound_pax_boarding_playing(&self) -> bool { + self.payload_manager.sound_pax_boarding_playing() } - fn sync_pax(&mut self, ps: A320Pax) { - self.pax[ps as usize].load_payload(); + #[cfg(test)] + fn sound_pax_deboarding_playing(&self) -> bool { + self.payload_manager.sound_pax_deboarding_playing() } - fn move_all_pax(&mut self, ps: A320Pax) { - self.pax[ps as usize].move_all_pax(); + #[cfg(test)] + fn sound_pax_complete_playing(&self) -> bool { + self.payload_manager.sound_pax_complete_playing() } - fn move_one_pax(&mut self, ps: A320Pax) { - self.pax[ps as usize].move_one_pax(); - } - - fn reset_pax_target(&mut self, ps: A320Pax) { - self.pax[ps as usize].reset_pax_target(); + #[cfg(test)] + fn sound_pax_ambience_playing(&self) -> bool { + self.payload_manager.sound_pax_ambience_playing() } +} - fn reset_cargo_target(&mut self, cs: A320Cargo) { - self.cargo[cs as usize].reset_cargo_target(); +impl NumberOfPassengers for A320Payload { + fn number_of_passengers(&self, ps: usize) -> i8 { + self.pax_num(ps) } - - fn cargo_is_sync(&mut self, cs: A320Cargo) -> bool { - self.cargo[cs as usize].payload_is_sync() +} +impl PassengerPayload for A320Payload { + fn total_passenger_load(&self) -> Mass { + self.total_passenger_load() } - fn cargo_is_target(&self, cs: A320Cargo) -> bool { - self.cargo[cs as usize].cargo_is_target() + fn total_target_passenger_load(&self) -> Mass { + self.total_target_passenger_load() } - fn move_all_cargo(&mut self, cs: A320Cargo) { - self.cargo[cs as usize].move_all_cargo(); + fn center_of_gravity(&self) -> Vector3 { + self.passenger_center_of_gravity() } - fn move_one_cargo(&mut self, cs: A320Cargo) { - self.cargo[cs as usize].move_one_cargo(); + fn fore_aft_center_of_gravity(&self) -> f64 { + self.passenger_center_of_gravity().x } - fn load_all_cargo_percent(&mut self, percent: f64) { - for cs in A320Cargo::iterator() { - self.load_cargo_percent(cs, percent); - } + fn target_center_of_gravity(&self) -> Vector3 { + self.target_passenger_center_of_gravity() } - fn load_cargo_percent(&mut self, cs: A320Cargo, percent: f64) { - self.cargo[cs as usize].load_cargo_percent(percent) + fn target_fore_aft_center_of_gravity(&self) -> f64 { + self.target_passenger_center_of_gravity().x } - - fn move_all_payload(&mut self) { - for ps in A320Pax::iterator() { - self.move_all_pax(ps) - } - for cs in A320Cargo::iterator() { - self.move_all_cargo(cs) - } +} +impl CargoPayload for A320Payload { + fn total_cargo_load(&self) -> Mass { + self.payload_manager.total_cargo_load() } - fn sync_cargo(&mut self, cs: A320Cargo) { - self.cargo[cs as usize].load_payload(); + fn total_target_cargo_load(&self) -> Mass { + self.payload_manager.total_target_cargo_load() } - fn is_boarding(&self) -> bool { - self.is_boarding + fn center_of_gravity(&self) -> Vector3 { + self.cargo_center_of_gravity() } - fn is_gsx_enabled(&self) -> bool { - self.is_gsx_enabled + fn fore_aft_center_of_gravity(&self) -> f64 { + self.cargo_center_of_gravity().x } - fn stop_boarding(&mut self) { - self.is_boarding = false; + fn target_center_of_gravity(&self) -> Vector3 { + self.target_cargo_center_of_gravity() } - fn per_pax_weight(&self) -> Mass { - self.per_pax_weight.get() + fn target_fore_aft_center_of_gravity(&self) -> f64 { + self.target_cargo_center_of_gravity().x } } impl SimulationElement for A320Payload { fn accept(&mut self, visitor: &mut T) { - accept_iterable!(self.pax, visitor); - accept_iterable!(self.cargo, visitor); - self.boarding_sounds.accept(visitor); + self.payload_manager.accept(visitor); visitor.visit(self); } - - fn read(&mut self, reader: &mut SimulatorReader) { - self.developer_state = reader.read(&self.developer_state_id); - self.is_boarding = reader.read(&self.is_boarding_id); - self.board_rate = reader.read(&self.board_rate_id); - self.is_gsx_enabled = reader.read(&self.is_gsx_enabled_id); - self.gsx_boarding_state = reader.read(&self.gsx_boarding_state_id); - self.gsx_deboarding_state = reader.read(&self.gsx_deboarding_state_id); - self.gsx_pax_boarding = reader.read(&self.gsx_pax_boarding_id); - self.gsx_pax_deboarding = reader.read(&self.gsx_pax_deboarding_id); - self.gsx_cargo_boarding_pct = reader.read(&self.gsx_cargo_boarding_pct_id); - self.gsx_cargo_deboarding_pct = reader.read(&self.gsx_cargo_deboarding_pct_id); - self.per_pax_weight - .replace(Mass::new::(reader.read(&self.per_pax_weight_id))); - } - - fn write(&self, writer: &mut SimulatorWriter) { - writer.write(&self.is_boarding_id, self.is_boarding); - writer.write( - &self.per_pax_weight_id, - self.per_pax_weight().get::(), - ); - } -} -impl NumberOfPassengers for A320Payload { - fn number_of_passengers(&self, ps: A320Pax) -> i8 { - self.pax_num(ps) - } } diff --git a/hdw-a339x/src/wasm/systems/a320_systems_wasm/src/lib.rs b/hdw-a339x/src/wasm/systems/a320_systems_wasm/src/lib.rs index 1bd4084a0..3a9c847b2 100644 --- a/hdw-a339x/src/wasm/systems/a320_systems_wasm/src/lib.rs +++ b/hdw-a339x/src/wasm/systems/a320_systems_wasm/src/lib.rs @@ -60,6 +60,7 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { (ElectricalBusType::DirectCurrentGndFltService, 15), ])? .with_auxiliary_power_unit(Variable::named("OVHD_APU_START_PB_IS_AVAILABLE"), 8, 7)? + .with_engines(2)? .with_failures(vec![ (24_000, FailureType::TransformerRectifier(1)), (24_001, FailureType::TransformerRectifier(2)), @@ -267,7 +268,12 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { .provides_aircraft_variable("AMBIENT WIND Z", "meter per second", 0)? .provides_aircraft_variable("ANTISKID BRAKES ACTIVE", "Bool", 0)? .provides_aircraft_variable("EXTERNAL POWER AVAILABLE", "Bool", 1)? - .provides_aircraft_variable("FUEL TANK LEFT MAIN QUANTITY", "Pounds", 0)? + .provides_aircraft_variable("FUEL TANK CENTER QUANTITY", "gallons", 0)? + .provides_aircraft_variable("FUEL TANK LEFT MAIN QUANTITY", "gallons", 0)? + .provides_aircraft_variable("FUEL TANK LEFT AUX QUANTITY", "gallons", 0)? + .provides_aircraft_variable("FUEL TANK RIGHT MAIN QUANTITY", "gallons", 0)? + .provides_aircraft_variable("FUEL TANK RIGHT AUX QUANTITY", "gallons", 0)? + .provides_aircraft_variable("FUEL TOTAL QUANTITY WEIGHT", "Pounds", 0)? .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 0)? .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 1)? .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 2)? @@ -343,16 +349,6 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { Variable::aircraft("APU GENERATOR SWITCH", "Bool", 0), Variable::aspect("OVHD_ELEC_APU_GEN_PB_IS_ON"), ); - - builder.copy( - Variable::aircraft("BLEED AIR ENGINE", "Bool", 1), - Variable::aspect("OVHD_PNEU_ENG_1_BLEED_PB_IS_AUTO"), - ); - builder.copy( - Variable::aircraft("BLEED AIR ENGINE", "Bool", 2), - Variable::aspect("OVHD_PNEU_ENG_2_BLEED_PB_IS_AUTO"), - ); - builder.copy( Variable::aircraft("EXTERNAL POWER AVAILABLE", "Bool", 1), Variable::aspect("OVHD_ELEC_EXT_PWR_PB_IS_AVAILABLE"), diff --git a/package.json b/package.json index 756b78134..dea61fdd9 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,13 @@ "edition": "development", "scripts": { "====== A339X =================": "==========================================", - "build-a339x:copy-base-package": "mkdir -p build-a339x/out/headwindsim-aircraft-a330-900 && (rsync -a build-a339x/src/base/headwindsim-aircraft-a330-900 build-a339x/out/ || cp -a -u build-a339x/src/base/headwindsim-aircraft-a330-900 build-a339x/out/)", "build-a339x:copy-base-lock-highlight-package": "mkdir -p build-a339x/out/headwindsim-aircraft-a330-900-lock-highlight && (rsync -a build-a339x/src/base/headwindsim-aircraft-a330-900-lock-highlight build-a339x/out/ || cp -a -u build-a339x/src/base/headwindsim-aircraft-a330-900-lock-highlight build-a339x/out/)", "build-a339x:copy-base-files": "npm run build-a339x:copy-base-package && npm run build-a339x:copy-base-lock-highlight-package", "build-a339x:efb-translation": "cd build-a339x/src/localization && node build-flypad-translation.js", "build-a339x:locPak-translation": "cd build-a339x/src/localization && node build-locPak-translation.js", - "build-a339x:model": "node build-a339x/src/model/build.js", "build-a339x:behavior": "node build-a339x/src/behavior/build.js", - "build-a339x:atsu-common": "node build-a339x/src/systems/atsu/common/build.js", "build-a339x:atsu-fms-client": "node build-a339x/src/systems/atsu/fmsclient/build.js", "build-a339x:extras-host": "node build-a339x/src/systems/extras-host/build.js", @@ -24,51 +21,38 @@ "build-a339x:simbridge-client": "node build-a339x/src/systems/simbridge-client/build.js", "build-a339x:systems-host": "node build-a339x/src/systems/systems-host/build.js", "build-a339x:tcas": "node build-a339x/src/systems/tcas/build.js", - - "build-a339x:fadec": "cd build-a339x/src/wasm/fadec_a320 && ./build.sh && wasm-opt -O1 -o /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_A330neo/panel/fadec.wasm /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_A330neo/panel/fadec.wasm", + "build-a339x:fadec": "cd build-a339x/src/wasm/fadec_a330 && ./build.sh && wasm-opt -O1 -o /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_A330neo/panel/fadec.wasm /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_A330neo/panel/fadec.wasm", "build-a339x:fbw": "cd build-a339x/src/wasm/fbw_a320 && ./build.sh && wasm-opt -O1 -o /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_A330neo/panel/fbw.wasm /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_A330neo/panel/fbw.wasm", "build-a339x:flypad-backend": "cd build-a339x/src/wasm/flypad-backend && ./build.sh && wasm-opt -O1 -o /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_A330neo/panel/flypad-backend.wasm /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_A330neo/panel/flypad-backend.wasm", "build-a339x:systems": "cargo build -p a320_systems_wasm --target wasm32-wasi --release && wasm-opt -O1 -o /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_A330neo/panel/systems.wasm /external/target/wasm32-wasi/release/a320_systems_wasm.wasm", - "build-a339x:metadata": "node scripts/metadata.js build-a339x/out/headwindsim-aircraft-a330-900 a339x", "build-a339x:manifest": "node scripts/build_a339x.js", - "build-a339x:ace": "node --max-old-space-size=8192 node_modules/rollup/dist/bin/rollup -c .\\build-a339x\\src\\systems\\instruments\\buildSrc\\aceBuild.mjs", "watch-a339x:ace": "node --max-old-space-size=8192 node_modules/rollup/dist/bin/rollup -wc .\\build-a339x\\src\\systems\\instruments\\buildSrc\\aceBuild.mjs", - "build-a339x:all": "npm run build-a339x:copy-base-files && npm run build-a339x:efb-translation && npm run build-a339x:model && npm run build-a339x:behavior && npm run build-a339x:atsu-common && npm run build-a339x:atsu-fms-client && npm run build-a339x:systems-host && npm run build-a339x:failures && npm run build-a339x:fmgc && npm run build-a339x:sentry-client && npm run build-a339x:simbridge-client && npm run build-a339x:tcas && npm run build-a339x:pfd && npm run build-a339x:systems && npm run build-a339x:fadec && npm run build-a339x:fbw && npm run build-a339x:flypad-backend && npm run build-a339x:metadata && npm run build-a339x:manifest", - "====== ACJ339x =================": "==========================================", - "build-a339x-acj:copy-base-package": "mkdir -p build-a339x/out/headwindsim-aircraft-a330-900 && (rsync -a build-a339x-acj/src/base/headwindsim-aircraft-a330-900 build-a339x/out/ || cp -a -u build-a339x-acj/src/base/headwindsim-aircraft-a330-900 build-a339x/out/)", "build-a339x-acj:copy-base-lock-highlight-package": "mkdir -p build-a339x/out/headwindsim-aircraft-a330-900-lock-highlight && (rsync -a build-a339x-acj/src/base/headwindsim-aircraft-a330-900-lock-highlight build-a339x/out/ || cp -a -u build-a339x-acj/src/base/headwindsim-aircraft-a330-900-lock-highlight build-a339x/out/)", "build-a339x-acj:copy-base-files": "npm run build-a339x-acj:copy-base-package && npm run build-a339x-acj:copy-base-lock-highlight-package", - - "build-a339x-acj:fadec": "cd build-a339x-acj/src/wasm/fadec_a320 && ./build.sh && wasm-opt -O1 -o /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_ACJ330_900/panel/fadec.wasm /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_ACJ330_900/panel/fadec.wasm", + "build-a339x-acj:fadec": "cd build-a339x-acj/src/wasm/fadec_a330 && ./build.sh && wasm-opt -O1 -o /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_ACJ330_900/panel/fadec.wasm /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_ACJ330_900/panel/fadec.wasm", "build-a339x-acj:fbw": "cd build-a339x-acj/src/wasm/fbw_a320 && ./build.sh && wasm-opt -O1 -o /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_ACJ330_900/panel/fbw.wasm /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_ACJ330_900/panel/fbw.wasm", "build-a339x-acj:flypad-backend": "cd build-a339x-acj/src/wasm/flypad-backend && ./build.sh && wasm-opt -O1 -o /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_ACJ330_900/panel/flypad-backend.wasm /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_ACJ330_900/panel/flypad-backend.wasm", "build-a339x-acj:systems": "cargo build -p acj330_systems_wasm --target wasm32-wasi --release && wasm-opt -O1 -o /external/build-a339x/out/headwindsim-aircraft-a330-900/SimObjects/Airplanes/Headwind_ACJ330_900/panel/systems.wasm /external/target/wasm32-wasi/release/acj330_systems_wasm.wasm", - "build-a339x-acj:ace": "node --max-old-space-size=8192 node_modules/rollup/dist/bin/rollup -c .\\build-a339x-acj\\src\\systems\\instruments\\buildSrc\\aceBuild.mjs", "watch-a339x-acj:ace": "node --max-old-space-size=8192 node_modules/rollup/dist/bin/rollup -wc .\\build-a339x-acj\\src\\systems\\instruments\\buildSrc\\aceBuild.mjs", - "build-a339x-acj:all": "npm run build-a339x-acj:copy-base-files && npm run build-a339x-acj:efb-translation && npm run build-a339x-acj:model && npm run build-a339x-acj:behavior && npm run build-a339x-acj:atsu-common && npm run build-a339x-acj:atsu-fms-client && npm run build-a339x-acj:systems-host && npm run build-a339x-acj:failures && npm run build-a339x-acj:fmgc && npm run build-a339x-acj:sentry-client && npm run build-a339x-acj:simbridge-client && npm run build-a339x-acj:tcas && npm run build-a339x-acj:pfd && npm run build-a339x-acj:systems && npm run build-a339x-acj:fadec && npm run build-a339x-acj:fbw && npm run build-a339x-acj:flypad-backend && npm run build-a339x-acj:metadata && npm run build-a339x-acj:manifest", - "=======deprecated=========": "===========================", "build-a339x:pfd": "cd build-a339x/src/systems/instruments/src/PFD && rollup -c", "build-a339x:clock": "cd build-a339x/src/systems/instruments/src/Clock && rollup -c", "build-a339x:ewd": "cd build-a339x/src/systems/instruments/src/EWD && rollup -c", - "prettier": "prettier --write **/*.json **/*.yml build-a339x/src/systems/instruments/**/*.css", "serve:efb": "cd build-a339x/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", - "====== A339X LIVERY PACKAGE ====": "==========================================", "build-a339x-livery-package:copy-base-package": "mkdir -p /hdw-a339x-liveries/out/headwindsim-a339x-livery-package && (rsync -a hdw-a339x-liveries/src/base/headwindsim-a339x-livery-package hdw-a339x-liveries/out || cp -a -u hdw-a339x-liveries/src/base/headwindsim-a339x-livery-package hdw-a339x-liveries/out)", "build-a339x-livery-package:copy-base-files": "npm run build-a339x-livery-package:copy-base-package", "build-a339x-livery-package:manifest": "node scripts/build-a339x-liveries.js", - "====== COMMON ================": "==========================================", "lint": "eslint --cache **/*.{js,mjs,jsx,ts,tsx}", "lint-fix": "npm run lint -- --fix", @@ -209,4 +193,4 @@ "uuid": "^9.0.0", "ws": "^7.4.5" } -} \ No newline at end of file +} diff --git a/scripts/copy_a339x.sh b/scripts/copy_a339x.sh index 6134eeb22..0b6f1f070 100755 --- a/scripts/copy_a339x.sh +++ b/scripts/copy_a339x.sh @@ -59,15 +59,12 @@ cp -rva ./flybywire/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages cp -rva ./flybywire/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/VLivery/Liveries/A32NX_Registration ./build-a339x/out/headwindsim-aircraft-a330-900/html_ui/Pages/VLivery/Liveries/Registration/A339X cp -rva ./flybywire/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/VLivery/Liveries/A32NX_Printer ./build-a339x/out/headwindsim-aircraft-a330-900/html_ui/Pages/VLivery/Liveries/Printer/A339X -# remove fbw submodule -rm -rvf ./flybywire - # copy base of A339X to out cp -rva ./hdw-a339x/src/base/headwindsim-aircraft-a330-900/. ./build-a339x/out/headwindsim-aircraft-a330-900 cp -rva ./hdw-a339x/src/base/headwindsim-aircraft-a330-900-lock-highlight/. ./build-a339x/out/headwindsim-aircraft-a330-900-lock-highlight chmod +x ./build-a339x/src/wasm/fbw_a320/build.sh -chmod +x ./build-a339x/src/wasm/fadec_a320/build.sh +chmod +x ./build-a339x/src/wasm/fadec_a330/build.sh chmod +x ./build-a339x/src/wasm/flypad-backend/build.sh ##### ACJ330neo @@ -98,5 +95,5 @@ cp -rva ./hdw-a339x-acj/mach.config.js ./build-a339x-acj/mach.config.js cp -rva ./hdw-a339x-acj/src/base/headwindsim-aircraft-a330-900/. ./build-a339x/out/headwindsim-aircraft-a330-900 chmod +x ./build-a339x-acj/src/wasm/fbw_a320/build.sh -chmod +x ./build-a339x-acj/src/wasm/fadec_a320/build.sh +chmod +x ./build-a339x-acj/src/wasm/fadec_a330/build.sh chmod +x ./build-a339x-acj/src/wasm/flypad-backend/build.sh