CircuitPython firmware for one-handed chording keyboards based on the KB2040 and other RP2040-based boards from adafruit!
- Layers!
- Map chords to key combinations on tap or hold.
- Map normal keys, media keys, and the mouse.
- One-shot modifiers.
- Auto Mod for if you want to apply a modifier globally on hold.
- LED layer and modifier state indicators.
- Layers have a color.
- When modifiers are active, the LED blinks a color for each in turn:
- Yellow - Shift
- Red - Control
- Blue - Alt
- Pink - GUI
- On locked keys the LED pulses.
- Follow the quickstart here to set up your board with CircuitPython.
- Copy required libraries to your
CIRCUITPY/lib
folder:adafruit_hid
(whole folder)adafruit_debouncer.mpy
adafruit_pixelbuf.mpy
neopixel.mpy
- Create your
code.py
based off ofexample code.py
. - Copy your
code.py
and thepurple
folder to yourCIRCUITPY
drive.
The core engine handles basic input and state management. All extra functionality and optional hardware is broken out into extras so that it can be enabled or disabled at will. Extras can be added after the keyboard and layout in code.py
.
core.py
contains some useful values that can be adjusted:
_HOLD_DELAY
- How long to wait in ms before a key is considered held._MOUSE_REPEAT_RATE
- How long to wait in ms before repeating a mouse movement when the key is held.
extras/hardware/led.py
contains values for using the built-in LED to indicate status:
_MODIFIER_BLINK
- How long to wait between modifier blinks._MODIFIER_BLINK_ON
- How long to turn on the LED per modifier blink._MOD_COLORS
- The LED color tuple(r, g, b)
to use for the modifier. Should be a bright value so it can be seen when on layers._LOCK_FADE
- How long the LED takes to fade in or out to indicate locked keys._LOCK_FADE_MIN
- The minimum brightness multiple when keys are locked._LOCK_FADE_NO_COLOR
- The LED color tuple(r, g, b)
to use when a key is locked and the current color would otherwise be black.
purple/keyboard/[kb_name]/[side]/keyboard.py
kb_name
- The name of the keyboard.side
-left
orright
if it's a split board.
A keyboard defines a class that has a name and a list of Key
objects specifying the board pins, eg:
class Keyboard:
name = "My Keyboard"
keys = [
Key(DigitalInOut(board.A0)),
Key(DigitalInOut(board.A1)),
Key(DigitalInOut(board.A2)),
]
Default Key
import is from purple.key import Key
. You can also use from purple.key_debounce import Key
if you are getting duplicate keypresses.
Keys are specified from left to right and top to bottom. For example, the Sweep's key order looks like this:
0 1 2 3 4
5 6 7 8 9
10 11 12 13 14
15 16
For split boards, the left side is specified this way. The right side should be mirrored. The right side for the above Sweep looks like this:
4 3 2 1 0
9 8 7 6 5
14 13 12 11 10
16 15
purple/keyboard/[kb_name]/[side]/layout/[layout_name].py
kb_name
- The name of the keyboard.side
-left
orright
if it's a split board.layout_name
- The name of the layout.
A layout defines what happens when a chord is pressed or held. It contains:
name
- The name of the layout.auto_mod
- A list of modifier keycodes to apply on hold.layers
- A list of layers. Chord resolution starts on the current layer and works its way back to 0 until it finds a layer that can handle the chord.Layer
- A class containing the layer name, a dictionary of chords and actions, and the layer color.chord
- A binary representation of the chord, with1
being pressed, and0
released. The bit location corresponds with the key, so for example0b0001
is key 0 pressed on a 4-key layout, and0b1100
is keys 2 and 3 on the same.purple.helpers
containskey(index)
that can be used to simplify creating chords, and the values can also be added. In the above example,key(0)
is key 0, andkey(2)+key(3)
is keys 2 and 3 pressed.
Action
- Class defining the action to take for a chord. TheAction(tap_action, hold_action=None, hold=False, auto_mod=True)
constructor has the following parameters:tap_action
- The action to take when the key is tapped.None
to do nothing.hold_action
- The action to take when the key is held.tap_action
is used ifhold_action
isNone
.hold
- Whether separate hold and release messages should be sent when the key is held down. Used for modifiers likeShift
or keys you want to repeat.auto_mod
- Whether Auto Mod should apply to this key when held down.- Available actions are:
Press(*keycodes, one_shot=True)
- Press the specified keys.one_shot
specifies whether any one shot keys should be applied.- Simple key map:
Action(Press(Keycode.A))
z
on tap,Ctrl+z
on hold:Action(Press(Keycode.Z), Press(Keycode.CONTROL, Keycode.Z))
- Simple key map:
OneShot(*keycodes)
- Add or remove the specified keys to the next normal keypress.- Shift the next key:
Action(OneShot(Keycode.SHIFT))
- Shift the next key:
Lock(*keycodes)
- Toggle whether the specified keys are locked or unlocked. Locked keys are added automatically to other presses.- Simple key lock:
Action(Lock(Keycode.SHIFT))
z
on tap, lockCtrl
on hold:Action(Press(Keycode.Z), Lock(Keycode.CONTROL))
- Simple key lock:
MediaPress(consumer_control_code)
- Press the specified media key, such aPLAY_PAUSE
orMUTE
.MousePress(mouse_button)
- Press the specified mouse button.MouseMove(x, y, wheel=0)
- Move the mouse.- Move the mouse to the right:
Action(MouseMove(8, 0))
- Scroll the mouse wheel:
Action(MouseMove(0, 0, 1))
- Move the mouse to the right:
ToLayer(index)
- Switch to the specified layer.- Simple layer switch:
Action(ToLayer(1))
z
on tap, layer switch on hold:Action(Press(Keycode.Z), ToLayer(0))
- Simple layer switch:
color
- The LED color tuple(r, g, b)
. Layer colors should be kept dim (no value exceeding 64) so as not to conflict with the modifier indicator.
Pull requests for bug fixes, new keyboards, and new layouts are all welcome!
None at this time.
- Added
- Keyboard class
- Keyboard, layout, and layer names
one_shot
parameter to thePress
actionAction(None)
for an action that does nothing
- Updated
- Folder and file naming conventions
key_buffer
toone_shot_key_buffer
to more accurately reflect its usage
- Added
- Key helper
- Updated
- Soar layout to 0.2.0
- Added
Status
class and extras frameworkOneShot
action- Optional
Key
debounce PerformanceCounter
extra- Soar layout
- Updated
- Renamed
repeat
tohold
inAction
- Reworked
Key
to match debounced version - Split
LED
extra fromCore
- Renamed
- Removed
- Placeholder purple layout
- Added
- Media and mouse actions
- Updated
- Modifier key list now contains both left and right modifier keys
- Unified modifier key list
- ARTSEY
- Added media keys
- Added mouse controls
- Added
- LED pulsing to indicate locked keys
- Updated
- Bit order for chords
- Version numbering
- Added
- Ability to lock and unlock keys
- Updated
- ARTSEY
- Added shift lock
- Added repeat to the backspace and arrow keys
- ARTSEY
- Initial version