Last active
November 7, 2024 07:48
-
-
Save Arlen22/40fb50e3b4945efd4db0b3b9cda2f8c6 to your computer and use it in GitHub Desktop.
3dx SpacePilot vJoy python script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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