From 87d83ca0bf7f8c8d6d1be3eb41c114b09471266e Mon Sep 17 00:00:00 2001 From: Liz Date: Tue, 28 May 2024 15:05:36 -0400 Subject: [PATCH 1/2] adding ATMakers TRRS Trinkey demos Adding two demos from ATMakers for the TRRS Trinkey guide --- .../XACsettings.py | 15 ++ .../CircuitPython_Chording_Joystick/boot.py | 75 ++++++++ .../CircuitPython_Chording_Joystick/code.py | 124 +++++++++++++ .../xac_gamepad.py | 168 ++++++++++++++++++ .../CircuitPython_Two_Switch_Keyboard/code.py | 46 +++++ 5 files changed, 428 insertions(+) create mode 100644 TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/XACsettings.py create mode 100644 TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/boot.py create mode 100644 TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/code.py create mode 100644 TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/xac_gamepad.py create mode 100644 TRRS_Trinkey_Demos/CircuitPython_Two_Switch_Keyboard/code.py diff --git a/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/XACsettings.py b/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/XACsettings.py new file mode 100644 index 000000000..7e33d8ff5 --- /dev/null +++ b/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/XACsettings.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2024 Bill Binko +# SPDX-License-Identifier: MIT + +#Change this to True to swap horizonatal and vertical axes +swapAxes = False + +#Change this to True to invert (flip) the horizontal axis +invertHor = False + +#Change this to True to invert (flip) the vertical axis +invertVert = True + +#Increase this to make the motion smoother (with more lag) +#Decrease to make more responsive (Min=1 Default=3 Max=Any but>20 is unreasonable) +smoothingFactor = 2 \ No newline at end of file diff --git a/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/boot.py b/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/boot.py new file mode 100644 index 000000000..a8bf8b24f --- /dev/null +++ b/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/boot.py @@ -0,0 +1,75 @@ +# SPDX-FileCopyrightText: 2024 Bill Binko +# SPDX-License-Identifier: MIT + +import usb_midi +import usb_hid + +print("In boot.py") + +# storage.disable_usb_device() + +# usb_cdc.enable(console=True, data=True) + +usb_midi.disable() +xac_descriptor=bytes( + # This descriptor mimics the simple joystick from PDP that the XBox likes + ( + 0x05, + 0x01, # Usage Page (Desktop), + 0x09, + 0x05, # Usage (Gamepad), + 0xA1, + 0x01, # Collection (Application), + ) + + ((0x85, 0x04) ) #report id + + ( + 0x15, + 0x00, # Logical Minimum (0), + 0x25, + 0x01, # Logical Maximum (1), + 0x35, + 0x00, # Physical Minimum (0), + 0x45, + 0x01, # Physical Maximum (1), + 0x75, + 0x01, # Report Size (1), + 0x95, + 0x08, # Report Count (8), + 0x05, + 0x09, # Usage Page (Button), + 0x19, + 0x01, # Usage Minimum (01h), + 0x29, + 0x08, # Usage Maximum (08h), + 0x81, + 0x02, # Input (Variable), + 0x05, + 0x01, # Usage Page (Desktop), + 0x26, + 0xFF, + 0x00, # Logical Maximum (255), + 0x46, + 0xFF, + 0x00, # Physical Maximum (255), + 0x09, + 0x30, # Usage (X), + 0x09, + 0x31, # Usage (Y), + 0x75, + 0x08, # Report Size (8), + 0x95, + 0x02, # Report Count (2), + 0x81, + 0x02, # Input (Variable), + 0xC0, # End Collection + )) +# pylint: disable=missing-kwoa +my_gamepad = usb_hid.Device( + report_descriptor=xac_descriptor, + usage_page=1, + usage=5, + report_ids=(4,), + in_report_lengths=(3,), + out_report_lengths=(0,),) +print("Enabling XAC Gamepad") +usb_hid.enable((my_gamepad,)) diff --git a/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/code.py b/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/code.py new file mode 100644 index 000000000..384c3c002 --- /dev/null +++ b/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/code.py @@ -0,0 +1,124 @@ +# SPDX-FileCopyrightText: 2024 Bill Binko +# SPDX-License-Identifier: MIT + +import time +import array +import board +import digitalio +import keypad +#Custom version of Gamepad compatible w/the XBox Adaptive Controller (XAC) +import xac_gamepad +# pylint: disable=wildcard-import, unused-wildcard-import +from XACsettings import * + +#Use Keypad library to read buttons wired between ground and Tip/Ring1 +keys = keypad.Keys((board.TIP,board.RING_1), value_when_pressed=False, pull=True) + +time.sleep(1.0) +gp = xac_gamepad.XACGamepad() + +class RollingAverage: + def __init__(self, size): + self.size=size + self.buffer = array.array('d') + for _ in range(size): + self.buffer.append(0.0) + self.pos = 0 + def addValue(self,val): + self.buffer[self.pos] = val + self.pos = (self.pos + 1) % self.size + def average(self): + return sum(self.buffer) / self.size + +ground = digitalio.DigitalInOut(board.RING_2) +ground.direction=digitalio.Direction.OUTPUT +ground.value = False + +ground2 = digitalio.DigitalInOut(board.SLEEVE) +ground2.direction=digitalio.Direction.OUTPUT +ground2.value = False + + +#Our joystick goes from 0-255 with a center at 128 +FORWARD = 0 +REVERSE=255 +CENTER=128 +LEFT=0 +RIGHT=255 + +#These two are how much we should smooth the joystick - higher numbers smooth more but add lag +VERT_AVG_COUNT=8 +HOR_AVG_COUNT=8 +#We need two Rolling Average Objects to smooth our values +xAvg = RollingAverage(HOR_AVG_COUNT) +yAvg = RollingAverage(VERT_AVG_COUNT) + +gp.reset_all() + +#Set Initial State variables +leftDown=False +rightDown=False +movingForward = False +joyChanged=False + +#main loop - read switches and set joystick output +while True: + event=keys.events.get() + #Calculate the rolling average for the X and Y + lastXAvg = xAvg.average() + lastYAvg = yAvg.average() + if event: + if event.pressed: + if event.key_number==0: + leftDown=True + elif event.key_number==1: + rightDown=True + else: + if event.key_number==0: + leftDown=False + elif event.key_number==1: + rightDown=False + + #At this point, we know whether we need to move the joystick + #Start with the assumption that we're in the center. + x=CENTER + y=CENTER + #If BOTH are down, we are always moving North + if leftDown and rightDown: + movingForward = True + x=CENTER + y=FORWARD + #Simlarly, if neither or down we are stopped + elif not leftDown and not rightDown: + movingForward = False + x=CENTER + y=CENTER + #Otherwise our direction depends on whether we WERE movingForward last iteration + elif movingForward: + #If So, we are moving NorthWest or NorthEast + if leftDown: + x=LEFT + y=FORWARD + elif rightDown: + x=RIGHT + y=FORWARD + else: + #If not, we are moving West or East + if leftDown: + x=LEFT + y=CENTER + elif rightDown: + x=RIGHT + y=CENTER + #We know x and y, so do some smoothing + xAvg.addValue(x) + yAvg.addValue(y) + #We need to send integers so calculate the average and truncate it + newX = int(xAvg.average()) + newY = int(yAvg.average()) + #We only call move_joysticks if one of the values has changed from last time + if (newX != lastXAvg or newY != lastYAvg): + gp.move_joysticks(x=newX,y=newY) + print((newX, newY,)) + #Sleep to avoid overwhelming the XAC + time.sleep(0.05) diff --git a/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/xac_gamepad.py b/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/xac_gamepad.py new file mode 100644 index 000000000..3d1cb97d9 --- /dev/null +++ b/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/xac_gamepad.py @@ -0,0 +1,168 @@ +# SPDX-FileCopyrightText: 2024 Bill Binko +# SPDX-License-Identifier: MIT + +# The MIT License (MIT) +# +# Copyright (c) 2018 Dan Halbert for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +""" +`adafruit_hid.gamepad.Gamepad` +==================================================== + +* Author(s): Dan Halbert +""" + +import sys +if sys.implementation.version[0] < 3: + raise ImportError('{0} is not supported in CircuitPython 2.x or lower'.format(__name__)) + +# pylint: disable=wrong-import-position +import struct +import time +import usb_hid + +class XACGamepad: + """Emulate a generic gamepad controller with 8 buttons, + numbered 1-8 and one joysticks, controlling + ``x` and ``y`` 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 255. +""" + + def __init__(self): + """Create a Gamepad object that will send USB gamepad HID reports.""" + self._hid_gamepad = None + for device in usb_hid.devices: + print(device) + if device.usage_page == 0x1 and device.usage == 0x05: + self._hid_gamepad = device + break + if not self._hid_gamepad: + raise OSError("Could not find an HID gamepad device.") + + # 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] joystick 0 x: 0 to 255 + # report[2] joystick 0 y: 0 to 255 + self._report = bytearray(3) + + # Remember the last report as well, so we can avoid sending + # duplicate reports. + self._last_report = bytearray(3) + + # Store settings separately before putting into report. Saves code + # especially for buttons. + self._buttons_state = 0 + self._joy_x = 0 + self._joy_y = 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): + """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 0 to 255 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) + self._send() + + def reset_all(self): + """Release all buttons and set joysticks to zero.""" + self._buttons_state = 0 + self._joy_x = 128 + self._joy_y = 128 + 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(' Date: Tue, 28 May 2024 15:11:03 -0400 Subject: [PATCH 2/2] Update XACsettings.py --- .../CircuitPython_Chording_Joystick/XACsettings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/XACsettings.py b/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/XACsettings.py index 7e33d8ff5..1a4a06cb8 100644 --- a/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/XACsettings.py +++ b/TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/XACsettings.py @@ -12,4 +12,4 @@ #Increase this to make the motion smoother (with more lag) #Decrease to make more responsive (Min=1 Default=3 Max=Any but>20 is unreasonable) -smoothingFactor = 2 \ No newline at end of file +smoothingFactor = 2