#!/usr/bin/env python3 """ Fake Gaming Wheel USB Device Emulator Creates a virtual USB HID gaming wheel device for testing purposes. Simulates a Logitech G29 Racing Wheel. Requirements: - Linux with uhid kernel module - Root/sudo access to access /dev/uhid - Python 3.6+ Usage: sudo python3 fake_wheel.py """ import os import sys import struct import time import select import signal # UHID event types UHID_CREATE2 = 11 UHID_DESTROY = 1 UHID_INPUT2 = 12 UHID_OUTPUT = 6 UHID_GET_REPORT = 9 UHID_SET_REPORT = 13 class FakeGamingWheel: def __init__(self): self.running = True self.fd = None # HID Report Descriptor for a gaming wheel with: # - Steering wheel axis (X axis) # - Throttle (Y axis) # - Brake (Z axis) # - Multiple buttons self.hid_descriptor = bytes([ 0x05, 0x01, # Usage Page (Generic Desktop) 0x09, 0x04, # Usage (Joystick) 0xA1, 0x01, # Collection (Application) 0x09, 0x01, # Usage (Pointer) 0xA1, 0x00, # Collection (Physical) # Steering wheel (X axis) - 16 bit 0x09, 0x30, # Usage (X) 0x15, 0x00, # Logical Minimum (0) 0x27, 0xFF, 0xFF, 0x00, 0x00, # Logical Maximum (65535) 0x35, 0x00, # Physical Minimum (0) 0x47, 0xFF, 0xFF, 0x00, 0x00, # Physical Maximum (65535) 0x75, 0x10, # Report Size (16 bits) 0x95, 0x01, # Report Count (1) 0x81, 0x02, # Input (Data, Variable, Absolute) # Throttle (Y axis) - 16 bit 0x09, 0x31, # Usage (Y) 0x15, 0x00, # Logical Minimum (0) 0x27, 0xFF, 0xFF, 0x00, 0x00, # Logical Maximum (65535) 0x75, 0x10, # Report Size (16 bits) 0x95, 0x01, # Report Count (1) 0x81, 0x02, # Input (Data, Variable, Absolute) # Brake (Z axis) - 16 bit 0x09, 0x32, # Usage (Z) 0x15, 0x00, # Logical Minimum (0) 0x27, 0xFF, 0xFF, 0x00, 0x00, # Logical Maximum (65535) 0x75, 0x10, # Report Size (16 bits) 0x95, 0x01, # Report Count (1) 0x81, 0x02, # Input (Data, Variable, Absolute) # Buttons (16 buttons) 0x05, 0x09, # Usage Page (Button) 0x19, 0x01, # Usage Minimum (Button 1) 0x29, 0x10, # Usage Maximum (Button 16) 0x15, 0x00, # Logical Minimum (0) 0x25, 0x01, # Logical Maximum (1) 0x75, 0x01, # Report Size (1 bit) 0x95, 0x10, # Report Count (16) 0x81, 0x02, # Input (Data, Variable, Absolute) 0xC0, # End Collection 0xC0 # End Collection ]) # Current state self.steering = 32768 # Center position (0-65535) self.throttle = 0 # No throttle self.brake = 0 # No brake self.buttons = 0 # No buttons pressed def handle_uhid_event(self): """Read and handle events from uhid.""" try: # Use select to check if data is available (non-blocking) readable, _, _ = select.select([self.fd], [], [], 0) if not readable: return data = os.read(self.fd, 4096) if len(data) < 4: return event_type = struct.unpack('