Skip to content

Instantly share code, notes, and snippets.

@jfurcean
Last active April 2, 2022 17:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jfurcean/6d15effe779367c8d51dddab12ba4cb9 to your computer and use it in GitHub Desktop.
Save jfurcean/6d15effe779367c8d51dddab12ba4cb9 to your computer and use it in GitHub Desktop.
Wii Classic Controller as HID Gamepad
# boot.py
import usb_hid
# This is only one example of a gamepad descriptor.
# It may not suit your needs, or be supported on your host computer.
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, 0x33, # 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)
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x34, # Usage (Rx)
0x09, 0x35, # Usage (Ry)
0x15, 0x81, # Logical Minimum (0)
0x25, 0x7F, # Logical Maximum (127)
0x75, 0x08, # Report Size (8)
0x95, 0x02, # Report Count (2)
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=(8,), # This gamepad sends 6 bytes in its report.
out_report_lengths=(0,), # It does not receive any reports.
)
usb_hid.enable(
(gamepad,)
)
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries, 2022 John Furcean
# SPDX-License-Identifier: MIT
# You must add a gamepad HID device inside your boot.py file
# in order to use this example.
# See this Learn Guide for details:
# https://learn.adafruit.com/customizing-usb-devices-in-circuitpython/hid-devices#custom-hid-devices-3096614-9
import board
import usb_hid
from adafruit_simplemath import map_range
from hid_gamepad import Gamepad
from wiichuck.classic_controller import ClassicController
def get_button_list(buttons, dpad):
# mapped they so they worked appropriately with https://gamepad-tester.com/
all_buttons=[0]*16
all_buttons[0] = buttons.B
all_buttons[1] = buttons.A
all_buttons[2] = buttons.Y
all_buttons[3] = buttons.X
all_buttons[4] = buttons.L # Change to 0 if you want to use analog triggers for some emulators
all_buttons[5] = buttons.R # Change to 0 if you want to use analog triggers for some emulators
all_buttons[6] = buttons.ZL
all_buttons[7] = buttons.ZR
all_buttons[8] = buttons.select
all_buttons[9] = buttons.start
all_buttons[10] = buttons.home
all_buttons[11] = 0 # not used
all_buttons[12] = dpad.up
all_buttons[13] = dpad.down
all_buttons[14] = dpad.left
all_buttons[15] = dpad.right
return all_buttons
gp = Gamepad(usb_hid.devices)
controller = ClassicController(board.STEMMA_I2C())
while True:
joysticks, buttons, dpad, triggers = controller.values
all_buttons = get_button_list(buttons,dpad)
for i, button in enumerate(all_buttons):
gamepad_button_num = i+1
if button:
gp.press_buttons(gamepad_button_num)
# print(" press", gamepad_button_num, end="")
else:
gp.release_buttons(gamepad_button_num)
# print(" release", gamepad_button_num, end="")
gp.move_joysticks(
x=int(map_range(joysticks.lx, 0, 63, -127, 127)),
y=int(map_range(joysticks.ly, 0, 63, -127, 127)),
z=int(map_range(joysticks.rx, 0, 31, -127, 127)),
r_z=int(map_range(joysticks.ry, 0, 31, -127, 127)),
)
gp.press_triggers(
lt=int(map_range(triggers.left, 0, 31, 0, 127)),
rt=int(map_range(triggers.right, 0, 31, 0, 127))
)
#print(f"rx:{joysticks.rx} ry:{joysticks.ry} lx:{joysticks.lx} ly:{joysticks.ly}")
# SPDX-FileCopyrightText: 2018 Dan Halbert for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`Gamepad`
====================================================
* Author(s): Dan Halbert
"""
import struct
import time
from adafruit_hid import find_device
class Gamepad:
"""Emulate a generic gamepad controller with 16 buttons,
numbered 1-16, and two joysticks, one controlling
``x` and ``y`` values, and the other controlling ``z`` and
``r_z`` (z rotation or ``Rz``) values.
The joystick values could be interpreted
differently by the receiving program: those are just the names used here.
The joystick values are in the range 0 to 127."""
def __init__(self, devices):
"""Create a Gamepad object that will send USB gamepad HID reports.
Devices can be a list of devices that includes a gamepad device or a gamepad device
itself. A device is any object that implements ``send_report()``, ``usage_page`` and
``usage``.
"""
self._gamepad_device = find_device(devices, usage_page=0x1, usage=0x05)
# Reuse this bytearray to send mouse reports.
# Typically controllers start numbering buttons at 1 rather than 0.
# report[0] buttons 1-8 (LSB is button 1)
# report[1] buttons 9-16
# report[2] joystick 0 x: -127 to 127
# report[3] joystick 0 y: -127 to 127
# report[4] joystick 1 x: -127 to 127
# report[5] joystick 1 y: -127 to 127
# report[5] trigger y: 0 to 127
# report[5] joystick 1 y: 0 to 127
self._report = bytearray(8)
# Remember the last report as well, so we can avoid sending
# duplicate reports.
self._last_report = bytearray(8)
# Store settings separately before putting into report. Saves code
# especially for buttons.
self._buttons_state = 0
self._joy_x = 0
self._joy_y = 0
self._joy_z = 0
self._joy_r_z = 0
self._trigger_l = 0
self._trigger_r = 0
# Send an initial report to test if HID device is ready.
# If not, wait a bit and try once more.
try:
self.reset_all()
except OSError:
time.sleep(1)
self.reset_all()
def press_buttons(self, *buttons):
"""Press and hold the given buttons."""
for button in buttons:
self._buttons_state |= 1 << self._validate_button_number(button) - 1
self._send()
def release_buttons(self, *buttons):
"""Release the given buttons."""
for button in buttons:
self._buttons_state &= ~(1 << self._validate_button_number(button) - 1)
self._send()
def release_all_buttons(self):
"""Release all the buttons."""
self._buttons_state = 0
self._send()
def click_buttons(self, *buttons):
"""Press and release the given buttons."""
self.press_buttons(*buttons)
self.release_buttons(*buttons)
def move_joysticks(self, x=None, y=None, z=None, r_z=None):
"""Set and send the given joystick values.
The joysticks will remain set with the given values until changed
One joystick provides ``x`` and ``y`` values,
and the other provides ``z`` and ``r_z`` (z rotation).
Any values left as ``None`` will not be changed.
All values must be in the range -127 to 127 inclusive.
Examples::
# Change x and y values only.
gp.move_joysticks(x=100, y=-50)
# Reset all joystick values to center position.
gp.move_joysticks(0, 0, 0, 0)
"""
if x is not None:
self._joy_x = self._validate_joystick_value(x)
if y is not None:
self._joy_y = self._validate_joystick_value(y)
if z is not None:
self._joy_z = self._validate_joystick_value(z)
if r_z is not None:
self._joy_r_z = self._validate_joystick_value(r_z)
self._send()
def press_triggers(self, lt=None, rt=None,):
"""Set and send the given joystick values.
The triggers will remain set with the given values until changed
Any values left as ``None`` will not be changed.
All values must be in the range 0 to 127 inclusive.
"""
if lt is not None:
self._trigger_l = self._validate_trigger_value(lt)
if rt is not None:
self._trigger_r = self._validate_trigger_value(rt)
self._send()
def reset_all(self):
"""Release all buttons and set joysticks to zero."""
self._buttons_state = 0
self._joy_x = 0
self._joy_y = 0
self._joy_z = 0
self._joy_r_z = 0
self._trigger_l = 0
self._trigger_r = 0
self._send(always=True)
def _send(self, always=False):
"""Send a report with all the existing settings.
If ``always`` is ``False`` (the default), send only if there have been changes.
"""
struct.pack_into(
"<Hbbbbbb",
self._report,
0,
self._buttons_state,
self._joy_x,
self._joy_y,
self._joy_z,
self._joy_r_z,
self._trigger_l,
self._trigger_r
)
if always or self._last_report != self._report:
self._gamepad_device.send_report(self._report)
# Remember what we sent, without allocating new storage.
self._last_report[:] = self._report
@staticmethod
def _validate_button_number(button):
if not 1 <= button <= 16:
raise ValueError("Button number must in range 1 to 16")
return button
@staticmethod
def _validate_joystick_value(value):
if not -127 <= value <= 127:
raise ValueError("Joystick value must be in range -127 to 127")
return value
@staticmethod
def _validate_trigger_value(value):
if not 0 <= value <= 127:
raise ValueError("Trigger value must be in range 0 to 127")
return value
@Jimmdean-1
Copy link

boot.py

import usb_hid

This is only one example of a gamepad descriptor.

It may not suit your needs, or be supported on your host computer.

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, 0x33, # 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)
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x34, # Usage (Rx)
0x09, 0x35, # Usage (Ry)
0x15, 0x81, # Logical Minimum (0)
0x25, 0x7F, # Logical Maximum (127)
0x75, 0x08, # Report Size (8)
0x95, 0x02, # Report Count (2)
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=(8,), # This gamepad sends 6 bytes in its report.
out_report_lengths=(0,), # It does not receive any reports.
)

usb_hid.enable(
(gamepad,)
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment