diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 4a011758012..9cf0837fd12 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -79,6 +79,8 @@ 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. [A32NX/FMS] Add pilot entry ETT (Estimated Takeoff Time) functionality - @BravoMike99 (bruno_pt99) +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 new file mode 100644 index 00000000000..7c3d750a0e9 --- /dev/null +++ b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/gsx_handler.py @@ -0,0 +1,32 @@ +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)" +fuelSync = LVariable("A32NX_GSX_FUEL_SYNC_ENABLED") + +def onJetwayConnected(self): + pass + +def onJetwayDisconnected(self): + pass + +def setExtPowerAvail(self): + pass + +def customTruckRequestedMessage(self): + 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): + if fuelSync.getValue() != 0: + return "Fuel Truck in Position - Refuel starts automatically" + else: + return self._super_customTruckInPositionMessage() 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..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 (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-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..2591fadae7e --- /dev/null +++ b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/gsx_handler.py @@ -0,0 +1,32 @@ +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)" +fuelSync = LVariable("A32NX_GSX_FUEL_SYNC_ENABLED") + +def onJetwayConnected(self): + pass + +def onJetwayDisconnected(self): + pass + +def setExtPowerAvail(self): + pass + +def customTruckRequestedMessage(self): + 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): + 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/model/A380_EXTERIOR.xml b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/A380_EXTERIOR.xml index b19991f374b..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: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-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/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 b2aa3e3c3ac..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,12 +27,14 @@ 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 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 { @@ -46,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; @@ -61,12 +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, 1); + protected readonly gpuConnection = ConsumerSubject.create(null, 0); + protected lastJetwayState = 0; + protected lastGpuConnection = 0; protected isRefuelActive = false; protected isDefuel = false; protected readonly fuelTimer = new DebounceTimer(); @@ -75,6 +80,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,26 +108,52 @@ 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.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()}`)); + 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.onLoadingCompleted.bind(this)); + this.stateDeboard.sub(this.onLoadingCompleted.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)); - 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'; } @@ -137,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); } } @@ -152,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) { @@ -188,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) { @@ -214,54 +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 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 && - departureState >= GsxServiceStates.REQUESTED - ) { - action = PowerAction.DISCONNECT; - // pushback is active (in 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 !== GsxServiceStates.ACTIVE + 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 === 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++) { @@ -270,72 +328,75 @@ 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); } } -} -export class GsxSyncA32NX extends GsxSync { - constructor(bus: EventBus) { - super(bus, 1, DEFUEL_DIFF_TARGET_A320); + protected isCargoOpen(): boolean { + return this.cargoDoorTarget.get() !== DoorTarget.CLOSED; } - protected getFob(): number { - return SimVar.GetSimVarValueFast('L:A32NX_TOTAL_FUEL_QUANTITY', SimVarValueType.Number); + protected isLoaderAttached(): boolean { + return this.attachedLoader1.get() === GsxStates.CONNECTED || this.attachedLoader2.get() === GsxStates.CONNECTED; } - protected getDesiredFuel(): number { - return SimVar.GetSimVarValueFast('L:A32NX_FUEL_LEFT_MAIN_DESIRED', SimVarValueType.Number); + 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 setDesiredFuel(desiredFuel: number): void { - SimVar.SetSimVarValue('L:A32NX_FUEL_LEFT_MAIN_DESIRED', SimVarValueType.Number, desiredFuel); - SimVar.SetSimVarValue('L:A32NX_FUEL_RIGHT_MAIN_DESIRED', SimVarValueType.Number, desiredFuel); + 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()); + } + } + + 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 setOvhdPushBtn(index: number, state: boolean): void { - if (index === 1) { - SimVar.SetSimVarValue(`L:A32NX_OVHD_ELEC_EXT_PWR_PB_IS_ON`, SimVarValueType.Bool, state); + 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()); } } -} -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); + 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 { constructor(bus: EventBus) { - super(bus, 4, DEFUEL_DIFF_TARGET_A380); + super(bus, 1, DEFUEL_DIFF_TARGET_A320); } - protected initSubscriptions(): void { + protected override 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 { @@ -343,64 +404,50 @@ export class GsxSyncA380X extends GsxSync { } protected getDesiredFuel(): number { - return SimVar.GetSimVarValueFast('L:A32NX_FUEL_DESIRED', 'kilograms'); + return SimVar.GetSimVarValueFast('L:A32NX_FUEL_LEFT_MAIN_DESIRED', SimVarValueType.Number); } protected setDesiredFuel(desiredFuel: number): void { - SimVar.SetSimVarValue('L:A32NX_FUEL_DESIRED', 'kilograms', desiredFuel); + SimVar.SetSimVarValue('L:A32NX_FUEL_LEFT_MAIN_DESIRED', SimVarValueType.Number, desiredFuel); + SimVar.SetSimVarValue('L:A32NX_FUEL_RIGHT_MAIN_DESIRED', SimVarValueType.Number, 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 { + return SimVar.GetSimVarValue(`L:A32NX_OVHD_ELEC_EXT_PWR_PB_IS_ON`, SimVarValueType.Bool) == true; } - 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 getCargoDoorIndex(): number { + return 5; } +} - 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); - } +export class GsxSyncA380X extends GsxSync { + constructor(bus: EventBus) { + super(bus, 4, DEFUEL_DIFF_TARGET_A380); } - protected onCargoBoardCompleted(percent: number): void { - this.startCargoTimer(percent, DELAY_CARGO_CLOSE); + protected getFob(): number { + return SimVar.GetSimVarValueFast('L:A32NX_TOTAL_FUEL_QUANTITY', SimVarValueType.Number); } - protected onCargoDeboardCompleted(percent: number): void { - this.startCargoTimer(percent, DELAY_CARGO_CLOSE); + protected getDesiredFuel(): number { + return SimVar.GetSimVarValueFast('L:A32NX_FUEL_DESIRED', 'kilograms'); } - 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 setDesiredFuel(desiredFuel: number): void { + SimVar.SetSimVarValue('L:A32NX_FUEL_DESIRED', 'kilograms', desiredFuel); } - 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 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 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..30b755a440f 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 */ @@ -22,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% */ @@ -36,11 +42,14 @@ 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 }], ['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 }], 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();