From fa47b5f990cd059e4ba882da3f98f74111982eed Mon Sep 17 00:00:00 2001 From: BBK <22713769+BlueberryKing@users.noreply.github.com> Date: Mon, 21 Nov 2022 20:00:54 +0100 Subject: [PATCH] feat(rmp): RMP navigation tuning #7241 (@494aa9f) --- .github/CHANGELOG.md | 1 + docs/a320-simvars.md | 49 +++ .../A32NX/AirlinerCommon.xml | 339 +++++++++++++++++- .../model/A320_NEO_INTERIOR.xml | 284 +++++++-------- flybywire-aircraft-a320-neo/en-US.locPak | 10 + flybywire-aircraft-a320-neo/fr-FR.locPak | 10 + .../CDU/A320_Neo_CDU_NavRadioPage.js | 167 ++++++--- .../CDU/A320_Neo_CDU_SelectedNavaids.js | 13 +- .../FMC/A32NX_FMCMainDisplay.js | 77 +++- src/behavior/src/A32NX_Interior_RMP.xml | 39 +- src/fmgc/src/radionav/NavRadioManager.ts | 37 +- .../src/ND/elements/RadioNavInfo.tsx | 13 +- src/instruments/src/ND/pages/RoseMode.tsx | 8 +- src/instruments/src/PFD/HeadingIndicator.tsx | 3 +- .../src/PFD/LandingSystemIndicator.tsx | 9 +- src/instruments/src/PFD/instrument.tsx | 1 + .../src/PFD/shared/PFDSimvarPublisher.tsx | 3 + .../src/RMP/Components/BaseRadioPanels.tsx | 97 ++++- .../src/RMP/Components/NavRadioPanel.tsx | 163 +++++++++ .../src/RMP/Components/RadioPanelDisplay.tsx | 28 +- .../src/RMP/Components/StandbyCourse.tsx | 44 +++ .../src/RMP/Components/StandbyFrequency.tsx | 82 ++++- .../src/RMP/Components/VhfRadioPanel.tsx | 12 +- 23 files changed, 1173 insertions(+), 316 deletions(-) create mode 100644 src/instruments/src/RMP/Components/NavRadioPanel.tsx create mode 100644 src/instruments/src/RMP/Components/StandbyCourse.tsx diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 4a803bc1a35..6261447351b 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -100,6 +100,7 @@ 1. [HYD] Implemented Electro Hydrostatic actuators - @Crocket63 (crocket) 1. [FAC] Move Speedscale computation to FAC - @lukecologne (luke) 1. [EFB] Added pause at T/D function - @2hwk (2Cas#1022) +1. [RMP] RMPs navigation backup - Julian Sebline (Julian Sebline#8476 on Discord) ## 0.8.0 diff --git a/docs/a320-simvars.md b/docs/a320-simvars.md index d58fea92000..fdada096d32 100644 --- a/docs/a320-simvars.md +++ b/docs/a320-simvars.md @@ -371,6 +371,10 @@ - Number - The current mode of the right radio management panel. +- A32NX_RMP_{L,R}_NAV_BUTTON_SELECTED + - Bool + - Whether the NAV push button on the corresponding RMP is pushed or not. + - A32NX_RMP_L_VHF2_STANDBY - Hz - The VHF 2 standby frequency for the left RMP. @@ -387,6 +391,42 @@ - Hz - The VHF 3 standby frequency for the right RMP. +- A32NX_RMP_{L,R}_SAVED_ACTIVE_FREQUENCY_VOR + - Hz + - The VOR active frequency that is saved for display for the left/right RMP. + +- A32NX_RMP_{L,R}_SAVED_ACTIVE_FREQUENCY_ILS + - Hz + - The ILS active frequency that is saved for display for the left/right RMP. + +- A32NX_RMP_{L,R}_SAVED_ACTIVE_FREQUENCY_ADF + - Hz + - The ADF active frequency that is saved for display for the left/right RMP. + +- A32NX_RMP_{L,R}_SAVED_STANDBY_FREQUENCY_VOR + - Hz + - The VOR standby frequency that is saved for display for the left/right RMP. + +- A32NX_RMP_{L,R}_SAVED_STANDBY_FREQUENCY_ILS + - Hz + - The ILS standby frequency that is saved for display for the left/right RMP. + +- A32NX_RMP_{L,R}_SAVED_STANDBY_FREQUENCY_ADF + - Hz + - The ADF standby frequency that is saved for display for the left/right RMP. + +- A32NX_RMP_{L,R}_SAVED_COURSE_VOR + - Number + - The VOR course tuned via the left/right RMP + +- A32NX_RMP_{L,R}_SAVED_COURSE_ILS + - Number + - The ILS course tuned via the left/right RMP + +- A32NX_RMP_ILS_TUNED + - Bool + - If the ILS is tuned via the RMP + - A32NX_TO_CONFIG_FLAPS_ENTERED - Bool - True if the pilot has entered a FLAPS value in the PERF TAKE OFF takeoff @@ -1011,6 +1051,15 @@ GOAROUND | 6 DONE | 7 +- A32NX_FMGC_RADIONAV_TUNING_MODE + - Enum + - Hold the FMGCs current tuning mode + Value | Meaning + --- | --- + 0 | AUTO + 1 | MANUAL + 2 | REMOTE VIA RMPs + - A32NX_FLAPS_HANDLE_INDEX - Number - Indicates the physical flaps handle position diff --git a/flybywire-aircraft-a320-neo/ModelBehaviorDefs/A32NX/AirlinerCommon.xml b/flybywire-aircraft-a320-neo/ModelBehaviorDefs/A32NX/AirlinerCommon.xml index cf366cabd1c..2dc26f20905 100644 --- a/flybywire-aircraft-a320-neo/ModelBehaviorDefs/A32NX/AirlinerCommon.xml +++ b/flybywire-aircraft-a320-neo/ModelBehaviorDefs/A32NX/AirlinerCommon.xml @@ -278,8 +278,11 @@ 1 0.04 0.01 + 0.02 AIRLINER - 1 + (L:A32NX_ELEC_DC_ESS_BUS_IS_POWERED, Bool) + (L:A32NX_ELEC_DC_ESS_BUS_IS_POWERED, Bool) + (L:A32NX_ELEC_DC_ESS_BUS_IS_POWERED, Bool) @@ -318,6 +321,7 @@ #PREFIX#_AudioReceiver_Push_mic_call_03#SUFFIX_ID# #PREFIX#_AudioReceiver_Push_mic_call_03_SEQ1#SUFFIX_ID# #PREFIX#_AudioReceiver_Push_mic_call_03_SEQ2#SUFFIX_ID# + #PREFIX#_AudioReceiver_Knob_MKR_LED#SUFFIX_ID# False @@ -396,6 +400,267 @@ TT:COCKPIT.TOOLTIPS.TRANSMITTER_VHF_R_AUDIO_TOGGLE TT:COCKPIT.TOOLTIPS.TRANSMITTER_SELECT_VHF_R + + + BUTTON + #NODE_ID_RECEIVER_VOICE# + #ANIM_NAME_PUSH_RECEIVER_VOICE# + #NODE_ID_BUTTON_VOICE_SEQ1# + #NODE_ID_BUTTON_VOICE_SEQ2# + + (L:XMLVAR_NAV_#SIDE#_VOICE_Switch_Down) ! (>L:XMLVAR_NAV_#SIDE#_VOICE_Switch_Down) + (L:XMLVAR_NAV_L_VOICE_Switch_Down) (L:XMLVAR_NAV_R_VOICE_Switch_Down) or (>L:XMLVAR_NAV_VOICE_Switch_Down) + + (L:XMLVAR_NAV_VOICE_Switch_Down) 0 == if{ + (L:XMLVAR_NAV_L_VOR1_Switch_Down) (L:XMLVAR_NAV_R_VOR1_Switch_Down) or if{ + 1 (>K:RADIO_VOR1_IDENT_ENABLE) + 1 (>K:RADIO_DME1_IDENT_ENABLE) + } + (L:XMLVAR_NAV_L_VOR2_Switch_Down) (L:XMLVAR_NAV_R_VOR2_Switch_Down) or if{ + 1 (>K:RADIO_VOR2_IDENT_ENABLE) + 1 (>K:RADIO_DME2_IDENT_ENABLE) + } + + (L:XMLVAR_NAV_L_ADF1_Switch_Down) (L:XMLVAR_NAV_R_ADF1_Switch_Down) or if{ + 1 (>K:RADIO_ADF_IDENT_ENABLE) + } + (L:XMLVAR_NAV_L_ADF2_Switch_Down) (L:XMLVAR_NAV_R_ADF2_Switch_Down) or if{ + 1 (>K:RADIO_ADF2_IDENT_ENABLE) + } + } + + (L:XMLVAR_NAV_VOICE_Switch_Down) 1 == if{ + 1 (>K:RADIO_VOR1_IDENT_DISABLE) + 1 (>K:RADIO_VOR2_IDENT_DISABLE) + 1 (>K:RADIO_ADF_IDENT_DISABLE) + 1 (>K:RADIO_ADF2_IDENT_DISABLE) + + 1 (>K:RADIO_DME1_IDENT_DISABLE) + 1 (>K:RADIO_DME2_IDENT_DISABLE) + } + + fcubutton + 0.1 + fcubutton + 0.5 + (L:XMLVAR_NAV_#SIDE#_VOICE_Switch_Down) + %((L:XMLVAR_NAV_#SIDE#_VOICE_Switch_Down))%{if}TT:COCKPIT.TOOLTIPS.NAV_PUSHBUTTON_VOICE_OFF%{else}TT:COCKPIT.TOOLTIPS.NAV_PUSHBUTTON_VOICE_ON%{end} + + + + KNOB + FBW_AIRLINER_Audio_NAV_Volume_Knob_Template + #NODE_ID_RECEIVER_VOR1# + #NODE_ID_LIGHT_RECEIVER_VOR1# + #ANIM_NAME_KNOB_RECEIVER_VOR1# + #ANIM_NAME_PUSH_RECEIVER_VOR1# + VOR1 + + + (L:XMLVAR_NAV_VOICE_Switch_Down) 0 == if{ + (A:NAV SOUND:1, Bool) 1 == (L:XMLVAR_NAV_L_VOR1_Switch_Down) 0 == (L:XMLVAR_NAV_R_VOR1_Switch_Down) 0 == and and if{ + 1 (>K:RADIO_VOR1_IDENT_TOGGLE) + 1 (>K:RADIO_DME1_IDENT_SET) + } + + (A:NAV SOUND:1, Bool) 0 == (L:XMLVAR_NAV_L_VOR1_Switch_Down) 1 == (L:XMLVAR_NAV_R_VOR1_Switch_Down) 1 == or and if{ + 1 (>K:RADIO_VOR1_IDENT_TOGGLE) + 1 (>K:RADIO_DME1_IDENT_SET) + } + } + + + + (L:XMLVAR_NAV_#SIDE#_VOR1_Volume) #VOLUME_INCREMENT_NAV# + 1 min (>L:XMLVAR_NAV_#SIDE#_VOR1_Volume) + (L:XMLVAR_NAV_#SIDE#_VOR1_Volume) (A:NAV VOLUME:1, percent over 100) > if{ + 1 (>K:NAV1_VOLUME_INC) + } + + + (L:XMLVAR_NAV_#SIDE#_VOR1_Volume) #VOLUME_INCREMENT_NAV# - 0 max (>L:XMLVAR_NAV_#SIDE#_VOR1_Volume) + (L:XMLVAR_NAV_L_VOR1_Volume) (L:XMLVAR_NAV_R_VOR1_Volume) max (A:NAV VOLUME:1, percent over 100) < if{ + 1 (>K:NAV1_VOLUME_DEC) + } + + TT:COCKPIT.TOOLTIPS.TRANSMITTER_VOR1_VOLUME_DEC + TT:COCKPIT.TOOLTIPS.TRANSMITTER_VOR1_VOLUME_INC + %((L:XMLVAR_NAV_#SIDE#_VOR1_Switch_Down))%{if}TT:INPUT.KEY_RADIO_VOR1_IDENT_DISABLE_DESC%{else}TT:INPUT.KEY_RADIO_VOR1_IDENT_ENABLE_DESC%{end} + + + + KNOB + FBW_AIRLINER_Audio_NAV_Volume_Knob_Template + #NODE_ID_RECEIVER_VOR2# + #NODE_ID_LIGHT_RECEIVER_VOR2# + #ANIM_NAME_KNOB_RECEIVER_VOR2# + #ANIM_NAME_PUSH_RECEIVER_VOR2# + VOR2 + + + (L:XMLVAR_NAV_VOICE_Switch_Down) 0 == if{ + (A:NAV SOUND:2, Bool) 1 == (L:XMLVAR_NAV_L_VOR2_Switch_Down) 0 == (L:XMLVAR_NAV_R_VOR2_Switch_Down) 0 == and and if{ + 1 (>K:RADIO_VOR2_IDENT_TOGGLE) + 1 (>K:RADIO_DME2_IDENT_SET) + } + + (A:NAV SOUND:2, Bool) 0 == (L:XMLVAR_NAV_L_VOR2_Switch_Down) 1 == (L:XMLVAR_NAV_R_VOR2_Switch_Down) 1 == or and if{ + 1 (>K:RADIO_VOR2_IDENT_TOGGLE) + 1 (>K:RADIO_DME2_IDENT_SET) + } + } + + + + (L:XMLVAR_NAV_#SIDE#_VOR2_Volume) #VOLUME_INCREMENT_NAV# + 1 min (>L:XMLVAR_NAV_#SIDE#_VOR2_Volume) + (L:XMLVAR_NAV_#SIDE#_VOR2_Volume) (A:NAV VOLUME:2, percent over 100) > if{ + 1 (>K:NAV2_VOLUME_INC) + } + + + (L:XMLVAR_NAV_#SIDE#_VOR2_Volume) #VOLUME_INCREMENT_NAV# - 0 max (>L:XMLVAR_NAV_#SIDE#_VOR2_Volume) + (L:XMLVAR_NAV_L_VOR2_Volume) (L:XMLVAR_NAV_R_VOR2_Volume) max (A:NAV VOLUME:2, percent over 100) < if{ + 1 (>K:NAV2_VOLUME_DEC) + } + + TT:COCKPIT.TOOLTIPS.TRANSMITTER_VOR2_VOLUME_DEC + TT:COCKPIT.TOOLTIPS.TRANSMITTER_VOR2_VOLUME_INC + %((L:XMLVAR_NAV_#SIDE#_VOR2_Switch_Down))%{if}TT:INPUT.KEY_RADIO_VOR2_IDENT_DISABLE_DESC%{else}TT:INPUT.KEY_RADIO_VOR2_IDENT_ENABLE_DESC%{end} + + + + KNOB + FBW_AIRLINER_Audio_NAV_Volume_Knob_Template + #NODE_ID_RECEIVER_MKR# + #NODE_ID_LIGHT_RECEIVER_MKR# + #ANIM_NAME_KNOB_RECEIVER_MKR# + #ANIM_NAME_PUSH_RECEIVER_MKR# + #NODE_ID_LED_RECEIVER_MKR# + MKR + + (A:MARKER SOUND, Bool) 1 == (L:XMLVAR_NAV_L_MKR_Switch_Down) 0 == (L:XMLVAR_NAV_R_MKR_Switch_Down) 0 == and and if{ + 1 (>K:MARKER_SOUND_TOGGLE) + } + + (A:MARKER SOUND, Bool) 0 == (L:XMLVAR_NAV_L_MKR_Switch_Down) 1 == (L:XMLVAR_NAV_R_MKR_Switch_Down) 1 == or and if{ + 1 (>K:MARKER_SOUND_TOGGLE) + } + + + (L:XMLVAR_NAV_#SIDE#_MKR_Volume) #VOLUME_INCREMENT_NAV# + 1 min (>L:XMLVAR_NAV_#SIDE#_MKR_Volume) + (L:XMLVAR_NAV_#SIDE#_MKR_Volume) #VOLUME_INCREMENT_NAV# - 0 max (>L:XMLVAR_NAV_#SIDE#_MKR_Volume) + TT:COCKPIT.TOOLTIPS.NAV_KNOB_MARKERS_VOLUME_DECREASE + TT:COCKPIT.TOOLTIPS.NAV_KNOB_MARKERS_VOLUME_INCREASE + %((L:XMLVAR_NAV_#SIDE#_MKR_Switch_Down))%{if}TT:COCKPIT.TOOLTIPS.NAV_KNOB_MARKERS_IDENT_DISABLE%{else}TT:COCKPIT.TOOLTIPS.NAV_KNOB_MARKERS_IDENT_ENABLE%{end} + + + + KNOB + FBW_AIRLINER_Audio_NAV_Volume_Knob_Template + #NODE_ID_RECEIVER_ILS# + #NODE_ID_LIGHT_RECEIVER_ILS# + #ANIM_NAME_KNOB_RECEIVER_ILS# + #ANIM_NAME_PUSH_RECEIVER_ILS# + #NODE_ID_LED_RECEIVER_ILS# + ILS + + + + (L:XMLVAR_NAV_#SIDE#_ILS_Volume) #VOLUME_INCREMENT_NAV# + 1 min (>L:XMLVAR_NAV_#SIDE#_ILS_Volume) + (L:XMLVAR_NAV_#SIDE#_ILS_Volume) #VOLUME_INCREMENT_NAV# - 0 max (>L:XMLVAR_NAV_#SIDE#_ILS_Volume) + TT:COCKPIT.TOOLTIPS.NAV_KNOB_ILS_VOLUME_DECREASE + TT:COCKPIT.TOOLTIPS.NAV_KNOB_ILS_VOLUME_INCREASE + %((L:XMLVAR_NAV_#SIDE#_ILS_Switch_Down))%{if}TT:COCKPIT.TOOLTIPS.NAV_KNOB_ILS_IDENT_DISABLE%{else}TT:COCKPIT.TOOLTIPS.NAV_KNOB_ILS_IDENT_ENABLE%{end} + + + + KNOB + FBW_AIRLINER_Audio_NAV_Volume_Knob_Template + #NODE_ID_RECEIVER_MLS# + #NODE_ID_LIGHT_RECEIVER_MLS# + #ANIM_NAME_KNOB_RECEIVER_MLS# + #ANIM_NAME_PUSH_RECEIVER_MLS# + #NODE_ID_LED_RECEIVER_MLS# + MLS + + + + (L:XMLVAR_NAV_#SIDE#_MLS_Volume) #VOLUME_INCREMENT_NAV# + 1 min (>L:XMLVAR_NAV_#SIDE#_MLS_Volume) + (L:XMLVAR_NAV_#SIDE#_MLS_Volume) #VOLUME_INCREMENT_NAV# - 0 max (>L:XMLVAR_NAV_#SIDE#_MLS_Volume) + TT:COCKPIT.TOOLTIPS.NAV_KNOB_ILS_VOLUME_DECREASE + TT:COCKPIT.TOOLTIPS.NAV_KNOB_ILS_VOLUME_INCREASE + %((L:XMLVAR_NAV_#SIDE#_MLS_Switch_Down))%{if}TT:COCKPIT.TOOLTIPS.NAV_KNOB_ILS_IDENT_DISABLE%{else}TT:COCKPIT.TOOLTIPS.NAV_KNOB_ILS_IDENT_ENABLE%{end} + + + + KNOB + FBW_AIRLINER_Audio_NAV_Volume_Knob_Template + #NODE_ID_RECEIVER_ADF1# + #NODE_ID_LIGHT_RECEIVER_ADF1# + #ANIM_NAME_KNOB_RECEIVER_ADF1# + #ANIM_NAME_PUSH_RECEIVER_ADF1# + #NODE_ID_LED_RECEIVER_ADF1# + ADF1 + + + (L:XMLVAR_NAV_VOICE_Switch_Down) 0 == if{ + (A:ADF SOUND, Bool) 1 == (L:XMLVAR_NAV_L_ADF1_Switch_Down) 0 == (L:XMLVAR_NAV_R_ADF1_Switch_Down) 0 == and and if{ + 1 (>K:RADIO_ADF_IDENT_DISABLE) + } + + (A:ADF SOUND, Bool) 0 == (L:XMLVAR_NAV_L_ADF1_Switch_Down) 1 == (L:XMLVAR_NAV_R_ADF1_Switch_Down) 1 == or and if{ + 1 (>K:RADIO_ADF_IDENT_ENABLE) + } + } + + + + (L:XMLVAR_NAV_#SIDE#_ADF1_Volume) #VOLUME_INCREMENT_NAV# + 1 min (>L:XMLVAR_NAV_#SIDE#_ADF1_Volume) + (L:XMLVAR_NAV_#SIDE#_ADF1_Volume) (A:ADF VOLUME:1, percent over 100) > if{ + 1 (>K:ADF_VOLUME_INC) + } + + + (L:XMLVAR_NAV_#SIDE#_ADF1_Volume) #VOLUME_INCREMENT_NAV# - 0 max (>L:XMLVAR_NAV_#SIDE#_ADF1_Volume) + (L:XMLVAR_NAV_L_ADF1_Volume) (L:XMLVAR_NAV_R_ADF1_Volume) max (A:ADF VOLUME:1, percent over 100) < if{ + 1 (>K:ADF_VOLUME_DEC) + } + + TT:COCKPIT.TOOLTIPS.TRANSMITTER_ADF1_VOLUME_DEC + TT:COCKPIT.TOOLTIPS.TRANSMITTER_ADF1_VOLUME_INC + %((L:XMLVAR_NAV_#SIDE#_ADF1_Switch_Down))%{if}TT:INPUT.KEY_RADIO_ADF_IDENT_DISABLE_DESC%{else}TT:INPUT.KEY_RADIO_ADF_IDENT_ENABLE_DESC%{end} + + + + KNOB + FBW_AIRLINER_Audio_NAV_Volume_Knob_Template + #NODE_ID_RECEIVER_ADF2# + #NODE_ID_LIGHT_RECEIVER_ADF2# + #ANIM_NAME_KNOB_RECEIVER_ADF2# + #ANIM_NAME_PUSH_RECEIVER_ADF2# + #NODE_ID_LED_RECEIVER_ADF2# + ADF2 + + + (L:XMLVAR_NAV_VOICE_Switch_Down) 0 == if{ + (A:ADF SOUND:2, Bool) 1 == (L:XMLVAR_NAV_L_ADF2_Switch_Down) 0 == (L:XMLVAR_NAV_R_ADF2_Switch_Down) 0 == and and if{ + 1 (>K:RADIO_ADF2_IDENT_TOGGLE) + } + + (A:ADF SOUND:2, Bool) 0 == (L:XMLVAR_NAV_L_ADF2_Switch_Down) 1 == (L:XMLVAR_NAV_R_ADF2_Switch_Down) 1 == or and if{ + 1 (>K:RADIO_ADF2_IDENT_TOGGLE) + } + } + + + (L:XMLVAR_NAV_#SIDE#_ADF2_Volume) #VOLUME_INCREMENT_NAV# + 1 min (>L:XMLVAR_NAV_#SIDE#_ADF2_Volume) + (L:XMLVAR_NAV_#SIDE#_ADF2_Volume) #VOLUME_INCREMENT_NAV# - 0 max (>L:XMLVAR_NAV_#SIDE#_ADF2_Volume) + TT:COCKPIT.TOOLTIPS.TRANSMITTER_ADF2_VOLUME_DEC + TT:COCKPIT.TOOLTIPS.TRANSMITTER_ADF2_VOLUME_INC + %((L:XMLVAR_NAV_#SIDE#_ADF2_Switch_Down))%{if}TT:INPUT.KEY_RADIO_ADF2_IDENT_DISABLE_DESC%{else}TT:INPUT.KEY_RADIO_ADF2_IDENT_ENABLE_DESC%{end} + + + + + + @@ -303,9 +307,10 @@ let modeId = #MODE_ID, number#; alias toggleSwitch = (L:A32NX_RMP_#SIDE#_TOGGLE_SWITCH, bool); alias selectedMode = (L:A32NX_RMP_#SIDE#_SELECTED_MODE, number); + alias navButtonPressed = (L:A32NX_RMP_#SIDE#_NAV_BUTTON_SELECTED, bool); alias annunciatorLight = (L:A32NX_OVHD_INTLT_ANN, number); let emissiveDim = if annunciatorLight == 2 { 0.05 } else { 1 }; - (if ((!inop and toggleSwitch and (selectedMode == modeId)) or (annunciatorLight == 0)) and indicatorsPowered { + (if ((!inop and toggleSwitch and ((selectedMode == modeId) or (modeId == 12 and navButtonPressed))) or (annunciatorLight == 0)) and indicatorsPowered { 1 * emissiveDim } else { 0 diff --git a/src/fmgc/src/radionav/NavRadioManager.ts b/src/fmgc/src/radionav/NavRadioManager.ts index f3cc3cc22f0..60ae4b15763 100644 --- a/src/fmgc/src/radionav/NavRadioManager.ts +++ b/src/fmgc/src/radionav/NavRadioManager.ts @@ -8,19 +8,40 @@ export enum TuningMode { * This is a placeholder for the new radio nav tuning logic... coming soon to an A32NX near you */ export class NavRadioManager { - tuningMode1: TuningMode = TuningMode.Auto; + tuningMode: TuningMode = TuningMode.Auto; - tuningMode2: TuningMode = TuningMode.Auto; + manualTuned: boolean; - tuningMode3: TuningMode = TuningMode.Auto; + rmpTuned: boolean; constructor(public _parentInstrument: BaseInstrument) { - SimVar.SetSimVarValue('L:A32NX_FMGC_RADIONAV_1_TUNING_MODE', 'Enum', TuningMode.Manual); - SimVar.SetSimVarValue('L:A32NX_FMGC_RADIONAV_2_TUNING_MODE', 'Enum', TuningMode.Manual); - SimVar.SetSimVarValue('L:A32NX_FMGC_RADIONAV_3_TUNING_MODE', 'Enum', TuningMode.Manual); + SimVar.SetSimVarValue('L:A32NX_FMGC_RADIONAV_TUNING_MODE', 'Enum', TuningMode.Auto); } - update(_: number): void { - // Do nothing + public update(deltaTime: number, manualTuned: boolean, rmpTuned: boolean): void { + if (this.manualTuned !== manualTuned || this.rmpTuned !== rmpTuned) { + // too avoid SetSimVar too often + this.manualTuned = manualTuned; + this.rmpTuned = rmpTuned; + + if (manualTuned) { + this.tuningMode = TuningMode.Manual; + } else if (rmpTuned) { + this.tuningMode = TuningMode.Remote; + } else { + if (this.tuningMode === TuningMode.Remote) { + // Happens when NAV push button is pushed back + // It resets all the frequencies (real life behavior) + SimVar.SetSimVarValue('K:ADF_ACTIVE_SET', 'Frequency ADF BCD32', 0); + SimVar.SetSimVarValue('K:ADF2_ACTIVE_SET', 'Frequency ADF BCD32', 0); + SimVar.SetSimVarValue('K:NAV1_RADIO_SET_HZ', 'Hz', 0); + SimVar.SetSimVarValue('K:NAV2_RADIO_SET_HZ', 'Hz', 0); + SimVar.SetSimVarValue('K:NAV3_RADIO_SET_HZ', 'Hz', 0); + } + + this.tuningMode = TuningMode.Auto; + } + SimVar.SetSimVarValue('L:A32NX_FMGC_RADIONAV_TUNING_MODE', 'Enum', this.tuningMode); + } } } diff --git a/src/instruments/src/ND/elements/RadioNavInfo.tsx b/src/instruments/src/ND/elements/RadioNavInfo.tsx index 31439bcd2bc..470be92d558 100644 --- a/src/instruments/src/ND/elements/RadioNavInfo.tsx +++ b/src/instruments/src/ND/elements/RadioNavInfo.tsx @@ -11,11 +11,11 @@ export enum NavAidMode { export type RadioNavInfoProps = { index: 1 | 2, side: EfisSide } -const TuningModeIndicator: React.FC<{ index: 1 | 2, frequency: number }> = ({ index, frequency }) => { - const [tuningMode] = useSimVar(`L:A32NX_FMGC_RADIONAV_${index}_TUNING_MODE`, 'enum'); +const TuningModeIndicator: React.FC<{ index: 1 | 2 }> = ({ index }) => { + const [tuningMode] = useSimVar('L:A32NX_FMGC_RADIONAV_TUNING_MODE', 'enum'); return ( - frequency > 1 && tuningMode !== TuningMode.Auto && ( + tuningMode !== TuningMode.Auto && ( {tuningMode === TuningMode.Manual ? 'M' : 'R'} ) || null ); @@ -81,7 +81,7 @@ const VorInfo: React.FC<{index: 1 | 2}> = ({ index }) => { 20 ? x + 46 : x + 58} y={759} fontSize={24} fill="#00ff00" textAnchor="end">{dmeText} NM - + ); }; @@ -108,14 +108,13 @@ const AdfInfo: React.FC<{index: 1 | 2}> = ({ index }) => { ADF {index} - {adfAvailable ? adfIdent : adfFrequency.toFixed(0)} {adfAvailable && ( {adfIdent} )} - {!adfAvailable && ( + {!adfAvailable && adfFrequency > 0 && ( {Math.floor(adfFrequency).toFixed(0)} )} - + ); }; diff --git a/src/instruments/src/ND/pages/RoseMode.tsx b/src/instruments/src/ND/pages/RoseMode.tsx index ed237365791..408106b7464 100644 --- a/src/instruments/src/ND/pages/RoseMode.tsx +++ b/src/instruments/src/ND/pages/RoseMode.tsx @@ -776,7 +776,7 @@ const VorInfo: FC<{side: EfisSide}> = memo(({ side }) => { const [vorIdent] = useSimVar(`NAV IDENT:${index}`, 'string'); const [vorFrequency] = useSimVar(`NAV ACTIVE FREQUENCY:${index}`, 'megahertz'); const [vorCourse] = useSimVar(`NAV OBS:${index}`, 'degrees'); - const [tuningMode] = useSimVar(`L:A32NX_FMGC_RADIONAV_${index}_TUNING_MODE`, 'enum'); + const [tuningMode] = useSimVar('L:A32NX_FMGC_RADIONAV_TUNING_MODE', 'enum'); const [vorAvailable] = useSimVar(`NAV HAS NAV:${index}`, 'boolean'); const [freqInt, freqDecimal] = vorFrequency.toFixed(2).split('.', 2); @@ -806,7 +806,7 @@ const VorInfo: FC<{side: EfisSide}> = memo(({ side }) => { {vorCourse >= 0 ? (`${Math.round(vorCourse)}`).padStart(3, '0') : '---'} ° - { vorFrequency > 0 && {tuningModeLabel} } + {tuningModeLabel} {vorIdent} ); @@ -816,7 +816,7 @@ const IlsInfo: FC = memo(() => { const [ilsIdent] = useSimVar('NAV IDENT:3', 'string'); const [ilsFrequency] = useSimVar('NAV ACTIVE FREQUENCY:3', 'megahertz'); const [ilsCourse] = useSimVar('NAV LOCALIZER:3', 'degrees'); - const [tuningMode] = useSimVar('L:A32NX_FMGC_RADIONAV_3_TUNING_MODE', 'enum'); + const [tuningMode] = useSimVar('L:A32NX_FMGC_RADIONAV_TUNING_MODE', 'enum'); const [locAvailable] = useSimVar('L:A32NX_RADIO_RECEIVER_LOC_IS_VALID', 'number'); const [freqInt, freqDecimal] = ilsFrequency.toFixed(2).split('.', 2); @@ -843,7 +843,7 @@ const IlsInfo: FC = memo(() => { {locAvailable ? (`${Math.round(ilsCourse)}`).padStart(3, '0') : '---'} ° - { ilsFrequency > 0 && {tuningModeLabel} } + {tuningModeLabel} {ilsIdent} ); diff --git a/src/instruments/src/PFD/HeadingIndicator.tsx b/src/instruments/src/PFD/HeadingIndicator.tsx index f7d00bf78c5..5b6c9df264e 100644 --- a/src/instruments/src/PFD/HeadingIndicator.tsx +++ b/src/instruments/src/PFD/HeadingIndicator.tsx @@ -312,7 +312,8 @@ class QFUIndicator extends DisplayComponent<{ ILSCourse: Subscribable, h this.props.lsPressed.sub((ls) => { this.lsPressed = ls; - if (ls) { + // ilsCourse may be negative if tuned via the RMP then back to MCDU + if (ls && this.ilsCourse >= 0) { this.qfuContainer.instance.classList.remove('HiddenElement'); } else { this.qfuContainer.instance.classList.add('HiddenElement'); diff --git a/src/instruments/src/PFD/LandingSystemIndicator.tsx b/src/instruments/src/PFD/LandingSystemIndicator.tsx index 96c87d480d7..8aa7028d81c 100644 --- a/src/instruments/src/PFD/LandingSystemIndicator.tsx +++ b/src/instruments/src/PFD/LandingSystemIndicator.tsx @@ -118,6 +118,8 @@ class LandingSystemInfo extends DisplayComponent<{ bus: EventBus }> { private dme = 0; + private rmpTuned = false; + private dmeVisibilitySub = Subject.create('hidden'); private destRef = FSComponent.createRef(); @@ -157,6 +159,11 @@ class LandingSystemInfo extends DisplayComponent<{ bus: EventBus }> { this.dme = dme; this.updateContents(); }); + + sub.on('ilsRMPTuned').whenChanged().handle((rmpTuned) => { + this.rmpTuned = rmpTuned; + this.updateContents(); + }); } private updateContents() { @@ -170,7 +177,7 @@ class LandingSystemInfo extends DisplayComponent<{ bus: EventBus }> { let distLeading = ''; let distTrailing = ''; - if (this.hasDme) { + if (this.hasDme && !this.rmpTuned) { this.dmeVisibilitySub.set('display: inline'); const dist = Math.round(this.dme * 10) / 10; diff --git a/src/instruments/src/PFD/instrument.tsx b/src/instruments/src/PFD/instrument.tsx index ce457e278df..b2a266a44a1 100644 --- a/src/instruments/src/PFD/instrument.tsx +++ b/src/instruments/src/PFD/instrument.tsx @@ -133,6 +133,7 @@ class A32NX_PFD extends BaseInstrument { this.simVarPublisher.subscribe('selectedFpa'); this.simVarPublisher.subscribe('targetSpeedManaged'); this.simVarPublisher.subscribe('ilsCourse'); + this.simVarPublisher.subscribe('ilsRMPTuned'); this.simVarPublisher.subscribe('tla1'); this.simVarPublisher.subscribe('tla2'); this.simVarPublisher.subscribe('metricAltToggle'); diff --git a/src/instruments/src/PFD/shared/PFDSimvarPublisher.tsx b/src/instruments/src/PFD/shared/PFDSimvarPublisher.tsx index 8863473e484..c2b0d1abb67 100644 --- a/src/instruments/src/PFD/shared/PFDSimvarPublisher.tsx +++ b/src/instruments/src/PFD/shared/PFDSimvarPublisher.tsx @@ -68,6 +68,7 @@ export interface PFDSimvars { groundTrackTrue: number; selectedFpa: number; ilsCourse: number; + ilsRMPTuned: boolean; metricAltToggle: boolean; tla1: number; tla2: number; @@ -225,6 +226,7 @@ export enum PFDVars { groundTrackTrue = 'GPS GROUND TRUE TRACK', selectedFpa = 'L:A32NX_AUTOPILOT_FPA_SELECTED', ilsCourse = 'L:A32NX_FM_LS_COURSE', + ilsRMPTuned = 'L:A32NX_RMP_ILS_TUNED', metricAltToggle = 'L:A32NX_METRIC_ALT_TOGGLE', tla1='L:A32NX_AUTOTHRUST_TLA:1', tla2='L:A32NX_AUTOTHRUST_TLA:2', @@ -383,6 +385,7 @@ export class PFDSimvarPublisher extends SimVarPublisher { ['groundTrackTrue', { name: PFDVars.groundTrackTrue, type: SimVarValueType.Degree }], ['selectedFpa', { name: PFDVars.selectedFpa, type: SimVarValueType.Degree }], ['ilsCourse', { name: PFDVars.ilsCourse, type: SimVarValueType.Number }], + ['ilsRMPTuned', { name: PFDVars.ilsRMPTuned, type: SimVarValueType.Bool }], ['metricAltToggle', { name: PFDVars.metricAltToggle, type: SimVarValueType.Bool }], ['tla1', { name: PFDVars.tla1, type: SimVarValueType.Number }], ['tla2', { name: PFDVars.tla2, type: SimVarValueType.Number }], diff --git a/src/instruments/src/RMP/Components/BaseRadioPanels.tsx b/src/instruments/src/RMP/Components/BaseRadioPanels.tsx index 7037175bd05..c9860e88961 100644 --- a/src/instruments/src/RMP/Components/BaseRadioPanels.tsx +++ b/src/instruments/src/RMP/Components/BaseRadioPanels.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React, { useState } from 'react'; import { useSimVar, useInteractionSimVar } from '@instruments/common/simVars'; import { useInteractionEvent } from '@instruments/common/hooks'; +import { TransceiverType } from './StandbyFrequency'; import { VhfRadioPanel } from './VhfRadioPanel'; +import { NavRadioPanel } from './NavRadioPanel'; import { RadioPanelDisplay } from './RadioPanelDisplay'; interface Props { @@ -44,21 +46,90 @@ const UnpoweredRadioPanel = () => ( * Renders appropriate mode sub-component (e.g. VhfRadioPanel). */ const PoweredRadioPanel = (props: Props) => { + const [navTransceiverType, setNavTransceiverType] = useState(TransceiverType.RADIO_VHF); + + // Used to turn on the associated led const [panelMode, setPanelMode] = useSimVar(`L:A32NX_RMP_${props.side}_SELECTED_MODE`, 'Number', 250); + // Used to determine (in the FGMC for instance) if the system is in NAV backup mode. L and R simvars have to be checked + const [navButtonPressed, setNavButton] = useSimVar(`L:A32NX_RMP_${props.side}_NAV_BUTTON_SELECTED`, 'boolean', 250); + // Used to return to the selected VHF once NAV is pushed again + const [previousPanelMode, setPreviousPanelMode] = useState(panelMode); // Hook radio management panel mode buttons to set panelMode SimVar. - useInteractionEvent(`A32NX_RMP_${props.side}_VHF1_BUTTON_PRESSED`, () => setPanelMode(1)); - useInteractionEvent(`A32NX_RMP_${props.side}_VHF2_BUTTON_PRESSED`, () => setPanelMode(2)); - useInteractionEvent(`A32NX_RMP_${props.side}_VHF3_BUTTON_PRESSED`, () => setPanelMode(3)); + useInteractionEvent(`A32NX_RMP_${props.side}_VHF1_BUTTON_PRESSED`, () => { + setPanelMode(1); + setPreviousPanelMode(1); + setNavTransceiverType(TransceiverType.RADIO_VHF); + }); + + useInteractionEvent(`A32NX_RMP_${props.side}_VHF2_BUTTON_PRESSED`, () => { + setPanelMode(2); + setPreviousPanelMode(2); + setNavTransceiverType(TransceiverType.RADIO_VHF); + }); + + useInteractionEvent(`A32NX_RMP_${props.side}_VHF3_BUTTON_PRESSED`, () => { + setPanelMode(3); + setPreviousPanelMode(3); + setNavTransceiverType(TransceiverType.RADIO_VHF); + }); + + useInteractionEvent(`A32NX_RMP_${props.side}_NAV_BUTTON_PRESSED`, () => { + if (navButtonPressed) { + setPanelMode(previousPanelMode); + setNavTransceiverType(TransceiverType.RADIO_VHF); + } + + setNavButton(!navButtonPressed); + }); + + useInteractionEvent(`A32NX_RMP_${props.side}_VOR_BUTTON_PRESSED`, () => { + if (navButtonPressed) { + setPanelMode(6); + setNavTransceiverType(TransceiverType.VOR); + } + }); + + useInteractionEvent(`A32NX_RMP_${props.side}_ILS_BUTTON_PRESSED`, () => { + if (navButtonPressed) { + setPanelMode(7); + setNavTransceiverType(TransceiverType.ILS); + } + }); + + /** + * MLS IMPLEMENTED IN THE XML BEHAVIOURS + * BUT DISABLED HERE SINCE THERE IS NOT ENOUGH REFERENCES + */ + // useInteractionEvent(`A32NX_RMP_${props.side}_MLS_BUTTON_PRESSED`, () => { + // if (navButtonPressed) { + // setPanelMode(8); + // setNavTransceiverType(TransceiverType.ILS); + // } + // }); + + useInteractionEvent(`A32NX_RMP_${props.side}_ADF_BUTTON_PRESSED`, () => { + if (navButtonPressed) { + setPanelMode(9); + setNavTransceiverType(TransceiverType.ADF); + } + }); // This means we're in a VHF communications mode. - if (panelMode === 1 || panelMode === 2 || panelMode === 3) return (); - - // If we reach this block, something's gone wrong. We'll just render a broken panel. - return ( - - - - - ); + switch (navTransceiverType) { + case TransceiverType.RADIO_VHF: + return (); + case TransceiverType.VOR: + case TransceiverType.ILS: + case TransceiverType.ADF: + return (); + default: + // If we reach this block, something's gone wrong. We'll just render a broken panel. + return ( + + + + + ); + } }; diff --git a/src/instruments/src/RMP/Components/NavRadioPanel.tsx b/src/instruments/src/RMP/Components/NavRadioPanel.tsx new file mode 100644 index 00000000000..b6351eda774 --- /dev/null +++ b/src/instruments/src/RMP/Components/NavRadioPanel.tsx @@ -0,0 +1,163 @@ +import React, { useEffect, useState } from 'react'; + +import { StandbyFrequency, TransceiverType } from './StandbyFrequency'; +import { StandbyCourse } from './StandbyCourse'; +import { useSimVar } from '../../Common/simVars'; +import { RadioPanelDisplay } from './RadioPanelDisplay'; +import { useInteractionEvent } from '../../Common/hooks'; + +interface Props { + /** + * The RMP side (e.g. 'L' or 'R'). + */ + side: string, + + /** + * The NAV transceiver (VOR, ILS, ADF). + */ + transceiver: number, + +} + +enum Mode { + FREQUENCY = 0, + COURSE = 1 +} + +/* +* Had to use this simvars to save the frequencies set via the RMP +* In real life, if you tune a navaid via the MCDU and then press NAV, the frequency is not the same +/ + +/** + * + * @param transceiver The transceiver type (VOR, ADF or ILS) + * @param side Side on which the transceiver is (L or R) + * @returns a tuble simvar + */ +const useActiveFrequency = (transceiver: number, side: string) => { + switch (transceiver) { + case TransceiverType.VOR: + return useSimVar(`L:A32NX_RMP_${side}_SAVED_ACTIVE_FREQUENCY_VOR`, 'number'); + case TransceiverType.ADF: + return useSimVar(`L:A32NX_RMP_${side}_SAVED_ACTIVE_FREQUENCY_ADF`, 'number'); + default: + return useSimVar(`L:A32NX_RMP_${side}_SAVED_ACTIVE_FREQUENCY_ILS`, 'number'); + } +}; + +/** + * + * @param transceiver The transceiver type (VOR, ADF or ILS) + * @param side Side on which the transceiver is (L or R) + * @returns a tuble simvar + */ +const useStandbyFrequency = (transceiver: number, side: string) => { + switch (transceiver) { + case TransceiverType.VOR: + return useSimVar(`L:A32NX_RMP_${side}_SAVED_STANDBY_FREQUENCY_VOR`, 'number'); + case TransceiverType.ADF: + return useSimVar(`L:A32NX_RMP_${side}_SAVED_STANDBY_FREQUENCY_ADF`, 'number'); + default: + return useSimVar(`L:A32NX_RMP_${side}_SAVED_STANDBY_FREQUENCY_ILS`, 'number'); + } +}; + +/** + * + * @param transceiver The transceiver type (VOR, ADF or ILS) + * @param side Side on which the transceiver is (L or R) + * @returns a tuble simvar + */ +const useCourse = (transceiver: number, side: string) => { + switch (transceiver) { + case TransceiverType.VOR: + return useSimVar(`L:A32NX_RMP_${side}_SAVED_COURSE_VOR`, 'number'); + default: + return useSimVar(`L:A32NX_RMP_${side}_SAVED_COURSE_ILS`, 'number'); + } +}; + +const setActiveFrequencySimVar = (transceiver: number, index: number, frequency: number) => { + if (transceiver === TransceiverType.ADF) { + SimVar.SetSimVarValue(`K:ADF${index === 1 ? '' : index}_ACTIVE_SET`, 'Frequency ADF BCD32', Avionics.Utils.make_adf_bcd32(frequency)); + } + + SimVar.SetSimVarValue(`K:NAV${index}_RADIO_SET_HZ`, 'Hz', frequency); +}; + +/** + * NAV radio management panel React component. + * Hooks into the mode active and standby frequency SimVars and wires transfer button. + * Renders active frequency RadioPanelDisplay and appropriate StandbyFrequency sub-components. + */ +export const NavRadioPanel = (props: Props) => { + let standbyWindow: JSX.Element; + + let index = props.side === 'L' ? 1 : 2; + if (props.transceiver === TransceiverType.ILS) { + index = 3; // Both RMPs manage the same ILS + } + + const [mode, setMode] = useState(Mode.FREQUENCY); + + const [activeFrequency, setActiveFrequencySaved] = useActiveFrequency(props.transceiver, props.side); + const [standbyFrequency, setStandbyFrequencySaved] = useStandbyFrequency(props.transceiver, props.side); + const [course, setCourseSaved] = useCourse(props.transceiver, props.side); + + const [, setCourse] = useSimVar(props.transceiver === TransceiverType.VOR ? `K:VOR${index}_SET` : 'L:A32NX_FM_LS_COURSE', 'number', 100); + + const [APPR] = useSimVar('L:A32NX_FCU_APPR_MODE_ACTIVE', 'bool'); + const [AP] = useSimVar('L:A32NX_AUTOPILOT_ACTIVE', 'bool'); + const [RA1] = useSimVar('L:A32NX_RA_1_RADIO_ALTITUDE', 'number'); + const [RA2] = useSimVar('L:A32NX_RA_2_RADIO_ALTITUDE', 'number'); + + useInteractionEvent(`A32NX_RMP_${props.side}_TRANSFER_BUTTON_PRESSED`, () => { + // Inhibit RMP tuning if below 700 RA, APPR engaged, at least one AP/FD engaged (FCOM compliant) + if ((RA1 >= 700 && RA2 >= 700) || !APPR || !AP) { + if (mode === Mode.FREQUENCY) { + if (props.transceiver !== TransceiverType.ADF) { + setMode(Mode.COURSE); + } + + // FCOM compliant: Both RMPs must be in nav backup mode in order to tune the ILS + if (props.transceiver !== TransceiverType.ILS + || (SimVar.GetSimVarValue('L:A32NX_RMP_L_NAV_BUTTON_SELECTED', 'Bool') + && SimVar.GetSimVarValue('L:A32NX_RMP_R_NAV_BUTTON_SELECTED', 'Bool'))) { + setActiveFrequencySimVar(props.transceiver, index, standbyFrequency); + } + setActiveFrequencySaved(standbyFrequency); + + if (props.transceiver === TransceiverType.ILS) { + SimVar.SetSimVarValue('L:A32NX_RMP_ILS_TUNED', 'boolean', true); + } + } else { + setCourse(course); + setMode(Mode.FREQUENCY); + } + } + }); + + // This effect to display frequency mode instead of course mode when switching between receivers. + // After few debug sessions, it was noticed standbyFrequency was the first valuable sign of switch. + // I could have listened props.transceiver but this caused flickering (very fast display of previous frequency) due to sequential render + useEffect(() => { + // Performance purpose. Could set Frequency everytime but setMode fires a render + if (mode === Mode.COURSE) { + setMode(Mode.FREQUENCY); + } + }, [standbyFrequency]); + + if (mode === Mode.FREQUENCY) { + standbyWindow = ; + } else { + standbyWindow = ; + } + + return ( + + + {standbyWindow} + + ); +}; diff --git a/src/instruments/src/RMP/Components/RadioPanelDisplay.tsx b/src/instruments/src/RMP/Components/RadioPanelDisplay.tsx index a2ada050a57..e44f5be801a 100644 --- a/src/instruments/src/RMP/Components/RadioPanelDisplay.tsx +++ b/src/instruments/src/RMP/Components/RadioPanelDisplay.tsx @@ -15,7 +15,17 @@ const TEXT_DATA_MODE_VHF3 = 'DATA'; * @param frequency The given frequency number in Hz. * @returns The formated frequency string in 123.456 */ -const formatFrequency = (frequency: number): string => (frequency / 1000000).toFixed(3).padEnd(7, '0'); +const formatFrequency = (frequency: number): string => { + // VHF COM, VOR, ILS + if (frequency >= 108000000) { + return (frequency / 1000000).toFixed(3).padEnd(7, '0'); + } + + // HF HERE + + // ADF + return (frequency / 1000).toFixed(1); +}; /** * Radio management panel seven-segment frequency/course display. @@ -33,18 +43,16 @@ export function RadioPanelDisplay(props: Props) { 8.8.8.8.8.8 ); + } else if ((typeof props.value === 'string')) { + content = ( + + {props.value} + + ); } else if (props.value > 0) { - let value = ''; - // If the passed value prop is a number, we'll use formatFrequency to get string format. - if (typeof props.value === 'number') { - value = formatFrequency(props.value); - } else { - value = props.value; - } - content = ( - {value} + {formatFrequency(props.value)} ); } else { diff --git a/src/instruments/src/RMP/Components/StandbyCourse.tsx b/src/instruments/src/RMP/Components/StandbyCourse.tsx new file mode 100644 index 00000000000..e0d1bc9f5fb --- /dev/null +++ b/src/instruments/src/RMP/Components/StandbyCourse.tsx @@ -0,0 +1,44 @@ +import React, { useCallback, useRef } from 'react'; +import { RadioPanelDisplay } from './RadioPanelDisplay'; +import { useInteractionEvent } from '../../Common/hooks'; +import { RateMultiplierKnob, UpdateValueCallback } from '../../Common/RateMultiplierKnob'; + +interface Props { + /** + * The RMP side (e.g. 'L' or 'R'). + */ + side: string, + + /** + * The current standby frequency value in Hz. + */ + value: number, + + /** + * A callback to set the current standby frequency value in Hz. + */ + setValue: (x: any) => void, +} + +/** + * Standby frequency radio management panel React component. + * Hooks to outer and inner rotary encoder knobs. + * Renders standby frequency RadioPanelDisplay sub-component. + */ +export const StandbyCourse = (props: Props) => { + // Handle inner knob turned. + const innerKnobUpdateCallback: UpdateValueCallback = useCallback((offset) => { + const integer = Math.floor((props.value + offset) / 360); + props.setValue(props.value - (integer * 360) + offset); + }, [props.value]); + + // Used to change decimal value of freq. + const innerKnob = useRef(new RateMultiplierKnob(200, 1)); + innerKnob.current.updateValue = innerKnobUpdateCallback; + + // Hook rotation events from simulator to custom knob class methods. + useInteractionEvent(`A32NX_RMP_${props.side}_INNER_KNOB_TURNED_CLOCKWISE`, () => innerKnob.current.increase()); + useInteractionEvent(`A32NX_RMP_${props.side}_INNER_KNOB_TURNED_ANTICLOCKWISE`, () => innerKnob.current.decrease()); + + return (); +}; diff --git a/src/instruments/src/RMP/Components/StandbyFrequency.tsx b/src/instruments/src/RMP/Components/StandbyFrequency.tsx index 6b2e46fc21e..d3500811852 100644 --- a/src/instruments/src/RMP/Components/StandbyFrequency.tsx +++ b/src/instruments/src/RMP/Components/StandbyFrequency.tsx @@ -6,6 +6,13 @@ import { RateMultiplierKnob, UpdateValueCallback } from '../../Common/RateMultip declare const Utils; // this can also be replaced once /typings are available +export enum TransceiverType { + RADIO_VHF, + VOR, + ILS, + ADF +} + interface Props { /** * The RMP side (e.g. 'L' or 'R'). @@ -17,6 +24,11 @@ interface Props { */ value: number, + /** + * Type of transceiver (e.g VHF, HF, VOR, ILS, MLS, ADF) + */ + transceiver: TransceiverType, + /** * A callback to set the current standby frequency value in Hz. */ @@ -37,6 +49,7 @@ const findNearestInArray = (value: number, array: number[]): number => array.red * High Frequency communications use 10 kHz spacing. * Vatsim VHF communications use 25 kHz spacing. * VOR / ILS frequencies use 50 kHz spacing. + * ADF frequencies use 1kHz spacing. */ type ChannelSpacing = 8.33 | 10 | 25 | 50; @@ -63,7 +76,7 @@ const offsetFrequencyChannel = (spacing: ChannelSpacing, channel: number, offset // Special cases, such as ADF, do not use the ending algorithm to find frequencies. if (endings === null) { - return (Math.floor(channel / 100) + spacing * offset) * 100; + return (Math.floor(channel % 100) + spacing * offset); } // Reverse the channel order if we're going backwards. @@ -100,17 +113,47 @@ const offsetFrequencyChannel = (spacing: ChannelSpacing, channel: number, offset * Renders standby frequency RadioPanelDisplay sub-component. */ export const StandbyFrequency = (props: Props) => { - const spacing = usePersistentProperty('RMP_VHF_SPACING_25KHZ', '0')[0] === '0' ? 8.33 : 25; + let spacing: ChannelSpacing; + let toMhz = 1000; + + switch (props.transceiver) { + case TransceiverType.ILS: + spacing = 25; + break; + case TransceiverType.VOR: + spacing = 50; + break; + case TransceiverType.ADF: + toMhz = 1; + break; + default: + spacing = usePersistentProperty('RMP_VHF_SPACING_25KHZ', '0')[0] === '0' ? 8.33 : 25; + } + // Handle outer knob turned. const outerKnobUpdateCallback: UpdateValueCallback = useCallback((offset) => { if (props.value !== 0) { - const frequency = Math.round(props.value / 1000); - const integer = Math.floor(frequency / 1000); - const decimal = frequency % 1000; + let frequency = props.value; + + if (props.transceiver !== TransceiverType.ADF) { + frequency = Math.round(frequency / 1000); // To kHz + } + + const integer = Math.floor(frequency / 1000) + offset; + // @todo determine min/max depending on mode. - const maxInteger = decimal > 975 ? 135 : 136; - const newInteger = Utils.Clamp(integer + offset, 118, maxInteger); - props.setValue((newInteger * 1000 + decimal) * 1000); + let newInteger = 0; + if (props.transceiver === TransceiverType.RADIO_VHF) { + newInteger = Utils.Clamp(integer, 118, 136); + } else if (props.transceiver === TransceiverType.ILS) { + newInteger = Utils.Clamp(integer, 108, 111); + } else if (props.transceiver === TransceiverType.VOR) { + newInteger = Utils.Clamp(integer, 108, 117); + } else if (props.transceiver === TransceiverType.ADF) { + newInteger = Utils.Clamp(integer, 190, 1750); + } + + props.setValue((newInteger * 1000 + frequency % 1000) * toMhz); } else { props.setValue(0); } @@ -118,18 +161,25 @@ export const StandbyFrequency = (props: Props) => { // Handle inner knob turned. const innerKnobUpdateCallback: UpdateValueCallback = useCallback((offset) => { - const frequency = Math.round(props.value / 1000); if (props.value !== 0) { - const integer = Math.floor(frequency / 1000); - // @todo determine correct frequency spacing depending on mode. - const decimal = offsetFrequencyChannel(spacing, frequency % 1000, offset); - // @todo determine min/max depending on mode. + let frequency = props.value; + + if (props.transceiver !== TransceiverType.ADF) { + frequency = Math.round(frequency / 1000); // To kHz + } + // Tested in real life: // Integer cannot return to 118 from 136 to the right // Decimal can return to 0 from 975 to the right - // const maxDecimal = integer === 136 ? 975 : 1000; - const newDecimal = Utils.Clamp(decimal, 0, 1000); - props.setValue((integer * 1000 + newDecimal) * 1000); + const integer = Math.floor(frequency / 1000); + let decimal = 0; + + if (props.transceiver !== TransceiverType.ADF) { + decimal = offsetFrequencyChannel(spacing, frequency % 1000, offset); + } else { // offsetFrequencyChannel does not fit ADF needs + decimal = frequency % 1000 === 0 ? 500 : 0; + } + props.setValue((integer * 1000 + decimal % 1000) * toMhz); } else { props.setValue(0); } diff --git a/src/instruments/src/RMP/Components/VhfRadioPanel.tsx b/src/instruments/src/RMP/Components/VhfRadioPanel.tsx index 8e358d309b6..f1559cd09f0 100644 --- a/src/instruments/src/RMP/Components/VhfRadioPanel.tsx +++ b/src/instruments/src/RMP/Components/VhfRadioPanel.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { StandbyFrequency } from './StandbyFrequency'; +import { StandbyFrequency, TransceiverType } from './StandbyFrequency'; import { useSplitSimVar } from '../../Common/simVars'; import { RadioPanelDisplay } from './RadioPanelDisplay'; import { useInteractionEvent } from '../../Common/hooks'; @@ -13,7 +13,7 @@ interface Props { /** * The VHF transceiver mode (VHF 1, 2, or 3). */ - transceiver: number, + vhf: number, } /** @@ -56,15 +56,15 @@ const useStandbyVhfFrequency = (side: string, transceiver: number) => { * Renders active frequency RadioPanelDisplay and appropriate StandbyFrequency sub-components. */ export const VhfRadioPanel = (props: Props) => { - const [active, setActive] = useActiveVhfFrequency(props.transceiver); - const [standby, setStandby] = useStandbyVhfFrequency(props.side, props.transceiver); + const [active, setActive] = useActiveVhfFrequency(props.vhf); + const [standby, setStandby] = useStandbyVhfFrequency(props.side, props.vhf); const [, setValueOppositePanelStandby] = props.side === 'L' ? useStandbyVhfFrequency('R', 3) : useStandbyVhfFrequency('L', 3); // Handle Transfer Button Pressed. useInteractionEvent(`A32NX_RMP_${props.side}_TRANSFER_BUTTON_PRESSED`, () => { // Force the standby opposite side otherwise we would lose the frequency/data format // Otherwise it would become frequency/frequency - if (props.transceiver === 3) { + if (props.vhf === 3) { setValueOppositePanelStandby(active); } setActive(standby); @@ -74,7 +74,7 @@ export const VhfRadioPanel = (props: Props) => { return ( - + ); };