Skip to content

feat(a380x/rmp): improve api for hardware users and add standby mode handling #10022

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
4 changes: 4 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
1. [A380X/SD] Fix BLEED indications for APU BLEED and cross bleeds; Add HP GND air supply triangle - @flogross89 (floridude)
1. [A380X/ELEC] Fix electrical distribution for ground servicing mode - @Gurgel100 (Pascal)
1. [A380X/ECAM] Improved STROBE LT OFF memo behaviour - @Jonny23787 (Jonathan)
1. [A380X/RMP] Added localvars for RMP frequencies and state for API users - @tracernz (Mike)
1. [A380X/RMP] Added handling of all COM key events for API users - @tracernz (Mike)
1. [A380X/RMP] Added standby mode handling for VHF3 - @tracernz (Mike)
1. [A380X/RMP] Keep the ATC frequency tuned when spawning on the runway in MSFS2024 - @tracernz (Mike)

## 0.13.0

Expand Down
2 changes: 1 addition & 1 deletion fbw-a32nx/mach.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function msfsAvionicsInstrument(name, index = 'instrument.tsx') {
templateId: `A32NX_${name}`,
mountElementId: `${name}_CONTENT`,
fileName: name.toLowerCase(),
imports: ['/JS/dataStorage.js'],
imports: ['/JS/dataStorage.js', '/JS/fbw-a32nx/A32NX_Simvars.js'],
},
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,4 @@ SimVar.SetSimVarValue = (name, unit, value, dataSource = defaultSource) => {
}
return Promise.resolve();
};
SimVar.SetSimVarValueRegNumber = (regID, value) => Coherent.call("setValueReg_Number", regID, value);
52 changes: 52 additions & 0 deletions fbw-a380x/docs/a380-simvars.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
- [Air Conditioning Pressurisation Ventilation ATA 21](#air-conditioning-pressurisation-ventilation-ata-21)
- [Auto Flight System ATA 22](#auto-flight-system-ata-22)
- [Flight Management System ATA 22](#flight-management-system-ata-22)
- [Communications ATA 23](#communications-ata-23)
- [Placeholder Types](#placeholder-types)
- [Local Vars](#local-vars)
- [Electrical ATA 24](#electrical-ata-24)
- [Fire and Smoke Protection ATA 26](#fire-and-smoke-protection-ata-26)
- [Flaps / Slats (ATA 27)](#flaps--slats-ata-27)
Expand Down Expand Up @@ -462,6 +465,55 @@
- Position (0-2)
- 0 is BOTH ON 2, 1 is NORM, 2 is BOTH ON 1

## Communications ATA 23

### Placeholder Types

- `{rmp_index}` 1, 2, or 3
- `{vhf_index}` 1, 2, or 3

### Local Vars

- `L:A380X_RMP_{rmp_index}_STATE`
- Enum
- Indicates the state of the RMP.
- | State | Number |
|------------|--------|
| OffFailed | 0 |
| OffStandby | 1 |
| On | 2 |
| OnFailed | 3 |

- `L:FBW_RMP_FREQUENCY_ACTIVE_{vhf_index}`
- Frequency BCD32 (can be read with MHz or any other frequency unit)
- Read only!
- Contains the active frequency value synced amongst the RMPs for this VHF radio. 0 when invalid. This will contain the actual frequency even in other modes.

- `L:FBW_RMP_FREQUENCY_STANDBY_{vhf_index}`
- Frequency BCD32 (can be read with MHz or any other frequency unit)
- Read only!
- Contains the standby frequency value synced amongst the RMPs for this VHF radio. 0 when invalid. This will contain the actual frequency even in other modes.

- `L:FBW_RMP_MODE_ACTIVE_{vhf_index}`
- Enum
- Read only!
- Indicates the active frequency mode synced amongst the RMPs for this VHF radio.
- | State | Number |
|------------|--------|
| Frequency | 0 |
| Data | 1 |
| Emergency | 2 |

- `L:FBW_RMP_MODE_STANDBY_{vhf_index}`
- Enum
- Read only!
- Indicates the standby frequency mode synced amongst the RMPs for this VHF radio.
- | State | Number |
|------------|--------|
| Frequency | 0 |
| Data | 1 |
| Emergency | 2 |

## Electrical ATA 24

- A32NX_ELEC_CONTACTOR_{name}_IS_CLOSED
Expand Down
2 changes: 1 addition & 1 deletion fbw-a380x/mach.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function msfsAvionicsInstrument(name, index = 'instrument.tsx') {
templateId: `A380X_${name}`,
mountElementId: `${name}_CONTENT`,
fileName: name.toLowerCase(),
imports: ['/JS/dataStorage.js'],
imports: ['/JS/dataStorage.js', '/JS/fbw-a380x/A380X_Simvars.js'],
},
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ ZVelBodyAxis=0
SimOnGround=True

[Avionics.0]
Comm1Active=122.800
; Do not set Comm1Active as it overwrites the ATC tower frequency the sim sets
; during the pre-flight IFR clearance flow.
; Comm1Active=122.800
Comm1Standby=124.225
Comm2Active=121.500
Comm2Standby=136.525
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,4 @@ SimVar.SetSimVarValue = (name, unit, value, dataSource = defaultSource) => {
}
return Promise.resolve();
};
SimVar.SetSimVarValueRegNumber = (regID, value) => Coherent.call("setValueReg_Number", regID, value);
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
// Copyright (c) 2022 FlyByWire Simulations
// SPDX-License-Identifier: GPL-3.0

import { EventBus, KeyEvents, KeyEventManager, KeyEventData, ComRadioIndex } from '@microsoft/msfs-sdk';
import {
EventBus,
KeyEvents,
KeyEventManager,
KeyEventData,
ComRadioIndex,
SimVarValueType,
ConsumerValue,
MathUtils,
} from '@microsoft/msfs-sdk';
import {
FrequencyMode,
InterRmpBusEvents,
NotificationManager,
NotificationType,
PilotSeat,
PilotSeatEvents,
PopUpDialog,
RadioUtils,
RmpState,
RmpUtils,
} from '@flybywiresim/fbw-sdk';
import { AircraftPresetsList } from '../common/AircraftPresetsList';

Expand Down Expand Up @@ -83,10 +95,17 @@ export class KeyInterceptor {
// --- EXTERNAL POWER events ---
};

private publisher = this.bus.getPublisher<InterRmpBusEvents>();

private keyInterceptManager: KeyEventManager;

private readonly pilotSeat = ConsumerValue.create(
this.bus.getSubscriber<PilotSeatEvents>().on('pilot_seat'),
PilotSeat.Left,
);

private readonly rmp1StateVar = SimVar.GetRegisteredId('L:A380X_RMP_1_STATE', SimVarValueType.Enum, document.title);
private readonly rmp2StateVar = SimVar.GetRegisteredId('L:A380X_RMP_2_STATE', SimVarValueType.Enum, document.title);
private readonly rmp3StateVar = SimVar.GetRegisteredId('L:A380X_RMP_3_STATE', SimVarValueType.Enum, document.title);

private dialogVisible = false;

constructor(
Expand Down Expand Up @@ -197,51 +216,174 @@ export class KeyInterceptor {
this.dialogVisible = false;
}

private onComFractIncrement(_index: ComRadioIndex, _sign: 1 | -1, _carry: boolean): void {
// FIXME implement, inop for now
private isRmpSyncEnabled(): boolean {
return SimVar.GetSimVarValue('L:A32NX_FO_SYNC_EFIS_ENABLED', SimVarValueType.Bool) > 0;
}

private onComWholeIncrement(_index: ComRadioIndex, _sign: 1 | -1): void {
// FIXME implement, inop for now
private shouldControlRmp1(): boolean {
const rmp1State = SimVar.GetSimVarValueFastReg(this.rmp1StateVar);
const rmp3State = SimVar.GetSimVarValueFastReg(this.rmp3StateVar);
const rmp1On = rmp1State === RmpState.On || rmp1State === RmpState.OnFailed;
const rmp3Off = rmp3State === RmpState.OffFailed || rmp3State === RmpState.OffStandby;
return this.isRmpSyncEnabled() || rmp3Off || (this.pilotSeat.get() === PilotSeat.Left && rmp1On);
}

private shouldControlRmp2(): boolean {
const rmp2State = SimVar.GetSimVarValueFastReg(this.rmp2StateVar);
const rmp3State = SimVar.GetSimVarValueFastReg(this.rmp3StateVar);
const rmp2On = rmp2State === RmpState.On || rmp2State === RmpState.OnFailed;
const rmp3Off = rmp3State === RmpState.OffFailed || rmp3State === RmpState.OffStandby;
return this.isRmpSyncEnabled() || rmp3Off || (this.pilotSeat.get() === PilotSeat.Right && rmp2On);
}

private shouldControlRmp3(): boolean {
const rmp1State = SimVar.GetSimVarValueFastReg(this.rmp1StateVar);
const rmp2State = SimVar.GetSimVarValueFastReg(this.rmp2StateVar);
const rmp1Off = rmp1State === RmpState.OffFailed || rmp1State === RmpState.OffStandby;
const rmp2Off = rmp2State === RmpState.OffFailed || rmp2State === RmpState.OffStandby;
return (
this.isRmpSyncEnabled() ||
(this.pilotSeat.get() === PilotSeat.Left && rmp1Off) ||
(this.pilotSeat.get() === PilotSeat.Right && rmp2Off)
);
}

private onComFractIncrement(index: ComRadioIndex, sign: 1 | -1, carry: boolean): void {
SimVar.SetSimVarValueRegNumber(
RmpUtils.getStandbyFrequencyLocalVar(index),
RadioUtils.getNextValidChannel(
SimVar.GetSimVarValueFastReg(RmpUtils.getStandbyFrequencyLocalVar(index)),
sign,
carry,
),
);
SimVar.SetSimVarValueRegNumber(RmpUtils.getStandbyModeLocalVar(index), FrequencyMode.Frequency);
}

private onComWholeIncrement(index: ComRadioIndex, sign: 1 | -1): void {
SimVar.SetSimVarValueRegNumber(
RmpUtils.getStandbyFrequencyLocalVar(index),
RadioUtils.incrementBcd32(SimVar.GetSimVarValueFastReg(RmpUtils.getStandbyFrequencyLocalVar(index)), 4, sign),
);
SimVar.SetSimVarValueRegNumber(RmpUtils.getStandbyModeLocalVar(index), FrequencyMode.Frequency);
}

private onComActiveSet(index: ComRadioIndex, isHertz: boolean, data: KeyEventData): void {
const frequencyEvent: keyof InterRmpBusEvents = `inter_rmp_set_vhf_active_${index}`;
const modeEvent: keyof InterRmpBusEvents = `inter_rmp_set_vhf_active_mode_${index}`;
const frequency = isHertz ? RadioUtils.packBcd32(data.value0) : RadioUtils.bcd16ToBcd32(data.value0);
this.publisher.pub(frequencyEvent, frequency, true);
this.publisher.pub(modeEvent, FrequencyMode.Frequency, true);
SimVar.SetSimVarValueRegNumber(RmpUtils.getActiveFrequencyLocalVar(index), frequency);
SimVar.SetSimVarValueRegNumber(RmpUtils.getActiveModeLocalVar(index), FrequencyMode.Frequency);
}

private onComStandbySet(index: ComRadioIndex, isHertz: boolean, data: KeyEventData): void {
const frequencyEvent: keyof InterRmpBusEvents = `inter_rmp_set_vhf_standby_${index}`;
const modeEvent: keyof InterRmpBusEvents = `inter_rmp_set_vhf_standby_mode_${index}`;
const frequency = isHertz ? RadioUtils.packBcd32(data.value0) : RadioUtils.bcd16ToBcd32(data.value0);
this.publisher.pub(frequencyEvent, frequency, true);
this.publisher.pub(modeEvent, FrequencyMode.Frequency, true);
SimVar.SetSimVarValueRegNumber(RmpUtils.getStandbyFrequencyLocalVar(index), frequency);
SimVar.SetSimVarValueRegNumber(RmpUtils.getStandbyModeLocalVar(index), FrequencyMode.Frequency);
}

private onComSwap(_index: ComRadioIndex): void {
// FIXME implement, inop for now
private onComSwap(index: ComRadioIndex): void {
RmpUtils.swapVhfFrequency(index);
}

private onComRxSelect(_index: ComRadioIndex, _data: KeyEventData): void {
// FIXME implement, inop for now
private onComRxSelect(index: ComRadioIndex, data: KeyEventData): void {
const rxOn = data.value0 === 1;
if (this.shouldControlRmp1()) {
SimVar.SetSimVarValue(`L:A380X_RMP_1_VHF_RX_${index}`, SimVarValueType.Bool, rxOn);
}
if (this.shouldControlRmp2()) {
SimVar.SetSimVarValue(`L:A380X_RMP_2_VHF_RX_${index}`, SimVarValueType.Bool, rxOn);
}
if (this.shouldControlRmp3()) {
SimVar.SetSimVarValue(`L:A380X_RMP_3_VHF_RX_${index}`, SimVarValueType.Bool, rxOn);
}
}

private onComTxSelect(_data: KeyEventData): void {
// FIXME implement, inop for now
private onComTxSelect(data: KeyEventData): void {
const vhfIndex = (data.value0 ?? 0) + 1;
if (this.shouldControlRmp1()) {
SimVar.SetSimVarValue(`H:RMP_1_VHF_CALL_${vhfIndex}_PRESSED`, SimVarValueType.Bool, 1);
requestAnimationFrame(() =>
SimVar.SetSimVarValue(`H:RMP_1_VHF_CALL_${vhfIndex}_RELEASED`, SimVarValueType.Bool, 1),
);
}
if (this.shouldControlRmp2()) {
SimVar.SetSimVarValue(`H:RMP_2_VHF_CALL_${vhfIndex}_PRESSED`, SimVarValueType.Bool, 1);
requestAnimationFrame(() =>
SimVar.SetSimVarValue(`H:RMP_2_VHF_CALL_${vhfIndex}_RELEASED`, SimVarValueType.Bool, 1),
);
}
if (this.shouldControlRmp3()) {
SimVar.SetSimVarValue(`H:RMP_3_VHF_CALL_${vhfIndex}_PRESSED`, SimVarValueType.Bool, 1);
requestAnimationFrame(() =>
SimVar.SetSimVarValue(`H:RMP_3_VHF_CALL_${vhfIndex}_RELEASED`, SimVarValueType.Bool, 1),
);
}
}

private onComVolumeSet(_index: ComRadioIndex, _data: KeyEventData): void {
// FIXME implement, inop for now
private onComVolumeSet(index: ComRadioIndex, data: KeyEventData): void {
const volume = MathUtils.clamp(data.value0 ?? 0, 0, 1);
if (this.shouldControlRmp1()) {
SimVar.SetSimVarValue(`L:A380X_RMP_1_VHF_VOL_${index}`, SimVarValueType.Number, volume);
}
if (this.shouldControlRmp2()) {
SimVar.SetSimVarValue(`L:A380X_RMP_2_VHF_VOL_${index}`, SimVarValueType.Number, volume);
}
if (this.shouldControlRmp3()) {
SimVar.SetSimVarValue(`L:A380X_RMP_3_VHF_VOL_${index}`, SimVarValueType.Number, volume);
}
}

private onComVolumeInc(_index: ComRadioIndex, _sign: 1 | -1, _data: KeyEventData): void {
// FIXME implement, inop for now
private onComVolumeInc(index: ComRadioIndex, sign: 1 | -1): void {
const increment = 2 * sign;
if (this.shouldControlRmp1()) {
const localVar = `A380X_RMP_1_VHF_VOL_${index}`;
SimVar.SetSimVarValue(
localVar,
SimVarValueType.Number,
MathUtils.clamp(SimVar.GetSimVarValue(localVar, SimVarValueType.Number) + increment, 0, 100),
);
}
if (this.shouldControlRmp2()) {
const localVar = `A380X_RMP_2_VHF_VOL_${index}`;
SimVar.SetSimVarValue(
localVar,
SimVarValueType.Number,
MathUtils.clamp(SimVar.GetSimVarValue(localVar, SimVarValueType.Number) + increment, 0, 100),
);
}
if (this.shouldControlRmp3()) {
const localVar = `A380X_RMP_3_VHF_VOL_${index}`;
SimVar.SetSimVarValue(
localVar,
SimVarValueType.Number,
MathUtils.clamp(SimVar.GetSimVarValue(localVar, SimVarValueType.Number) + increment, 0, 100),
);
}
}

private onComReceiveAll(_toggle: boolean): void {
// FIXME implement, inop for now
private onComReceiveAll(toggle: boolean, data: KeyEventData): void {
if (this.shouldControlRmp1()) {
const rx1On = toggle ? SimVar.GetSimVarValue('L:A380X_RMP_1_VHF_RX_1', SimVarValueType.Bool) : data.value0 > 0;
SimVar.SetSimVarValue('L:A380X_RMP_1_VHF_RX_1', SimVarValueType.Bool, rx1On);
const rx2On = toggle ? SimVar.GetSimVarValue('L:A380X_RMP_1_VHF_RX_2', SimVarValueType.Bool) : data.value0 > 0;
SimVar.SetSimVarValue('L:A380X_RMP_1_VHF_RX_2', SimVarValueType.Bool, rx2On);
const rx3On = toggle ? SimVar.GetSimVarValue('L:A380X_RMP_1_VHF_RX_3', SimVarValueType.Bool) : data.value0 > 0;
SimVar.SetSimVarValue('L:A380X_RMP_1_VHF_RX_3', SimVarValueType.Bool, rx3On);
}
if (this.shouldControlRmp2()) {
const rx1On = toggle ? SimVar.GetSimVarValue('L:A380X_RMP_2_VHF_RX_1', SimVarValueType.Bool) : data.value0 > 0;
SimVar.SetSimVarValue('L:A380X_RMP_2_VHF_RX_1', SimVarValueType.Bool, rx1On);
const rx2On = toggle ? SimVar.GetSimVarValue('L:A380X_RMP_2_VHF_RX_2', SimVarValueType.Bool) : data.value0 > 0;
SimVar.SetSimVarValue('L:A380X_RMP_2_VHF_RX_2', SimVarValueType.Bool, rx2On);
const rx3On = toggle ? SimVar.GetSimVarValue('L:A380X_RMP_2_VHF_RX_3', SimVarValueType.Bool) : data.value0 > 0;
SimVar.SetSimVarValue('L:A380X_RMP_2_VHF_RX_3', SimVarValueType.Bool, rx3On);
}
if (this.shouldControlRmp3()) {
const rx1On = toggle ? SimVar.GetSimVarValue('L:A380X_RMP_3_VHF_RX_1', SimVarValueType.Bool) : data.value0 > 0;
SimVar.SetSimVarValue('L:A380X_RMP_3_VHF_RX_1', SimVarValueType.Bool, rx1On);
const rx2On = toggle ? SimVar.GetSimVarValue('L:A380X_RMP_3_VHF_RX_2', SimVarValueType.Bool) : data.value0 > 0;
SimVar.SetSimVarValue('L:A380X_RMP_3_VHF_RX_2', SimVarValueType.Bool, rx2On);
const rx3On = toggle ? SimVar.GetSimVarValue('L:A380X_RMP_3_VHF_RX_3', SimVarValueType.Bool) : data.value0 > 0;
SimVar.SetSimVarValue('L:A380X_RMP_3_VHF_RX_3', SimVarValueType.Bool, rx3On);
}
}
}
Loading