Skip to content

adding ATMakers TRRS Trinkey demos #2820

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

Merged
merged 2 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 15 additions & 0 deletions TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/XACsettings.py
Original file line number Diff line number Diff line change
@@ -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
75 changes: 75 additions & 0 deletions TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/boot.py
Original file line number Diff line number Diff line change
@@ -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,))
124 changes: 124 additions & 0 deletions TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/code.py
Original file line number Diff line number Diff line change
@@ -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)
168 changes: 168 additions & 0 deletions TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/xac_gamepad.py
Original file line number Diff line number Diff line change
@@ -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('<BBB', self._report, 0,
self._buttons_state,
self._joy_x, self._joy_y)

if always or self._last_report != self._report:
self._hid_gamepad.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 <= 8:
raise ValueError("Button number must in range 1 to 8")
return button

@staticmethod
def _validate_joystick_value(value):
if not 0 <= value <= 255:
raise ValueError("Joystick value must be in range 0 to 255")
return value
Loading