Hey everyone,
I've built a custom script to inject fuel directly into the Fenix A320 fuel tanks because the native EFB lacks a simple "Refuel Only" option (without calling any other gsx service).
The good news is that GSX recognizes the fueling request, calls the truck, connects the hoses, and starts the refuel animation/sound.
The Problem: The GSX fueling animation terminates prematurely. For instance, if I'm going from 3000kg to 8000kg, the truck and animation stop around 5000kg, even though my custom script continues injecting fuel until the 8000kg target is reached.
Which specific GSX variables or SimVars do I need to manipulate within my custom script to accurately synchronize the fueling progress and duration with the GSX animation?
Alternatively, does GSX expose an API command or variable where I could simply input the desired target fuel load (e.g., Refuel_To_Target(8000kg)) and let GSX handle both the visual animation and the SimVar injection itself?
When GSX refuels via the EFB, the ECAM fuel quantity updates smoothly (e.g., in 20-30kg increments). When my script runs (even at a low fuel flow of 15kg/sec), the ECAM counter jumps up in much larger 100+ kg steps.
What are the correct SimVars that GSX uses to fill the Fenix tanks to ensure the ECAM display updates smoothly?
I've tried numerous combinations of GSX variables without success. Any insights into the required variables or the necessary synchronization logic would be highly appreciated!
The Script
import os
import sys
import time
import math
# --- Path Correction ---
HERE = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.abspath(os.path.join(HERE, '..'))
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
import command_executor
from core.services import simconnect_service
# ==============================================================================
# --- SCRIPT CONFIGURATION ---
# ==============================================================================
# Set to True to call the GSX fuel truck and wait for the hose animation.
# Set to False to skip GSX and start refueling immediately.
USE_GSX = True
# --- Fuel Conversion Constant ---
# Based on standard Jet A1 density. Gallons are required for Fenix A-Vars.
KG_TO_GAL_FACTOR = 3.039 # Approx. KG per US Gallon
# --- Tweakable Parameters ---
REFUEL_RATE_KG_SEC = 15 # Kilograms per second loading rate
UPDATE_INTERVAL_SEC = 1.0 # Seconds between updates for smoother ECAM display and faster GSX state monitoring
# ==============================================================================
def perform_flow(vr, active_profile_path=None):
"""
Progressively adds fuel until the target is reached.
Can optionally integrate with GSX to call the truck and sync with animations.
"""
print("\n--- Progressive Refuel Process ---")
if USE_GSX:
print("--- GSX Integration: ENABLED ---")
else:
print("--- GSX Integration: DISABLED ---")
print("[SETUP] Adding subscriptions...")
command_executor.add_subscription("(L:N_FUEL_PRESEL, Number)")
command_executor.add_subscription("(A:FUEL TANK LEFT MAIN QUANTITY, Gallons)")
command_executor.add_subscription("(A:FUEL TANK RIGHT MAIN QUANTITY, Gallons)")
if USE_GSX:
command_executor.add_subscription("(L:FSDT_GSX_REFUELING_STATE, Number)")
command_executor.add_subscription("(L:FSDT_GSX_FUELHOSE_CONNECTED, Number)")
time.sleep(1.0)
try:
if USE_GSX:
# === PART 1: SILENTLY CALL THE FUEL TRUCK ===
# CRITICAL: Disable GSX internal logic to cede full control to this script.
# This prevents GSX from prematurely ending the service.
print("[GSX_CTL] Disabling internal GSX progressive refueling logic...")
simconnect_service.set_lvar(vr, "FSDT_GSX_SET_DETECT_CUST_REFUEL", 1)
simconnect_service.set_lvar(vr, "FSDT_GSX_SET_PROGRESS_REFUEL", 1)
time.sleep(0.5) # Allow settings to register before calling menu
print("\n[GSX] Requesting Refuel via GSX L-Vars (Silent)...")
simconnect_service.set_lvar(vr, "FSDT_GSX_MENU_OPEN", 1)
time.sleep(1.5)
simconnect_service.set_lvar(vr, "FSDT_GSX_MENU_CHOICE", 2) # 2 = Refueling
time.sleep(1.0)
simconnect_service.set_lvar(vr, "FSDT_GSX_MENU_CHOICE", 0) # Close menu
# --- Wait for a valid fuel target from the user ---
target_fuel_kg = 0.0
while True:
target_fuel_kg_raw = simconnect_service._direct_get_lvar(vr, "N_FUEL_PRESEL")
if target_fuel_kg_raw is not None and target_fuel_kg_raw > 0:
temp_target = float(target_fuel_kg_raw) * 1000.0 if target_fuel_kg_raw < 100 else float(target_fuel_kg_raw)
if temp_target > 100:
target_fuel_kg = temp_target
print(f" Target Fuel Found: {target_fuel_kg:.0f} KG")
if USE_GSX:
# Sync GSX counter max value with the AMOUNT TO FUEL (Delta)
# Not the total amount!
current_fuel_kg = (simconnect_service.get_sim_variable("L:N_FUEL_TOTAL_QTY") or 0.0) * 1000.0
amount_to_refuel = target_fuel_kg - current_fuel_kg
if amount_to_refuel < 0: amount_to_refuel = 0
simconnect_service.set_lvar(vr, "FSDT_GSX_FUEL_COUNTER_MAX", amount_to_refuel)
break
print("Waiting for valid Fuel Pre-selection (>100kg)...")
time.sleep(1.0)
if USE_GSX:
# === PART 2: WAIT FOR GSX TO BE READY ===
print("\n[GSX] Waiting for GSX service to become active (State: 5, Hose: 1)...")
service_ready = False
wait_start = time.time()
last_gsx_state = None
last_hose_state = None
while time.time() - wait_start < 360:
gsx_state = simconnect_service.get_sim_variable("L:FSDT_GSX_REFUELING_STATE")
hose_state = simconnect_service.get_sim_variable("L:FSDT_GSX_FUELHOSE_CONNECTED")
# Only log when state changes
if gsx_state != last_gsx_state or hose_state != last_hose_state:
print(f" > GSX State: {gsx_state}, Hose Connected: {hose_state}")
last_gsx_state = gsx_state
last_hose_state = hose_state
if gsx_state == 5 and hose_state == 1:
print("\n✅ [GSX] Service is active and hose is connected. Starting fuel loading.")
service_ready = True
break
time.sleep(1.0)
if not service_ready:
print("\n❌ [FAILURE] Timeout waiting for GSX service to become active.")
return
# ====================================================================
# --- CORE REFUELING LOGIC (Virtual Accumulator Method) ---
# ====================================================================
# 1. Get current fuel state from the sim ONCE.
initial_left_gal = simconnect_service._direct_get_avar(vr, "FUEL TANK LEFT MAIN QUANTITY", "Gallons")
initial_right_gal = simconnect_service._direct_get_avar(vr, "FUEL TANK RIGHT MAIN QUANTITY", "Gallons")
if initial_left_gal is None or initial_right_gal is None:
print(" [FAILURE] Could not read initial fuel tank quantities. Aborting.")
return
# 2. Initialize VIRTUAL accumulators. These are now the source of truth for the loop.
virtual_left_gal = initial_left_gal
virtual_right_gal = initial_right_gal
virtual_total_kg = (virtual_left_gal + virtual_right_gal) * KG_TO_GAL_FACTOR
# Check if we are defueling or refueling
if virtual_total_kg > target_fuel_kg:
print(f" > Defueling not supported by this script. Aborting.")
return
print(f"\n[REFUEL] Starting State: {virtual_total_kg:.0f} KG | Target: {target_fuel_kg:.0f} KG")
last_loop_time = time.time()
# 3. Loop based on our VIRTUAL state, not the sim's state.
while virtual_total_kg < target_fuel_kg:
current_time = time.time()
dt = current_time - last_loop_time
last_loop_time = current_time
# Calculate how much fuel to add this cycle
kg_to_add_this_cycle = REFUEL_RATE_KG_SEC * dt
# --- ACCURACY FIX: Prevent overshoot on the final step ---
remaining_kg = target_fuel_kg - virtual_total_kg
if kg_to_add_this_cycle > remaining_kg:
kg_to_add_this_cycle = remaining_kg
# 4. Increment VIRTUAL accumulators
gal_to_add_per_tank = (kg_to_add_this_cycle / 2.0) / KG_TO_GAL_FACTOR
virtual_left_gal += gal_to_add_per_tank
virtual_right_gal += gal_to_add_per_tank
virtual_total_kg += kg_to_add_this_cycle
# 5. Force-feed the new VIRTUAL state to the sim.
simconnect_service.execute_rpn(vr, f"{virtual_left_gal} (>A:FUEL TANK LEFT MAIN QUANTITY, Gallons)")
simconnect_service.execute_rpn(vr, f"{virtual_right_gal} (>A:FUEL TANK RIGHT MAIN QUANTITY, Gallons)")
log_msg = f" > [SIMULATING] Current: {virtual_total_kg:.0f} KG / Target: {target_fuel_kg:.0f} KG"
# Optional check for GSX state if enabled
if USE_GSX:
gsx_state = simconnect_service.get_sim_variable("L:FSDT_GSX_REFUELING_STATE")
hose_state = simconnect_service.get_sim_variable("L:FSDT_GSX_FUELHOSE_CONNECTED")
log_msg += f" | GSX State: {gsx_state}"
# The main 'while' loop already checks if the target is met.
# Therefore, if this code is running, we are NOT at the target yet.
# Any GSX state other than "actively refueling" is premature and must be overridden.
if gsx_state != 5 or hose_state != 1:
print(f" > [GSX_OVERRIDE] GSX state not active (State: {gsx_state}, Hose: {hose_state}). Forcing continue.")
simconnect_service.set_lvar(vr, "FSDT_GSX_REFUELING_STATE", 5) # Force back to "in progress"
simconnect_service.set_lvar(vr, "FSDT_GSX_FUELHOSE_CONNECTED", 1) # Force hose to "connected"
print(log_msg)
time.sleep(UPDATE_INTERVAL_SEC)
print(f" [SUCCESS] Target Fuel Level Reached.")
if USE_GSX:
print("[GSX] Fuel target met. Releasing GSX service and stopping animation.")
simconnect_service.set_lvar(vr, "FSDT_GSX_REFUELING_STATE", 6) # Command GSX to "Completed"
# Finalization: Set the exact value one last time to guarantee precision
target_liters_per_tank = (target_fuel_kg / 2.0) / KG_TO_GAL_FACTOR
simconnect_service.execute_rpn(vr, f"{target_liters_per_tank} (>A:FUEL TANK LEFT MAIN QUANTITY, Gallons)")
simconnect_service.execute_rpn(vr, f"{target_liters_per_tank} (>A:FUEL TANK RIGHT MAIN QUANTITY, Gallons)")
print(f" > Final precise fuel set to {target_fuel_kg:.0f} KG.")
except KeyboardInterrupt:
print("\nProcess interrupted by user.")
except Exception as e:
print(f"\n[ERROR] An unexpected error occurred: {e}")
def main():
if not command_executor.start_manager():
print("[FATAL] Could not connect to the simulator. Exiting.")
return
vr = command_executor.get_vr_connection()
if not vr:
print("[FATAL] Failed to get connection object. Exiting.")
command_executor.stop_manager()
return
try:
perform_flow(vr)
except Exception as e:
print(f"An error occurred during the flow: {e}")
finally:
print("\n[TEARDOWN] Disconnecting from the simulator.")
command_executor.stop_manager()
print("--- Test Complete ---")
if __name__ == "__main__":
main()