Open horsto opened 6 months ago
code.py
import board
import usb_hid
import board
from micropython import const
from hid_gamepad import Gamepad
gp = Gamepad(usb_hid.devices)
from adafruit_seesaw.seesaw import Seesaw
# Equivalent of Arduino's map() function.
def range_map(x, in_min, in_max, out_min, out_max):
return (x - in_min) * (out_max - out_min) // (in_max - in_min) + out_min
BUTTON_X = const(6)
BUTTON_Y = const(2)
BUTTON_A = const(5)
BUTTON_B = const(1)
BUTTON_SELECT = const(0)
BUTTON_START = const(16)
button_mask = const(
(1 << BUTTON_X)
| (1 << BUTTON_Y)
| (1 << BUTTON_A)
| (1 << BUTTON_B)
| (1 << BUTTON_SELECT)
| (1 << BUTTON_START)
)
i2c_bus = board.STEMMA_I2C() # The built-in STEMMA QT connector on the microcontroller
# i2c_bus = board.I2C() # Uses board.SCL and board.SDA. Use with breadboard.
gamepad1 = Seesaw(i2c_bus, addr=0x50)
gamepad1.pin_mode_bulk(button_mask, gamepad1.INPUT_PULLUP)
# Equivalent of Arduino's map() function.
def range_map(x, in_min, in_max, out_min, out_max):
return (x - in_min) * (out_max - out_min) // (in_max - in_min) + out_min
last_x = 0
last_y = 0
while True:
x = 1023 - gamepad1.analog_read(14)
y = 1023 - gamepad1.analog_read(15)
if (abs(x - last_x) > 3) or (abs(y - last_y) > 3):
print(x, y)
last_x = x
last_y = y
buttons = gamepad1.digital_read_bulk(button_mask)
if not buttons & (1 << BUTTON_X):
print("Button x pressed")
gp.click_buttons(5)
# # Buttons are grounded when pressed (.value = False).
# for i, button in enumerate(buttons):
# gamepad_button_num = gamepad_buttons[i]
# if button.value:
# gp.release_buttons(gamepad_button_num)
# print(" release", gamepad_button_num, end="")
# else:
# gp.press_buttons(gamepad_button_num)
# print(" press", gamepad_button_num, end="")
# Convert range[0, 65535] to -127 to 127
gp.move_joysticks(
x=range_map(x, 0, 1023, -127, 127),
y=range_map(y, 0, 1023, -127, 127),
)
# print(" x", ax.value, "y", ay.value)
boot.py
import usb_hid
# This is only one example of a gamepad report descriptor,
# and may not suit your needs.
GAMEPAD_REPORT_DESCRIPTOR = bytes((
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x05, # Usage (Game Pad)
0xA1, 0x01, # Collection (Application)
0x85, 0x04, # Report ID (4)
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)
0x95, 0x10, # Report Count (16)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x15, 0x81, # Logical Minimum (-127)
0x25, 0x7F, # Logical Maximum (127)
0x09, 0x30, # Usage (X)
0x09, 0x31, # Usage (Y)
0x09, 0x32, # Usage (Z)
0x09, 0x35, # Usage (Rz)
0x75, 0x08, # Report Size (8)
0x95, 0x04, # Report Count (4)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, # End Collection
))
gamepad = usb_hid.Device(
report_descriptor=GAMEPAD_REPORT_DESCRIPTOR,
usage_page=0x01, # Generic Desktop Control
usage=0x05, # Gamepad
report_ids=(4,), # Descriptor uses report ID 4.
in_report_lengths=(6,), # This gamepad sends 6 bytes in its report.
out_report_lengths=(0,), # It does not receive any reports.
)
usb_hid.enable(
(usb_hid.Device.KEYBOARD,
usb_hid.Device.MOUSE,
usb_hid.Device.CONSUMER_CONTROL,
gamepad)
)
Check that boot_out.txt
does not show any errors. Also try including only the gamepad device in the usb_hid.enable(...)
. A long time ago, we discovered that the gamepad device needed to go after the mouse: https://github.com/adafruit/circuitpython/pull/4558, which you did, but it macOS could be finicky in other ways.
Try this also on a Windows machine, if you have one, and see if it shows up.
Thanks for the quick reply! boot_out.txt
seems clean:
Adafruit CircuitPython 9.0.5 on 2024-05-22; Adafruit Feather ESP32-S3 Reverse TFT with ESP32S3
Board ID:adafruit_feather_esp32s3_reverse_tft
UID:0740D1DCD48C
[2K[0Gboot.py output:
I cannot test this on a windows machine.
Only leaving gamepad
in usb_hid.enable()
did not change things for me.
I put your boot.py on a CircuitPython board. It works, and presents a gamepad device, which the OS is aware of. This is true on Linux, Windows, and macOS Sonoma 14.5 (I tested on an M1 Mac Mini).
I verified the gamepad's presence with this tool: https://github.com/todbot/hidapitester (there is a .pkg installer in the release). You could try this yourself as well.
% hidapitester --vidpid 239a --list-detail
[other stuff omitted]
239A/8100: Adafruit Industries LLC - NeoKey Trinkey M0
vendorId: 0x239A
productId: 0x8100
usagePage: 0x0001
usage: 0x0005
serial_number: D517091D4C51535020312E37301619FF
interface: 3
path: DevSrvsID:4294974679
However, the question is what macOS is going to do with the gamepad, whether the game can find it, and what your game is expecting. That I don't know, unfortunately.
We found in the past that which gamepads are usable on which OS's varies. It can even depend on the range of the X/Y/Z values. You might ask in our discord, https://adafru.it/discord, whether anyone has succeeded in making a usable gamepad on macOS
Here is another "proof of life" for the Gamepad controller. I installed a free app, Controllers Lite, from the App Store. You have to give it permission to monitor input events (when you run it the first time it will be obvious what to do, I think). Here you can see that it is seeing the Gamepad in boot.py:
A hypothesis is that your game doesn't like this particular gamepad definition, for some reason. If you know of a gamepad that works with the game, you could find out its HID report descriptor and emulate that. I know there are also remappers that will transform one kind of gamepad into another, though I know very little about them other than that they exist.
Thanks for the input, this is great. I did try Controller Lite, and yes, it shows up. I also tried emulating XBOX and Playstation controllers (I changed the report descriptor accordingly), but without success.
I am trying to run STEAM games, but those do not recognize my (custom) controller as valid input device, nor does STEAM itself.
I have reached out on Discord, hoping somebody tried something similar.
Also tried https://steamcommunity.com/discussions/forum/2/3117025249776480006/?ctp=6
I have not gotten much further on this. Any input would be appreciated!
Hi I know I'm pretty late on this and I haven't quite broken through to getting things to work fully, but I managed to get Final Fantasy XIV to recognize my device as a GamePad by setting a BLE Vendor ID and Product ID. https://github.com/adafruit/Adafruit_CircuitPython_BLE/pull/202
I haven't made my jump to macOS Sequoia just yet so I can't confirm if this will work but maybe using supervisor.set_usb_identification
will work for you.
And just as a sidenote, I was informed sometime ago that only Bluetooth Gamepads are usable on macOS pre-Sequoia.
https://docs.circuitpython.org/en/latest/shared-bindings/supervisor/#supervisor.set_usb_identification
As for my current status, I have been able to get my mac to recognize my gamepad by haven't been able to actually send reports that yield any action in game yet. I've confirmed that the reports are being received on the mac using the https://hardwaretester.com/gamepad website so my current hypothesis is that the game requires a properly crafted HID descriptor to accept input.
FYI here's the descriptor that I'm trying to get to work for Vendor ID 0x045e
and Product ID 0x02e0
. The following descriptor requires tweaking the adafruit_ble library though and I'm still going through the process of tinkering with the HDIService
class so I'll come back for updates if I manage to sort things out.
GAMEPAD_REPORT_DESCRIPTOR = bytes([
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x05, # Usage (Game Pad)
0xA1, 0x01, # Collection (Application)
0x85, 0x01, # Report ID (1)
0x09, 0x01, # Usage (Pointer)
0xA1, 0x00, # Collection (Physical)
0x09, 0x30, # Usage (X)
0x09, 0x31, # Usage (Y)
0x15, 0x00, # Logical Minimum (0)
0x27, 0xFF, 0xFF, 0x00, 0x00, # Logical Maximum (65534)
0x95, 0x02, # Report Count (2)
0x75, 0x10, # Report Size (16)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, # End Collection
0x09, 0x01, # Usage (Pointer)
0xA1, 0x00, # Collection (Physical)
0x09, 0x33, # Usage (Rx)
0x09, 0x34, # Usage (Ry)
0x15, 0x00, # Logical Minimum (0)
0x27, 0xFF, 0xFF, 0x00, 0x00, # Logical Maximum (65534)
0x95, 0x02, # Report Count (2)
0x75, 0x10, # Report Size (16)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, # End Collection
0x05, 0x02, # Usage Page (Generic Desktop Ctrls)
0x09, 0x32, # Usage (Z)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x03, # Logical Maximum (1023)
0x95, 0x01, # Report Count (1)
0x75, 0x0A, # Report Size (10)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x00, # Logical Maximum (0)
0x75, 0x06, # Report Size (6)
0x95, 0x01, # Report Count (1)
0x81, 0x03, # Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x35, # Usage (Rz)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x03, # Logical Maximum (1023)
0x95, 0x01, # Report Count (1)
0x75, 0x0A, # Report Size (10)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x00, # Logical Maximum (0)
0x75, 0x06, # Report Size (6)
0x95, 0x01, # Report Count (1)
0x81, 0x03, # Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x39, # Usage (Hat switch)
0x15, 0x01, # Logical Minimum (1)
0x25, 0x08, # Logical Maximum (8)
0x35, 0x00, # Physical Minimum (0)
0x46, 0x3B, 0x01, # Physical Maximum (315)
0x66, 0x14, 0x00, # Unit (System: English Rotation, Length: Centimeter)
0x75, 0x04, # Report Size (4)
0x95, 0x01, # Report Count (1)
0x81, 0x42, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
0x75, 0x04, # Report Size (4)
0x95, 0x01, # Report Count (1)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x00, # Logical Maximum (0)
0x35, 0x00, # Physical Minimum (0)
0x45, 0x00, # Physical Maximum (0)
0x65, 0x00, # Unit (None)
0x81, 0x03, # Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x09, # Usage Page (Button)
0x19, 0x01, # Usage Minimum (0x01)
0x29, 0x0A, # Usage Maximum (0x0A)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x01, # Logical Maximum (1)
0x75, 0x01, # Report Size (1)
0x95, 0x0A, # Report Count (10)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x00, # Logical Maximum (0)
0x75, 0x06, # Report Size (6)
0x95, 0x01, # Report Count (1)
0x81, 0x03, # Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x80, # Usage (Sys Control)
0x85, 0x02, # Report ID (2)
0xA1, 0x00, # Collection (Physical)
0x09, 0x85, # Usage (Sys Main Menu)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x01, # Logical Maximum (1)
0x95, 0x01, # Report Count (1)
0x75, 0x01, # Report Size (1)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x00, # Logical Maximum (0)
0x75, 0x07, # Report Size (7)
0x95, 0x01, # Report Count (1)
0x81, 0x03, # Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, # End Collection
0x05, 0x0F, # Usage Page (PID Page)
0x09, 0x21, # Usage (0x21)
0x85, 0x03, # Report ID (3)
0xA1, 0x02, # Collection (Logical)
0x09, 0x97, # Usage (0x97)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x01, # Logical Maximum (1)
0x75, 0x04, # Report Size (4)
0x95, 0x01, # Report Count (1)
0x91, 0x02, # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x00, # Logical Maximum (0)
0x75, 0x04, # Report Size (4)
0x95, 0x01, # Report Count (1)
0x91, 0x03, # Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x09, 0x70, # Usage (0x70)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x64, # Logical Maximum (100)
0x75, 0x08, # Report Size (8)
0x95, 0x04, # Report Count (4)
0x91, 0x02, # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x09, 0x50, # Usage (0x50)
0x66, 0x01, 0x10, # Unit (System: SI Linear, Time: Seconds)
0x55, 0x0E, # Unit Exponent (-2)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x75, 0x08, # Report Size (8)
0x95, 0x01, # Report Count (1)
0x91, 0x02, # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x09, 0xA7, # Usage (0xA7)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x75, 0x08, # Report Size (8)
0x95, 0x01, # Report Count (1)
0x91, 0x02, # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x65, 0x00, # Unit (None)
0x55, 0x00, # Unit Exponent (0)
0x09, 0x7C, # Usage (0x7C)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x75, 0x08, # Report Size (8)
0x95, 0x01, # Report Count (1)
0x91, 0x02, # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0, # End Collection
0x85, 0x04, # Report ID (4)
0x05, 0x06, # Usage Page (Generic Dev Ctrls)
0x09, 0x20, # Usage (Battery Strength)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x75, 0x08, # Report Size (8)
0x95, 0x01, # Report Count (1)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, # End Collection
])
Hi a very quick update indeed: I got this to work and it was just as I speculated! The following is the code required to reproduce. Note that I'm using Seeed XIAO Sense nrf52840 with an IMU so do alter the reporting code according to your needs! https://gist.github.com/ktnyt/33ad0462a0e7cc49176c78530e320291
Hi, all
I was curious to find out more about the USB connection. The environment is as follows.
Board : M5Stack AtomS3 Connection: USB Machine : M2 Mac Mini OS: 15.1.1
First of all, the definition of “Gamepad is recognized by Mac” that I adopted is what is shown in System Settings - > Gamepad. hardwaretester.com almost always recognizes if Usage is Gamepad (0x09, 0x05, # Usage (Gamepad)), but I was concerned that in some cases it is not recognized on the Mac OS side. So I tried some tests on Gamepad on Mac OS, and it seems that the following two conditions must be met in order for it to be recognized.
(1) It has all the standard Gamepad Usages
There is a description of the standard mappings of Gamepad (https://learn.microsoft.com/en-us/windows/win32/xinput/directinput-and-xusb-devices). If it has all of these elements, it can be considered a standard gamepad. If you remove the items, it is no longer recognized.
import usb_hid
gamepad = usb_hid.Device(
report_descriptor=bytes((
0x05, 0x01, # Usage Page (Generic Desktop)
0x09, 0x05, # Usage (Gamepad)
0xa1, 0x01, # Collection (Application)
0x85, 0x01, # Report ID
0xa1, 0x02, # Collection (logical)
0x05, 0x01, # Usage Page (Generic Desktop)
0x09, 0x30, # Usage (X)
0x09, 0x31, # Usage (Y)
0x09, 0x32, # Usage (Z)
0x09, 0x33, # Usage (RX)
0x09, 0x34, # Usage (RY)
0x09, 0x39, # Usage (Hat Switch)
0x15, 0x80, # Logical Minimum (-128)
0x25, 0x7F, # Logical Maximum (127)
0x75, 0x08, # Report Size (8)
0x95, 0x06, # Report Count (6)
0x81, 0x02, # Input (Data, Variable, Absolute)
0xc0, # End Collection
0xa1, 0x02, # Collection (logical)
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)
0x95, 0x10, # Report Count (16)
0x81, 0x02, # Input (Data, Variable, Absolute)
0xc0, # End Collection
0xc0 # End Collection
)),
usage_page=0x01,
usage=0x05,
report_ids=(0x01,),
in_report_lengths=(8, ),
out_report_lengths=(0, )
)
usb_hid.enable([gamepad])
(2) It has a specific USB vendor/product id.
Before trying with CircuitPython, I first tried Logitech and Elecom USB Gamepads in Direct Input mode. Both are recognized fine on Windows, but the latter was not recognized on the Mac. So I specified the vendor/product id of the Logitech Gamepad on my board(M5StacK AtomS3) and it showed up in the OS settings.
supervisor.set_usb_identification(vid=0x046D, pid=0xC218)
The following is the recognition status of the Mac and the hardwaretester.com when I changed only the pid with the same reportmap.
Recognized
Not recognized(only pid changed)
(2) may be controlled by Apple in some way. Try at your own risk.
I am using https://learn.adafruit.com/gamepad-qt with https://learn.adafruit.com/esp32-s3-reverse-tft-feather.
My goal is to let the gamepad be recognized as gamecontroller input on my Mac to play a game with it. I have looked at the gamepad example here: https://docs.circuitpython.org/projects/hid/en/latest/examples.html#id4 and added the gamepad boiler plate code shown here: https://learn.adafruit.com/customizing-usb-devices-in-circuitpython/hid-devices#custom-hid-devices-3096614-9 to my boot.py. This doesn't run into any errors, but also does not lead to recognition of a gamecontroller as input device under MacOS.
Has anybody tried this?