Как заставить USB-контроллер / геймпад работать с python

#python #bluetooth #pygame #serial-port #socat

#python #Windows #pygame #pyglet #psychopy

Вопрос:

У меня есть USB-контроллер, с которого я пытаюсь получить входные данные, игровая панель Microsoft® SideWinder® Plug amp; Play. У меня возникают трудности, пытаясь понять, как правильно получать его входные данные. К сожалению, я не могу использовать pygame, поскольку для получения входных данных требуется окно, но я должен сгенерировать окно pyglet (через PsychoPy) для запуска моей программы. С pygame он может подключаться и отображать состояние кнопок, но он не может получать входные данные без создания окна. Я пытался искать другие библиотеки, но все, с чем я столкнулся, это входные данные, которые несовместимы с моим контроллером (не обнаруживает устройство после установки). Сам контроллер работает так, как я тестировал его с помощью онлайн-тестера геймпада. API джойстика PsychoPy в настоящее время сломан и не работает, так что там тоже не повезло.

Я действительно надеялся, что у кого-нибудь есть совет о том, как получать входные данные с моего контроллера / геймпада в мою программу?

Комментарии:

1. Вы застряли в использовании PyGlet? Я нашел это: entitycrisis.blogspot.com/2008/02/pyglet-joystick.html

2. К сожалению, я использую компьютер с Windows, и, похоже, он работает только для Linux, спасибо!

3. «он не может получать входные данные без создания окна» — конечно. В Windows вам понадобится цикл сообщений окна для получения входных данных.

Ответ №1:

Для Windows вы можете использовать WINMM.dll напрямую.

Используйте библиотеку ctypes для загрузки dll (см. раздел Загрузка общих библиотек). Используется ctypes.WINFUNCTYPE для создания прототипов функции:

 import ctypes

winmmdll = ctypes.WinDLL('winmm.dll')

# [joyGetNumDevs](https://learn.microsoft.com/en-us/windows/win32/api/joystickapi/nf-joystickapi-joygetnumdevs)
"""
UINT joyGetNumDevs();
"""
joyGetNumDevs_proto = ctypes.WINFUNCTYPE(ctypes.c_uint)
joyGetNumDevs_func  = joyGetNumDevs_proto(("joyGetNumDevs", winmmdll))

# [joyGetDevCaps](https://learn.microsoft.com/en-us/windows/win32/api/joystickapi/nf-joystickapi-joygetdevcaps)
"""
MMRESULT joyGetDevCaps(UINT uJoyID, LPJOYCAPS pjc, UINT cbjc);

32 bit: joyGetDevCapsA
64 bit: joyGetDevCapsW

sizeof(JOYCAPS): 728
"""
joyGetDevCaps_proto = ctypes.WINFUNCTYPE(ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p, ctypes.c_uint)
joyGetDevCaps_param = (1, "uJoyID", 0), (1, "pjc", None), (1, "cbjc", 0)
joyGetDevCaps_func  = joyGetDevCaps_proto(("joyGetDevCapsW", winmmdll), joyGetDevCaps_param)

# [joyGetPosEx](https://learn.microsoft.com/en-us/windows/win32/api/joystickapi/nf-joystickapi-joygetposex)
"""
MMRESULT joyGetPosEx(UINT uJoyID, LPJOYINFOEX pji);
sizeof(JOYINFOEX): 52
"""
joyGetPosEx_proto = ctypes.WINFUNCTYPE(ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p)
joyGetPosEx_param = (1, "uJoyID", 0), (1, "pji", None)
joyGetPosEx_func  = joyGetPosEx_proto(("joyGetPosEx", winmmdll), joyGetPosEx_param)
  

Создайте функцию python joyGetNumDevs joyGetDevCaps и joyGetPosEx делегируйте ее в DLL. И создайте классы для типов JOYCAPS соответственно JOYINFOEX :

 # joystickapi - joyGetNumDevs
def joyGetNumDevs():
    try:
        num = joyGetNumDevs_func()
    except:
        num = 0
    return num

# joystickapi - joyGetDevCaps
def joyGetDevCaps(uJoyID):
    try:
        buffer = (ctypes.c_ubyte * JOYCAPS.SIZE_W)()
        p1 = ctypes.c_uint(uJoyID)
        p2 = ctypes.cast(buffer, ctypes.c_void_p)
        p3 = ctypes.c_uint(JOYCAPS.SIZE_W)
        ret_val = joyGetDevCaps_func(p1, p2, p3)
        ret = (False, None) if ret_val != JOYERR_NOERROR else (True, JOYCAPS(buffer))   
    except:
        ret = False, None
    return ret 

