Adding advanced HID keyboard example with multiple layers.
This commit is contained in:
parent
065353ae09
commit
3fbf827b1e
5 changed files with 271 additions and 76 deletions
148
README.md
148
README.md
|
@ -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.
|
167
examples/hid-keys-advanced.py
Normal file
167
examples/hid-keys-advanced.py
Normal 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
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue