Adding advanced HID keyboard example with multiple layers.

This commit is contained in:
sandyjmacdonald 2021-03-12 16:56:59 +00:00
parent 065353ae09
commit 3fbf827b1e
5 changed files with 271 additions and 76 deletions

148
README.md
View file

@ -54,13 +54,13 @@ changes, then it should load up and run the code!
* [LED sleep](#led-sleep) * [LED sleep](#led-sleep)
* [Attaching functions to keys with decorators](#attaching-functions-to-keys-with-decorators) * [Attaching functions to keys with decorators](#attaching-functions-to-keys-with-decorators)
* [Key combos](#key-combos) * [Key combos](#key-combos)
* [USB MIDI](#usb-midi)
* [Setup](#setup)
* [Sending MIDI notes](#sending-midi-notes)
* [USB HID](#usb-hid) * [USB HID](#usb-hid)
* [Setup](#setup-1) * [Setup](#setup)
* [Sending key presses](#sending-key-presses) * [Sending key presses](#sending-key-presses)
* [Sending strings of text](#sending-strings-of-text) * [Sending strings of text](#sending-strings-of-text)
* [USB MIDI](#usb-midi)
* [Setup](#setup-1)
* [Sending MIDI notes](#sending-midi-notes)
# Library functionality # Library functionality
@ -368,74 +368,6 @@ held and a third to be pressed, and so on...
The [colour-picker.py example](examples/colour-picker.py) has an example of The [colour-picker.py example](examples/colour-picker.py) has an example of
using a modifier key to change the hue of the keys. using a modifier key to change the hue of the keys.
# USB MIDI
This covers basic MIDI note messages and how to link them to key presses.
## Setup
USB MIDI requires the `adafruit_midi` CircuitPython library. Download it from
the link below and then drop the `adafruit_midi` folder into the `lib` folder on
your `CIRCUITPY` drive.
[Download the Adafruit MIDI CircuitPython library](https://github.com/adafruit/Adafruit_CircuitPython_MIDI)
You'll need to connect your Keybow 2040 with a USB cable to a computer running a
software synth or DAW like Ableton Live, to a hardware synth that accepts USB
MIDI, or through a MIDI interface that will convert the USB MIDI messages to
regular serial MIDI through a DIN connector.
Using USB MIDI, Keybow 2040 shows up as a device with the name
`Keybow 2040 (CircuitPython usb midi.ports[1])`
In my testing, Keybow 2040 works with the Teenage Engineering OP-Z quite nicely.
## Sending MIDI notes
Here's a complete, minimal example of how to send a single MIDI note (middle C,
or MIDI note number 60) when key 0 is pressed, sending a note on message when
pressed and a note off message when released.
```
import board
from keybow2040 import Keybow2040
import usb_midi
import adafruit_midi
from adafruit_midi.note_off import NoteOff
from adafruit_midi.note_on import NoteOn
i2c = board.I2C()
keybow = Keybow2040(i2c)
keys = keybow.keys
midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)
key = keys[0]
note = 60
velocity = 127
was_pressed = False
while True:
keybow.update()
if key.pressed:
midi.send(NoteOn(note, velocity))
was_pressed = True
elif not key.pressed and was_pressed:
midi.send(NoteOff(note, 0))
was_pressed = False
```
There'a more complete example of how to set up all of Keybow's keys with
associated MIDI notes using decorators in the
[midi-keys.py example](examples/midi-keys.py).
The example above, and the `midi-keys.py` example both send notes on MIDI
channel 0 (all channels), but you can set this to a specific channel, if you
like, by changing `out_channel=` when you instantiate your `midi` object.
# USB HID # USB HID
This covers setting up a USB HID keyboard and linking physical key presses to This covers setting up a USB HID keyboard and linking physical key presses to
@ -535,7 +467,7 @@ while True:
``` ```
This code is available in the This code is available in the
hid-keys-simple.py example](examples/hid-keys-simple.py). [hid-keys-simple.py example](examples/hid-keys-simple.py).
As well as sending a single keypress, you can send multiple keypresses at once, As well as sending a single keypress, you can send multiple keypresses at once,
simply by adding them as additional argumemnts to `keyboard.send()`, e.g. simply by adding them as additional argumemnts to `keyboard.send()`, e.g.
@ -582,4 +514,72 @@ and then check against a `time_elapsed` variable created with
Also, be aware that the Adafruit HID CircuitPython library only currently Also, be aware that the Adafruit HID CircuitPython library only currently
supports US Keyboard layouts, so you'll have to work around that and map any supports US Keyboard layouts, so you'll have to work around that and map any
keycodes that differ from their US counterpart to whatever your is. keycodes that differ from their US counterpart to whatever your is.
# USB MIDI
This covers basic MIDI note messages and how to link them to key presses.
## Setup
USB MIDI requires the `adafruit_midi` CircuitPython library. Download it from
the link below and then drop the `adafruit_midi` folder into the `lib` folder on
your `CIRCUITPY` drive.
[Download the Adafruit MIDI CircuitPython library](https://github.com/adafruit/Adafruit_CircuitPython_MIDI)
You'll need to connect your Keybow 2040 with a USB cable to a computer running a
software synth or DAW like Ableton Live, to a hardware synth that accepts USB
MIDI, or through a MIDI interface that will convert the USB MIDI messages to
regular serial MIDI through a DIN connector.
Using USB MIDI, Keybow 2040 shows up as a device with the name
`Keybow 2040 (CircuitPython usb midi.ports[1])`
In my testing, Keybow 2040 works with the Teenage Engineering OP-Z quite nicely.
## Sending MIDI notes
Here's a complete, minimal example of how to send a single MIDI note (middle C,
or MIDI note number 60) when key 0 is pressed, sending a note on message when
pressed and a note off message when released.
```
import board
from keybow2040 import Keybow2040
import usb_midi
import adafruit_midi
from adafruit_midi.note_off import NoteOff
from adafruit_midi.note_on import NoteOn
i2c = board.I2C()
keybow = Keybow2040(i2c)
keys = keybow.keys
midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)
key = keys[0]
note = 60
velocity = 127
was_pressed = False
while True:
keybow.update()
if key.pressed:
midi.send(NoteOn(note, velocity))
was_pressed = True
elif not key.pressed and was_pressed:
midi.send(NoteOff(note, 0))
was_pressed = False
```
There'a more complete example of how to set up all of Keybow's keys with
associated MIDI notes using decorators in the
[midi-keys.py example](examples/midi-keys.py).
The example above, and the `midi-keys.py` example both send notes on MIDI
channel 0 (all channels), but you can set this to a specific channel, if you
like, by changing `out_channel=` when you instantiate your `midi` object.

View file

@ -0,0 +1,167 @@
# SPDX-FileCopyrightText: 2021 Sandy Macdonald
#
# SPDX-License-Identifier: MIT
# An advanced example of how to set up a HID keyboard.
# There are three layers, selected by pressing and holding key 0 (bottom left),
# then tapping one of the coloured layer selector keys above it to switch layer.
# The layer colours are as follows:
# * layer 1: pink: numpad-style keys, 0-9, delete, and enter.
# * layer 2: blue: sends strings on each key press
# * layer 3: media controls, rev, play/pause, fwd on row one, vol. down, mute,
# vol. up on row two
# You'll need to connect Keybow 2040 to a computer, as you would with a regular
# USB keyboard.
# Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive.
# NOTE! Requires the adafruit_hid CircuitPython library also!
import board
import time
from keybow2040 import Keybow2040
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keycode import Keycode
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode
# Set up Keybow
i2c = board.I2C()
keybow = Keybow2040(i2c)
keys = keybow.keys
# Set up the keyboard and layout
keyboard = Keyboard(usb_hid.devices)
layout = KeyboardLayoutUS(keyboard)
# Set up consumer control (used to send media key presses)
consumer_control = ConsumerControl(usb_hid.devices)
# Our layers. The key of item in the layer dictionary is the key number on
# Keybow to map to, and the value is the key press to send.
# Note that keys 0-3 are reserved as the modifier and layer selector keys
# respectively.
layer_1 = {4: Keycode.ZERO,
5: Keycode.ONE,
6: Keycode.FOUR,
7: Keycode.SEVEN,
8: Keycode.DELETE,
9: Keycode.TWO,
10: Keycode.FIVE,
11: Keycode.EIGHT,
12: Keycode.ENTER,
13: Keycode.THREE,
14: Keycode.SIX,
15: Keycode.NINE}
layer_2 = {7: "pack ",
11: "my ",
15: "box ",
6: "with ",
10: "five ",
14: "dozen ",
5: "liquor ",
9: "jugs "}
layer_3 = {6: ConsumerControlCode.VOLUME_DECREMENT,
7: ConsumerControlCode.SCAN_PREVIOUS_TRACK,
10: ConsumerControlCode.MUTE,
11: ConsumerControlCode.PLAY_PAUSE,
14: ConsumerControlCode.VOLUME_INCREMENT,
15: ConsumerControlCode.SCAN_NEXT_TRACK}
layers = {1: layer_1,
2: layer_2,
3: layer_3}
# Define the modifier key and layer selector keys
modifier = keys[0]
selectors = {1: keys[1],
2: keys[2],
3: keys[3]}
# Start on layer 1
current_layer = 1
# The colours for each layer
colours = {1: (255, 0, 255),
2: (0, 255, 255),
3: (255, 255, 0)}
layer_keys = range(4, 16)
# Set the LEDs for each key in the current layer
for k in layers[current_layer].keys():
keys[k].set_led(*colours[current_layer])
# To prevent the strings (as opposed to single key presses) that are sent from
# refiring on a single key press, the debounce time for the strings has to be
# longer.
short_debounce = 0.025
long_debounce = 0.25
debounce = 0.025
fired = False
while True:
# Always remember to call keybow.update()!
keybow.update()
# This handles the modifier and layer selector behaviour
if modifier.held:
# If the modifier key is held, light up the layer selector keys
for layer in layers.keys():
keys[layer].set_led(*colours[layer])
# Change layer if layer key is pressed
if current_layer != layer:
if selectors[layer].pressed:
current_layer = layer
# Set the key LEDs first to off, then to their layer colour
for k in layer_keys:
keys[k].set_led(0, 0, 0)
for k in layers[layer].keys():
keys[k].set_led(*colours[layer])
# Turn off the layer selector LEDs if the modifier isn't held
else:
for layer in layers.keys():
keys[layer].led_off()
# Loop through all of the keys in the layer and if they're pressed, get the
# key code from the layer's key map
for k in layers[current_layer].keys():
if keys[k].pressed:
key_press = layers[current_layer][k]
# If the key hasn't just fired (prevents refiring)
if not fired:
fired = True
# Send the right sort of key press and set debounce for each
# layer accordingly (layer 2 needs a long debounce)
if current_layer == 1:
debounce = short_debounce
keyboard.send(key_press)
elif current_layer == 2:
debounce = long_debounce
layout.write(key_press)
elif current_layer == 3:
debounce = short_debounce
consumer_control.send(key_press)
# If enough time has passed, reset the fired variable
if fired and time.monotonic() - keybow.time_of_last_press > debounce:
fired = False

View file

@ -1,3 +1,16 @@
# SPDX-FileCopyrightText: 2021 Sandy Macdonald
#
# SPDX-License-Identifier: MIT
# A simple example of how to set up a keymap and HID keyboard on Keybow 2040.
# You'll need to connect Keybow 2040 to a computer, as you would with a regular
# USB keyboard.
# Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive.
# NOTE! Requires the adafruit_hid CircuitPython library also!
import board import board
from keybow2040 import Keybow2040 from keybow2040 import Keybow2040
@ -6,13 +19,16 @@ from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keycode import Keycode from adafruit_hid.keycode import Keycode
# Set up Keybow
i2c = board.I2C() i2c = board.I2C()
keybow = Keybow2040(i2c) keybow = Keybow2040(i2c)
keys = keybow.keys keys = keybow.keys
# Set up the keyboard and layout
keyboard = Keyboard(usb_hid.devices) keyboard = Keyboard(usb_hid.devices)
layout = KeyboardLayoutUS(keyboard) layout = KeyboardLayoutUS(keyboard)
# A map of keycodes that will be mapped sequentially to each of the keys, 0-15
keymap = [Keycode.ZERO, keymap = [Keycode.ZERO,
Keycode.ONE, Keycode.ONE,
Keycode.TWO, Keycode.TWO,
@ -30,11 +46,23 @@ keymap = [Keycode.ZERO,
Keycode.E, Keycode.E,
Keycode.F] Keycode.F]
# The colour to set the keys when pressed, yellow.
rgb = (255, 255, 0)
# Attach handler functions to all of the keys
for key in keys: for key in keys:
# A press handler that sends the keycode and turns on the LED
@keybow.on_press(key) @keybow.on_press(key)
def press_handler(key): def press_handler(key):
keycode = keymap[key.number] keycode = keymap[key.number]
keyboard.send(keycode) keyboard.send(keycode)
key.set_led(*rgb)
# A release handler that turns off the LED
@keybow.on_release(key)
def release_handler(key):
key.led_off()
while True: while True:
# Always remember to call keybow.update()!
keybow.update() keybow.update()

View file

@ -11,7 +11,7 @@
# Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive. # Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive.
# NOTE! Requires the adafruit_midi CircuitPython library! # NOTE! Requires the adafruit_midi CircuitPython library also!
import time import time
import board import board

View file

@ -10,7 +10,7 @@
# Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive. # Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive.
# NOTE! Requires the adafruit_midi CircuitPython library! # NOTE! Requires the adafruit_midi CircuitPython library also!
import time import time
import board import board