diff --git a/ffb_rhino.py b/ffb_rhino.py index 8587182..4d32795 100644 --- a/ffb_rhino.py +++ b/ffb_rhino.py @@ -32,6 +32,7 @@ import inspect import usb1 from PyQt5.QtCore import QObject, QTimerEvent +from dataclasses import dataclass try: hidapi_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dll', 'hidapi.dll') @@ -395,20 +396,63 @@ def setPeriodic(self, freq, magnitude, direction, duration=0, **kwargs): return self +@dataclass +class DeviceInfo: + interface_number: int + manufacturer_string: str + path: str + product_id: int + product_string: str + release_number: int + serial_number: str + usage: int + usage_page: int + vendor_id: int - class FFBRhino(hid.Device, QObject): - def __init__(self, vid = 0xFFFF, pid=0x2055, serial=None) -> None: + def __init__(self, vid = 0xFFFF, pid=0x2055, serial=None, path=None) -> None: + self.vid = vid self.pid = pid + self.info : DeviceInfo = None + + if not path: + devs = FFBRhino.enumerate(pid) + if serial: + devs = list(filter(lambda x: x.serial_number == serial, devs)) + if not len(devs): + raise hid.HIDException('unable to open device') + path = devs[0].path + self.info = devs[0] + self._in_reports = {} self._effectHandles : List[FFBEffectHandle] = [] - hid.Device.__init__(self, vid, pid, serial) + hid.Device.__init__(self, path=self.info.path) QObject.__init__(self) self.nonblocking = True self.startTimer(1) # start Qt timer to read HID reports every 1ms + @staticmethod + def enumerate(pid=0) -> List[DeviceInfo]: + devs = hid.enumerate(vid=0xffff, pid=pid) + devs = [DeviceInfo(**dev) for dev in devs] + # returns a list of valid VPforce devices + #[{'interface_number': 0, + # 'manufacturer_string': 'VPforce', + # 'path': b'\\\\?\\HID#VID_FFFF&PID_2055&MI_00#9&3450694a&0&0000#{4d1e55b2-f16f' + # b'-11cf-88cb-001111000030}', + # 'product_id': 8277, + # 'product_string': 'Rhino FFB Joystick', + # 'release_number': 516, + # 'serial_number': '3359534E0400004C61001600', + # 'usage': 4, + # 'usage_page': 1, + # 'vendor_id': 65535}] + return list(filter(lambda x: x.interface_number == 0 and x.usage == 4, devs)) + + + # runs on mainThread def timerEvent(self, a0: QTimerEvent) -> None: try: @@ -517,10 +561,14 @@ def __init__(self): def __repr__(self): return f"HapticEffect({self.effect})" + # Open defaut Rhino device, specific devices can be specified using serial or path arguments + # path example: \\\\?\\HID#VID_FFFF&PID_2055&MI_00#9&3450694a&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} + # path can be obtained using FFBRhino.enumerate function @classmethod - def open(cls, vid = 0xFFFF, pid=0x2055, serial=None) -> FFBRhino: + def open(cls, vid = 0xFFFF, pid=0x2055, serial=None, path=None) -> FFBRhino: logging.info(f"Open Rhino HID {vid:04X}:{pid:04X}") - cls.device = FFBRhino(vid, pid, serial) + cls.device = FFBRhino(vid, pid, serial, path) + logging.info(f"Successfully opened HID '{cls.device.info.path.decode('utf-8')}'") return cls.device @@ -632,6 +680,14 @@ def __del__(self): import utils import random + devs = FFBRhino.enumerate() + print() + for dev in devs: + print(f"Found {dev['product_string']} with serial {dev['serial_number']}") + print(f"-- path {dev['path']}") + print() + exit() + d = FFBRhino(0xffff, 0x2055) d.resetEffects() print(d.get_firmware_version()) diff --git a/main.py b/main.py index ac0ce46..590f047 100644 --- a/main.py +++ b/main.py @@ -33,7 +33,7 @@ import subprocess import traceback -from ffb_rhino import HapticEffect +from ffb_rhino import HapticEffect, FFBRhino from configobj import ConfigObj from settingsmanager import * @@ -5004,6 +5004,18 @@ def main(): except: pass + devs = FFBRhino.enumerate() + logging.info("Available Rhino Devices:") + logging.info("-------") + for dev in devs: + + logging.info(f"* {dev.vendor_id:04X}:{dev.product_id:04X} - {dev.product_string} - {dev.serial_number}") + logging.info(f"* Path:{dev.path}") + logging.info(f"*") + + logging.info("-------") + + try: dev = HapticEffect.open(vid_pid[0], vid_pid[1]) # try to open RHINO if args.reset: