Adding much more to README, and adding simple HID keys example.

This commit is contained in:
sandyjmacdonald 2021-03-12 12:38:06 +00:00
parent de2dff4011
commit 74eb624217
2 changed files with 339 additions and 9 deletions

308
README.md
View file

@ -12,7 +12,7 @@ individual keys and the whole Keybow (a collection of Key instances).
![Keybow 2040 with backlit keys on marble background](keybow-2040-github-1.jpg)
## Getting started quickly!
# Getting started quickly!
You'll need to grab the latest version of Adafruit's Keybow 2040-flavoured
CircuitPython, from the link below.
@ -43,6 +43,12 @@ code, and save it in the `code.py` file on your `CIRCUITPY` drive using your
favourite text editor. As soon as you save the `code.py` file, or make any other
changes, then it should load up and run the code!
# In depth
This section covers most of the functionality of the library itself, without
delving into additional functions like USB MIDI or HID (they're both covered
later!)
## Imports and setup
All of your programs will need to start with the following:
@ -103,7 +109,7 @@ press events. Just don't do it.
If you need introduce timed events, then you have to go about it in a slightly
(!!) roundabout fashion, by using `time.monotonic()` a constantly incremented
count of seconds elapsed, and use it to check the time elapsed since your last
event, for example:
event, for example you could do this inside your `while True` loop:
```
time_interval = 10
@ -157,8 +163,11 @@ If we want to check whether key 0 is pressed, we can do so as follows:
```
keys = keybow.keys()
if keys[0].pressed:
# Do something!
while True:
keybow.update()
if keys[0].pressed:
# Do something!
```
The `.pressed` attribute returns a Boolean that is `True` if the key is pressed
@ -180,13 +189,19 @@ This means that we could extend the example above to be:
```
keys = keybow.keys()
if keys[0].pressed:
# Do something!
while True:
keybow.update()
if keys[0].held:
# Do something else!
if keys[0].pressed:
# Do something!
if keys[0].held:
# Do something else!
```
The [reactive-press.py example](examples/reactive-press.py) shows in more detail
how to handle key presses.
## LEDs!
LEDs can be set either globally for all keys, using the `Keybow` class instance,
@ -238,6 +253,33 @@ v = 1.0 # Value
r, g, b = hsv_to_rgb(h, s, v)
```
The [rainbow.py example](examples/rainbow.py) shows a more complex example of
how to animate the keys' LEDs, including the use of the `hsv_to_rgb()` function.
## LED sleep
The `Keybow` class has an `.led_sleep_enabled` attribute that is disabled (set to
`False`) by default, and an `.led_sleep_time` attribute (set to 60 seconds by
default) that determines how many seconds need to elapse before LED sleep is
triggered and the LEDs turn off.
The time elapsed since the last key press is constantly updated when
`keybow.update()` is called in your main loop, and if the `.led_sleep_time` is
exceeded then LED sleep is triggered.
Because keys retain their RGB values when toggled off, when asleep, a tap on any
key will wake all of the LEDs up at their last state before sleep.
Enabling LED sleep with a sleep time of 10 seconds could be done as simply as:
```
keybow.led_sleep_enabled = True
keybow.led_sleep_time = 10
```
There's also a `.sleeping` attribute that returns a Boolean, that you can check
to see whether the LEDs are sleeping or not.
## Attaching functions to keys with decorators
There are three decorators that can be attached to functions to link that
@ -273,4 +315,252 @@ def hold_handler(key):
while True:
keybow.update()
```
```
The [decorators.py example](examples/decorators.py) has another example of how
to use the `.on_hold()` decorator to toggle LEDs on and off when a key is held.
## Key combos
Key combos can provide a way to add additional behaviours to keys that only get
triggered if a combination of keys is pressed. The best way to achieve this is
using the `.held` attribute of a key, meaning that the key can also have a
`.pressed` behaviour too.
Here's a brief example of how you could do this inside your main loop, with key
0 as the modifier key, and key 1 as the action key:
```
keys = keybow.keys
modifier_key = keys[0]
action_key = keys[1]
while True:
keybow.update()
if modifier_key.held and action_key.pressed:
# Do something!
```
Of course, you could chain these together, to require two modifer keys to be
held and a third to be pressed, and so on...
The [colour-picker.py example](examples/colour-picker.py) has an example of
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
This covers setting up a USB HID keyboard and linking physical key presses to
keyboard key presses on a connected computer.
## Setup
USB HID requires the `adafruit_hid` CircuitPython library. Download it from the
link below and drop the `adafruit_hid` folder into the `lib` folder on your
`CIRCUITPY` drive.
[Download the Adafruit HID CircuitPython library](https://github.com/adafruit/Adafruit_CircuitPython_HID)
You'll need to connect your Keybow to a computer using a USB cable, just like
you would with a regular USB keyboard.
## Sending key presses
Here's an example of setting up a keyboard object and sending a `0` key press
when key 0 is pressed, using an `.on_press()` decorator:
```
import board
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
i2c = board.I2C()
keybow = Keybow2040(i2c)
keys = keybow.keys
keyboard = Keyboard(usb_hid.devices)
layout = KeyboardLayoutUS(keyboard)
key = keys[0]
@keybow.on_press(key)
def press_handler(key):
keyboard.send(Keycode.ZERO)
while True:
keybow.update()
```
You can find a list of all of the keycodes available at the
[HID CircuitPython library documentation here](https://circuitpython.readthedocs.io/projects/hid/en/latest/api.html#adafruit-hid-keycode-keycode).
If you wanted to take this a bit further and make a full keymap for your
keyboard, then you could create a list of 16 different keycodes and then use the
number of the key press registered by the `press_handler` function as an index
into your keymap to get the keycode to send for each key.
```
import board
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
i2c = board.I2C()
keybow = Keybow2040(i2c)
keys = keybow.keys
keyboard = Keyboard(usb_hid.devices)
layout = KeyboardLayoutUS(keyboard)
keymap = [Keycode.ZERO,
Keycode.ONE,
Keycode.TWO,
Keycode.THREE,
Keycode.FOUR,
Keycode.FIVE,
Keycode.SIX,
Keycode.SEVEN,
Keycode.EIGHT,
Keycode.NINE,
Keycode.A,
Keycode.B,
Keycode.C,
Keycode.D,
Keycode.E,
Keycode.F]
for key in keys:
@keybow.on_press(key)
def press_handler(key):
keycode = keymap[key.number]
keyboard.send(keycode)
while True:
keybow.update()
```
This code is available in the
hid-keys-simple.py example](examples/hid-keys-simple.py).
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.
`keyboard.send(Keycode.A, Keycode.B)` and so on.
## Sending strings of text
Rather than the incovenience of sending multiple keycodes using
`keyboard.send()`, there's a different method to send whole strings of text at
once, using the `layout` object we created.
```
import board
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
i2c = board.I2C()
keybow = Keybow2040(i2c)
keys = keybow.keys
keyboard = Keyboard(usb_hid.devices)
layout = KeyboardLayoutUS(keyboard)
key = keys[0]
@keybow.on_press(key)
def press_handler(key):
layout.write("Pack my box with five dozen liquor jugs.")
while True:
keybow.update()
```
A press of key 0 will send that whole string of text at once!
Be aware that strings sent like that take a little while to virtually "type",
so you might want to incorporate a delay using `keybow.time_of_last_press`,
and then check against a `time_elapsed` variable created with
`time_elapsed = time.monotonic() - keybow.time_of_last_press`.
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
keycodes that differ from their US counterpart to whatever your is.

View file

@ -0,0 +1,40 @@
import board
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
i2c = board.I2C()
keybow = Keybow2040(i2c)
keys = keybow.keys
keyboard = Keyboard(usb_hid.devices)
layout = KeyboardLayoutUS(keyboard)
keymap = [Keycode.ZERO,
Keycode.ONE,
Keycode.TWO,
Keycode.THREE,
Keycode.FOUR,
Keycode.FIVE,
Keycode.SIX,
Keycode.SEVEN,
Keycode.EIGHT,
Keycode.NINE,
Keycode.A,
Keycode.B,
Keycode.C,
Keycode.D,
Keycode.E,
Keycode.F]
for key in keys:
@keybow.on_press(key)
def press_handler(key):
keycode = keymap[key.number]
keyboard.send(keycode)
while True:
keybow.update()