# joystickapi - joyGetPosEx
def joyGetPosEx(uJoyID):
    try:
        buffer = (ctypes.c_uint32 * (JOYINFOEX.SIZE // 4))()
        buffer[0] = JOYINFOEX.SIZE
        buffer[1] = JOY_RETURNALL
        p1 = ctypes.c_uint(uJoyID)
        p2 = ctypes.cast(buffer, ctypes.c_void_p)
        ret_val = joyGetPosEx_func(p1, p2)
        ret = (False, None) if ret_val != JOYERR_NOERROR else (True, JOYINFOEX(buffer))   
    except:
        ret = False, None
    return ret 

JOYERR_NOERROR = 0
JOY_RETURNX = 0x00000001
JOY_RETURNY = 0x00000002
JOY_RETURNZ = 0x00000004
JOY_RETURNR = 0x00000008
JOY_RETURNU = 0x00000010
JOY_RETURNV = 0x00000020
JOY_RETURNPOV = 0x00000040
JOY_RETURNBUTTONS = 0x00000080
JOY_RETURNRAWDATA = 0x00000100
JOY_RETURNPOVCTS = 0x00000200
JOY_RETURNCENTERED = 0x00000400
JOY_USEDEADZONE = 0x00000800
JOY_RETURNALL = (JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | 
                 JOY_RETURNR | JOY_RETURNU | JOY_RETURNV | 
                 JOY_RETURNPOV | JOY_RETURNBUTTONS)

# joystickapi - JOYCAPS
class JOYCAPS:
    SIZE_W = 728
    OFFSET_V = 4   32*2
    def __init__(self, buffer):
        ushort_array = (ctypes.c_uint16 * 2).from_buffer(buffer)
        self.wMid, self.wPid = ushort_array  

        wchar_array = (ctypes.c_wchar * 32).from_buffer(buffer, 4)
        self.szPname = ctypes.cast(wchar_array, ctypes.c_wchar_p).value
        
        uint_array = (ctypes.c_uint32 * 19).from_buffer(buffer, JOYCAPS.OFFSET_V) 
        self.wXmin, self.wXmax, self.wYmin, self.wYmax, self.wZmin, self.wZmax, 
        self.wNumButtons, self.wPeriodMin, self.wPeriodMax, 
        self.wRmin, self.wRmax, self.wUmin, self.wUmax, self.wVmin, self.wVmax, 
        self.wCaps, self.wMaxAxes, self.wNumAxes, self.wMaxButtons = uint_array

# joystickapi - JOYINFOEX
class JOYINFOEX:
  SIZE = 52
  def __init__(self, buffer):
      uint_array = (ctypes.c_uint32 * (JOYINFOEX.SIZE // 4)).from_buffer(buffer) 
      self.dwSize, self.dwFlags, 
      self.dwXpos, self.dwYpos, self.dwZpos, self.dwRpos, self.dwUpos, self.dwVpos, 
      self.dwButtons, self.dwButtonNumber, self.dwPOV, self.dwReserved1, self.dwReserved2 = uint_array
  

Смотрите простой пример для тестирования API:

 import joystickapi
import msvcrt
import time

print("start")

num = joystickapi.joyGetNumDevs()
ret, caps, startinfo = False, None, None
for id in range(num):
    ret, caps = joystickapi.joyGetDevCaps(id)
    if ret:
        print("gamepad detected: "   caps.szPname)
        ret, startinfo = joystickapi.joyGetPosEx(id)
        break
else:
    print("no gamepad detected")

run = ret
while run:
    time.sleep(0.1)
    if msvcrt.kbhit() and msvcrt.getch() == chr(27).encode(): # detect ESC
        run = False

    ret, info = joystickapi.joyGetPosEx(id)
    if ret:
        btns = [(1 << i) amp; info.dwButtons != 0 for i in range(caps.wNumButtons)]
        axisXYZ = [info.dwXpos-startinfo.dwXpos, info.dwYpos-startinfo.dwYpos, info.dwZpos-startinfo.dwZpos]
        axisRUV = [info.dwRpos-startinfo.dwRpos, info.dwUpos-startinfo.dwUpos, info.dwVpos-startinfo.dwVpos]
        if info.dwButtons:
            print("buttons: ", btns)
        if any([abs(v) > 10 for v in axisXYZ]):
            print("axis:", axisXYZ)
        if any([abs(v) > 10 for v in axisRUV]):
            print("roation axis:", axisRUV)

print("end")
  

Привязка api и пример предоставлены в репозитории GitHub python_windows_joystickapi

Комментарии:

1. Примечание: ctypes у модуля обычно уже есть подписи, поэтому вы можете вызывать ctypes.windll.winmm.joyGetDevCapsW etc. как обычная функция, без необходимости объявлять сигнатуру функции. Прочитайте документацию модуля для получения более подробной информации. Кроме того, коды ошибок можно найти по адресу autohotkey.com/board/topic/17212-midi-output-from-ahk или hbtapi.com/help/Units/mmsystem.pas/Constants /…