1. Problem Description
In a Linux environment, when using an Xbox Series X/S or Xbox One controller to operate X-Plane 12, you may encounter a classic "rudder deadlock" issue:
- Symptom: The default
xpaddriver recognizes LT and RT as two independent absolute axes ranging from 0 to 100%. - Conflict: X-Plane's Yaw axis expects a single centered axis (ranging from -1 to +1). If you map LT to Rudder Left and RT to Rudder Right, it may work acceptably on smaller aircraft like the Cessna, but on advanced plugin aircraft such as the ZIBO 737, the rudder typically gets stuck on one side and cannot be centered or operated at all.
2. Why not use xboxdrv?
The traditional solution is to use xboxdrv --trigger-as-zaxis. However, for modern controllers (especially those connected via Bluetooth or newer Series X models):
xboxdrvhas not been updated in 15 years and fails to recognize USB Endpoints on newer controllers, frequently throwing the errorcouldn't find matching endpoint.xboxdrvstarts unstably and often causes controller permission conflicts.
3. AnduinOS Best Practices: Python Axis Bridging Logic
We manually intercept LT/RT signals using Python's evdev library and create a virtual gamepad device at the lower level that never disconnects.
Core Logic:
- Calculation formula:
Merged_Axis = (RT_Value - LT_Value) - Range mapping: Maps the original 0–1023 range to the standard 16-bit joystick range (-32768 to 32767), providing higher control surface precision.
- Persistence: The virtual device is created only once when the service starts, so X-Plane does not lose calibration information even when a physical controller is plugged in or unplugged.
4. Implementation Plan
A. Write the bridge script ~/xbox_rudder_bridge.py
#!/usr/bin/env python3
import evdev
from evdev import ecodes, UInput, AbsInfo
import time
def find_controller():
try:
devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
for device in devices:
name = device.name
# 匹配主控制器,排除鼠标/键盘模拟节点
if "Xbox" in name and not any(x in name for x in ["Mouse", "Keyboard", "Consumer", "Virtual"]):
return device
except Exception:
pass
return None
def main():
# 创建高精度持久化虚拟设备
cap = {
ecodes.EV_ABS: [
(ecodes.ABS_RZ, AbsInfo(value=0, min=-32768, max=32767, fuzz=0, flat=0, resolution=0))
],
ecodes.EV_KEY: [ecodes.BTN_JOYSTICK]
}
vdev = UInput(cap, name="Anduin Virtual Rudder", vendor=0x1234, product=0x5678)
while True:
device = find_controller()
if not device:
time.sleep(5)
continue
try:
lt_val, rt_val = 0, 0
for event in device.read_loop():
if event.type == ecodes.EV_ABS:
# 适配不同连接模式下的触发器代码
if event.code in [ecodes.ABS_Z, ecodes.ABS_BRAKE]:
lt_val = event.value
elif event.code in [ecodes.ABS_RZ, ecodes.ABS_GAS]:
rt_val = event.value
# 映射并输出
merged_val = (rt_val - lt_val) * 32
vdev.write(ecodes.EV_ABS, ecodes.ABS_RZ, merged_val)
vdev.syn()
except (OSError, FileNotFoundError):
time.sleep(2)
B. Configure the Systemd Service
Create /etc/systemd/system/xbox-rudder.service:
[Unit]
Description=Python Xbox Rudder Trigger Bridge
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 -u /home/anduin/xbox_rudder_bridge.py
Restart=always
User=root
[Install]
WantedBy=multi-user.target
C. Resolving Permission and Identification Issues
Create the udev rule /etc/udev/rules.d/99-xbox-rudder.rules:
SUBSYSTEM=="input", ATTRS{name}=="Anduin Virtual Rudder", ENV{ID_INPUT}="1", ENV{ID_INPUT_JOYSTICK}="1", MODE="0666"
5. X-Plane 12 Setup Guide
- Restart the service:
sudo systemctl restart xbox-rudder.service. - Go to X-Plane settings, where you will see a new device called Anduin Virtual Rudder.
- Bind its only axis to Yaw.
- In the original Xbox controller settings, remove all bindings related to Rudder or Yaw.
- Calibrate once, and no further calibration is needed even after restarting the computer.
By Anduin, 2026. Wishing all Linux aviation enthusiasts safe takeoffs and landings.
Comments (0)
No comments yet. Be the first to leave one!