adding ATMakers TRRS Trinkey demos
Adding two demos from ATMakers for the TRRS Trinkey guide
This commit is contained in:
parent
4b134e0109
commit
87d83ca0bf
5 changed files with 428 additions and 0 deletions
|
|
@ -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
TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/boot.py
Normal file
75
TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/boot.py
Normal file
|
|
@ -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
TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/code.py
Normal file
124
TRRS_Trinkey_Demos/CircuitPython_Chording_Joystick/code.py
Normal file
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
46
TRRS_Trinkey_Demos/CircuitPython_Two_Switch_Keyboard/code.py
Normal file
46
TRRS_Trinkey_Demos/CircuitPython_Two_Switch_Keyboard/code.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# SPDX-FileCopyrightText: 2024 Bill Binko
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import digitalio
|
||||
import board
|
||||
import keypad
|
||||
import usb_hid
|
||||
from adafruit_hid.keyboard import Keyboard
|
||||
from adafruit_hid.keycode import Keycode
|
||||
|
||||
#We are only using Ring1 and Tip as input - set Ring2 & sleeve to GND
|
||||
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
|
||||
|
||||
# Initialize Keyboard to send HID to host
|
||||
kbd = Keyboard(usb_hid.devices)
|
||||
|
||||
#Setup which keystrokes to send on each switch press
|
||||
#Any Keycodes can be sent
|
||||
#Common AT device keystrokes:
|
||||
|
||||
# Web Browser: Enter/Space
|
||||
#keycodeList = [Keycode.ENTER, Keycode.SPACE]
|
||||
# Speech Devices (Tobii/PRC): ONE/TWO
|
||||
#keycodeList = [Keycode.ONE, Keycode.TWO]
|
||||
# iOS Switch Control: A/B (reads only from "Switch Keyboard" not other keyboards)
|
||||
keycodeList = [Keycode.A, Keycode.B]
|
||||
|
||||
|
||||
#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)
|
||||
|
||||
#Main loop for events
|
||||
while True:
|
||||
event=keys.events.get()
|
||||
if event:
|
||||
if event.pressed:
|
||||
kbd.press(keycodeList[event.key_number])
|
||||
else:
|
||||
kbd.release(keycodeList[event.key_number])
|
||||
Loading…
Reference in a new issue