From daa56cb589e3351fb3d2a6e3667694eae94636b5 Mon Sep 17 00:00:00 2001 From: Fragtality <78614902+Fragtality@users.noreply.github.com> Date: Thu, 19 Feb 2026 01:08:36 +0100 Subject: [PATCH 1/5] fix(gsx): Changes to support new Loader Behavior, Handlers and Service Cancellation --- .../FlyByWire_A320_NEO/gsx_handler.py | 88 ++++++++++ .../FlyByWire_A320_NEO/model/A320_NEO.xml | 2 +- .../legacy_pages/A320_Neo_CDU_InitPage.ts | 2 +- .../FlyByWire_A380_842/gsx_handler.py | 88 ++++++++++ .../model/A380_EXTERIOR.xml | 2 +- .../EFB/Dashboard/Widgets/FlightWidget.tsx | 7 +- .../Ground/Pages/Fuel/A320_251N/A320Fuel.tsx | 4 +- .../Ground/Pages/Fuel/A380_842/A380Fuel.tsx | 4 +- .../Pages/Payload/NarrowBody/A320Payload.tsx | 8 +- .../Pages/Payload/WideBody/A380Payload.tsx | 8 +- fbw-common/src/systems/shared/src/GsxSync.ts | 157 +++++++++--------- .../publishers/Extras/GsxSimVarPublisher.ts | 6 + .../wasm/systems/systems/src/payload/mod.rs | 19 ++- 13 files changed, 302 insertions(+), 93 deletions(-) create mode 100644 fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/gsx_handler.py create mode 100644 fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/gsx_handler.py diff --git a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/gsx_handler.py b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/gsx_handler.py new file mode 100644 index 00000000000..1245f7b2456 --- /dev/null +++ b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/gsx_handler.py @@ -0,0 +1,88 @@ +friendlyName = "FlyByWire A32NX Handler" +chocksTest = "(L:A32NX_PARK_BRAKE_LEVER_POS,number) (L:A32NX_GND_EQP_IS_VISIBLE,number) ||" +brakesTest = "(L:A32NX_PARK_BRAKE_LEVER_POS,number)" +zfw = LVariable("A32NX_AIRFRAME_ZFW") + +def onAircraftEngaged(self): + pass + +def onAircraftDisengaged(self): + pass + +def onBeforeVehicleSelect(self): + pass + +def onBoardingRequested(self): + pass + +def onDeboardingRequested(self): + pass + +def onDepartureRequested(self): + pass + +def onCateringRequested(self): + pass + +def onWaterServiceRequested(self): + pass + +def onWaterServiceCompleted(self): + pass + +def onLavatoryServiceRequested(self): + pass + +def onDeicingAction(self): + pass + +def onJetwayConnected(self): + pass + +def onJetwayDisconnected(self): + pass + +def onBypassPinConnected(self): + pass + +def onBypassPinDisconnected(self): + pass + +def onWaterOperateDoor(self, value=True): + pass + +def onLavatoryOperateDoor(self, value=True): + pass + +def operateChocks(self): + pass + +def operateGpu(self): + pass + +def setExtPowerAvail(self): + pass + +def setFuelLevel(self, level): + pass + +def setPlannedFuel(self): + pass + +def getCurrentZFW(self): + return zfw.getValue() + +def fuelPumpInsist(self): + return 1 + +def setPayload(self): + pass + +def removePayload(self): + pass + +def customTruckRequestedMessage(self): + pass + +def customTruckInPositionMessage(self): + pass diff --git a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/model/A320_NEO.xml b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/model/A320_NEO.xml index 6a8911901c3..233b1a68c1f 100644 --- a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/model/A320_NEO.xml +++ b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/model/A320_NEO.xml @@ -554,7 +554,7 @@ 10 (A:SURFACE RELATIVE GROUND SPEED, feet per second) 0.1 > ! (>L:A32NX_IS_STATIONARY, bool) - (A:SIM ON GROUND, bool) (L:A32NX_ENGINE_N1:1, Number) 3.5 < and (L:A32NX_ENGINE_N1:2, Number) 3.5 < and (A:LIGHT BEACON, bool) 0 == and (L:FSDT_GSX_DEPARTURE_STATE, number) 5 != and (>L:A32NX_GND_EQP_IS_VISIBLE, bool) + (A:SIM ON GROUND, bool) (L:A32NX_ENGINE_N1:1, Number) 3.5 < and (L:A32NX_ENGINE_N1:2, Number) 3.5 < and (L:A32NX_ENGINE_N1:3, Number) 3.5 < and (L:A32NX_ENGINE_N1:4, Number) 3.5 < and (L:A32NX_HYD_NW_STRG_DISC_ECAM_MEMO, bool) 0 == and (A:LIGHT BEACON, bool) 0 == and (L:FSDT_GSX_DEPARTURE_STATE, number) 5 != and (>L:A32NX_GND_EQP_IS_VISIBLE, bool) diff --git a/fbw-a32nx/src/systems/instruments/src/MCDU/legacy_pages/A320_Neo_CDU_InitPage.ts b/fbw-a32nx/src/systems/instruments/src/MCDU/legacy_pages/A320_Neo_CDU_InitPage.ts index 6ed407cbdf5..f4650bef856 100644 --- a/fbw-a32nx/src/systems/instruments/src/MCDU/legacy_pages/A320_Neo_CDU_InitPage.ts +++ b/fbw-a32nx/src/systems/instruments/src/MCDU/legacy_pages/A320_Neo_CDU_InitPage.ts @@ -430,7 +430,7 @@ export class CDUInitPage { let zfwCg = undefined; const a32nxBoarding = SimVar.GetSimVarValue('L:A32NX_BOARDING_STARTED_BY_USR', 'bool'); const gsxBoarding = SimVar.GetSimVarValue('L:FSDT_GSX_BOARDING_STATE', 'number'); - if (a32nxBoarding || (gsxBoarding >= 4 && gsxBoarding < 6)) { + if (a32nxBoarding || gsxBoarding == 4 || gsxBoarding == 5) { zfw = NXUnits.kgToUser(SimVar.GetSimVarValue('L:A32NX_AIRFRAME_ZFW_DESIRED', 'number')); zfwCg = SimVar.GetSimVarValue('L:A32NX_AIRFRAME_ZFW_CG_PERCENT_MAC_DESIRED', 'number'); } else if (Number.isFinite(getZfw()) && Number.isFinite(getZfwcg())) { diff --git a/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/gsx_handler.py b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/gsx_handler.py new file mode 100644 index 00000000000..1245f7b2456 --- /dev/null +++ b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/gsx_handler.py @@ -0,0 +1,88 @@ +friendlyName = "FlyByWire A32NX Handler" +chocksTest = "(L:A32NX_PARK_BRAKE_LEVER_POS,number) (L:A32NX_GND_EQP_IS_VISIBLE,number) ||" +brakesTest = "(L:A32NX_PARK_BRAKE_LEVER_POS,number)" +zfw = LVariable("A32NX_AIRFRAME_ZFW") + +def onAircraftEngaged(self): + pass + +def onAircraftDisengaged(self): + pass + +def onBeforeVehicleSelect(self): + pass + +def onBoardingRequested(self): + pass + +def onDeboardingRequested(self): + pass + +def onDepartureRequested(self): + pass + +def onCateringRequested(self): + pass + +def onWaterServiceRequested(self): + pass + +def onWaterServiceCompleted(self): + pass + +def onLavatoryServiceRequested(self): + pass + +def onDeicingAction(self): + pass + +def onJetwayConnected(self): + pass + +def onJetwayDisconnected(self): + pass + +def onBypassPinConnected(self): + pass + +def onBypassPinDisconnected(self): + pass + +def onWaterOperateDoor(self, value=True): + pass + +def onLavatoryOperateDoor(self, value=True): + pass + +def operateChocks(self): + pass + +def operateGpu(self): + pass + +def setExtPowerAvail(self): + pass + +def setFuelLevel(self, level): + pass + +def setPlannedFuel(self): + pass + +def getCurrentZFW(self): + return zfw.getValue() + +def fuelPumpInsist(self): + return 1 + +def setPayload(self): + pass + +def removePayload(self): + pass + +def customTruckRequestedMessage(self): + pass + +def customTruckInPositionMessage(self): + pass diff --git a/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/A380_EXTERIOR.xml b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/A380_EXTERIOR.xml index b19991f374b..821a3511783 100755 --- a/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/A380_EXTERIOR.xml +++ b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/A380_EXTERIOR.xml @@ -689,7 +689,7 @@ 10 (A:SURFACE RELATIVE GROUND SPEED, feet per second) 0.1 > ! (>L:A32NX_IS_STATIONARY, bool) - (A:SIM ON GROUND, bool) (L:A32NX_ENGINE_N1:1, Number) 3.5 < and (L:A32NX_ENGINE_N1:2, Number) 3.5 < and (L:A32NX_HYD_NW_STRG_DISC_ECAM_MEMO, bool) 0 == and (A:LIGHT BEACON, bool) 0 == and (>L:A32NX_GND_EQP_IS_VISIBLE, bool) + (A:SIM ON GROUND, bool) (L:A32NX_ENGINE_N1:1, Number) 3.5 < and (L:A32NX_ENGINE_N1:2, Number) 3.5 < and (L:A32NX_HYD_NW_STRG_DISC_ECAM_MEMO, bool) 0 == and (A:LIGHT BEACON, bool) 0 == and (L:FSDT_GSX_DEPARTURE_STATE, number) 5 != and (>L:A32NX_GND_EQP_IS_VISIBLE, bool) diff --git a/fbw-common/src/systems/instruments/src/EFB/Dashboard/Widgets/FlightWidget.tsx b/fbw-common/src/systems/instruments/src/EFB/Dashboard/Widgets/FlightWidget.tsx index c9970d5b985..c445e771a26 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Dashboard/Widgets/FlightWidget.tsx +++ b/fbw-common/src/systems/instruments/src/EFB/Dashboard/Widgets/FlightWidget.tsx @@ -101,7 +101,12 @@ export const FlightWidget = () => { dispatch(setSimbriefDataPending(true)); const gsxInProgress = - (gsxDeBoardingState >= 4 && gsxDeBoardingState < 6) || (gsxBoardingState >= 4 && gsxBoardingState < 6); + gsxDeBoardingState == 4 || + gsxDeBoardingState == 5 || + gsxDeBoardingState == 7 || + gsxBoardingState == 4 || + gsxBoardingState == 5 || + gsxBoardingState == 7; const generalBoardingInProgress = gsxPayloadSyncEnabled ? gsxInProgress : boardingStarted; if (generalBoardingInProgress || refuelStartedByUser) { diff --git a/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Fuel/A320_251N/A320Fuel.tsx b/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Fuel/A320_251N/A320Fuel.tsx index 1dcb994d3e9..ec020ef2eeb 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Fuel/A320_251N/A320Fuel.tsx +++ b/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Fuel/A320_251N/A320Fuel.tsx @@ -147,7 +147,9 @@ export const A320Fuel: React.FC = ({ }, [simbriefDataLoaded, simbriefPlanRamp, totalTarget, refuelStartedByUser]); const gsxRefuelActive = () => - gsxRefuelState === GsxServiceStates.REQUESTED || gsxRefuelState === GsxServiceStates.ACTIVE; + gsxRefuelState === GsxServiceStates.REQUESTED || + gsxRefuelState === GsxServiceStates.ACTIVE || + gsxRefuelState === GsxServiceStates.COMPLETING; const gsxRefuelCallable = () => gsxRefuelState === GsxServiceStates.CALLABLE; diff --git a/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Fuel/A380_842/A380Fuel.tsx b/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Fuel/A380_842/A380Fuel.tsx index 6a55688bae9..b92bb0d500e 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Fuel/A380_842/A380Fuel.tsx +++ b/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Fuel/A380_842/A380Fuel.tsx @@ -192,7 +192,9 @@ export const A380Fuel: React.FC = ({ }, [simbriefDataLoaded, simbriefPlanRamp, fuelDesiredKg, refuelStartedByUser]); const gsxRefuelActive = () => - gsxRefuelState === GsxServiceStates.REQUESTED || gsxRefuelState === GsxServiceStates.ACTIVE; + gsxRefuelState === GsxServiceStates.REQUESTED || + gsxRefuelState === GsxServiceStates.ACTIVE || + gsxRefuelState === GsxServiceStates.COMPLETING; const gsxRefuelCallable = () => gsxRefuelState === GsxServiceStates.CALLABLE; diff --git a/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Payload/NarrowBody/A320Payload.tsx b/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Payload/NarrowBody/A320Payload.tsx index 1f4e010b8f2..3052bc91969 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Payload/NarrowBody/A320Payload.tsx +++ b/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Payload/NarrowBody/A320Payload.tsx @@ -160,7 +160,12 @@ export const A320Payload: React.FC = ({ const [gsxBoardingState] = useSimVar('L:FSDT_GSX_BOARDING_STATE', 'Number', 227); const [gsxDeBoardingState] = useSimVar('L:FSDT_GSX_DEBOARDING_STATE', 'Number', 229); const gsxInProgress = () => - (gsxDeBoardingState >= 4 && gsxDeBoardingState < 6) || (gsxBoardingState >= 4 && gsxBoardingState < 6); + gsxDeBoardingState == 4 || + gsxDeBoardingState == 5 || + gsxDeBoardingState == 7 || + gsxBoardingState == 4 || + gsxBoardingState == 5 || + gsxBoardingState == 7; const gsxStates = { AVAILABLE: 1, NOT_AVAILABLE: 2, @@ -168,6 +173,7 @@ export const A320Payload: React.FC = ({ REQUESTED: 4, PERFORMING: 5, COMPLETED: 6, + COMPLETING: 7, }; const dispatch = useAppDispatch(); diff --git a/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Payload/WideBody/A380Payload.tsx b/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Payload/WideBody/A380Payload.tsx index 73f73befda8..7d06d9973a0 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Payload/WideBody/A380Payload.tsx +++ b/fbw-common/src/systems/instruments/src/EFB/Ground/Pages/Payload/WideBody/A380Payload.tsx @@ -304,7 +304,12 @@ export const A380Payload: React.FC = ({ const [gsxBoardingState] = useSimVar('L:FSDT_GSX_BOARDING_STATE', 'Number', 227); const [gsxDeBoardingState] = useSimVar('L:FSDT_GSX_DEBOARDING_STATE', 'Number', 229); const gsxInProgress = () => - (gsxDeBoardingState >= 4 && gsxDeBoardingState < 6) || (gsxBoardingState >= 4 && gsxBoardingState < 6); + gsxDeBoardingState == 4 || + gsxDeBoardingState == 5 || + gsxDeBoardingState == 7 || + gsxBoardingState == 4 || + gsxBoardingState == 5 || + gsxBoardingState == 7; const gsxStates = { AVAILABLE: 1, NOT_AVAILABLE: 2, @@ -312,6 +317,7 @@ export const A380Payload: React.FC = ({ REQUESTED: 4, PERFORMING: 5, COMPLETED: 6, + COMPLETING: 7, }; const dispatch = useAppDispatch(); diff --git a/fbw-common/src/systems/shared/src/GsxSync.ts b/fbw-common/src/systems/shared/src/GsxSync.ts index b2aa3e3c3ac..c3a6e56c1b4 100644 --- a/fbw-common/src/systems/shared/src/GsxSync.ts +++ b/fbw-common/src/systems/shared/src/GsxSync.ts @@ -27,6 +27,7 @@ export enum GsxServiceStates { REQUESTED = 4, //service has been requested ACTIVE = 5, //service is being performed COMPLETED = 6, //service has been completed + COMPLETING = 7, //service is completing (was canceled gracefully in the Menu) } enum RefuelRate { @@ -65,6 +66,7 @@ abstract class GsxSync implements Instrument { protected readonly couatlStarted = ConsumerSubject.create(null, 0); protected readonly stateJetway = ConsumerSubject.create(null, 1); protected readonly stateBoard = ConsumerSubject.create(null, 1); + protected readonly stateDeboard = ConsumerSubject.create(null, 1); protected readonly stateDeparture = ConsumerSubject.create(null, 1); protected readonly stateGpu = ConsumerSubject.create(null, 1); protected isRefuelActive = false; @@ -75,6 +77,11 @@ abstract class GsxSync implements Instrument { protected readonly fuelHose = ConsumerSubject.create(null, 0); protected readonly refuelStartedByUser = ConsumerSubject.create(null, false); protected initialIngameFrame = false; + protected readonly cargoDoorTarget = ConsumerSubject.create(null, 0); + protected readonly toggleCargo1 = ConsumerSubject.create(null, 0); + protected readonly toggleCargo2 = ConsumerSubject.create(null, 0); + protected readonly attachedLoader1 = ConsumerSubject.create(null, 0); + protected readonly attachedLoader2 = ConsumerSubject.create(null, 0); constructor( protected readonly bus: EventBus, @@ -98,10 +105,25 @@ abstract class GsxSync implements Instrument { this.couatlStarted.sub(this.onCouatlStarted.bind(this)); this.stateJetway.setConsumer(this.sub.on('gsx_jetway_state')); this.stateBoard.setConsumer(this.sub.on('gsx_boarding_state')); + this.stateDeboard.setConsumer(this.sub.on('gsx_deboarding_state')); this.stateDeparture.setConsumer(this.sub.on('gsx_departure_state')); this.stateGpu.setConsumer(this.sub.on('gsx_gpu_state')); this.fuelHose.setConsumer(this.sub.on('gsx_fuelhose_connected')); this.refuelStartedByUser.setConsumer(this.sub.on('a32nx_refuel_started_by_user')); + this.cargoDoorTarget.setConsumer(this.sub.on(`msfs_interactive_point_goal_${this.getCargoDoorIndex()}`)); + this.toggleCargo1.setConsumer(this.sub.on('gsx_aircraft_cargo_1_toggle')); + this.toggleCargo2.setConsumer(this.sub.on('gsx_aircraft_cargo_2_toggle')); + this.attachedLoader1.setConsumer(this.sub.on('gsx_aircraft_loader_1_attached')); + this.attachedLoader2.setConsumer(this.sub.on('gsx_aircraft_loader_2_attached')); + + this.stateBoard.sub(this.onBoardingCompleted.bind(this)); + this.stateDeboard.sub(this.onBoardingCompleted.bind(this)); + this.stateDeparture.sub(this.onPushbackActive.bind(this)); + + this.toggleCargo1.sub(this.onToggleCargo.bind(this)); + this.toggleCargo2.sub(this.onToggleCargo.bind(this)); + this.attachedLoader1.sub(this.onLoaderAttached.bind(this)); + this.attachedLoader2.sub(this.onLoaderAttached.bind(this)); this.fuelHose.sub(this.onFuelHoseConnected.bind(this)); this.refuelStartedByUser.sub(this.onRefuelStartedByUser.bind(this)); @@ -223,6 +245,7 @@ abstract class GsxSync implements Instrument { const jetwayState = this.stateJetway.get(); const gpuState = this.stateGpu.get(); const boardState = this.stateBoard.get(); + const deboardState = this.stateDeboard.get(); const departureState = this.stateDeparture.get(); let action = PowerAction.NOOP; @@ -232,10 +255,11 @@ abstract class GsxSync implements Instrument { if ( boardState !== GsxServiceStates.REQUESTED && boardState !== GsxServiceStates.ACTIVE && + boardState !== GsxServiceStates.COMPLETING && departureState >= GsxServiceStates.REQUESTED ) { action = PowerAction.DISCONNECT; - // pushback is active (in it wasn't catched before) + // pushback is active (if it wasn't catched before) } else if (departureState === GsxServiceStates.ACTIVE) { action = PowerAction.DISCONNECT; // jetway and gpu both not connected @@ -249,7 +273,9 @@ abstract class GsxSync implements Instrument { // external is not available - check if connect is needed } else if ( //jetway or gpu connected - (jetwayState >= GsxServiceStates.REQUESTED || gpuState === GsxServiceStates.ACTIVE) && + (jetwayState >= GsxServiceStates.REQUESTED || + gpuState === GsxServiceStates.ACTIVE || + deboardState === GsxServiceStates.ACTIVE) && departureState < GsxServiceStates.REQUESTED ) { action = PowerAction.CONNECT; @@ -280,6 +306,53 @@ abstract class GsxSync implements Instrument { } } } + + protected isCargoOpen(): boolean { + return this.cargoDoorTarget.get() !== DoorTarget.CLOSED; + } + + protected abstract getCargoDoorIndex(): number; + + protected setCargoDoor(goal: number, index: number): void { + if (index == null) { + index = this.getCargoDoorIndex(); + } + SimVar.SetSimVarValue(`A:INTERACTIVE POINT GOAL:${index}`, SimVarValueType.PercentOver100, goal); + } + + protected onBoardingCompleted(state: number): void { + //just to be safe, check again when boarding was completed + if (state === GsxServiceStates.COMPLETED && this.isCargoOpen()) { + this.setCargoDoor(DoorTarget.CLOSED, this.getCargoDoorIndex()); + } + } + + protected onPushbackActive(pushState: number): void { + //just to be safe, check doors again when pushback is active - maybe something bad happened on boarding + if ( + this.isCargoOpen() && + (pushState === GsxServiceStates.ACTIVE || + (pushState === GsxServiceStates.REQUESTED && this.stateBoard.get() !== GsxServiceStates.ACTIVE)) + ) { + this.setCargoDoor(DoorTarget.CLOSED, this.getCargoDoorIndex()); + } + } + + protected onToggleCargo(toggle: number) { + //received first toggle from gsx - only used for opening the doors + if (toggle !== GsxStates.FALSE && !this.isCargoOpen()) { + this.setCargoDoor(DoorTarget.OPEN, this.getCargoDoorIndex()); + } + } + + protected onLoaderAttached(attached: number) { + if (attached === GsxStates.TRUE && !this.isCargoOpen()) { + this.setCargoDoor(DoorTarget.OPEN, this.getCargoDoorIndex()); + } + if (attached === GsxStates.FALSE && this.isCargoOpen()) { + this.setCargoDoor(DoorTarget.CLOSED, this.getCargoDoorIndex()); + } + } } export class GsxSyncA32NX extends GsxSync { @@ -305,39 +378,17 @@ export class GsxSyncA32NX extends GsxSync { SimVar.SetSimVarValue(`L:A32NX_OVHD_ELEC_EXT_PWR_PB_IS_ON`, SimVarValueType.Bool, state); } } + + protected getCargoDoorIndex(): number { + return 5; + } } export class GsxSyncA380X extends GsxSync { - protected readonly toggleCargo1 = ConsumerSubject.create(null, 0); - protected readonly toggleCargo2 = ConsumerSubject.create(null, 0); - protected readonly stateDeboard = ConsumerSubject.create(null, 1); - protected readonly cargoPercentBoard = ConsumerSubject.create(null, 0); - protected readonly cargoPercentDeboard = ConsumerSubject.create(null, 0); - protected readonly cargoTimer = new DebounceTimer(); - protected readonly cargoDoorTarget = ConsumerSubject.create(null, 0); - constructor(bus: EventBus) { super(bus, 4, DEFUEL_DIFF_TARGET_A380); } - protected initSubscriptions(): void { - super.initSubscriptions(); - this.toggleCargo1.setConsumer(this.sub.on('gsx_aircraft_cargo_1_toggle')); - this.toggleCargo2.setConsumer(this.sub.on('gsx_aircraft_cargo_2_toggle')); - this.stateDeboard.setConsumer(this.sub.on('gsx_deboarding_state')); - this.cargoPercentBoard.setConsumer(this.sub.on('gsx_boarding_cargo_percent')); - this.cargoPercentDeboard.setConsumer(this.sub.on('gsx_deboarding_cargo_percent')); - this.cargoDoorTarget.setConsumer(this.sub.on('msfs_interactive_point_goal_16')); - - this.toggleCargo1.sub(this.onToggleCargo.bind(this)); - this.toggleCargo2.sub(this.onToggleCargo.bind(this)); - this.stateBoard.sub(this.onBoardingCompleted.bind(this)); - this.stateDeboard.sub(this.onBoardingCompleted.bind(this)); - this.cargoPercentBoard.sub(this.onCargoBoardCompleted.bind(this)); - this.cargoPercentDeboard.sub(this.onCargoDeboardCompleted.bind(this)); - this.stateDeparture.sub(this.onPushbackActive.bind(this)); - } - protected getFob(): number { return SimVar.GetSimVarValueFast('L:A32NX_TOTAL_FUEL_QUANTITY', SimVarValueType.Number); } @@ -354,53 +405,7 @@ export class GsxSyncA380X extends GsxSync { SimVar.SetSimVarValue(`L:A32NX_OVHD_ELEC_EXT_PWR_${index}_PB_IS_ON`, SimVarValueType.Bool, state); } - protected isCargoOpen(): boolean { - return this.cargoDoorTarget.get() !== DoorTarget.CLOSED; - } - - protected setCargoDoor(goal: number, index: number = 16): void { - SimVar.SetSimVarValue(`A:INTERACTIVE POINT GOAL:${index}`, SimVarValueType.PercentOver100, goal); - } - - protected onToggleCargo(toggle: number) { - //received first toggle from gsx - only used for opening the doors - if (toggle !== GsxStates.FALSE && !this.isCargoOpen()) { - this.setCargoDoor(DoorTarget.OPEN); - } - } - - protected onCargoBoardCompleted(percent: number): void { - this.startCargoTimer(percent, DELAY_CARGO_CLOSE); - } - - protected onCargoDeboardCompleted(percent: number): void { - this.startCargoTimer(percent, DELAY_CARGO_CLOSE); - } - - protected startCargoTimer(percent: number, delay: number): void { - //close doors with a certain delay, after (un)loading was completed (==100%) - if (percent === 100 && !this.cargoTimer.isPending() && this.isCargoOpen()) { - this.fuelTimer.schedule(() => { - this.setCargoDoor(DoorTarget.CLOSED); - }, delay); - } - } - - protected onBoardingCompleted(state: number): void { - //just to be safe, check again when boarding was completed - if (state === GsxServiceStates.COMPLETED && this.isCargoOpen()) { - this.setCargoDoor(DoorTarget.CLOSED); - } - } - - protected onPushbackActive(pushState: number): void { - //just to be safe, check again when pushback is active - maybe something bad happened on boarding - if ( - this.isCargoOpen() && - (pushState === GsxServiceStates.ACTIVE || - (pushState === GsxServiceStates.REQUESTED && this.stateBoard.get() !== GsxServiceStates.ACTIVE)) - ) { - this.setCargoDoor(DoorTarget.CLOSED); - } + protected getCargoDoorIndex(): number { + return 16; } } diff --git a/fbw-common/src/systems/shared/src/publishers/Extras/GsxSimVarPublisher.ts b/fbw-common/src/systems/shared/src/publishers/Extras/GsxSimVarPublisher.ts index ec3fb141fa1..39a81cba5d6 100644 --- a/fbw-common/src/systems/shared/src/publishers/Extras/GsxSimVarPublisher.ts +++ b/fbw-common/src/systems/shared/src/publishers/Extras/GsxSimVarPublisher.ts @@ -8,6 +8,10 @@ export interface GsxSimVarEvents { gsx_aircraft_cargo_1_toggle: number; /** AFT Cargo Door Toggle - true => non-zero*/ gsx_aircraft_cargo_2_toggle: number; + /** FWD Cargo Loader attached - true => non-zero*/ + gsx_aircraft_loader_1_attached: number; + /** AFT Cargo Loader attached - true => non-zero*/ + gsx_aircraft_loader_2_attached: number; /** GSX Fuelhose Connected - true => non-zero*/ gsx_fuelhose_connected: number; /** GSX Refuel Service - 4 => requested, 5 => active */ @@ -36,6 +40,8 @@ export class GsxSimVarPublisher extends SimVarPublisher { private static readonly simVars = new Map([ ['gsx_aircraft_cargo_1_toggle', { name: 'L:FSDT_GSX_AIRCRAFT_CARGO_1_TOGGLE', type: SimVarValueType.Number }], ['gsx_aircraft_cargo_2_toggle', { name: 'L:FSDT_GSX_AIRCRAFT_CARGO_2_TOGGLE', type: SimVarValueType.Number }], + ['gsx_aircraft_loader_1_attached', { name: 'L:FSDT_GSX_LOADER_EXIT_0', type: SimVarValueType.Number }], + ['gsx_aircraft_loader_2_attached', { name: 'L:FSDT_GSX_LOADER_EXIT_1', type: SimVarValueType.Number }], ['gsx_jetway_state', { name: 'L:FSDT_GSX_JETWAY', type: SimVarValueType.Number }], ['gsx_boarding_state', { name: 'L:FSDT_GSX_BOARDING_STATE', type: SimVarValueType.Number }], ['gsx_deboarding_state', { name: 'L:FSDT_GSX_DEBOARDING_STATE', type: SimVarValueType.Number }], diff --git a/fbw-common/src/wasm/systems/systems/src/payload/mod.rs b/fbw-common/src/wasm/systems/systems/src/payload/mod.rs index d1194ab035d..8a621a30918 100644 --- a/fbw-common/src/wasm/systems/systems/src/payload/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/payload/mod.rs @@ -1087,6 +1087,7 @@ pub enum GsxState { Requested, Performing, Completed, + Completing, } impl From for GsxState { fn from(value: f64) -> Self { @@ -1098,6 +1099,7 @@ impl From for GsxState { 4 => GsxState::Requested, 5 => GsxState::Performing, 6 => GsxState::Completed, + 7 => GsxState::Completing, i => panic!("Cannot convert from {} to GsxState.", i), } } @@ -1248,7 +1250,10 @@ impl GsxDriver { boarding_sounds.play_sound_pax_boarding(self.boarding_state() == GsxState::Performing); boarding_sounds.play_sound_pax_deboarding(self.deboarding_state() == GsxState::Performing); boarding_sounds.play_sound_pax_ambience(self.has_pax(passenger_deck)); - boarding_sounds.play_sound_pax_complete(self.boarding_state() == GsxState::Completed) + boarding_sounds.play_sound_pax_complete( + self.boarding_state() == GsxState::Completed + || self.boarding_state() == GsxState::Completing, + ) } fn update_boarding( @@ -1257,14 +1262,10 @@ impl GsxDriver { cargo_deck: &mut CargoDeck, ) { match self.boarding_state() { - GsxState::None - | GsxState::Available - | GsxState::NotAvailable - | GsxState::Bypassed - | GsxState::Requested => { + GsxState::None | GsxState::NotAvailable | GsxState::Bypassed | GsxState::Requested => { self.performing_board = false; } - GsxState::Completed => { + GsxState::Available | GsxState::Completed | GsxState::Completing => { if self.performing_board { passenger_deck.spawn_all_pax(); cargo_deck.spawn_all_cargo(); @@ -1285,7 +1286,7 @@ impl GsxDriver { cargo_deck: &mut CargoDeck, ) { match self.deboarding_state() { - GsxState::None | GsxState::Available | GsxState::NotAvailable | GsxState::Bypassed => { + GsxState::None | GsxState::NotAvailable | GsxState::Bypassed => { self.deboarding_total = 0; self.performing_deboard = false; } @@ -1296,7 +1297,7 @@ impl GsxDriver { self.deboarding_total = passenger_deck.total_pax_num(); self.performing_deboard = false; } - GsxState::Completed => { + GsxState::Available | GsxState::Completed | GsxState::Completing => { if self.performing_deboard { passenger_deck.spawn_all_pax(); cargo_deck.spawn_all_cargo(); From 0a7b52d6f57c3e7e635dc29b8d80287aa39534cb Mon Sep 17 00:00:00 2001 From: Fragtality <78614902+Fragtality@users.noreply.github.com> Date: Thu, 19 Feb 2026 01:11:44 +0100 Subject: [PATCH 2/5] Fixed Changelog --- .github/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 9a3c9157587..c7bb1596b64 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -78,6 +78,7 @@ 1. [FMS] Fixed incorrect course for enroute holds and direct tos - @tracernz (Mike) 1. [FMS] Show true bearings correctly in various FMS pages on the MCDU/MFD - @tracernz (Mike) 1. [A380X/FWS] Improve various ABN SENSED procedure accuracy - @Jonny23787 (Jonathan) +1. [GSX] Changes to support new Loader Behavior, Handlers and Service Cancellation for A380X/A32NX - @Fragtality (Fragtality) ## 0.14.0 From 7a87a420840e059c15ca02b53c9462a3d7ebaa29 Mon Sep 17 00:00:00 2001 From: Fragtality <78614902+Fragtality@users.noreply.github.com> Date: Thu, 19 Feb 2026 01:28:08 +0100 Subject: [PATCH 3/5] Fixed XML Chock Condition --- .../SimObjects/AirPlanes/FlyByWire_A320_NEO/model/A320_NEO.xml | 2 +- .../AirPlanes/FlyByWire_A380_842/model/A380_EXTERIOR.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/model/A320_NEO.xml b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/model/A320_NEO.xml index 233b1a68c1f..103876be945 100644 --- a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/model/A320_NEO.xml +++ b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/model/A320_NEO.xml @@ -554,7 +554,7 @@ 10 (A:SURFACE RELATIVE GROUND SPEED, feet per second) 0.1 > ! (>L:A32NX_IS_STATIONARY, bool) - (A:SIM ON GROUND, bool) (L:A32NX_ENGINE_N1:1, Number) 3.5 < and (L:A32NX_ENGINE_N1:2, Number) 3.5 < and (L:A32NX_ENGINE_N1:3, Number) 3.5 < and (L:A32NX_ENGINE_N1:4, Number) 3.5 < and (L:A32NX_HYD_NW_STRG_DISC_ECAM_MEMO, bool) 0 == and (A:LIGHT BEACON, bool) 0 == and (L:FSDT_GSX_DEPARTURE_STATE, number) 5 != and (>L:A32NX_GND_EQP_IS_VISIBLE, bool) + (A:SIM ON GROUND, bool) (L:A32NX_ENGINE_N1:1, Number) 3.5 < and (L:A32NX_ENGINE_N1:2, Number) 3.5 < and (L:A32NX_HYD_NW_STRG_DISC_ECAM_MEMO, bool) 0 == and (A:LIGHT BEACON, bool) 0 == and (L:FSDT_GSX_DEPARTURE_STATE, number) 5 != and (>L:A32NX_GND_EQP_IS_VISIBLE, bool) diff --git a/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/A380_EXTERIOR.xml b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/A380_EXTERIOR.xml index 821a3511783..d6bbda5886f 100755 --- a/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/A380_EXTERIOR.xml +++ b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/A380_EXTERIOR.xml @@ -689,7 +689,7 @@ 10 (A:SURFACE RELATIVE GROUND SPEED, feet per second) 0.1 > ! (>L:A32NX_IS_STATIONARY, bool) - (A:SIM ON GROUND, bool) (L:A32NX_ENGINE_N1:1, Number) 3.5 < and (L:A32NX_ENGINE_N1:2, Number) 3.5 < and (L:A32NX_HYD_NW_STRG_DISC_ECAM_MEMO, bool) 0 == and (A:LIGHT BEACON, bool) 0 == and (L:FSDT_GSX_DEPARTURE_STATE, number) 5 != and (>L:A32NX_GND_EQP_IS_VISIBLE, bool) + (A:SIM ON GROUND, bool) (L:A32NX_ENGINE_N1:1, Number) 3.5 < and (L:A32NX_ENGINE_N1:2, Number) 3.5 < and (L:A32NX_ENGINE_N1:3, Number) 3.5 < and (L:A32NX_ENGINE_N1:4, Number) 3.5 < and (L:A32NX_HYD_NW_STRG_DISC_ECAM_MEMO, bool) 0 == and (A:LIGHT BEACON, bool) 0 == and (L:FSDT_GSX_DEPARTURE_STATE, number) 5 != and (>L:A32NX_GND_EQP_IS_VISIBLE, bool) From d3cefc68d5546467e57a3a975bd0fbf2da7eea36 Mon Sep 17 00:00:00 2001 From: Fragtality <78614902+Fragtality@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:01:59 +0100 Subject: [PATCH 4/5] Changed GSX Power Sync to use connected State instead of Service State --- fbw-common/src/systems/shared/src/GsxSync.ts | 8 ++++---- .../shared/src/publishers/Extras/GsxSimVarPublisher.ts | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/fbw-common/src/systems/shared/src/GsxSync.ts b/fbw-common/src/systems/shared/src/GsxSync.ts index c3a6e56c1b4..348b0883d79 100644 --- a/fbw-common/src/systems/shared/src/GsxSync.ts +++ b/fbw-common/src/systems/shared/src/GsxSync.ts @@ -68,7 +68,7 @@ abstract class GsxSync implements Instrument { protected readonly stateBoard = ConsumerSubject.create(null, 1); protected readonly stateDeboard = ConsumerSubject.create(null, 1); protected readonly stateDeparture = ConsumerSubject.create(null, 1); - protected readonly stateGpu = ConsumerSubject.create(null, 1); + protected readonly stateGpu = ConsumerSubject.create(null, 0); protected isRefuelActive = false; protected isDefuel = false; protected readonly fuelTimer = new DebounceTimer(); @@ -107,7 +107,7 @@ abstract class GsxSync implements Instrument { this.stateBoard.setConsumer(this.sub.on('gsx_boarding_state')); this.stateDeboard.setConsumer(this.sub.on('gsx_deboarding_state')); this.stateDeparture.setConsumer(this.sub.on('gsx_departure_state')); - this.stateGpu.setConsumer(this.sub.on('gsx_gpu_state')); + this.stateGpu.setConsumer(this.sub.on('gsx_gpu_connected')); this.fuelHose.setConsumer(this.sub.on('gsx_fuelhose_connected')); this.refuelStartedByUser.setConsumer(this.sub.on('a32nx_refuel_started_by_user')); this.cargoDoorTarget.setConsumer(this.sub.on(`msfs_interactive_point_goal_${this.getCargoDoorIndex()}`)); @@ -266,7 +266,7 @@ abstract class GsxSync implements Instrument { } else if ( jetwayState >= GsxServiceStates.CALLABLE && jetwayState <= GsxServiceStates.BYPASSED && - gpuState !== GsxServiceStates.ACTIVE + gpuState !== GsxStates.CONNECTED ) { action = PowerAction.DISCONNECT; } @@ -274,7 +274,7 @@ abstract class GsxSync implements Instrument { } else if ( //jetway or gpu connected (jetwayState >= GsxServiceStates.REQUESTED || - gpuState === GsxServiceStates.ACTIVE || + gpuState === GsxStates.CONNECTED || deboardState === GsxServiceStates.ACTIVE) && departureState < GsxServiceStates.REQUESTED ) { diff --git a/fbw-common/src/systems/shared/src/publishers/Extras/GsxSimVarPublisher.ts b/fbw-common/src/systems/shared/src/publishers/Extras/GsxSimVarPublisher.ts index 39a81cba5d6..30b755a440f 100644 --- a/fbw-common/src/systems/shared/src/publishers/Extras/GsxSimVarPublisher.ts +++ b/fbw-common/src/systems/shared/src/publishers/Extras/GsxSimVarPublisher.ts @@ -26,6 +26,8 @@ export interface GsxSimVarEvents { gsx_departure_state: number; /** GSX GPU Service - 4 => requested, 5 => active */ gsx_gpu_state: number; + /** GSX GPU Connected - true => non-zero*/ + gsx_gpu_connected: number; /** GSX Boarding Cargo loaded 0-100% */ gsx_boarding_cargo_percent: number; /** GSX Deboarding Cargo loaded 0-100% */ @@ -47,6 +49,7 @@ export class GsxSimVarPublisher extends SimVarPublisher { ['gsx_deboarding_state', { name: 'L:FSDT_GSX_DEBOARDING_STATE', type: SimVarValueType.Number }], ['gsx_departure_state', { name: 'L:FSDT_GSX_DEPARTURE_STATE', type: SimVarValueType.Number }], ['gsx_gpu_state', { name: 'L:FSDT_GSX_GPU_STATE', type: SimVarValueType.Number }], + ['gsx_gpu_connected', { name: 'L:FSDT_GSX_GPU_CONNECTED', type: SimVarValueType.Number }], ['gsx_boarding_cargo_percent', { name: 'L:FSDT_GSX_BOARDING_CARGO_PERCENT', type: SimVarValueType.Number }], ['gsx_deboarding_cargo_percent', { name: 'L:FSDT_GSX_DEBOARDING_CARGO_PERCENT', type: SimVarValueType.Number }], ['gsx_refuel_state', { name: 'L:FSDT_GSX_REFUELING_STATE', type: SimVarValueType.Number }], From 00825bb4d7b281a1eeeb95c037060a8952648f31 Mon Sep 17 00:00:00 2001 From: Fragtality <78614902+Fragtality@users.noreply.github.com> Date: Sun, 22 Feb 2026 20:09:42 +0100 Subject: [PATCH 5/5] Reduced overrides in GSX Handler (ofc there are caveats) / Reworked GSX Power Sync / Changes to MSFS Power Sync --- .github/CHANGELOG.md | 3 +- .../FlyByWire_A320_NEO/gsx_handler.py | 88 ++------ .../FlyByWire_A380_842/gsx_handler.py | 90 ++------ .../instruments/src/EFB/Settings/sync.ts | 12 ++ .../src/systems/shared/src/GPUManagement.ts | 26 ++- fbw-common/src/systems/shared/src/GsxSync.ts | 202 +++++++++++------- 6 files changed, 191 insertions(+), 230 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index c7bb1596b64..a17e338b604 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -78,7 +78,8 @@ 1. [FMS] Fixed incorrect course for enroute holds and direct tos - @tracernz (Mike) 1. [FMS] Show true bearings correctly in various FMS pages on the MCDU/MFD - @tracernz (Mike) 1. [A380X/FWS] Improve various ABN SENSED procedure accuracy - @Jonny23787 (Jonathan) -1. [GSX] Changes to support new Loader Behavior, Handlers and Service Cancellation for A380X/A32NX - @Fragtality (Fragtality) +1. [GSX] Changes to support new Features: Loader Behavior, Python Handlers and Service Cancellation - @Fragtality (Fragtality) +1. [GSX] Reworked Ground Power Synch with Jetway/GSX GPU - @Fragtality (Fragtality) ## 0.14.0 diff --git a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/gsx_handler.py b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/gsx_handler.py index 1245f7b2456..7c3d750a0e9 100644 --- a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/gsx_handler.py +++ b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/gsx_handler.py @@ -1,40 +1,8 @@ friendlyName = "FlyByWire A32NX Handler" +# Ensuring that Chocks and Brakes Tests are not overriden by an User-Profile chocksTest = "(L:A32NX_PARK_BRAKE_LEVER_POS,number) (L:A32NX_GND_EQP_IS_VISIBLE,number) ||" brakesTest = "(L:A32NX_PARK_BRAKE_LEVER_POS,number)" -zfw = LVariable("A32NX_AIRFRAME_ZFW") - -def onAircraftEngaged(self): - pass - -def onAircraftDisengaged(self): - pass - -def onBeforeVehicleSelect(self): - pass - -def onBoardingRequested(self): - pass - -def onDeboardingRequested(self): - pass - -def onDepartureRequested(self): - pass - -def onCateringRequested(self): - pass - -def onWaterServiceRequested(self): - pass - -def onWaterServiceCompleted(self): - pass - -def onLavatoryServiceRequested(self): - pass - -def onDeicingAction(self): - pass +fuelSync = LVariable("A32NX_GSX_FUEL_SYNC_ENABLED") def onJetwayConnected(self): pass @@ -42,47 +10,23 @@ def onJetwayConnected(self): def onJetwayDisconnected(self): pass -def onBypassPinConnected(self): - pass - -def onBypassPinDisconnected(self): - pass - -def onWaterOperateDoor(self, value=True): - pass - -def onLavatoryOperateDoor(self, value=True): - pass - -def operateChocks(self): - pass - -def operateGpu(self): - pass - def setExtPowerAvail(self): pass -def setFuelLevel(self, level): - pass - -def setPlannedFuel(self): - pass - -def getCurrentZFW(self): - return zfw.getValue() - -def fuelPumpInsist(self): - return 1 - -def setPayload(self): - pass - -def removePayload(self): - pass - def customTruckRequestedMessage(self): - pass + if fuelSync.getValue() != 0: + return "Fuel Truck requested - load OFP/set planned Fuel in FlyPad" + else: + return self._super_customTruckRequestedMessage() + +def customRefuelMessage(self): + if fuelSync.getValue() != 0: + return "GSX Refuel in Progress" + else: + return self._super_customRefuelMessage() def customTruckInPositionMessage(self): - pass + if fuelSync.getValue() != 0: + return "Fuel Truck in Position - Refuel starts automatically" + else: + return self._super_customTruckInPositionMessage() diff --git a/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/gsx_handler.py b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/gsx_handler.py index 1245f7b2456..2591fadae7e 100644 --- a/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/gsx_handler.py +++ b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/gsx_handler.py @@ -1,40 +1,8 @@ -friendlyName = "FlyByWire A32NX Handler" +friendlyName = "FlyByWire A380X Handler" +# Ensuring that Chocks and Brakes Tests are not overriden by an User-Profile chocksTest = "(L:A32NX_PARK_BRAKE_LEVER_POS,number) (L:A32NX_GND_EQP_IS_VISIBLE,number) ||" brakesTest = "(L:A32NX_PARK_BRAKE_LEVER_POS,number)" -zfw = LVariable("A32NX_AIRFRAME_ZFW") - -def onAircraftEngaged(self): - pass - -def onAircraftDisengaged(self): - pass - -def onBeforeVehicleSelect(self): - pass - -def onBoardingRequested(self): - pass - -def onDeboardingRequested(self): - pass - -def onDepartureRequested(self): - pass - -def onCateringRequested(self): - pass - -def onWaterServiceRequested(self): - pass - -def onWaterServiceCompleted(self): - pass - -def onLavatoryServiceRequested(self): - pass - -def onDeicingAction(self): - pass +fuelSync = LVariable("A32NX_GSX_FUEL_SYNC_ENABLED") def onJetwayConnected(self): pass @@ -42,47 +10,23 @@ def onJetwayConnected(self): def onJetwayDisconnected(self): pass -def onBypassPinConnected(self): - pass - -def onBypassPinDisconnected(self): - pass - -def onWaterOperateDoor(self, value=True): - pass - -def onLavatoryOperateDoor(self, value=True): - pass - -def operateChocks(self): - pass - -def operateGpu(self): - pass - def setExtPowerAvail(self): pass -def setFuelLevel(self, level): - pass - -def setPlannedFuel(self): - pass - -def getCurrentZFW(self): - return zfw.getValue() - -def fuelPumpInsist(self): - return 1 - -def setPayload(self): - pass - -def removePayload(self): - pass - def customTruckRequestedMessage(self): - pass + if fuelSync.getValue() != 0: + return "Fuel Truck requested - set planned Fuel in FlyPad" + else: + return self._super_customTruckRequestedMessage() + +def customRefuelMessage(self): + if fuelSync.getValue() != 0: + return "GSX Refuel in Progress" + else: + return self._super_customRefuelMessage() def customTruckInPositionMessage(self): - pass + if fuelSync.getValue() != 0: + return "Fuel Truck in Position - Refuel starts automatically" + else: + return self._super_customTruckInPositionMessage() diff --git a/fbw-common/src/systems/instruments/src/EFB/Settings/sync.ts b/fbw-common/src/systems/instruments/src/EFB/Settings/sync.ts index ef3f9836d6f..c5a91babe4b 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Settings/sync.ts +++ b/fbw-common/src/systems/instruments/src/EFB/Settings/sync.ts @@ -122,12 +122,24 @@ export const globalSyncedSettings: SyncedSettingDefinition[] = [ localVarUnit: 'bool', defaultValue: '0', }, + { + configKey: 'GSX_FUEL_SYNC', + localVarName: 'L:A32NX_GSX_FUEL_SYNC_ENABLED', + localVarUnit: 'bool', + defaultValue: '0', + }, { configKey: 'GSX_PAYLOAD_SYNC', localVarName: 'L:A32NX_GSX_PAYLOAD_SYNC_ENABLED', localVarUnit: 'bool', defaultValue: '0', }, + { + configKey: 'GSX_POWER_SYNC', + localVarName: 'L:A32NX_GSX_POWER_SYNC_ENABLED', + localVarUnit: 'bool', + defaultValue: '0', + }, { configKey: 'CONFIG_USING_METRIC_UNIT', localVarName: 'L:A32NX_EFB_USING_METRIC_UNIT', diff --git a/fbw-common/src/systems/shared/src/GPUManagement.ts b/fbw-common/src/systems/shared/src/GPUManagement.ts index b2fcbb7ae91..cc6db7edab3 100644 --- a/fbw-common/src/systems/shared/src/GPUManagement.ts +++ b/fbw-common/src/systems/shared/src/GPUManagement.ts @@ -36,6 +36,8 @@ export class GPUManagement implements Instrument { private readonly ExtPowerAvailStates = new Map>(); + private readonly msfsSyncEnabled = ConsumerSubject.create(this.sub.on('msfs_power_sync_state'), true); + // state 2 is cockpit, state 3 is external private readonly isIngame = MappedSubject.create( ([gameState, cameraState]) => gameState === GameState.ingame && (cameraState === 2 || cameraState === 3), @@ -63,11 +65,17 @@ export class GPUManagement implements Instrument { public init(): void { Wait.awaitSubscribable(this.isIngame, (state) => state, true).then(() => { this.sub.on('gpu_toggle').handle(this.toggleGPU.bind(this)); - this.gpuHookedUp.sub((v) => this.setEXTpower(v)); + this.sub.on('set_ext_power').handle(this.onExtPowerEvent.bind(this)); + this.gpuHookedUp.sub((v) => { + if (this.msfsSyncEnabled.get() === true) this.setEXTpower(v); + }); this.groundVelocity.sub((v) => { // disable ext power when aircraft starts moving if (v > 0.3 && this.anyGPUAvail()) { - this.toggleGPU(); + this.setEXTpower(false); + if (this.gpuHookedUp.get()) { + this.toggleMSFSGpu(); + } } }); this.initialIngameFrame = true; @@ -87,14 +95,14 @@ export class GPUManagement implements Instrument { private toggleGPU(): void { if (!this.anyGPUAvail()) { - if (this.anyMSFSGPUAvail()) { + if (this.anyMSFSGPUAvail() || this.msfsSyncEnabled.get() === false) { // if msfs ground power is avail we are at a powered stand this.setEXTpower(true); } else { this.toggleMSFSGpu(); // if msfs ground power is not avail we call for gpu cart } } else { - if (this.gpuHookedUp.get()) { + if (this.gpuHookedUp.get() && this.msfsSyncEnabled.get() === true) { this.toggleMSFSGpu(); } else { this.setEXTpower(false); @@ -102,6 +110,14 @@ export class GPUManagement implements Instrument { } } + private onExtPowerEvent(state: boolean): void { + if (!this.anyGPUAvail() && state) { + this.setEXTpower(true); + } else if (this.anyGPUAvail() && !state) { + this.setEXTpower(false); + } + } + private toggleMSFSGpu(): void { SimVar.SetSimVarValue('K:REQUEST_POWER_SUPPLY', 'Bool', true); } @@ -140,4 +156,6 @@ export class GPUManagement implements Instrument { export interface GPUControlEvents { /** event to toggle the GPU*/ gpu_toggle: unknown; + set_ext_power: boolean; + msfs_power_sync_state: boolean; } diff --git a/fbw-common/src/systems/shared/src/GsxSync.ts b/fbw-common/src/systems/shared/src/GsxSync.ts index 348b0883d79..370270d6554 100644 --- a/fbw-common/src/systems/shared/src/GsxSync.ts +++ b/fbw-common/src/systems/shared/src/GsxSync.ts @@ -11,7 +11,13 @@ import { SimVarValueType, Wait, } from '@microsoft/msfs-sdk'; -import { GroundSupportEvents, GsxSimVarEvents, MsfsFlightModelEvents, NXDataStore } from '@flybywiresim/fbw-sdk'; +import { + GroundSupportEvents, + GsxSimVarEvents, + MsfsFlightModelEvents, + GPUControlEvents, + NXDataStore, +} from '@flybywiresim/fbw-sdk'; export enum GsxStates { FALSE = 0, @@ -21,6 +27,7 @@ export enum GsxStates { } export enum GsxServiceStates { + UNKNOWN = 0, //service state is unknown (i.e. GSX not started / initialized) CALLABLE = 1, //service can be called UNVAILABLE = 2, //service is not available BYPASSED = 3, //service has been bypassed @@ -47,13 +54,6 @@ enum DoorTarget { OPEN = 1, } -enum PowerAction { - NOOP = -1, - DISCONNECT = 0, - CONNECT = 1, -} - -const DELAY_CARGO_CLOSE = 60000; const DELAY_REFUEL_RESTART = 2000; const DELAY_RATE_RESTORE = DELAY_REFUEL_RESTART + 1000; const DELAY_FUEL_START = 500; @@ -62,13 +62,16 @@ const DEFUEL_DIFF_TARGET_A380 = 2500; abstract class GsxSync implements Instrument { protected readonly sub = this.bus.getSubscriber(); + protected readonly pub = this.bus.getPublisher(); protected readonly extPowerAvailStates = new Map>(); protected readonly couatlStarted = ConsumerSubject.create(null, 0); - protected readonly stateJetway = ConsumerSubject.create(null, 1); + protected readonly stateJetway = ConsumerSubject.create(null, 0); protected readonly stateBoard = ConsumerSubject.create(null, 1); protected readonly stateDeboard = ConsumerSubject.create(null, 1); protected readonly stateDeparture = ConsumerSubject.create(null, 1); - protected readonly stateGpu = ConsumerSubject.create(null, 0); + protected readonly gpuConnection = ConsumerSubject.create(null, 0); + protected lastJetwayState = 0; + protected lastGpuConnection = 0; protected isRefuelActive = false; protected isDefuel = false; protected readonly fuelTimer = new DebounceTimer(); @@ -107,7 +110,7 @@ abstract class GsxSync implements Instrument { this.stateBoard.setConsumer(this.sub.on('gsx_boarding_state')); this.stateDeboard.setConsumer(this.sub.on('gsx_deboarding_state')); this.stateDeparture.setConsumer(this.sub.on('gsx_departure_state')); - this.stateGpu.setConsumer(this.sub.on('gsx_gpu_connected')); + this.gpuConnection.setConsumer(this.sub.on('gsx_gpu_connected')); this.fuelHose.setConsumer(this.sub.on('gsx_fuelhose_connected')); this.refuelStartedByUser.setConsumer(this.sub.on('a32nx_refuel_started_by_user')); this.cargoDoorTarget.setConsumer(this.sub.on(`msfs_interactive_point_goal_${this.getCargoDoorIndex()}`)); @@ -116,8 +119,8 @@ abstract class GsxSync implements Instrument { this.attachedLoader1.setConsumer(this.sub.on('gsx_aircraft_loader_1_attached')); this.attachedLoader2.setConsumer(this.sub.on('gsx_aircraft_loader_2_attached')); - this.stateBoard.sub(this.onBoardingCompleted.bind(this)); - this.stateDeboard.sub(this.onBoardingCompleted.bind(this)); + this.stateBoard.sub(this.onLoadingCompleted.bind(this)); + this.stateDeboard.sub(this.onLoadingCompleted.bind(this)); this.stateDeparture.sub(this.onPushbackActive.bind(this)); this.toggleCargo1.sub(this.onToggleCargo.bind(this)); @@ -128,18 +131,29 @@ abstract class GsxSync implements Instrument { this.fuelHose.sub(this.onFuelHoseConnected.bind(this)); this.refuelStartedByUser.sub(this.onRefuelStartedByUser.bind(this)); - this.stateJetway.sub(this.evaluateGsxPowerSource.bind(this)); - this.stateGpu.sub(this.evaluateGsxPowerSource.bind(this)); - this.stateDeparture.sub(this.evaluateGsxPowerSource.bind(this)); + this.stateJetway.sub(this.onJetwayChange.bind(this)); + this.gpuConnection.sub(this.onGpuConnected.bind(this)); + this.stateDeparture.sub(this.onDepartureChange.bind(this)); + this.stateDeboard.sub(this.onDeboardChange.bind(this)); } public onUpdate(): void { if (this.initialIngameFrame) { - this.onCouatlStarted(SimVar.GetSimVarValueFast('L:FSDT_GSX_COUATL_STARTED', SimVarValueType.Number)); + NXDataStore.subscribeLegacy(SyncServices.POWER, (key, value) => this.onSyncSettingChanged(key, value)); this.initialIngameFrame = false; } } + protected onSyncSettingChanged(key: string, value: string): void { + if (key === SyncServices.POWER) { + if (value === '1') { + this.pub.pub('msfs_power_sync_state', false); + } else { + this.pub.pub('msfs_power_sync_state', true); + } + } + } + protected isSyncEnabled(service: SyncServices): boolean { return NXDataStore.getLegacy(service, '0') === '1'; } @@ -159,7 +173,16 @@ abstract class GsxSync implements Instrument { protected onCouatlStarted(state: number): void { if (state === GsxStates.TRUE) { //gsx-ism: we don't want gsx progressive fuel when the aircraft progressively refuels - SimVar.SetSimVarValue('L:FSDT_GSX_SET_PROGRESS_REFUEL', SimVarValueType.Number, -1); + if (this.isSyncEnabled(SyncServices.FUEL) == true) { + SimVar.SetSimVarValue('L:FSDT_GSX_SET_PROGRESS_REFUEL', SimVarValueType.Number, -1); + } + + //disable sync to msfs + if (this.isSyncEnabled(SyncServices.POWER) == true) { + this.pub.pub('msfs_power_sync_state', false); + } + } else { + this.pub.pub('msfs_power_sync_state', true); } } @@ -174,9 +197,7 @@ abstract class GsxSync implements Instrument { protected abstract setDesiredFuel(desiredFuel: number): void; protected onFuelHoseConnected(hose: number): void { - if (!this.isSyncEnabled(SyncServices.FUEL)) { - return; - } + if (this.isSyncEnabled(SyncServices.FUEL) == false) return; //start De/Refuel when Hose is connected if (hose === GsxStates.CONNECTED && !this.refuelStartedByUser.get() && !this.isRefuelActive) { @@ -210,9 +231,7 @@ abstract class GsxSync implements Instrument { } protected onRefuelStartedByUser(refuel: boolean): void { - if (!this.isSyncEnabled(SyncServices.FUEL)) { - return; - } + if (this.isSyncEnabled(SyncServices.FUEL) == false) return; //defuel has finished - instant refuel to desired to get the truck triggered to leave if (!refuel && this.fuelHose.get() === GsxStates.CONNECTED && this.isDefuel) { @@ -236,58 +255,71 @@ abstract class GsxSync implements Instrument { } } - protected evaluateGsxPowerSource(): void { - if (!this.isSyncEnabled(SyncServices.POWER)) { - return; + protected onJetwayChange(state: number): void { + if ( + this.isSyncEnabled(SyncServices.POWER) == true && + this.lastJetwayState !== state && + state > GsxServiceStates.UNKNOWN && + this.couatlStarted.get() === GsxStates.TRUE + ) { + //sync Power to Jetway Connectionn (as of GSX) + if (state === GsxServiceStates.ACTIVE && !this.isExtPowerAvail()) { + this.setExtPower(true); + } else if ( + state !== GsxServiceStates.ACTIVE && + this.isExtPowerAvail() && + this.gpuConnection.get() === GsxStates.DISCONNECTED + ) { + this.setExtPower(false); + } } - const extAvail = this.isExtPowerAvail(); - const jetwayState = this.stateJetway.get(); - const gpuState = this.stateGpu.get(); - const boardState = this.stateBoard.get(); - const deboardState = this.stateDeboard.get(); - const departureState = this.stateDeparture.get(); - let action = PowerAction.NOOP; - - // external is available (as of internal State) - check if disconnect is needed - if (extAvail) { - // pushback was requested (when boarding not running) - if ( - boardState !== GsxServiceStates.REQUESTED && - boardState !== GsxServiceStates.ACTIVE && - boardState !== GsxServiceStates.COMPLETING && - departureState >= GsxServiceStates.REQUESTED - ) { - action = PowerAction.DISCONNECT; - // pushback is active (if it wasn't catched before) - } else if (departureState === GsxServiceStates.ACTIVE) { - action = PowerAction.DISCONNECT; - // jetway and gpu both not connected + this.lastJetwayState = state; + } + + protected onGpuConnected(state: number): void { + if ( + this.isSyncEnabled(SyncServices.POWER) == true && + this.lastGpuConnection !== state && + this.couatlStarted.get() === GsxStates.TRUE + ) { + //sync Power to GSX GPU Connectionn + if (state === GsxStates.CONNECTED && !this.isExtPowerAvail()) { + this.setExtPower(true); } else if ( - jetwayState >= GsxServiceStates.CALLABLE && - jetwayState <= GsxServiceStates.BYPASSED && - gpuState !== GsxStates.CONNECTED + state === GsxStates.DISCONNECTED && + this.isExtPowerAvail() && + this.stateJetway.get() !== GsxServiceStates.ACTIVE ) { - action = PowerAction.DISCONNECT; + this.setExtPower(false); } - // external is not available - check if connect is needed - } else if ( - //jetway or gpu connected - (jetwayState >= GsxServiceStates.REQUESTED || - gpuState === GsxStates.CONNECTED || - deboardState === GsxServiceStates.ACTIVE) && - departureState < GsxServiceStates.REQUESTED - ) { - action = PowerAction.CONNECT; } + this.lastGpuConnection = state; + } - if (action == PowerAction.CONNECT) { - this.setExtPower(true); - } else if (action == PowerAction.DISCONNECT) { + protected onDepartureChange(state: number): void { + if (this.isSyncEnabled(SyncServices.POWER) == false) return; + + //remove Power when Pushback is requested or gets active + if ((state === GsxServiceStates.REQUESTED || state === GsxServiceStates.ACTIVE) && this.isExtPowerAvail()) { this.setExtPower(false); } } + protected onDeboardChange(state: number): void { + if (this.isSyncEnabled(SyncServices.POWER) == false) return; + + //set Power when Deboard is active and jetway state is not valid (gsx-ism on arrival or User didn't request GSX GPU or set Power via FlyPad) + if ( + state === GsxServiceStates.ACTIVE && + this.stateJetway.get() !== GsxServiceStates.UNVAILABLE && + this.stateJetway.get() === GsxServiceStates.ACTIVE && + !this.isExtPowerAvail() + ) { + this.setExtPower(true); + } + } + protected isExtPowerAvail(): boolean { let state = false; for (let index = 1; index <= this.numberOfGPUs; index++) { @@ -296,14 +328,13 @@ abstract class GsxSync implements Instrument { return state; } - protected abstract setOvhdPushBtn(index: number, state: boolean): void; + protected abstract isPowerConnected(): boolean; protected setExtPower(connect: boolean): void { - for (let index = 1; index <= this.numberOfGPUs; index++) { - SimVar.SetSimVarValue(`L:A32NX_EXT_PWR_AVAIL:${index}`, SimVarValueType.Bool, connect); - if (!connect) { - this.setOvhdPushBtn(index, connect); - } + if (connect && !this.isExtPowerAvail()) { + this.pub.pub('set_ext_power', true); + } else if (!this.isPowerConnected() && this.isExtPowerAvail()) { + this.pub.pub('set_ext_power', false); } } @@ -311,6 +342,10 @@ abstract class GsxSync implements Instrument { return this.cargoDoorTarget.get() !== DoorTarget.CLOSED; } + protected isLoaderAttached(): boolean { + return this.attachedLoader1.get() === GsxStates.CONNECTED || this.attachedLoader2.get() === GsxStates.CONNECTED; + } + protected abstract getCargoDoorIndex(): number; protected setCargoDoor(goal: number, index: number): void { @@ -320,9 +355,9 @@ abstract class GsxSync implements Instrument { SimVar.SetSimVarValue(`A:INTERACTIVE POINT GOAL:${index}`, SimVarValueType.PercentOver100, goal); } - protected onBoardingCompleted(state: number): void { - //just to be safe, check again when boarding was completed - if (state === GsxServiceStates.COMPLETED && this.isCargoOpen()) { + protected onLoadingCompleted(state: number): void { + //check if doors can be closed when boarding/deboarding was completed + if (state === GsxServiceStates.COMPLETED && this.isCargoOpen() && !this.isLoaderAttached()) { this.setCargoDoor(DoorTarget.CLOSED, this.getCargoDoorIndex()); } } @@ -360,6 +395,10 @@ export class GsxSyncA32NX extends GsxSync { super(bus, 1, DEFUEL_DIFF_TARGET_A320); } + protected override initSubscriptions(): void { + super.initSubscriptions(); + } + protected getFob(): number { return SimVar.GetSimVarValueFast('L:A32NX_TOTAL_FUEL_QUANTITY', SimVarValueType.Number); } @@ -373,10 +412,8 @@ export class GsxSyncA32NX extends GsxSync { SimVar.SetSimVarValue('L:A32NX_FUEL_RIGHT_MAIN_DESIRED', SimVarValueType.Number, desiredFuel); } - protected setOvhdPushBtn(index: number, state: boolean): void { - if (index === 1) { - SimVar.SetSimVarValue(`L:A32NX_OVHD_ELEC_EXT_PWR_PB_IS_ON`, SimVarValueType.Bool, state); - } + protected isPowerConnected(): boolean { + return SimVar.GetSimVarValue(`L:A32NX_OVHD_ELEC_EXT_PWR_PB_IS_ON`, SimVarValueType.Bool) == true; } protected getCargoDoorIndex(): number { @@ -401,8 +438,13 @@ export class GsxSyncA380X extends GsxSync { SimVar.SetSimVarValue('L:A32NX_FUEL_DESIRED', 'kilograms', desiredFuel); } - protected setOvhdPushBtn(index: number, state: boolean): void { - SimVar.SetSimVarValue(`L:A32NX_OVHD_ELEC_EXT_PWR_${index}_PB_IS_ON`, SimVarValueType.Bool, state); + protected isPowerConnected(): boolean { + for (let index = 1; index <= this.numberOfGPUs; index++) { + if (SimVar.GetSimVarValue(`L:A32NX_OVHD_ELEC_EXT_PWR_${index}_PB_IS_ON`, SimVarValueType.Bool) == true) { + return true; + } + } + return false; } protected getCargoDoorIndex(): number {