Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Control the lights on the gamepad using Serial #1

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,9 @@
"--disable=W0622",
],
"python.analysis.diagnosticSeverityOverrides": {
"reportMissingModuleSource": "none"
"reportMissingModuleSource": "none",
"reportShadowedImports": "none"
},
"python.analysis.extraPaths": [
"c:\\Users\\johng\\.vscode-insiders\\extensions\\joedevivo.vscode-circuitpython-0.1.20-win32-x64\\boards\\0x239A\\0x8030",
"c:\\Users\\johng\\.vscode-insiders\\extensions\\joedevivo.vscode-circuitpython-0.1.20-win32-x64\\stubs",
"c:\\Users\\johng\\AppData\\Roaming\\Code - Insiders\\User\\globalStorage\\joedevivo.vscode-circuitpython\\bundle\\20231130\\adafruit-circuitpython-bundle-py-20231130\\lib"
],
"circuitpython.board.version": "8.2.8",
"circuitpython.board.vid": "0x239A",
"circuitpython.board.pid": "0x8030"
Expand Down
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
# NeoTrellisGamepad
Have the Adafruit NeoTrellis M4 appear as a 32-button USB Gamepad.

This extends from the adafruit_hid examples, modifying them from 16-button to 32-button (which is the highest value the FRC Driver Station appears to support)
This extends from the adafruit_hid examples, modifying them from 16-button to 32-button (which is the highest value the FRC Driver Station appears to support)

FRC Driver Station cannot send data back to the controller directly, so listen for data over Serial to control the lights on the gamepad.

The gamepad has two serial connections:

1. The console stream, provides debug data and the Python REPL
2. The control stream, used for input from an application

The data format that the controller is expecting looks like:

`LLRRRGGGBBB\n`

- `LL` is the LED number to change, 01-32.
- `RRR` is the red portion of the color, 000-255.
- `GGG` is the green portion of the color, 000-255.
- `BBB` is the blue portion of the color, 000-255.

The console output will provide feedback if the gamepad doesn't understand the command.

Provide any non-numeric command with the correct length to clear all LEDs.
18 changes: 9 additions & 9 deletions boot.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import usb_hid
import usb_cdc

# This is an updated gamepad report descriptor that supports 32 buttons.
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)
0x85, 0x01, # Report ID (1)
0x05, 0x09, # Usage Page (Button)
0x19, 0x01, # Usage Minimum (Button 1)
0x29, 0x20, # Usage Maximum (Button 32)
Expand All @@ -31,14 +32,13 @@
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.
report_ids=(1,), # Descriptor uses report ID 1.
in_report_lengths=(8,), # This gamepad sends 8 bytes in its report.
out_report_lengths=(0,), # It receives no output over the HID protocol.
)

usb_hid.enable(
(usb_hid.Device.KEYBOARD,
usb_hid.Device.MOUSE,
usb_hid.Device.CONSUMER_CONTROL,
gamepad)
)
(gamepad,)
)

usb_cdc.enable(console=True,data=True)
72 changes: 58 additions & 14 deletions code_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,24 @@
from adafruit_hid import find_device
from hid_gamepad import Gamepad
import usb_hid
import usb_cdc

trellis = adafruit_trellism4.TrellisM4Express()

serial = usb_cdc.data
gp = Gamepad(usb_hid.devices)
gp.press_buttons(2)

"""
while True:
trellis.pixels[0, 0] = (0, 0, 0)
gp.press_buttons(2)
time.sleep(0.5)
trellis.pixels[0, 0] = (0, 0, 0)
gp.release_buttons(2)
time.sleep(0.5)
"""
if serial is None:
print("No serial connection!")
else:
print("Serial present")
serial.timeout = 0.1

def clearLeds(trellis):
trellis.pixels.fill(0)

def inRange(value, min, max):
stephenjust marked this conversation as resolved.
Show resolved Hide resolved
return value >= min and value <= max

while True:
pressed = trellis.pressed_keys
Expand All @@ -30,14 +32,56 @@
for x, y in pressed:
button = (x + y * 8) + 1
pressed_buttons.append(button)
trellis.pixels[x, y] = (255, 0, 0) # Set pressed buttons to red
print("Pressed buttons:", pressed_buttons)
gp.press_buttons(*pressed_buttons)
for i in range(1, 17):
if i not in pressed_buttons:
gp.release_buttons(i)
trellis.pixels[(i - 1) % 8, (i - 1) // 8] = (0, 0, 0) # Set released buttons to black
else:
gp.release_all_buttons()
trellis.pixels.fill((0, 0, 0)) # Set all buttons to black
time.sleep(0.001)
if serial.connected:
while serial.in_waiting >= 12:
# Serial byte packed format: LLRRRGGGBBB\n
# LL: Led number
# RRR: Red intensity
# GGG: Green intensity
# BBB: Blue intensity
command = serial.readline()
if command is None:
break
if (len(command)) != 12:
print("Unexpected command length: ", len(command))
clearLeds(trellis)
serial.reset_input_buffer()
continue
commandString = str(command[:11], 'ascii')
print("Received command:", str(commandString))

if not commandString.isdigit():
print("Not a numeric command. Clearing!")
clearLeds(trellis)
continue

ledNumber = int(commandString[:2])
redValue = int(commandString[2:5])
greenValue = int(commandString[5:8])
blueValue = int(commandString[8:11])
if not inRange(ledNumber, 1, 32):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these really not 0 indexed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it this way to match the buttons, which start at 1 per the USB spec.

print("Unexpected led number:", ledNumber)
continue
if not inRange(redValue, 0, 255):
print("Unexpected red value:", redValue)
continue
if not inRange(greenValue, 0, 255):
print("Unexpected green value:", greenValue)
continue
if not inRange(blueValue, 0, 255):
print("Unexpected blue value:", blueValue)
continue

x = (ledNumber-1) % 8
y = int((ledNumber-1) // 8)
trellis.pixels[(x, y)] = (redValue, greenValue, blueValue)
else:
print("Serial not connected")
time.sleep(0.001)
4 changes: 1 addition & 3 deletions deploy.cmd
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
for /f %%D in ('wmic volume get DriveLetter^, Label ^| find "CIRCUITPY"') do set myDrive=%%D
echo %myDrive%

set myDrive=D:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happened here? Did deploy.cmd not find the CIRCUITPY drive on your machine? Did it show up with a different name?

REM delete any existing python files so we know it's only going to run our new ones
del %myDrive%\*.py
REM copy over our python files
Expand Down
2 changes: 1 addition & 1 deletion hid_gamepad.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def __init__(self, devices):
# 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
Expand Down