From 0afca040b9bdd1dd57f1cdf9135f3665483e9293 Mon Sep 17 00:00:00 2001 From: BBK <22713769+BlueberryKing@users.noreply.github.com> Date: Thu, 26 Oct 2023 00:49:48 +0200 Subject: [PATCH 1/4] fix(mcdu): improve PERF pages (#8205) * Implement CHECK SPEED MODE message * Improve layout on PERF pages * Allow modification of PRED TO altitudes * Fix displayed managed speed * Take speed constraint into account for clb speed * Update changelog * Fix slash formatting * Use Mach 0.80 for DES CAS selection only * Fix input parsing * Fix layout before FROM/TO entry * Inhibit CHECK SPEED MODE in excessive attitude * Fix slash formatting again * Allow Mach only entries * Make * on CRZ page small * fix: changelog 0.12 --------- Co-authored-by: Michael Corcoran --- .github/CHANGELOG.md | 3 +- fbw-a32nx/docs/a320-simvars.md | 4 + .../Pages/A32NX_Utils/NXSystemMessages.js | 1 + .../CDU/A320_Neo_CDU_PerformancePage.js | 393 ++++++++++++------ .../FMC/A32NX_FMCMainDisplay.js | 388 +++++++++++++---- .../fmgc/src/guidance/GuidanceController.ts | 1 - .../VerticalProfileComputationParameters.ts | 2 - .../src/guidance/vnav/climb/SpeedProfile.ts | 5 +- .../vnav/profile/BaseGeometryProfile.ts | 39 +- .../src/systems/instruments/src/PFD/FMA.tsx | 29 +- .../src/PFD/shared/PFDSimvarPublisher.tsx | 3 + 11 files changed, 635 insertions(+), 233 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 934343ee499..21500f98021 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -9,6 +9,8 @@ 1. [EFB/ATSU] Added NOAA (aviationweather.gov) as a METAR source - @tracernz (Mike) 1. [EFB] Fixed the main page and landing calculator to use the selected METAR source - @tracernz (Mike) +1. [FMS] Improve layout of PERF CLB, PERF CRZ and PERF DES pages according to H3 - @BlueberryKing (BlueberryKing) +1. [FMS] Implement CHECK SPEED MODE message - @BlueberryKing (BlueberryKing) ## 0.11.0 @@ -189,7 +191,6 @@ 1. [LIGHTS] Fixed trim decal emissive and floods - @FinalLightNL (FinalLight#2113) 1. [FLIGHTMODEL/FUEL] Fix outer tank transfer behaviour - @donstim (donbikes#4084) - ## 0.9.0 1. [MODEL] Add Wheel Chocks and GSE Safety Cones - @bouveng (Johan Bouveng) diff --git a/fbw-a32nx/docs/a320-simvars.md b/fbw-a32nx/docs/a320-simvars.md index aacf895737c..690133f01e9 100644 --- a/fbw-a32nx/docs/a320-simvars.md +++ b/fbw-a32nx/docs/a320-simvars.md @@ -1383,6 +1383,10 @@ These variables are the interface between the 3D model and the systems/code. - Bool - Indicates if the T/D REACHED message is shown on the PFD +- A32NX_PFD_MSG_CHECK_SPEED_MODE + - Bool + - Indicates if the CHECK SPEED MODE message is shown on the PFD + - A32NX_PFD_LINEAR_DEVIATION_ACTIVE - Bool - Indicates if the linear deviation is shown on the PFD diff --git a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Utils/NXSystemMessages.js b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Utils/NXSystemMessages.js index 7fae8cf815b..a1280ef30ca 100644 --- a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Utils/NXSystemMessages.js +++ b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Utils/NXSystemMessages.js @@ -64,6 +64,7 @@ const NXSystemMessages = { awyWptMismatch: new TypeIMessage("AWY/WPT MISMATCH"), cancelAtisUpdate: new TypeIMessage("CANCEL UPDATE BEFORE"), checkMinDestFob: new TypeIIMessage("CHECK MIN DEST FOB"), + checkSpeedMode: new TypeIIMessage("CHECK SPEED MODE"), checkToData: new TypeIIMessage("CHECK TAKE OFF DATA", true), checkWeight: new TypeIIMessage("CHECK WEIGHT", true), comUnavailable: new TypeIMessage("COM UNAVAILABLE"), diff --git a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/CDU/A320_Neo_CDU_PerformancePage.js b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/CDU/A320_Neo_CDU_PerformancePage.js index 70f62b9356a..b9a2d572062 100644 --- a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/CDU/A320_Neo_CDU_PerformancePage.js +++ b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/CDU/A320_Neo_CDU_PerformancePage.js @@ -376,47 +376,54 @@ class CDUPerformancePage { } } }; + + const hasFromToPair = mcdu.flightPlanManager.getPersistentOrigin() && mcdu.flightPlanManager.getDestination(); + const showManagedSpeed = hasFromToPair && mcdu.costIndexSet && Number.isFinite(mcdu.costIndex); const isPhaseActive = mcdu.flightPhaseManager.phase === FmgcFlightPhases.CLIMB; + const isTakeoffOrClimbActive = isPhaseActive || (mcdu.flightPhaseManager.phase === FmgcFlightPhases.TAKEOFF); const titleColor = isPhaseActive ? "green" : "white"; - const isSelected = Simplane.getAutoPilotAirspeedSelected(); + const isSelected = (isPhaseActive && Simplane.getAutoPilotAirspeedSelected()) || (!isPhaseActive && mcdu.preSelectedClbSpeed !== undefined); const actModeCell = isSelected ? "SELECTED" : "MANAGED"; - const costIndexCell = isFinite(mcdu.costIndex) ? mcdu.costIndex.toFixed(0) + "[color]cyan" : "[][color]cyan"; + const costIndexCell = CDUPerformancePage.formatCostIndexCell(mcdu, hasFromToPair, true); + const canClickManagedSpeed = showManagedSpeed && mcdu.preSelectedClbSpeed !== undefined && !isPhaseActive; // Predictions to altitude const vnavDriver = mcdu.guidanceController.vnavDriver; const cruiseAltitude = mcdu.cruiseFlightLevel * 100; const fcuAltitude = SimVar.GetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR:3", "feet"); - const altitudeToPredict = Math.min(cruiseAltitude, fcuAltitude); + const altitudeToPredict = mcdu.perfClbPredToAltitudePilot !== undefined ? mcdu.perfClbPredToAltitudePilot : Math.min(cruiseAltitude, fcuAltitude); - const predToCell = CDUPerformancePage.formatAltitudeOrLevel(altitudeToPredict, mcdu.flightPlanManager.getOriginTransitionAltitude()) + "[color]cyan"; + const predToLabel = isTakeoffOrClimbActive ? "\xa0\xa0\xa0\xa0\xa0{small}PRED TO{end}" : ""; + const predToCell = isTakeoffOrClimbActive ? `${CDUPerformancePage.formatAltitudeOrLevel(altitudeToPredict, mcdu.flightPlanManager.getOriginTransitionAltitude())}[color]cyan` : ""; - let predToDistanceCell = "---"; - let predToTimeCell = "----"; + let predToDistanceCell = ""; + let predToTimeCell = ""; - let expeditePredToDistanceCell = "---"; - let expeditePredToTimeCell = "----"; + let expeditePredToDistanceCell = ""; + let expeditePredToTimeCell = ""; - if (vnavDriver) { - [predToDistanceCell, predToTimeCell] = CDUPerformancePage.getTimeAndDistancePredictionsFromGeometryProfile(vnavDriver.mcduProfile, altitudeToPredict, mcdu.flightPhaseManager.phase >= FmgcFlightPhases.TAKEOFF); - - if (isPhaseActive) { - const expediteProfile = vnavDriver.expediteProfile; - [expeditePredToDistanceCell, expeditePredToTimeCell] = CDUPerformancePage.getTimeAndDistancePredictionsFromGeometryProfile(expediteProfile, altitudeToPredict, mcdu.flightPhaseManager.phase >= FmgcFlightPhases.TAKEOFF, true); - } + if (isTakeoffOrClimbActive && vnavDriver) { + [predToDistanceCell, predToTimeCell] = CDUPerformancePage.getTimeAndDistancePredictionsFromGeometryProfile(vnavDriver.ndProfile, altitudeToPredict, true); + [expeditePredToDistanceCell, expeditePredToTimeCell] = CDUPerformancePage.getTimeAndDistancePredictionsFromGeometryProfile(vnavDriver.expediteProfile, altitudeToPredict, true, true); } - let managedSpeedCell = ""; + let managedSpeedCell = ''; if (isPhaseActive) { if (mcdu.managedSpeedTarget === mcdu.managedSpeedClimb) { - managedSpeedCell = "{small}" + mcdu.managedSpeedClimb.toFixed(0) + "/" + mcdu.managedSpeedClimbMach.toFixed(2).replace("0.", ".") + "{end}"; - } else if (Simplane.getAutoPilotMachModeActive() || SimVar.GetSimVarValue("K:AP_MANAGED_SPEED_IN_MACH_ON", "Bool")) { - managedSpeedCell = "{small}" + mcdu.managedSpeedClimbMach.toFixed(2).replace("0.", ".") + "{end}"; + managedSpeedCell = `\xa0${mcdu.managedSpeedClimb.toFixed(0)}/${mcdu.managedSpeedClimbMach.toFixed(2).replace('0.', '.')}`; + } else if (mcdu.managedSpeedTargetIsMach) { + managedSpeedCell = `\xa0${mcdu.managedSpeedClimbMach.toFixed(2).replace('0.', '.')}`; } else { - managedSpeedCell = "{small}" + mcdu.managedSpeedTarget.toFixed(0) + "{end}"; + managedSpeedCell = `\xa0${mcdu.managedSpeedTarget.toFixed(0)}`; } } else { - managedSpeedCell = (isSelected ? "*" : "") + mcdu.managedSpeedClimb > mcdu.climbSpeedLimit ? mcdu.climbSpeedLimit.toFixed(0) : mcdu.managedSpeedClimb.toFixed(0); + let climbSpeed = Math.min(mcdu.managedSpeedClimb, mcdu.getNavModeSpeedConstraint()); + if (mcdu.climbSpeedLimit !== undefined && SimVar.GetSimVarValue("INDICATED ALTITUDE", "feet") < mcdu.climbSpeedLimitAlt) { + climbSpeed = Math.min(climbSpeed, mcdu.climbSpeedLimit); + } + + managedSpeedCell = `${canClickManagedSpeed ? '*' : '\xa0'}${climbSpeed.toFixed(0)}`; mcdu.onLeftInput[3] = (value, scratchpadCallback) => { if (mcdu.trySetPreSelectedClimbSpeed(value)) { @@ -426,32 +433,55 @@ class CDUPerformancePage { } }; } - const [selectedSpeedTitle, selectedSpeedCell] = CDUPerformancePage.getSelectedTitleAndValue(isPhaseActive, isSelected, mcdu.preSelectedClbSpeed); - mcdu.onLeftInput[1] = (value, scratchpadCallback) => { - if (mcdu.tryUpdateCostIndex(value)) { - CDUPerformancePage.ShowCLBPage(mcdu); - } else { + const [selectedSpeedTitle, selectedSpeedCell] = CDUPerformancePage.getClbSelectedTitleAndValue(mcdu, isPhaseActive, isSelected, mcdu.preSelectedClbSpeed); + + if (hasFromToPair) { + mcdu.onLeftInput[1] = (value, scratchpadCallback) => { + if (mcdu.tryUpdateCostIndex(value)) { + CDUPerformancePage.ShowCLBPage(mcdu); + } else { + scratchpadCallback(); + } + }; + } + + if (canClickManagedSpeed) { + mcdu.onLeftInput[2] = (_, scratchpadCallback) => { + if (mcdu.trySetPreSelectedClimbSpeed(FMCMainDisplay.clrValue)) { + CDUPerformancePage.ShowCLBPage(mcdu); + } + scratchpadCallback(); + }; + } + + if (isTakeoffOrClimbActive) { + mcdu.onRightInput[1] = (value, scratchpadCallback) => { + if (mcdu.trySetPerfClbPredToAltitude(value)) { + CDUPerformancePage.ShowCLBPage(mcdu); + } else { + scratchpadCallback(); + } } - }; - const timeLabel = mcdu.flightPhaseManager.phase >= FmgcFlightPhases.TAKEOFF ? "UTC" : "TIME"; - const bottomRowLabels = ["\xa0PREV", "NEXT\xa0"]; - const bottomRowCells = [""]; - mcdu.leftInputDelay[5] = () => { - return mcdu.getDelaySwitchPage(); - }; + } + + const [toUtcLabel, toDistLabel] = isTakeoffOrClimbActive ? ["\xa0UTC", "DIST"] : ["", ""]; + + const bottomRowLabels = ['\xa0PREV', 'NEXT\xa0']; + const bottomRowCells = ['']; + mcdu.leftInputDelay[5] = () => mcdu.getDelaySwitchPage(); if (isPhaseActive) { if (confirmAppr) { - bottomRowLabels[0] = "\xa0CONFIRM[color]amber"; - bottomRowCells[0] = "*APPR PHASE[color]amber"; + bottomRowLabels[0] = '\xa0CONFIRM[color]amber'; + bottomRowCells[0] = '*APPR PHASE[color]amber'; mcdu.onLeftInput[5] = async () => { if (mcdu.flightPhaseManager.tryGoInApproachPhase()) { CDUPerformancePage.ShowAPPRPage(mcdu); } }; } else { - bottomRowLabels[0] = "\xa0ACTIVATE[color]cyan"; - bottomRowCells[0] = "{APPR PHASE[color]cyan"; + bottomRowLabels[0] = '\xa0ACTIVATE[color]cyan'; + bottomRowCells[0] = '{APPR PHASE[color]cyan'; mcdu.onLeftInput[5] = () => { CDUPerformancePage.ShowCLBPage(mcdu, true); }; @@ -461,28 +491,28 @@ class CDUPerformancePage { CDUPerformancePage.ShowTAKEOFFPage(mcdu); }; } - mcdu.rightInputDelay[5] = () => { - return mcdu.getDelaySwitchPage(); - }; + + mcdu.rightInputDelay[5] = () => mcdu.getDelaySwitchPage(); mcdu.onRightInput[5] = () => { CDUPerformancePage.ShowCRZPage(mcdu); }; mcdu.setTemplate([ - ["CLB[color]" + titleColor], + [`\xa0CLB[color]${titleColor}`], ["ACT MODE"], - [actModeCell + "[color]green"], - ["\xa0CI"], - [costIndexCell + "[color]cyan", predToCell, "\xa0\xa0\xa0{small}PRED TO{end}"], - ["\xa0MANAGED", "DIST", timeLabel], - ["\xa0" + managedSpeedCell + "[color]green", !isSelected ? predToDistanceCell : "", !isSelected ? predToTimeCell : ""], - ["\xa0" + selectedSpeedTitle], - ["\xa0" + selectedSpeedCell, isSelected ? predToDistanceCell : "", isSelected ? predToTimeCell : ""], + [`${actModeCell}[color]green`], + ["CI"], + [costIndexCell, predToCell, predToLabel], + ["MANAGED", toDistLabel, toUtcLabel], + [`{small}${showManagedSpeed ? managedSpeedCell : "\xa0---/---"}{end}[color]${showManagedSpeed ? "green" : "white"}`, !isSelected ? predToDistanceCell : "", !isSelected ? predToTimeCell : ""], + [selectedSpeedTitle], + [selectedSpeedCell, isSelected ? predToDistanceCell : "", isSelected ? predToTimeCell : ""], [""], - isPhaseActive ? ["\xa0{small}EXPEDITE{end}[color]green", expeditePredToDistanceCell, expeditePredToTimeCell] : [""], + isPhaseActive ? ["{small}EXPEDITE{end}[color]green", expeditePredToDistanceCell, expeditePredToTimeCell] : [""], bottomRowLabels, - bottomRowCells + bottomRowCells, ]); } + static ShowCRZPage(mcdu, confirmAppr = false) { mcdu.clearDisplay(); mcdu.page.Current = mcdu.page.PerformancePageCrz; @@ -498,35 +528,48 @@ class CDUPerformancePage { } } }; + + const hasFromToPair = mcdu.flightPlanManager.getPersistentOrigin() && mcdu.flightPlanManager.getDestination(); const isPhaseActive = mcdu.flightPhaseManager.phase === FmgcFlightPhases.CRUISE; const titleColor = isPhaseActive ? "green" : "white"; - const isSelected = Simplane.getAutoPilotAirspeedSelected(); + const isSelected = (isPhaseActive && Simplane.getAutoPilotAirspeedSelected()) || (!isPhaseActive && mcdu.preSelectedCrzSpeed !== undefined); const isFlying = mcdu.flightPhaseManager.phase >= FmgcFlightPhases.TAKEOFF; const actModeCell = isSelected ? "SELECTED" : "MANAGED"; - const costIndexCell = isFinite(mcdu.costIndex) ? mcdu.costIndex.toFixed(0) + "[color]cyan" : "[][color]cyan"; - let managedSpeedCell = "---/---"; - const managedSpeed = isPhaseActive ? mcdu.managedSpeedTarget : mcdu.managedSpeedCruise; - if (isFinite(managedSpeed)) { - managedSpeedCell = managedSpeed.toFixed(0) + "[color]green"; + const costIndexCell = CDUPerformancePage.formatCostIndexCell(mcdu, hasFromToPair, true); + + // TODO: Figure out correct condition + const showManagedSpeed = hasFromToPair && mcdu.costIndexSet && Number.isFinite(mcdu.costIndex); + const canClickManagedSpeed = showManagedSpeed && mcdu.preSelectedCrzSpeed !== undefined && !isPhaseActive; + let managedSpeedCell = "{small}\xa0---/---{end}[color]white"; + if (showManagedSpeed && mcdu._cruiseEntered && Number.isFinite(mcdu.cruiseFlightLevel) && Number.isFinite(mcdu.managedSpeedCruise) && Number.isFinite(mcdu.managedSpeedCruiseMach)) { + const shouldShowCruiseMach = mcdu.cruiseFlightLevel > 250; + managedSpeedCell = `{small}${canClickManagedSpeed ? "*" : "\xa0"}${shouldShowCruiseMach ? mcdu.managedSpeedCruiseMach.toFixed(2).replace("0.", ".") : mcdu.managedSpeedCruise.toFixed(0)}{end}[color]green`; } - const preselTitle = isPhaseActive ? "" : "\xa0PRESEL"; + const preselTitle = isPhaseActive ? "" : "PRESEL"; let preselCell = ""; if (!isPhaseActive) { - preselCell = (isFinite(mcdu.preSelectedCrzSpeed) ? Math.round(mcdu.preSelectedCrzSpeed).toFixed(0) : "\xa0*[ ]") + "[color]cyan"; - } - mcdu.onLeftInput[1] = (value, scratchpadCallback) => { - if (mcdu.tryUpdateCostIndex(value)) { - CDUPerformancePage.ShowCRZPage(mcdu); + const hasPreselectedSpeedOrMach = mcdu.preSelectedCrzSpeed !== undefined; + if (hasPreselectedSpeedOrMach) { + preselCell = `\xa0${mcdu.preSelectedCrzSpeed < 1 ? mcdu.preSelectedCrzSpeed.toFixed(2).replace("0.", ".") : mcdu.preSelectedCrzSpeed.toFixed(0)}[color]cyan`; } else { - scratchpadCallback(); + preselCell = "{small}*{end}[ ][color]cyan"; } - }; - let timeLabel = "TIME"; - if (isFlying) { - timeLabel = "UTC"; } + + if (hasFromToPair) { + mcdu.onLeftInput[1] = (value, scratchpadCallback) => { + if (mcdu.tryUpdateCostIndex(value)) { + CDUPerformancePage.ShowCRZPage(mcdu); + } else { + scratchpadCallback(); + } + }; + } + + const timeLabel = isFlying ? '\xa0UTC' : 'TIME'; + const [destEfobCell, destTimeCell] = CDUPerformancePage.formatDestEfobAndTime(mcdu, isFlying); - const [toUtcLabel, toDistLabel] = isFlying ? ["UTC", "DIST"] : ["", ""]; + const [toUtcLabel, toDistLabel] = isFlying ? ["\xa0UTC", "DIST"] : ["", ""]; const [toReasonCell, toDistCell, toTimeCell] = isFlying ? CDUPerformancePage.formatToReasonDistanceAndTime(mcdu) : ["", "", ""]; const desCabinRateCell = "{small}-350{end}"; const shouldShowStepAltsOption = mcdu._cruiseEntered && mcdu._cruiseFlightLevel @@ -565,6 +608,15 @@ class CDUPerformancePage { CDUPerformancePage.ShowCLBPage(mcdu); }; } + if (canClickManagedSpeed) { + mcdu.onLeftInput[2] = (_, scratchpadCallback) => { + if (mcdu.trySetPreSelectedCruiseSpeed(FMCMainDisplay.clrValue)) { + CDUPerformancePage.ShowCRZPage(mcdu); + } + + scratchpadCallback(); + }; + } mcdu.onRightInput[3] = () => { // DES CABIN RATE mcdu.setScratchpadMessage(NXFictionalMessages.notYetImplemented); @@ -577,20 +629,18 @@ class CDUPerformancePage { CDUStepAltsPage.ShowPage(mcdu); }; } - mcdu.rightInputDelay[5] = () => { - return mcdu.getDelaySwitchPage(); - }; + mcdu.rightInputDelay[5] = () => mcdu.getDelaySwitchPage(); mcdu.onRightInput[5] = () => { CDUPerformancePage.ShowDESPage(mcdu); }; mcdu.setTemplate([ - ["CRZ[color]" + titleColor], + [`\xa0CRZ[color]${titleColor}`], ["ACT MODE", "DEST EFOB", timeLabel], - [actModeCell + "[color]green", destEfobCell, destTimeCell], - ["\xa0CI"], - [costIndexCell + "[color]cyan", toReasonCell], - ["\xa0MANAGED", toDistLabel, toUtcLabel], - ["\xa0" + managedSpeedCell, toDistCell, toTimeCell], + [`${actModeCell}[color]green`, destEfobCell, destTimeCell], + ["CI"], + [costIndexCell, toReasonCell], + ["MANAGED", toDistLabel, toUtcLabel], + [managedSpeedCell, toDistCell, toTimeCell], [preselTitle, "DES CABIN RATE"], [preselCell, `\xa0{cyan}${desCabinRateCell}{end}{white}{small}FT/MN{end}{end}`], [""], @@ -599,6 +649,7 @@ class CDUPerformancePage { bottomRowCells ]); } + static ShowDESPage(mcdu, confirmAppr = false) { mcdu.clearDisplay(); mcdu.page.Current = mcdu.page.PerformancePageDes; @@ -614,30 +665,62 @@ class CDUPerformancePage { } } }; + + const hasFromToPair = mcdu.flightPlanManager.getPersistentOrigin() && mcdu.flightPlanManager.getDestination(); const isPhaseActive = mcdu.flightPhaseManager.phase === FmgcFlightPhases.DESCENT; const titleColor = isPhaseActive ? "green" : "white"; const isFlying = mcdu.flightPhaseManager.phase >= FmgcFlightPhases.TAKEOFF; - const isSelected = Simplane.getAutoPilotAirspeedSelected(); + const isSelected = isPhaseActive && Simplane.getAutoPilotAirspeedSelected(); const actModeCell = isSelected ? "SELECTED" : "MANAGED"; - const costIndexCell = isFinite(mcdu.costIndex) ? mcdu.costIndex.toFixed(0) + "[color]cyan" : "[][color]cyan"; - let managedSpeedCell = ""; - const managedSpeed = isPhaseActive ? mcdu.managedSpeedTarget : mcdu.managedSpeedDescend; - if (isFinite(managedSpeed)) { - managedSpeedCell = (isSelected ? "*" : "") + managedSpeed.toFixed(0); - } - const [selectedSpeedTitle, selectedSpeedCell] = CDUPerformancePage.getSelectedTitleAndValue(isPhaseActive, isSelected, mcdu.preSelectedDesSpeed); - let timeLabel = "TIME"; - if (isFlying) { - timeLabel = "UTC"; + + // Predictions to altitude + const vnavDriver = mcdu.guidanceController.vnavDriver; + const fcuAltitude = SimVar.GetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR:3", "feet"); + const altitudeToPredict = mcdu.perfDesPredToAltitudePilot !== undefined ? mcdu.perfDesPredToAltitudePilot : fcuAltitude; + + const predToLabel = isPhaseActive ? "\xa0\xa0\xa0\xa0\xa0{small}PRED TO{end}" : ""; + const predToCell = isPhaseActive ? `${CDUPerformancePage.formatAltitudeOrLevel(altitudeToPredict, mcdu.flightPlanManager.getDestinationTransitionLevel() * 100)}[color]cyan` : ""; + + let predToDistanceCell = ""; + let predToTimeCell = ""; + + if (isPhaseActive && vnavDriver) { + [predToDistanceCell, predToTimeCell] = CDUPerformancePage.getTimeAndDistancePredictionsFromGeometryProfile(vnavDriver.ndProfile, altitudeToPredict, false); } + + const costIndexCell = CDUPerformancePage.formatCostIndexCell(mcdu, hasFromToPair, !isPhaseActive); + + const econDesPilotEntered = mcdu.managedSpeedDescendPilot !== undefined; + const econDes = econDesPilotEntered ? mcdu.managedSpeedDescendPilot : mcdu.managedSpeedDescend; + const econDesMachPilotEntered = mcdu.managedSpeedDescendMachPilot !== undefined; + const econDesMach = econDesMachPilotEntered ? mcdu.managedSpeedDescendMachPilot : mcdu.managedSpeedDescendMach; + + // TODO: Figure out correct condition + const showManagedSpeed = hasFromToPair && mcdu.costIndexSet && Number.isFinite(mcdu.costIndex) && econDesMach !== undefined && econDes !== undefined; + const managedDescentSpeedCellMach = `{${econDesMachPilotEntered ? "big" : "small"}}${econDesMach.toFixed(2).replace("0.", ".")}{end}`; + const managedDescentSpeedCellSpeed = `{${econDesPilotEntered ? "big" : "small"}}/${econDes.toFixed(0)}{end}`; + + const managedDescentSpeedCell = showManagedSpeed + ? `\xa0${managedDescentSpeedCellMach}${managedDescentSpeedCellSpeed}[color]cyan` + : "\xa0{small}---/---{end}[color]white"; + + const [selectedSpeedTitle, selectedSpeedCell] = CDUPerformancePage.getDesSelectedTitleAndValue(mcdu, isPhaseActive, isSelected); + const timeLabel = isFlying ? "\xa0UTC" : "TIME"; const [destEfobCell, destTimeCell] = CDUPerformancePage.formatDestEfobAndTime(mcdu, isFlying); + const [toUtcLabel, toDistLabel] = isPhaseActive ? ["\xa0UTC", "DIST"] : ["", ""]; const bottomRowLabels = ["\xa0PREV", "NEXT\xa0"]; const bottomRowCells = [""]; - mcdu.leftInputDelay[5] = () => { - return mcdu.getDelaySwitchPage(); - }; + mcdu.leftInputDelay[5] = () => mcdu.getDelaySwitchPage(); if (isPhaseActive) { + mcdu.onRightInput[1] = (value, scratchpadCallback) => { + if (mcdu.trySetPerfDesPredToAltitude(value)) { + CDUPerformancePage.ShowDESPage(mcdu); + } else { + scratchpadCallback(); + } + } + if (confirmAppr) { bottomRowLabels[0] = "\xa0CONFIRM[color]amber"; bottomRowCells[0] = "*APPR PHASE[color]amber"; @@ -654,44 +737,49 @@ class CDUPerformancePage { }; } } else { - mcdu.onLeftInput[3] = (value, scratchpadCallback) => { - if (mcdu.trySetPreSelectedDescentSpeed(value)) { + mcdu.onLeftInput[5] = () => { + CDUPerformancePage.ShowCRZPage(mcdu); + }; + } + // Can only modify cost index until the phase is active + if (hasFromToPair && !isPhaseActive) { + mcdu.onLeftInput[1] = (value, scratchpadCallback) => { + if (mcdu.tryUpdateCostIndex(value)) { CDUPerformancePage.ShowDESPage(mcdu); } else { scratchpadCallback(); } }; - mcdu.onLeftInput[5] = () => { - CDUPerformancePage.ShowCRZPage(mcdu); + } + + if (showManagedSpeed) { + mcdu.onLeftInput[2] = (value, scratchpadCallback) => { + if (mcdu.trySetManagedDescentSpeed(value)) { + CDUPerformancePage.ShowDESPage(mcdu); + } else { + scratchpadCallback(); + } }; } - mcdu.onLeftInput[1] = (value, scratchpadCallback) => { - if (mcdu.tryUpdateCostIndex(value)) { - CDUPerformancePage.ShowDESPage(mcdu); - } else { - scratchpadCallback(); - } - }; - mcdu.rightInputDelay[5] = () => { - return mcdu.getDelaySwitchPage(); - }; + + mcdu.rightInputDelay[5] = () => mcdu.getDelaySwitchPage(); mcdu.onRightInput[5] = () => { CDUPerformancePage.ShowAPPRPage(mcdu); }; mcdu.setTemplate([ - ["DES[color]" + titleColor], + [`\xa0DES[color]${titleColor}`], ["ACT MODE", "DEST EFOB", timeLabel], - [actModeCell + "[color]green", destEfobCell, destTimeCell], - ["\xa0CI"], - [costIndexCell + "[color]cyan"], - ["\xa0MANAGED"], - ["\xa0" + managedSpeedCell + "[color]green"], - ["\xa0" + selectedSpeedTitle], - ["\xa0" + selectedSpeedCell], + [`${actModeCell}[color]green`, destEfobCell, destTimeCell], + ["CI"], + [costIndexCell, predToCell, predToLabel], + ["MANAGED", toDistLabel, toUtcLabel], + [managedDescentSpeedCell, !isSelected ? predToDistanceCell : "", !isSelected ? predToTimeCell : ""], + [selectedSpeedTitle], + [selectedSpeedCell, isSelected ? predToDistanceCell : "", isSelected ? predToTimeCell : ""], [""], [""], bottomRowLabels, - bottomRowCells + bottomRowCells, ]); } @@ -1029,17 +1117,48 @@ class CDUPerformancePage { ]); } - static getSelectedTitleAndValue(_isPhaseActive, _isSelected, _preSel) { - if (_isPhaseActive) { - return _isSelected - ? ["SELECTED", "" + Math.round(Simplane.getAutoPilotMachModeActive() - ? SimVar.GetGameVarValue('FROM MACH TO KIAS', 'number', Simplane.getAutoPilotMachHoldValue()) - : Simplane.getAutoPilotAirspeedHoldValue()) + "[color]green"] - : ["", ""]; + static getClbSelectedTitleAndValue(mcdu, isPhaseActive, isSelected, preSel) { + if (!isPhaseActive) { + return ["PRESEL", (isFinite(preSel) ? "\xa0" + preSel : "*[ ]") + "[color]cyan"]; + } + + if (!isSelected) { + return ["", ""]; + } + + const aircraftAltitude = SimVar.GetSimVarValue('INDICATED ALTITUDE', 'feet'); + const selectedSpdMach = SimVar.GetSimVarValue('L:A32NX_AUTOPILOT_SPEED_SELECTED', 'number'); + + if (selectedSpdMach < 1) { + return ["SELECTED", `\xa0${selectedSpdMach.toFixed(2).replace('0.', '.')}[color]green`]; + } else { + const machAtManualCrossoverAlt = mcdu.casToMachManualCrossoverCurve.evaluate(selectedSpdMach) + const manualCrossoverAltitude = mcdu.computeManualCrossoverAltitude(machAtManualCrossoverAlt); + const shouldShowMach = aircraftAltitude < manualCrossoverAltitude && (!mcdu._cruiseEntered || !mcdu.cruiseFlightLevel || manualCrossoverAltitude < mcdu.cruiseFlightLevel * 100); + + return ["SELECTED", `\xa0${Math.round(selectedSpdMach)}${shouldShowMach ? ("{small}/" + machAtManualCrossoverAlt.toFixed(2).replace('0.', '.') + "{end}") : ""}[color]green`]; + } + } + + static getDesSelectedTitleAndValue(mcdu, isPhaseActive, isSelected) { + if (!isPhaseActive || !isSelected) { + return ["", ""]; + } + + const aircraftAltitude = SimVar.GetSimVarValue('INDICATED ALTITUDE', 'feet'); + const selectedSpdMach = SimVar.GetSimVarValue('L:A32NX_AUTOPILOT_SPEED_SELECTED', 'number'); + + if (selectedSpdMach < 1) { + const casAtCrossoverAltitude = mcdu.machToCasManualCrossoverCurve.evaluate(selectedSpdMach); + const manualCrossoverAltitude = mcdu.computeManualCrossoverAltitude(selectedSpdMach); + const shouldShowCas = aircraftAltitude > manualCrossoverAltitude; + + return ["SELECTED", `\xa0${shouldShowCas ? "{small}" + Math.round(casAtCrossoverAltitude) + "/{end}" : ""}${selectedSpdMach.toFixed(2).replace('0.', '.')}[color]green`]; } else { - return ["PRESEL", (isFinite(_preSel) ? "" + _preSel : "*[ ]") + "[color]cyan"]; + return ["SELECTED", `\xa0${Math.round(selectedSpdMach)}[color]green`]; } } + static formatAltitudeOrLevel(altitudeToFormat, transitionAltitude) { if (transitionAltitude >= 100 && altitudeToFormat > transitionAltitude) { return `FL${(altitudeToFormat / 100).toFixed(0).toString().padStart(3,"0")}`; @@ -1047,7 +1166,8 @@ class CDUPerformancePage { return (10 * Math.round(altitudeToFormat / 10)).toFixed(0).toString().padStart(5,"\xa0"); } - static getTimeAndDistancePredictionsFromGeometryProfile(geometryProfile, altitudeToPredict, isFlying, printSmall = false) { + + static getTimeAndDistancePredictionsFromGeometryProfile(geometryProfile, altitudeToPredict, isClimbVsDescent, printSmall = false) { let predToDistanceCell = "---"; let predToTimeCell = "----"; @@ -1055,7 +1175,10 @@ class CDUPerformancePage { return [predToTimeCell, predToDistanceCell]; } - const predictions = geometryProfile.computePredictionToFcuAltitude(altitudeToPredict); + const predictions = isClimbVsDescent + ? geometryProfile.computeClimbPredictionToAltitude(altitudeToPredict) + : geometryProfile.computeDescentPredictionToAltitude(altitudeToPredict); + if (predictions) { if (Number.isFinite(predictions.distanceFromStart)) { @@ -1068,10 +1191,7 @@ class CDUPerformancePage { if (Number.isFinite(predictions.secondsFromPresent)) { const utcTime = SimVar.GetGlobalVarValue("ZULU TIME", "seconds"); - - const predToTimeCellText = isFlying - ? FMCMainDisplay.secondsToUTC(utcTime + predictions.secondsFromPresent) - : FMCMainDisplay.minutesTohhmm(predictions.secondsFromPresent); + const predToTimeCellText = FMCMainDisplay.secondsToUTC(utcTime + predictions.secondsFromPresent) if (printSmall) { predToTimeCell = "{small}" + predToTimeCellText + "{end}[color]green"; @@ -1083,6 +1203,7 @@ class CDUPerformancePage { return [predToDistanceCell, predToTimeCell]; } + static formatDestEfobAndTime(mcdu, isFlying) { const destinationPrediction = mcdu.guidanceController.vnavDriver.getDestinationPrediction(); @@ -1107,6 +1228,7 @@ class CDUPerformancePage { return [destEfobCell, destTimeCell]; } + static formatToReasonDistanceAndTime(mcdu) { const toPrediction = mcdu.guidanceController.vnavDriver.getPerfCrzToPrediction(); @@ -1122,7 +1244,7 @@ class CDUPerformancePage { if (Number.isFinite(toPrediction.secondsFromPresent)) { const utcTime = SimVar.GetGlobalVarValue("ZULU TIME", "seconds"); - timeCell = FMCMainDisplay.secondsToUTC(utcTime + toPrediction.secondsFromPresent) + "\xa0[color]green"; + timeCell = FMCMainDisplay.secondsToUTC(utcTime + toPrediction.secondsFromPresent) + "[color]green"; } if (toPrediction.reason === "StepClimb") { @@ -1134,5 +1256,18 @@ class CDUPerformancePage { return ["{small}TO{end}\xa0{green}" + reasonCell + "{end}", distCell, timeCell]; } + + static formatCostIndexCell(mcdu, hasFromToPair, allowModification) { + let costIndexCell = "---"; + if (hasFromToPair) { + if (mcdu.costIndexSet && Number.isFinite(mcdu.costIndex)) { + costIndexCell = `${mcdu.costIndex.toFixed(0)}[color]${allowModification ? "cyan" : "green"}`; + } else { + costIndexCell = "___[color]amber"; + } + } + + return costIndexCell; + } } CDUPerformancePage._timer = 0; diff --git a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/FMC/A32NX_FMCMainDisplay.js b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/FMC/A32NX_FMCMainDisplay.js index fc8403851b5..5b9f9ceb530 100644 --- a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/FMC/A32NX_FMCMainDisplay.js +++ b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/FMC/A32NX_FMCMainDisplay.js @@ -102,7 +102,6 @@ class FMCMainDisplay extends BaseAirliners { this._progBrgDist = undefined; this.preSelectedClbSpeed = undefined; this.preSelectedCrzSpeed = undefined; - this.preSelectedDesSpeed = undefined; this.managedSpeedTarget = undefined; this.managedSpeedTargetIsMach = undefined; this.climbSpeedLimit = undefined; @@ -120,8 +119,9 @@ class FMCMainDisplay extends BaseAirliners { this.managedSpeedCruiseMach = undefined; // this.managedSpeedCruiseMachIsPilotEntered = undefined; this.managedSpeedDescend = undefined; - this.managedSpeedDescendIsPilotEntered = undefined; + this.managedSpeedDescendPilot = undefined; this.managedSpeedDescendMach = undefined; + this.managedSpeedDescendMachPilot = undefined; // this.managedSpeedDescendMachIsPilotEntered = undefined; this.cruiseFlightLevelTimeOut = undefined; /** @type {0 | 1 | 2 | 3 | null} Takeoff config entered on PERF TO */ @@ -155,6 +155,9 @@ class FMCMainDisplay extends BaseAirliners { this.destinationLongitude = undefined; /** Speed in KCAS when the first engine failed during takeoff */ this.takeoffEngineOutSpeed = undefined; + this.checkSpeedModeMessageActive = undefined; + this.perfClbPredToAltitudePilot = undefined; + this.perfDesPredToAltitudePilot = undefined; // ATSU data this.atsu = undefined; @@ -255,6 +258,30 @@ class FMCMainDisplay extends BaseAirliners { this.tempCurve.add(700 * 3.28084, -53.57); this.tempCurve.add(800 * 3.28084, -74.51); + // This is used to determine the Mach number corresponding to a CAS at the manual crossover altitude + // The curve was calculated numerically and approximated using a few interpolated values + this.casToMachManualCrossoverCurve = new Avionics.Curve(); + this.casToMachManualCrossoverCurve.interpolationFunction = Avionics.CurveTool.NumberInterpolation; + this.casToMachManualCrossoverCurve.add(0, 0); + this.casToMachManualCrossoverCurve.add(100, 0.27928); + this.casToMachManualCrossoverCurve.add(150, 0.41551); + this.casToMachManualCrossoverCurve.add(200, 0.54806); + this.casToMachManualCrossoverCurve.add(250, 0.67633); + this.casToMachManualCrossoverCurve.add(300, 0.8); + this.casToMachManualCrossoverCurve.add(350, 0.82); + + // This is used to determine the CAS corresponding to a Mach number at the manual crossover altitude + // Effectively, the manual crossover altitude is FL305 up to M.80, then decreases linearly to the crossover altitude of (VMO, MMO) + this.machToCasManualCrossoverCurve = new Avionics.Curve(); + this.machToCasManualCrossoverCurve.interpolationFunction = Avionics.CurveTool.NumberInterpolation; + this.machToCasManualCrossoverCurve.add(0, 0); + this.machToCasManualCrossoverCurve.add(0.27928, 100); + this.machToCasManualCrossoverCurve.add(0.41551, 150); + this.machToCasManualCrossoverCurve.add(0.54806, 200); + this.machToCasManualCrossoverCurve.add(0.67633, 250); + this.machToCasManualCrossoverCurve.add(0.8, 300); + this.machToCasManualCrossoverCurve.add(0.82, 350); + this.cruiseFlightLevel = SimVar.GetGameVarValue("AIRCRAFT CRUISE ALTITUDE", "feet"); this.cruiseFlightLevel /= 100; this._cruiseFlightLevel = this.cruiseFlightLevel; @@ -454,7 +481,6 @@ class FMCMainDisplay extends BaseAirliners { this._progBrgDist = undefined; this.preSelectedClbSpeed = undefined; this.preSelectedCrzSpeed = undefined; - this.preSelectedDesSpeed = undefined; this.managedSpeedTarget = NaN; this.managedSpeedTargetIsMach = false; this.climbSpeedLimit = 250; @@ -472,8 +498,9 @@ class FMCMainDisplay extends BaseAirliners { this.managedSpeedCruiseMach = 0.78; // this.managedSpeedCruiseMachIsPilotEntered = false; this.managedSpeedDescend = 290; - this.managedSpeedDescendIsPilotEntered = false; + this.managedSpeedDescendPilot = undefined; this.managedSpeedDescendMach = 0.78; + this.managedSpeedDescendMachPilot = undefined; // this.managedSpeedDescendMachIsPilotEntered = false; this.cruiseFlightLevelTimeOut = undefined; this.altDestination = undefined; @@ -498,6 +525,9 @@ class FMCMainDisplay extends BaseAirliners { this.toSpeedsTooLow = false; this.vSpeedDisagree = false; this.takeoffEngineOutSpeed = undefined; + this.checkSpeedModeMessageActive = false; + this.perfClbPredToAltitudePilot = undefined; + this.perfDesPredToAltitudePilot = undefined; this.onAirport = () => {}; @@ -605,6 +635,7 @@ class FMCMainDisplay extends BaseAirliners { this.updateTransitionAltitudeLevel(); this.updateMinimums(); this.updateIlsCourse(); + this.updatePerfPageAltPredictions(); } this.A32NXCore.update(); @@ -733,6 +764,7 @@ class FMCMainDisplay extends BaseAirliners { /** Activate pre selected speed/mach */ if (prevPhase === FmgcFlightPhases.CLIMB) { + this.triggerCheckSpeedModeMessage(this.preSelectedCrzSpeed); this.activatePreSelSpeedMach(this.preSelectedCrzSpeed); } @@ -759,13 +791,7 @@ class FMCMainDisplay extends BaseAirliners { Coherent.call("GENERAL_ENG_THROTTLE_MANAGED_MODE_SET", ThrottleMode.AUTO).catch(console.error).catch(console.error); - /** Activate pre selected speed/mach */ - if (prevPhase === FmgcFlightPhases.CRUISE) { - this.activatePreSelSpeedMach(this.preSelectedDesSpeed); - } - - /** Clear pre selected speed/mach */ - this.updatePreSelSpeedMach(undefined); + this.triggerCheckSpeedModeMessage(undefined); this.cruiseFlightLevel = undefined; @@ -851,6 +877,32 @@ class FMCMainDisplay extends BaseAirliners { } } + triggerCheckSpeedModeMessage(preselectedSpeed) { + const isSpeedSelected = !Simplane.getAutoPilotAirspeedManaged(); + const hasPreselectedSpeed = preselectedSpeed !== undefined; + + if (!this.checkSpeedModeMessageActive && isSpeedSelected && !hasPreselectedSpeed) { + this.checkSpeedModeMessageActive = true; + this.addMessageToQueue( + NXSystemMessages.checkSpeedMode, + () => !this.checkSpeedModeMessageActive, + () => { + this.checkSpeedModeMessageActive = false; + SimVar.SetSimVarValue("L:A32NX_PFD_MSG_CHECK_SPEED_MODE", "bool", false); + }, + ); + SimVar.SetSimVarValue("L:A32NX_PFD_MSG_CHECK_SPEED_MODE", "bool", true); + } + } + + clearCheckSpeedModeMessage() { + if (this.checkSpeedModeMessageActive && Simplane.getAutoPilotAirspeedManaged()) { + this.checkSpeedModeMessageActive = false; + this.removeMessageFromQueue(NXSystemMessages.checkSpeedMode.text); + SimVar.SetSimVarValue("L:A32NX_PFD_MSG_CHECK_SPEED_MODE", "bool", false); + } + } + /** FIXME these functions are in the new VNAV but not in this branch, remove when able */ /** * @@ -1065,9 +1117,8 @@ class FMCMainDisplay extends BaseAirliners { if (!this.managedSpeedCruiseIsPilotEntered) { this.managedSpeedCruise = this.getCrzManagedSpeedFromCostIndex(); } - if (!this.managedSpeedDescendIsPilotEntered) { - this.managedSpeedDescend = this.getDesManagedSpeedFromCostIndex(); - } + + this.managedSpeedDescend = this.getDesManagedSpeedFromCostIndex(); } updateManagedSpeed() { @@ -1075,6 +1126,7 @@ class FMCMainDisplay extends BaseAirliners { let isMach = false; this.updateHoldingSpeed(); + this.clearCheckSpeedModeMessage(); if (SimVar.GetSimVarValue("L:A32NX_FMA_EXPEDITE_MODE", "number") === 1) { const verticalMode = SimVar.GetSimVarValue("L:A32NX_FMA_VERTICAL_MODE", "number"); @@ -1156,7 +1208,7 @@ class FMCMainDisplay extends BaseAirliners { // Whether to use Mach or not should be based on the original managed speed, not whatever VNAV uses under the hood to vary it. // Also, VNAV already does the conversion from Mach if necessary - isMach = this.getManagedTargets(this.managedSpeedDescend, this.managedSpeedDescendMach)[1]; + isMach = this.getManagedTargets(this.getManagedDescentSpeed(), this.getManagedDescentSpeedMach())[1]; break; } case FmgcFlightPhases.APPROACH: { @@ -1443,6 +1495,10 @@ class FMCMainDisplay extends BaseAirliners { return Infinity; } + return this.getNavModeSpeedConstraint(); + } + + getNavModeSpeedConstraint() { const activeLegIndex = this.guidanceController.activeTransIndex >= 0 ? this.guidanceController.activeTransIndex : this.guidanceController.activeLegIndex; const constraints = this.managedProfile.get(activeLegIndex); if (constraints) { @@ -1452,7 +1508,7 @@ class FMCMainDisplay extends BaseAirliners { if (this.flightPhaseManager.phase > FmgcFlightPhases.CRUISE && this.flightPhaseManager.phase < FmgcFlightPhases.GOAROUND) { // FIXME proper decel calc - if (this.guidanceController.activeLegDtg < this.calculateDecelDist(Math.min(constraints.previousDescentSpeed, this.managedSpeedDescend), constraints.descentSpeed)) { + if (this.guidanceController.activeLegDtg < this.calculateDecelDist(Math.min(constraints.previousDescentSpeed, this.getManagedDescentSpeed()), constraints.descentSpeed)) { return constraints.descentSpeed; } else { return constraints.previousDescentSpeed; @@ -1543,7 +1599,7 @@ class FMCMainDisplay extends BaseAirliners { } wp.additionalData.predictedAltitude = Math.min(profilePoint.climbAltitude, this._cruiseFlightLevel * 100); } else if (wp.additionalData.constraintType === 2 /* DES */) { - wp.additionalData.predictedSpeed = Math.min(profilePoint.descentSpeed, this.managedSpeedDescend); + wp.additionalData.predictedSpeed = Math.min(profilePoint.descentSpeed, this.getManagedDescentSpeed()); if (this.descentSpeedLimitAlt && profilePoint.climbAltitude < this.descentSpeedLimitAlt) { wp.additionalData.predictedSpeed = Math.min(wp.additionalData.predictedSpeed, this.descentSpeedLimit); } @@ -3677,30 +3733,34 @@ class FMCMainDisplay extends BaseAirliners { if (s === FMCMainDisplay.clrValue) { this.preSelectedClbSpeed = undefined; if (isNextPhase) { - SimVar.SetSimVarValue("L:A32NX_MachPreselVal", "mach", -1); - SimVar.SetSimVarValue("L:A32NX_SpeedPreselVal", "knots", -1); + this.updatePreSelSpeedMach(undefined); } return true; } - const v = parseFloat(s); - if (isFinite(v)) { - if (v < 1) { - this.preSelectedClbSpeed = v; - if (isNextPhase) { - SimVar.SetSimVarValue("L:A32NX_MachPreselVal", "mach", v); - SimVar.SetSimVarValue("L:A32NX_SpeedPreselVal", "knots", -1); - } - } else { - this.preSelectedClbSpeed = Math.round(v); - if (isNextPhase) { - SimVar.SetSimVarValue("L:A32NX_SpeedPreselVal", "knots", this.preSelectedClbSpeed); - SimVar.SetSimVarValue("L:A32NX_MachPreselVal", "mach", -1); - } - } - return true; + + const SPD_REGEX = /\d{1,3}/; + if (s.match(SPD_REGEX) === null) { + this.setScratchpadMessage(NXSystemMessages.formatError); + return false; } - this.setScratchpadMessage(NXSystemMessages.notAllowed); - return false; + + const spd = parseInt(s); + if (!Number.isFinite(spd)) { + this.setScratchpadMessage(NXSystemMessages.formatError); + return false + } + + if (spd < 100 || spd > 350) { + this.setScratchpadMessage(NXSystemMessages.entryOutOfRange); + return false; + } + + this.preSelectedClbSpeed = spd; + if (isNextPhase) { + this.updatePreSelSpeedMach(spd); + } + + return true; } trySetPreSelectedCruiseSpeed(s) { @@ -3708,61 +3768,46 @@ class FMCMainDisplay extends BaseAirliners { if (s === FMCMainDisplay.clrValue) { this.preSelectedCrzSpeed = undefined; if (isNextPhase) { - SimVar.SetSimVarValue("L:A32NX_MachPreselVal", "mach", -1); - SimVar.SetSimVarValue("L:A32NX_SpeedPreselVal", "knots", -1); + this.updatePreSelSpeedMach(undefined); } return true; } + + const MACH_OR_SPD_REGEX = /^(\.\d{1,2}|\d{1,3})$/; + if (s.match(MACH_OR_SPD_REGEX) === null) { + this.setScratchpadMessage(NXSystemMessages.formatError); + return false; + } + const v = parseFloat(s); - if (isFinite(v)) { - if (v < 1) { - this.preSelectedCrzSpeed = v; - if (isNextPhase) { - SimVar.SetSimVarValue("L:A32NX_MachPreselVal", "mach", v); - SimVar.SetSimVarValue("L:A32NX_SpeedPreselVal", "knots", -1); - } - } else { - this.preSelectedCrzSpeed = Math.round(v); - if (isNextPhase) { - SimVar.SetSimVarValue("L:A32NX_SpeedPreselVal", "knots", this.preSelectedCrzSpeed); - SimVar.SetSimVarValue("L:A32NX_MachPreselVal", "mach", -1); - } - } - return true; + if (!Number.isFinite(v)) { + this.setScratchpadMessage(NXSystemMessages.formatError); + return false; } - this.setScratchpadMessage(NXSystemMessages.notAllowed); - return false; - } - trySetPreSelectedDescentSpeed(s) { - const isNextPhase = this.flightPhaseManager.phase === FmgcFlightPhases.CRUISE; - if (s === FMCMainDisplay.clrValue) { - this.preSelectedDesSpeed = undefined; - if (isNextPhase) { - SimVar.SetSimVarValue("L:A32NX_MachPreselVal", "mach", -1); - SimVar.SetSimVarValue("L:A32NX_SpeedPreselVal", "knots", -1); + if (v < 1) { + const mach = Math.round(v * 100) / 100; + if (mach < 0.15 || mach > 0.82) { + this.setScratchpadMessage(NXSystemMessages.entryOutOfRange); + return false; } - return true; - } - const v = parseFloat(s); - if (isFinite(v)) { - if (v < 1) { - this.preSelectedDesSpeed = v; - if (isNextPhase) { - SimVar.SetSimVarValue("L:A32NX_MachPreselVal", "mach", v); - SimVar.SetSimVarValue("L:A32NX_SpeedPreselVal", "knots", -1); - } - } else { - this.preSelectedDesSpeed = Math.round(v); - if (isNextPhase) { - SimVar.SetSimVarValue("L:A32NX_SpeedPreselVal", "knots", this.preSelectedDesSpeed); - SimVar.SetSimVarValue("L:A32NX_MachPreselVal", "mach", -1); - } + + this.preSelectedCrzSpeed = mach; + } else { + const spd = Math.round(v); + if (spd < 100 || spd > 350) { + this.setScratchpadMessage(NXSystemMessages.entryOutOfRange); + return false; } - return true; + + this.preSelectedCrzSpeed = spd; } - this.setScratchpadMessage(NXSystemMessages.notAllowed); - return false; + + if (isNextPhase) { + this.updatePreSelSpeedMach(this.preSelectedCrzSpeed); + } + + return true; } setPerfApprQNH(s) { @@ -4986,54 +5031,65 @@ class FMCMainDisplay extends BaseAirliners { getFlightPhase() { return this.flightPhaseManager.phase; } + getClimbSpeedLimit() { return { speed: this.climbSpeedLimit, underAltitude: this.climbSpeedLimitAlt, }; } + getDescentSpeedLimit() { return { speed: this.descentSpeedLimit, underAltitude: this.descentSpeedLimitAlt, }; } + getPreSelectedClbSpeed() { return this.preSelectedClbSpeed; } + getPreSelectedCruiseSpeed() { return this.preSelectedCrzSpeed; } - getPreSelectedDescentSpeed() { - return this.preSelectedDesSpeed; - } + getTakeoffFlapsSetting() { return this.flaps; } + getManagedDescentSpeed() { - return this.managedSpeedDescend; + return this.managedSpeedDescendPilot !== undefined ? this.managedSpeedDescendPilot : this.managedSpeedDescend; } + getManagedDescentSpeedMach() { - return this.managedSpeedDescendMach; + return this.managedSpeedDescendMachPilot !== undefined ? this.managedSpeedDescendMachPilot : this.managedSpeedDescendMach; } + getApproachSpeed() { return this.approachSpeeds && this.approachSpeeds.valid ? this.approachSpeeds.vapp : 0; } + getFlapRetractionSpeed() { return this.approachSpeeds && this.approachSpeeds.valid ? this.approachSpeeds.f : 0; } + getSlatRetractionSpeed() { return this.approachSpeeds && this.approachSpeeds.valid ? this.approachSpeeds.s : 0; } + getCleanSpeed() { return this.approachSpeeds && this.approachSpeeds.valid ? this.approachSpeeds.gd : 0; } + getTripWind() { return this.averageWind; } + getWinds() { return this.winds; } + getApproachWind() { const destination = this.flightPlanManager.getDestination(); if (!destination || !destination.infos && !destination.infos.coordinates || !isFinite(this.perfApprWindHeading)) { @@ -5045,15 +5101,171 @@ class FMCMainDisplay extends BaseAirliners { return { direction: trueHeading, speed: this.perfApprWindSpeed }; } + getApproachQnh() { return this.perfApprQNH; } + getApproachTemperature() { return this.perfApprTemp; } + getDestinationElevation() { return Number.isFinite(this.landingElevation) ? this.landingElevation : 0; } + + trySetManagedDescentSpeed(value) { + if (value === FMCMainDisplay.clrValue) { + this.managedSpeedDescendPilot = undefined; + this.managedSpeedDescendMachPilot = undefined; + return true; + } + + const MACH_SLASH_SPD_REGEX = /^(\.\d{1,2})?\/(\d{3})?$/; + const machSlashSpeedMatch = value.match(MACH_SLASH_SPD_REGEX); + + const MACH_REGEX = /^\.\d{1,2}$/; + const SPD_REGEX = /^\d{1,3}$/; + + if (machSlashSpeedMatch !== null /* ".NN/" or "/NNN" entry */) { + const speed = parseInt(machSlashSpeedMatch[2]); + if (Number.isFinite(speed)) { + if (speed < 100 || speed > 350) { + this.setScratchpadMessage(NXSystemMessages.entryOutOfRange); + return false; + } + + this.managedSpeedDescendPilot = speed; + } + + const mach = Math.round(parseFloat(machSlashSpeedMatch[1]) * 1000) / 1000; + if (Number.isFinite(mach)) { + if (mach < 0.15 || mach > 0.82) { + this.setScratchpadMessage(NXSystemMessages.entryOutOfRange); + return false + } + + this.managedSpeedDescendMachPilot = mach; + } + + return true; + } else if (value.match(MACH_REGEX) !== null /* ".NN" */) { + // Entry of a Mach number only without a slash is allowed + const mach = Math.round(parseFloat(value) * 1000) / 1000; + if (Number.isFinite(mach)) { + if (mach < 0.15 || mach > 0.82) { + this.setScratchpadMessage(NXSystemMessages.entryOutOfRange); + return false + } + + this.managedSpeedDescendMachPilot = mach; + } + + return true; + } else if (value.match(SPD_REGEX) !== null /* "NNN" */) { + const speed = parseInt(value); + if (Number.isFinite(speed)) { + if (speed < 100 || speed > 350) { + this.setScratchpadMessage(NXSystemMessages.entryOutOfRange); + return false; + } + + // This is the maximum managed Mach number you can get, even with CI 100. + // Through direct testing by a pilot, it was also determined that the plane gives Mach 0.80 for all of the tested CAS entries. + const mach = 0.8; + + this.managedSpeedDescendPilot = speed; + this.managedSpeedDescendMachPilot = mach; + + return true; + } + } + + this.setScratchpadMessage(NXSystemMessages.formatError); + return false; + } + + trySetPerfClbPredToAltitude(value) { + if (value === FMCMainDisplay.clrValue) { + this.perfClbPredToAltitudePilot = undefined; + return true; + } + + const currentAlt = SimVar.GetSimVarValue('INDICATED ALTITUDE', 'feet'); + const match = value.match(/^(FL\d{3}|\d{1,5})$/); + if (match === null || match.length < 1) { + this.setScratchpadMessage(NXSystemMessages.formatError); + return false; + } + + const altOrFlString = match[1].replace("FL", ""); + const altitude = altOrFlString.length < 4 ? 100 * parseInt(altOrFlString) : parseInt(altOrFlString); + + if (!Number.isFinite(altitude)) { + this.setScratchpadMessage(NXSystemMessages.formatError); + return false; + } + + if (altitude < currentAlt || (this._cruiseEntered && altitude > this.cruiseFlightLevel * 100)) { + this.setScratchpadMessage(NXSystemMessages.entryOutOfRange); + return false; + } + + this.perfClbPredToAltitudePilot = altitude; + return true; + } + + trySetPerfDesPredToAltitude(value) { + if (value === FMCMainDisplay.clrValue) { + this.perfDesPredToAltitudePilot = undefined; + return true; + } + + const currentAlt = SimVar.GetSimVarValue('INDICATED ALTITUDE', 'feet'); + const match = value.match(/^(FL\d{3}|\d{1,5})$/); + if (match === null || match.length < 1) { + this.setScratchpadMessage(NXSystemMessages.formatError); + return false; + } + + const altOrFlString = match[1].replace("FL", ""); + const altitude = altOrFlString.length < 4 ? 100 * parseInt(altOrFlString) : parseInt(altOrFlString); + + if (!Number.isFinite(altitude)) { + this.setScratchpadMessage(NXSystemMessages.formatError); + return false; + } + + if (altitude > currentAlt) { + this.setScratchpadMessage(NXSystemMessages.entryOutOfRange); + return false; + } + + this.perfDesPredToAltitudePilot = altitude; + return true; + } + + updatePerfPageAltPredictions() { + const currentAlt = SimVar.GetSimVarValue('INDICATED ALTITUDE', 'feet'); + if (this.perfClbPredToAltitudePilot !== undefined && currentAlt > this.perfClbPredToAltitudePilot) { + this.perfClbPredToAltitudePilot = undefined; + } + + if (this.perfDesPredToAltitudePilot !== undefined && currentAlt < this.perfDesPredToAltitudePilot) { + this.perfDesPredToAltitudePilot = undefined; + } + } + + computeManualCrossoverAltitude(mach) { + const maximumCrossoverAltitude = 30594; // Crossover altitude of (300, 0.8) + const mmoCrossoverAltitide = 24554; // Crossover altitude of (VMO, MMO) + + if (mach < 0.8) { + return maximumCrossoverAltitude; + } + + return maximumCrossoverAltitude + (mmoCrossoverAltitide - maximumCrossoverAltitude) * (mach - 0.8) / 0.02; + } } FMCMainDisplay.clrValue = "\xa0\xa0\xa0\xa0\xa0CLR"; diff --git a/fbw-a32nx/src/systems/fmgc/src/guidance/GuidanceController.ts b/fbw-a32nx/src/systems/fmgc/src/guidance/GuidanceController.ts index 2b2df1fe2b1..938994c1ec4 100644 --- a/fbw-a32nx/src/systems/fmgc/src/guidance/GuidanceController.ts +++ b/fbw-a32nx/src/systems/fmgc/src/guidance/GuidanceController.ts @@ -47,7 +47,6 @@ export interface Fmgc { getDescentSpeedLimit(): SpeedLimit, getPreSelectedClbSpeed(): Knots, getPreSelectedCruiseSpeed(): Knots, - getPreSelectedDescentSpeed(): Knots, getTakeoffFlapsSetting(): FlapConf | undefined getManagedDescentSpeed(): Knots, getManagedDescentSpeedMach(): Mach, diff --git a/fbw-a32nx/src/systems/fmgc/src/guidance/vnav/VerticalProfileComputationParameters.ts b/fbw-a32nx/src/systems/fmgc/src/guidance/vnav/VerticalProfileComputationParameters.ts index 6dd85012f2a..ed7791d1e12 100644 --- a/fbw-a32nx/src/systems/fmgc/src/guidance/vnav/VerticalProfileComputationParameters.ts +++ b/fbw-a32nx/src/systems/fmgc/src/guidance/vnav/VerticalProfileComputationParameters.ts @@ -44,7 +44,6 @@ export interface VerticalProfileComputationParameters { flightPhase: FmgcFlightPhase, preselectedClbSpeed: Knots, preselectedCruiseSpeed: Knots, - preselectedDescentSpeed: Knots, takeoffFlapsSetting?: FlapConf estimatedDestinationFuel: Pounds, @@ -106,7 +105,6 @@ export class VerticalProfileComputationParametersObserver { flightPhase: this.fmgc.getFlightPhase(), preselectedClbSpeed: this.fmgc.getPreSelectedClbSpeed(), preselectedCruiseSpeed: this.fmgc.getPreSelectedCruiseSpeed(), - preselectedDescentSpeed: this.fmgc.getPreSelectedDescentSpeed(), takeoffFlapsSetting: this.fmgc.getTakeoffFlapsSetting(), estimatedDestinationFuel: UnitType.TONNE.convertTo(this.fmgc.getDestEFOB(false), UnitType.POUND), diff --git a/fbw-a32nx/src/systems/fmgc/src/guidance/vnav/climb/SpeedProfile.ts b/fbw-a32nx/src/systems/fmgc/src/guidance/vnav/climb/SpeedProfile.ts index 44316e97075..0eac622d223 100644 --- a/fbw-a32nx/src/systems/fmgc/src/guidance/vnav/climb/SpeedProfile.ts +++ b/fbw-a32nx/src/systems/fmgc/src/guidance/vnav/climb/SpeedProfile.ts @@ -86,16 +86,15 @@ export class McduSpeedProfile implements SpeedProfile { } getTarget(distanceFromStart: NauticalMiles, altitude: Feet, managedSpeedType: ManagedSpeedType): Knots { - const { fcuSpeed, flightPhase, preselectedClbSpeed, preselectedCruiseSpeed, preselectedDescentSpeed } = this.parameters.get(); + const { fcuSpeed, flightPhase, preselectedClbSpeed, preselectedCruiseSpeed } = this.parameters.get(); let preselectedSpeed = -1; if (flightPhase < FmgcFlightPhase.Climb && preselectedClbSpeed > 100) { preselectedSpeed = preselectedClbSpeed; } else if (flightPhase < FmgcFlightPhase.Cruise && preselectedCruiseSpeed > 100) { preselectedSpeed = preselectedCruiseSpeed; - } else if (flightPhase < FmgcFlightPhase.Descent && preselectedDescentSpeed > 100) { - preselectedSpeed = preselectedDescentSpeed; } + const hasPreselectedSpeed = preselectedSpeed > 0; const isPredictingForCurrentPhase = managedSpeedType === ManagedSpeedType.Climb && flightPhase === FmgcFlightPhase.Climb diff --git a/fbw-a32nx/src/systems/fmgc/src/guidance/vnav/profile/BaseGeometryProfile.ts b/fbw-a32nx/src/systems/fmgc/src/guidance/vnav/profile/BaseGeometryProfile.ts index a0316a48f26..135aeb4aa2c 100644 --- a/fbw-a32nx/src/systems/fmgc/src/guidance/vnav/profile/BaseGeometryProfile.ts +++ b/fbw-a32nx/src/systems/fmgc/src/guidance/vnav/profile/BaseGeometryProfile.ts @@ -15,7 +15,7 @@ import { import { MathUtils } from '@flybywiresim/fbw-sdk'; import { VnavConfig } from '@fmgc/guidance/vnav/VnavConfig'; -export interface PerfClbToAltPrediction { +export interface PerfToAltPrediction { altitude: Feet, distanceFromStart: NauticalMiles, secondsFromPresent: Seconds, @@ -377,23 +377,50 @@ export abstract class BaseGeometryProfile { this.isReadyToDisplay = true; } - computePredictionToFcuAltitude(fcuAltitude: Feet): PerfClbToAltPrediction | undefined { - const maxAltitude = this.checkpoints.reduce((currentMax, checkpoint) => Math.max(currentMax, checkpoint.altitude), 0); + computeClimbPredictionToAltitude(altitude: Feet): PerfToAltPrediction | undefined { + const [minAlt, maxAlt] = this.checkpoints.reduce( + ([currentMin, currentMax], checkpoint) => [Math.min(currentMin, checkpoint.altitude), Math.max(currentMax, checkpoint.altitude)], [Infinity, -Infinity], + ); - if (fcuAltitude < this.checkpoints[0].altitude || fcuAltitude > maxAltitude) { + if (altitude < minAlt || altitude > maxAlt) { return undefined; } - const distanceToFcuAltitude = this.interpolateFromCheckpoints(fcuAltitude, (checkpoint) => checkpoint.altitude, (checkpoint) => checkpoint.distanceFromStart); + const distanceToFcuAltitude = this.interpolateFromCheckpoints(altitude, (checkpoint) => checkpoint.altitude, (checkpoint) => checkpoint.distanceFromStart); const timeToFcuAltitude = this.interpolateTimeAtDistance(distanceToFcuAltitude); return { - altitude: fcuAltitude, + altitude, distanceFromStart: distanceToFcuAltitude, secondsFromPresent: timeToFcuAltitude, }; } + computeDescentPredictionToAltitude(altitude: Feet): PerfToAltPrediction | undefined { + const [minAlt, maxAlt] = this.checkpoints.reduce( + ([currentMin, currentMax], checkpoint) => [Math.min(currentMin, checkpoint.altitude), Math.max(currentMax, checkpoint.altitude)], [Infinity, -Infinity], + ); + + if (altitude < minAlt || altitude > maxAlt) { + return undefined; + } + + const ppos = this.findVerticalCheckpoint(VerticalCheckpointReason.PresentPosition); + if (!ppos) { + return undefined; + } + + // TODO: Do this in one call + const distanceToFcuAltitude = this.interpolateFromCheckpointsBackwards(altitude, (checkpoint) => checkpoint.altitude, (checkpoint) => checkpoint.distanceFromStart, true); + const timeToFcuAltitude = this.interpolateFromCheckpointsBackwards(altitude, (checkpoint) => checkpoint.altitude, (checkpoint) => checkpoint.secondsFromPresent, true); + + return { + altitude, + distanceFromStart: distanceToFcuAltitude - ppos.distanceFromStart, + secondsFromPresent: timeToFcuAltitude, + }; + } + addPresentPositionCheckpoint(presentPosition: LatLongAlt, remainingFuelOnBoard: number, mach: Mach, vman: Knots) { this.checkpoints.push({ reason: VerticalCheckpointReason.PresentPosition, diff --git a/fbw-a32nx/src/systems/instruments/src/PFD/FMA.tsx b/fbw-a32nx/src/systems/instruments/src/PFD/FMA.tsx index 97ba8e30897..fe29325e32f 100644 --- a/fbw-a32nx/src/systems/instruments/src/PFD/FMA.tsx +++ b/fbw-a32nx/src/systems/instruments/src/PFD/FMA.tsx @@ -56,6 +56,8 @@ export class FMA extends DisplayComponent<{ bus: ArincEventBus, isAttExcessive: private tdReached = false; + private checkSpeedMode = false; + private tcasRaInhibited = Subject.create(false); private trkFpaDeselected = Subject.create(false); @@ -74,7 +76,7 @@ export class FMA extends DisplayComponent<{ bus: ArincEventBus, isAttExcessive: const sharedModeActive = this.activeLateralMode === 32 || this.activeLateralMode === 33 || this.activeLateralMode === 34 || (this.activeLateralMode === 20 && this.activeVerticalMode === 24); const BC3Message = getBC3Message(this.props.isAttExcessive.get(), this.armedVerticalModeSub.get(), - this.setHoldSpeed, this.trkFpaDeselected.get(), this.tcasRaInhibited.get(), this.fcdcDiscreteWord1, this.fwcFlightPhase, this.tdReached)[0] !== null; + this.setHoldSpeed, this.trkFpaDeselected.get(), this.tcasRaInhibited.get(), this.fcdcDiscreteWord1, this.fwcFlightPhase, this.tdReached, this.checkSpeedMode)[0] !== null; const engineMessage = this.athrModeMessage; const AB3Message = (this.machPreselVal !== -1 @@ -162,6 +164,11 @@ export class FMA extends DisplayComponent<{ bus: ArincEventBus, isAttExcessive: this.tdReached = tdr; this.handleFMABorders(); }); + + sub.on('checkSpeedMode').whenChanged().handle((csm) => { + this.checkSpeedMode = csm; + this.handleFMABorders(); + }); } render(): VNode { @@ -1192,6 +1199,7 @@ const getBC3Message = ( fcdcWord1: Arinc429Word, fwcFlightPhase: number, tdReached: boolean, + checkSpeedMode: boolean, ) => { const armedVerticalBitmask = armedVerticalMode; const TCASArmed = (armedVerticalBitmask >> 6) & 1; @@ -1237,7 +1245,7 @@ const getBC3Message = ( } else if (false) { text = 'MORE DRAG'; className = 'FontMedium White'; - } else if (false) { + } else if (checkSpeedMode && !isAttExcessive) { text = 'CHECK SPEED MODE'; className = 'FontMedium White'; } else if (false) { @@ -1283,9 +1291,19 @@ class BC3Cell extends DisplayComponent<{ isAttExcessive: Subscribable, private tdReached = false; + private checkSpeedMode = false; + private fillBC3Cell() { const [text, className] = getBC3Message( - this.isAttExcessive, this.armedVerticalMode, this.setHoldSpeed, this.trkFpaDeselected, this.tcasRaInhibited, this.fcdcDiscreteWord1, this.fwcFlightPhase, this.tdReached, + this.isAttExcessive, + this.armedVerticalMode, + this.setHoldSpeed, + this.trkFpaDeselected, + this.tcasRaInhibited, + this.fcdcDiscreteWord1, + this.fwcFlightPhase, + this.tdReached, + this.checkSpeedMode, ); this.classNameSub.set(`MiddleAlign ${className}`); if (text !== null) { @@ -1338,6 +1356,11 @@ class BC3Cell extends DisplayComponent<{ isAttExcessive: Subscribable, this.tdReached = tdr; this.fillBC3Cell(); }); + + sub.on('checkSpeedMode').whenChanged().handle((csm) => { + this.checkSpeedMode = csm; + this.fillBC3Cell(); + }); } render(): VNode { diff --git a/fbw-a32nx/src/systems/instruments/src/PFD/shared/PFDSimvarPublisher.tsx b/fbw-a32nx/src/systems/instruments/src/PFD/shared/PFDSimvarPublisher.tsx index df9d5dee5f5..2178cf7be86 100644 --- a/fbw-a32nx/src/systems/instruments/src/PFD/shared/PFDSimvarPublisher.tsx +++ b/fbw-a32nx/src/systems/instruments/src/PFD/shared/PFDSimvarPublisher.tsx @@ -87,6 +87,7 @@ export type PFDSimvars = AdirsSimVars & SwitchingPanelVSimVars & { tdReached: boolean; trkFpaDeselectedTCAS: boolean; tcasRaInhibited: boolean; + checkSpeedMode: boolean; radioAltitude1: number; radioAltitude2: number; crzAltMode: boolean; @@ -241,6 +242,7 @@ export enum PFDVars { trkFpaDeselectedTCAS = 'L:A32NX_AUTOPILOT_TCAS_MESSAGE_TRK_FPA_DESELECTION', tdReached = 'L:A32NX_PFD_MSG_TD_REACHED', tcasRaInhibited = 'L:A32NX_AUTOPILOT_TCAS_MESSAGE_RA_INHIBITED', + checkSpeedMode = 'L:A32NX_PFD_MSG_CHECK_SPEED_MODE', radioAltitude1 = 'L:A32NX_RA_1_RADIO_ALTITUDE', radioAltitude2 = 'L:A32NX_RA_2_RADIO_ALTITUDE', crzAltMode = 'L:A32NX_FMA_CRUISE_ALT_MODE', @@ -400,6 +402,7 @@ export class PFDSimvarPublisher extends UpdatableSimVarPublisher { ['tdReached', { name: PFDVars.tdReached, type: SimVarValueType.Bool }], ['trkFpaDeselectedTCAS', { name: PFDVars.trkFpaDeselectedTCAS, type: SimVarValueType.Bool }], ['tcasRaInhibited', { name: PFDVars.tcasRaInhibited, type: SimVarValueType.Bool }], + ['checkSpeedMode', { name: PFDVars.checkSpeedMode, type: SimVarValueType.Bool }], ['radioAltitude1', { name: PFDVars.radioAltitude1, type: SimVarValueType.Number }], ['radioAltitude2', { name: PFDVars.radioAltitude2, type: SimVarValueType.Number }], ['crzAltMode', { name: PFDVars.crzAltMode, type: SimVarValueType.Bool }], From 48fda7c7af1a8f82ff4d7f6c83b098b1964cd7a9 Mon Sep 17 00:00:00 2001 From: Saschl <19493808+Saschl@users.noreply.github.com> Date: Thu, 26 Oct 2023 16:42:29 +0200 Subject: [PATCH 2/4] fix: swap ils receiver index (#8268) * fix: swap ils receiver index * update package lock after version update --- fbw-a32nx/src/systems/instruments/src/ND/ND.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fbw-a32nx/src/systems/instruments/src/ND/ND.tsx b/fbw-a32nx/src/systems/instruments/src/ND/ND.tsx index d1025ab71cd..524796167d3 100644 --- a/fbw-a32nx/src/systems/instruments/src/ND/ND.tsx +++ b/fbw-a32nx/src/systems/instruments/src/ND/ND.tsx @@ -282,7 +282,7 @@ export class NDComponent extends DisplayComponent { trueTrackWord={this.trueTrackWord} rangeValue={this.mapRangeRadius} isUsingTrackUpMode={this.isUsingTrackUpMode} - index={this.props.side === 'L' ? 1 : 2} + index={this.props.side === 'L' ? 2 : 1} /> { trueTrackWord={this.trueTrackWord} rangeValue={this.mapRangeRadius} isUsingTrackUpMode={this.isUsingTrackUpMode} - index={this.props.side === 'L' ? 1 : 2} + index={this.props.side === 'L' ? 2 : 1} /> Date: Thu, 26 Oct 2023 17:22:20 +0200 Subject: [PATCH 3/4] fix(fwc): Fixes the Master Warning on startup (#8267) * Fixes the issue * removed leftover logging --- fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts index e086a14a281..d03869c37f0 100644 --- a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts +++ b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts @@ -920,6 +920,9 @@ export class PseudoFWC { this.computedAirSpeed.set(Arinc429Word.fromSimVarValue('L:A32NX_ADIRS_ADR_1_COMPUTED_AIRSPEED')); + // needs to happen before dual eng failure check + this.aircraftOnGround.set(SimVar.GetSimVarValue('SIM ON GROUND', 'Bool')); + /* ENGINE AND THROTTLE */ this.engine1State.set(SimVar.GetSimVarValue('L:A32NX_ENGINE_STATE:1', 'Enum')); @@ -1021,7 +1024,7 @@ export class PseudoFWC { // const [right1LandingGear] = useSimVar('L:A32NX_LGCIU_1_RIGHT_GEAR_COMPRESSED', 'bool', 500); // const aircraftOnGround = left1LandingGear === 1 || right1LandingGear === 1; // FIXME The landing gear triggers the dual engine failure on loading - this.aircraftOnGround.set(SimVar.GetSimVarValue('SIM ON GROUND', 'Bool')); + this.antiskidActive.set(SimVar.GetSimVarValue('ANTISKID BRAKES ACTIVE', 'bool')); this.brakeFan.set(SimVar.GetSimVarValue('L:A32NX_BRAKE_FAN', 'bool')); this.brakesHot.set(SimVar.GetSimVarValue('L:A32NX_BRAKES_HOT', 'bool')); From 8f6f01721371c3d817d33604e618752dad69e734 Mon Sep 17 00:00:00 2001 From: Saschl <19493808+Saschl@users.noreply.github.com> Date: Fri, 27 Oct 2023 10:56:12 +0200 Subject: [PATCH 4/4] fix: capt nd shows VOR1 (#8270) --- fbw-a32nx/src/systems/instruments/src/ND/ND.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fbw-a32nx/src/systems/instruments/src/ND/ND.tsx b/fbw-a32nx/src/systems/instruments/src/ND/ND.tsx index 524796167d3..69286394303 100644 --- a/fbw-a32nx/src/systems/instruments/src/ND/ND.tsx +++ b/fbw-a32nx/src/systems/instruments/src/ND/ND.tsx @@ -282,6 +282,7 @@ export class NDComponent extends DisplayComponent { trueTrackWord={this.trueTrackWord} rangeValue={this.mapRangeRadius} isUsingTrackUpMode={this.isUsingTrackUpMode} + /* Capt ND shows ILS2 */ index={this.props.side === 'L' ? 2 : 1} /> { trueTrackWord={this.trueTrackWord} rangeValue={this.mapRangeRadius} isUsingTrackUpMode={this.isUsingTrackUpMode} - index={this.props.side === 'L' ? 2 : 1} + /* Capt ND shows VOR1 */ + index={this.props.side === 'L' ? 1 : 2} />