Skip to content

Instantly share code, notes, and snippets.

@Arlen22
Last active November 7, 2024 07:48
Show Gist options
  • Save Arlen22/40fb50e3b4945efd4db0b3b9cda2f8c6 to your computer and use it in GitHub Desktop.
Save Arlen22/40fb50e3b4945efd4db0b3b9cda2f8c6 to your computer and use it in GitHub Desktop.
3dx SpacePilot vJoy python script
I think it only requires pywinusb, which can be installed via pip.
Credit to ChatGPT for filling in a lot of the blanks left by everyone's spotty python documentation.
These links were my starting point, but I don't think the vjoy part was from any of them.
https://github.com/JakubAndrysek/PySpaceMouse
https://forum.3dconnexion.com/viewtopic.php?t=3721
https://forums.cast-soft.com/index.php?threads/using-3dconnexion-mouse-with-r34-update-issue-with-3dxware-v10-2-2.432/
https://github.com/brianpeiris/spacepad
https://github.com/libusb/hidapi/releases
https://handmade.network/forums/t/7638-how_to_read_joystick_data_from_raw_input
import pywinusb.hid as hid
import time
import ctypes
import struct, time
from enum import Enum
CONST_DLL_VJOY = "C:\\Program Files\\vJoy\\x64\\vJoyInterface.dll"
class vJoy(object):
def __init__(self, reference = 1):
self.handle = None
self.dll = ctypes.CDLL( CONST_DLL_VJOY )
self.reference = reference
self.acquired = False
def open(self):
if self.dll.AcquireVJD( self.reference ):
self.acquired = True
return True
return False
def close(self):
if self.dll.RelinquishVJD( self.reference ):
self.acquired = False
return True
return False
def update(self, joystickPosition):
if self.dll.UpdateVJD( self.reference, joystickPosition ):
return True
return False
def generateJoystickPosition(
reference,
wThrottle = 0, wRudder = 0, wAileron = 0,
wAxisX = 0, wAxisY = 0, wAxisZ = 0,
wAxisXRot = 0, wAxisYRot = 0, wAxisZRot = 0,
wSlider = 0, wDial = 0, wWheel = 0,
wAxisVX = 0, wAxisVY = 0, wAxisVZ = 0,
wAxisVBRX = 0, wAxisVBRY = 0, wAxisVBRZ = 0,
lButtons = 0, bHats = 0, bHatsEx1 = 0, bHatsEx2 = 0, bHatsEx3 = 0):
"""
typedef struct _JOYSTICK_POSITION
{
BYTE bDevice; // Index of device. 1-based
LONG wThrottle;
LONG wRudder;
LONG wAileron;
LONG wAxisX;
LONG wAxisY;
LONG wAxisZ;
LONG wAxisXRot;
LONG wAxisYRot;
LONG wAxisZRot;
LONG wSlider;
LONG wDial;
LONG wWheel;
LONG wAxisVX;
LONG wAxisVY;
LONG wAxisVZ;
LONG wAxisVBRX;
LONG wAxisVBRY;
LONG wAxisVBRZ;
LONG lButtons; // 32 buttons: 0x00000001 means button1 is pressed, 0x80000000 -> button32 is pressed
DWORD bHats; // Lower 4 bits: HAT switch or 16-bit of continuous HAT switch
DWORD bHatsEx1; // 16-bit of continuous HAT switch
DWORD bHatsEx2; // 16-bit of continuous HAT switch
DWORD bHatsEx3; // 16-bit of continuous HAT switch
} JOYSTICK_POSITION, *PJOYSTICK_POSITION;
"""
joyPosFormat = "BlllllllllllllllllllIIII"
pos = struct.pack( joyPosFormat, reference, wThrottle, wRudder,
wAileron, wAxisX, wAxisY, wAxisZ, wAxisXRot, wAxisYRot,
wAxisZRot, wSlider, wDial, wWheel, wAxisVX, wAxisVY, wAxisVZ,
wAxisVBRX, wAxisVBRY, wAxisVBRZ, lButtons, bHats, bHatsEx1, bHatsEx2, bHatsEx3 )
return pos
class HIDUsageAxes(Enum):
X = 0x30
Y = 0x31
Z = 0x32
RX = 0x33
RY = 0x34
RZ = 0x35
SL0 = 0x36
SL1 = 0x37
WHL = 0x38
POV = 0x39
vj = vJoy()
max_constant = 1.5
def map_value(value):
center = 2**14
scale_factor = center / 512
return round(max(-center, min(center, value * scale_factor))) + center
space_navigator_data = {
'wAxisX': 0,
'wAxisY': 0,
'wAxisZ': 0,
'wAxisXRot': 0,
'wAxisYRot': 0,
'wAxisZRot': 0,
'lButtons': 0
}
def sample_handler(data):
"""Callback function to handle incoming data from the HID device."""
# print(f"Received data: {data}")
# print(type(data))
if(data[0] == 1):
x, y, z = struct.unpack('<hhh', bytearray(data[1:7]))
# x = int.from_bytes(data[1:2], byteorder='little', signed=True)
# y = int.from_bytes(data[3:4], byteorder='little', signed=True)
# z = int.from_bytes(data[5:6], byteorder='little', signed=True)
space_navigator_data['wAxisX'] = map_value(x)
space_navigator_data['wAxisY'] = map_value(y)
space_navigator_data['wAxisZ'] = map_value(z)
if(data[0] == 2):
x, y, z = struct.unpack('<hhh', bytearray(data[1:7]))
# x = int.from_bytes(data[1:2], byteorder='little', signed=True)
# y = int.from_bytes(data[3:4], byteorder='little', signed=True)
# z = int.from_bytes(data[5:6], byteorder='little', signed=True)
space_navigator_data['wAxisXRot'] = map_value(x)
space_navigator_data['wAxisYRot'] = map_value(y)
space_navigator_data['wAxisZRot'] = map_value(z)
if(data[0] == 3):
space_navigator_data['lButtons'], = struct.unpack('<I', bytes(data[1:5]))
print(space_navigator_data)
joystick = generateJoystickPosition(vj.reference, **space_navigator_data)
vj.update(joystick)
def main():
"""List all connected HID devices and their details."""
all_hid_devices = hid.HidDeviceFilter().get_devices()
if not all_hid_devices:
print("No HID devices found")
return []
devices_info = []
# print("Connected HID Devices:")
for index, device in enumerate(all_hid_devices):
vendor_id = device.vendor_id
product_id = device.product_id
product_name = device.product_name
serial_number = device.serial_number
# print(f"Device {index}:")
# print(f" Vendor ID : {hex(vendor_id)}")
# print(f" Product ID : {hex(product_id)}")
# print(f" Product Name : {product_name}")
# print(f" Serial Number: {serial_number}\n")
devices_info.append({
'index': index,
'vendor_id': vendor_id,
'product_id': product_id,
'device': device
})
if not devices_info:
return
# Replace these with your device's Vendor ID and Product ID
TARGET_VENDOR_ID = 0x46d
TARGET_PRODUCT_ID = 0xc625
# Filter devices by VID and PID
target_devices = [
info['device'] for info in devices_info
if info['vendor_id'] == TARGET_VENDOR_ID and info['product_id'] == TARGET_PRODUCT_ID
]
if not target_devices:
print(f"No device found with Vendor ID {hex(TARGET_VENDOR_ID)} and Product ID {hex(TARGET_PRODUCT_ID)}")
return
else:
target_device = target_devices[0]
print(f"Selected Device: {target_device.product_name}")
try:
target_device.open()
print("Device opened successfully")
vj.open()
# Set the custom handler to receive raw data
target_device.set_raw_data_handler(sample_handler)
# Keep the script running to receive data
print("Listening for data... Press Ctrl+C to exit")
while True:
time.sleep(0.5)
except Exception as e:
print(f"Error: {e}")
finally:
target_device.close()
vj.close()
print("Device closed")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment