diff --git a/.vscode/settings.json b/.vscode/settings.json index f69c3cc..719a7d4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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" diff --git a/README.md b/README.md index 423bd6e..e243d3c 100644 --- a/README.md +++ b/README.md @@ -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) \ No newline at end of file +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. diff --git a/boot.py b/boot.py index bf495af..eb02b99 100644 --- a/boot.py +++ b/boot.py @@ -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) @@ -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) -) \ No newline at end of file + (gamepad,) +) + +usb_cdc.enable(console=True,data=True) \ No newline at end of file diff --git a/code_1.py b/code_1.py index 0af09d2..a5bb474 100644 --- a/code_1.py +++ b/code_1.py @@ -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 inRangeInclusive(value, min, max): + return value >= min and value <= max while True: pressed = trellis.pressed_keys @@ -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) \ No newline at end of file + 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 inRangeInclusive(ledNumber, 1, 32): + print("Unexpected led number:", ledNumber) + continue + if not inRangeInclusive(redValue, 0, 255): + print("Unexpected red value:", redValue) + continue + if not inRangeInclusive(greenValue, 0, 255): + print("Unexpected green value:", greenValue) + continue + if not inRangeInclusive(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) diff --git a/deploy.cmd b/deploy.cmd index 68803f8..08e6265 100644 --- a/deploy.cmd +++ b/deploy.cmd @@ -1,6 +1,4 @@ -for /f %%D in ('wmic volume get DriveLetter^, Label ^| find "CIRCUITPY"') do set myDrive=%%D -echo %myDrive% - +set myDrive=D: 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 diff --git a/hid_gamepad.py b/hid_gamepad.py index 56f8846..2c9d708 100644 --- a/hid_gamepad.py +++ b/hid_gamepad.py @@ -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