├── .gitignore
├── LICENSE
├── README.md
├── demos
└── demo1.py
├── examples
├── colour-picker.py
├── decorators.py
├── hid-keys-simple.py
├── rainbow.py
└── reactive-press.py
├── rgbkeypad-1.jpg
└── rgbkeypad.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 | .idea
9 |
10 | # Distribution / packaging
11 | .Python
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | pip-wheel-metadata/
25 | share/python-wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 | MANIFEST
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .nox/
45 | .coverage
46 | .coverage.*
47 | .cache
48 | nosetests.xml
49 | coverage.xml
50 | *.cover
51 | *.py,cover
52 | .hypothesis/
53 | .pytest_cache/
54 |
55 | # Translations
56 | *.mo
57 | *.pot
58 |
59 | # Django stuff:
60 | *.log
61 | local_settings.py
62 | db.sqlite3
63 | db.sqlite3-journal
64 |
65 | # Flask stuff:
66 | instance/
67 | .webassets-cache
68 |
69 | # Scrapy stuff:
70 | .scrapy
71 |
72 | # Sphinx documentation
73 | docs/_build/
74 |
75 | # PyBuilder
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | .python-version
87 |
88 | # pipenv
89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
92 | # install all needed dependencies.
93 | #Pipfile.lock
94 |
95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
96 | __pypackages__/
97 |
98 | # Celery stuff
99 | celerybeat-schedule
100 | celerybeat.pid
101 |
102 | # SageMath parsed files
103 | *.sage.py
104 |
105 | # Environments
106 | .env
107 | .venv
108 | env/
109 | venv/
110 | ENV/
111 | env.bak/
112 | venv.bak/
113 |
114 | # Spyder project settings
115 | .spyderproject
116 | .spyproject
117 |
118 | # Rope project settings
119 | .ropeproject
120 |
121 | # mkdocs documentation
122 | /site
123 |
124 | # mypy
125 | .mypy_cache/
126 | .dmypy.json
127 | dmypy.json
128 |
129 | # Pyre type checker
130 | .pyre/
131 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Angainor Development
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rgbkeypad-circuitpython
2 | CircuitPython library for the Pimoroni RGB Keypad base - PIM551 - Raspberry Pi Pico.
3 | This is a 16-key keypad with RGB backlit keys. Find out more about RGB Keypad at the link below.
4 |
5 | [Learn more about Pico RGB Keypad at pimoroni.com](https://shop.pimoroni.com/products/pico-rgb-keypad-base)
6 |
7 | This library is a fork of the awesome Keybow 2040 library by @SandyJMacDonald, adjusted for the RGB Keypad.
8 |
9 | [See the Original Keybow 2040 library](https://github.com/sandyjmacdonald/keybow2040-circuitpython)
10 |
11 | The library abstracts away most of the complexity of having to check pin states,
12 | interact with the LED driver library and exposes classes for
13 | individual keys and the whole Keypad (a collection of Key instances).
14 |
15 | 
16 |
17 | # Getting started quickly!
18 |
19 | You'll need to grab the latest version of Adafruit's Pico CircuitPython firmware, from the link below.
20 |
21 | [Download the Adafruit CircuitPython binary for Raspberry Pico and follow install instructions](https://circuitpython.org/board/pimoroni_rgbkeypad/)
22 |
23 | The Adafruit Dotstar LED driver library for CircuitPython is a prerequisite for
24 | this RGB Keypad library, so you'll need to download it from GitHub at the link
25 | below, and then drop the `adafruit_dotstar.mpy` folder into the `lib` folder on
26 | your `CIRCUITPY` drive.
27 |
28 | [Download the Adafruit DotStar CircuitPython library](https://github.com/adafruit/Adafruit_CircuitPython_DotStar/releases)
29 | This lib is part of the Adafruit Bundle.
30 | [Adafruit CircuitPython Bundle](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases)
31 |
32 | Finally, drop the `rgbkeypad.py` file from this library into the `lib` folder
33 | on your `CIRCUITPY` drive also, and you're all set!
34 |
35 | Pick one of the [demos](demos), copy the
36 | code, and save it in the `code.py` file on your `CIRCUITPY` drive using your
37 | favourite text editor. As soon as you save the `code.py` file, or make any other
38 | changes, then it should load up and run the code!
39 |
40 | You can also check and just slightly adapt SandyJMacDonald's examples from his Keybow lib.
41 | Apart from the constructor (no need for an I2C port to be provided), all should be compatible.
42 |
43 | Some exemples have been converted for rgbkeypad, see [examples](examples)
44 |
45 | [Almost compatible Keybow examples](https://github.com/sandyjmacdonald/keybow2040-circuitpython/tree/master/examples)
46 |
47 | ## Index
48 |
49 | * [Library functionality](#library-functionality)
50 | * [Imports and setup](#imports-and-setup)
51 | * [The RgbKeypad class](#the-rgbkeypad-class)
52 | * [An interlude on timing!](#an-interlude-on-timing)
53 | * [Key presses](#key-presses)
54 | * [LEDs!](#leds)
55 | * [LED sleep](#led-sleep)
56 | * [Attaching functions to keys with decorators](#attaching-functions-to-keys-with-decorators)
57 | * [Key combos](#key-combos)
58 | * [USB HID](#usb-hid)
59 | * [Setup](#setup)
60 | * [Sending key presses](#sending-key-presses)
61 | * [Sending strings of text](#sending-strings-of-text)
62 | * [USB MIDI](#usb-midi)
63 | * [Sending MIDI notes](#sending-midi-notes)
64 |
65 | # Library functionality
66 |
67 | This section covers most of the functionality of the library itself, without
68 | delving into additional functions like USB MIDI or HID (they're both covered
69 | later!)
70 |
71 | ## Imports and setup
72 |
73 | All of your programs will need to start with the following:
74 |
75 | ```
76 | from rgbkeypad import RgbKeypad
77 |
78 | keypad = RgbKeypad()
79 | ```
80 |
81 | The `RgbKeypad()` class, imported from the `rgbkeypad` module, is instantiated.
82 | Instantiating this sets up all of the pins, keys and LEDs, and provides access to all of the attributes and methods associated with it.
83 |
84 | ## The RgbKeypad class
85 |
86 | The RgbKeypad class exposes a number of handy attributes and methods. The main one
87 | you'll be interested in is the `.keys` attribute, which is a list of `Key`
88 | class instances, one for each key.
89 |
90 | ```
91 | keys = keypad.keys
92 | ```
93 |
94 | The indices of the keys in that list correspond to their position on the keypad,
95 | staring from the bottom left corner (when the USB connector is at the top),
96 | which is key 0, going upwards in columns, and ending at the top right corner,
97 | which is key 15.
98 |
99 | More about the `Key` class later...
100 |
101 | A **super** important method of the `RgbKeypad` class is `.update()` method. It
102 | updates all of the keys, key states, and other attributes like the time of the
103 | last key press, and sleep state of the LEDs.
104 |
105 | **You need to call this method on your `RgbKeypad` class at the very start of each
106 | iteration of your program's main loop, as follows:**
107 |
108 | ```
109 | while True:
110 | keypad.update()
111 | ```
112 |
113 | ## An interlude on timing!
114 |
115 | Another **super** important thing is **not to include any `time.sleep()`s in
116 | your main loop!** Doing so will ruin the latency and mean that you'll miss key
117 | press events. Just don't do it.
118 |
119 | If you need introduce timed events, then you have to go about it in a slightly
120 | (!!) roundabout fashion, by using `time.monotonic()` a constantly incremented
121 | count of seconds elapsed, and use it to check the time elapsed since your last
122 | event, for example you could do this inside your `while True` loop:
123 |
124 | ```
125 | time_interval = 10
126 |
127 | # An event just happened!
128 |
129 | time_last_fired = time.monotonic()
130 | time_elapsed = 0
131 |
132 | # ... some iterations later
133 |
134 | time_elapsed = time.monotonic() - time_last_fired
135 |
136 | if time_elapsed > time_interval:
137 | # Fire your event again!
138 | ```
139 |
140 | There's a handy `keypad.time_of_last_press` attribute that allows you to quickly
141 | check if a certain amount of time has elapsed since any key press, and that
142 | attribute gets updated every time `keypad.update()` is called.
143 |
144 | ## Key presses
145 |
146 | There are a few ways that you can go about detecting key presses, some
147 | global methods on the `RgbKeypad` class instance, and some on the `Key` class
148 | instances themselves.
149 |
150 | ### RgbKeypad class methods for detecting presses and key states
151 |
152 | `keypad.get_states()` will return a list of the state of all of the keys, in
153 | order, with a state of `0` being not pressed, and `1` being pressed. You can
154 | then loop through that list to do whatever you like.
155 |
156 | `keypad.get_pressed()` will return a list of the key numbers (indices in the
157 | list of keys) that are currently pressed. If you only care about key presses,
158 | then this is an efficient way to do things, especially since you have all the
159 | key numbers in a list.
160 |
161 | `keypad.any_pressed()` returns a Boolean (`True`/`False`) that tells you whether
162 | any keys are currently being pressed. Handy if you want to attach a behaviour to
163 | all of the keys, which this is effectively a proxy for.
164 |
165 | `keypad.none_pressed()` is similar to `.any_pressed()`, in that it returns a
166 | Boolean also, but... you guessed it, it returns `True` if no keys are being
167 | pressed, and `False` if any keys are pressed.
168 |
169 | ### Key class methods for detecting key presses
170 |
171 | If we want to check whether key 0 is pressed, we can do so as follows:
172 |
173 | ```
174 | keys = keypad.keys()
175 |
176 | while True:
177 | keypad.update()
178 |
179 | if keys[0].pressed:
180 | # Do something!
181 | ```
182 |
183 | The `.pressed` attribute returns a Boolean that is `True` if the key is pressed
184 | and `False` if it is not pressed.
185 |
186 | `key.state` is another way to check the state of a key. It will equal `1` if the
187 | key is pressed and `0` if it is not pressed.
188 |
189 | If you want to attach an additional behaviour to your key, you can use
190 | `key.held` to check if a key is being key rather than being pressed and released
191 | quickly. It returns `True` if the key is held and `False` if it is not.
192 |
193 | The default hold time (after which `key.held` is `True`) for all of the keys is
194 | 0.75 seconds, but you can change `key.hold_time` to adjust this to your liking,
195 | on a per key basis.
196 |
197 | This means that we could extend the example above to be:
198 |
199 | ```
200 | keys = keypad.keys()
201 |
202 | while True:
203 | keypad.update()
204 |
205 | if keys[0].pressed:
206 | # Do something!
207 |
208 | if keys[0].held:
209 | # Do something else!
210 | ```
211 |
212 | The [reactive-press.py example](examples/reactive-press.py) shows in more detail
213 | how to handle key presses.
214 |
215 | ## LEDs!
216 |
217 | LEDs can be set either globally for all keys, using the `RgbKeypad` class instance,
218 | or on a per-key basis, either through the `RgbKeypad` class, or using a `Key` class
219 | instance.
220 |
221 | To set all of the keys to the same colour, you can use the `.set_all()` method
222 | of the `RgbKeypad` class, to which you pass three 0-255 integers for red, green,
223 | and blue. For example, to set all of the keys to magenta:
224 |
225 | ```
226 | keypad.set_all(255, 0, 255)
227 | ```
228 |
229 | To set an individal key through your `RgbKeypad` class instance, you can do as
230 | follows, to set key 0 to white:
231 |
232 | ```
233 | keypad.set_led(0, 255, 255, 255)
234 | ```
235 |
236 | To set the colour on the key itself, you could do as follows, again to set key
237 | 0 to white:
238 |
239 | ```
240 | keypad.keys[0].set_led(255, 255, 255)
241 | ```
242 |
243 | A key retains its RGB value, even if it is turned off, so once a key has its
244 | colour set with `key.rgb = (255, 0, 0)` for example, you can turn it off using
245 | `key.led_off()` or even `key.set_led(0, 0, 0)` and then when you turn it back on
246 | with `key.led_on()`, then it will still be red when it comes back on.
247 |
248 | As a convenience, and to avoid having to check `key.lit`, there is a
249 | `key.toggle_led()` method that will toggle the current state of the key's LED
250 | (on to off, and _vice versa_).
251 |
252 | There's a handy `hsv_to_rgb()` function that can be imported from the
253 | `rgbkeypad` module to convert an HSV colour (a tuple of floats from 0.0 to 1.0)
254 | to an RGB colour (a tuple of integers from 0 to 255), as follows:
255 |
256 | ```
257 | from rgbkeypad import hsv_to_rgb
258 |
259 | h = 0.5 # Hue
260 | s = 1.0 # Saturation
261 | v = 1.0 # Value
262 |
263 | r, g, b = hsv_to_rgb(h, s, v)
264 | ```
265 |
266 | The [rainbow.py example](examples/rainbow.py) shows a more complex example of
267 | how to animate the keys' LEDs, including the use of the `hsv_to_rgb()` function.
268 |
269 | ==============
270 |
271 | ## LED sleep
272 |
273 | The `RgbKeypad` class has an `.led_sleep_enabled` attribute that is disabled (set to
274 | `False`) by default, and an `.led_sleep_time` attribute (set to 60 seconds by
275 | default) that determines how many seconds need to elapse before LED sleep is
276 | triggered and the LEDs turn off.
277 |
278 | The time elapsed since the last key press is constantly updated when
279 | `keypad.update()` is called in your main loop, and if the `.led_sleep_time` is
280 | exceeded then LED sleep is triggered.
281 |
282 | Because keys retain their RGB values when toggled off, when asleep, a tap on any
283 | key will wake all of the LEDs up at their last state before sleep.
284 |
285 | Enabling LED sleep with a sleep time of 10 seconds could be done as simply as:
286 |
287 | ```
288 | keypad.led_sleep_enabled = True
289 | keypad.led_sleep_time = 10
290 | ```
291 |
292 | There's also a `.sleeping` attribute that returns a Boolean, that you can check
293 | to see whether the LEDs are sleeping or not.
294 |
295 | ## Attaching functions to keys with decorators
296 |
297 | There are three decorators that can be attached to functions to link that
298 | function to, i) a key press, ii) a key release, or iii) a key hold.
299 |
300 | Here's an example of how you could attach a decorator to a function that lights
301 | up that key yellow when it is pressed, turns all of the LEDs on when held, and
302 | turns them all off when released:
303 |
304 | ```
305 | from rgbkeypad import RgbKeypad
306 |
307 | keypad = RgbKeypad()
308 | keys = keypad.keys
309 |
310 | key = keys[0]
311 | rgb = (255, 255, 0)
312 | key.rgb = rgb
313 |
314 | @keypad.on_press(key)
315 | def press_handler(key):
316 | key.led_on()
317 |
318 | @keypad.on_release(key)
319 | def release_handler(key):
320 | keypad.set_all(0, 0, 0)
321 |
322 | @keypad.on_hold(key)
323 | def hold_handler(key):
324 | keypad.set_all(*rgb)
325 |
326 | while True:
327 | keypad.update()
328 | ```
329 |
330 | The [decorators.py example](examples/decorators.py) has another example of how
331 | to use the `.on_hold()` decorator to toggle LEDs on and off when a key is held.
332 |
333 | ## Key combos
334 |
335 | Key combos can provide a way to add additional behaviours to keys that only get
336 | triggered if a combination of keys is pressed. The best way to achieve this is
337 | using the `.held` attribute of a key, meaning that the key can also have a
338 | `.pressed` behaviour too.
339 |
340 | Here's a brief example of how you could do this inside your main loop, with key
341 | 0 as the modifier key, and key 1 as the action key:
342 |
343 | ```
344 | keys = keypad.keys
345 |
346 | modifier_key = keys[0]
347 | action_key = keys[1]
348 |
349 | while True:
350 | keypad.update()
351 |
352 | if modifier_key.held and action_key.pressed:
353 | # Do something!
354 | ```
355 |
356 | Of course, you could chain these together, to require two modifer keys to be
357 | held and a third to be pressed, and so on...
358 |
359 | The [colour-picker.py example](examples/colour-picker.py) has an example of
360 | using a modifier key to change the hue of the keys.
361 |
362 | # USB HID
363 |
364 | This covers setting up a USB HID keyboard and linking physical key presses to
365 | keyboard key presses on a connected computer.
366 |
367 | ## Setup
368 |
369 | USB HID requires the `adafruit_hid` CircuitPython library. Download it from the
370 | link below and drop the `adafruit_hid` folder into the `lib` folder on your
371 | `CIRCUITPY` drive.
372 |
373 | [Download the Adafruit HID CircuitPython library](https://github.com/adafruit/Adafruit_CircuitPython_HID)
374 |
375 | You'll need to connect your RgbKeypad to a computer using a USB cable, just like
376 | you would with a regular USB keyboard.
377 |
378 | ## Sending key presses
379 |
380 | Here's an example of setting up a keyboard object and sending a `0` key press
381 | when key 0 is pressed, using an `.on_press()` decorator:
382 |
383 | ```
384 | import board
385 | from rgbkeypad import RgbKeypad
386 |
387 | import usb_hid
388 | from adafruit_hid.keyboard import Keyboard
389 | from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
390 | from adafruit_hid.keycode import Keycode
391 |
392 | keypad = RgbKeypad()
393 | keys = keypad.keys
394 |
395 | keyboard = Keyboard(usb_hid.devices)
396 | layout = KeyboardLayoutUS(keyboard)
397 |
398 | key = keys[0]
399 |
400 | @keypad.on_press(key)
401 | def press_handler(key):
402 | keyboard.send(Keycode.ZERO)
403 |
404 | while True:
405 | keypad.update()
406 | ```
407 |
408 | You can find a list of all of the keycodes available at the
409 | [HID CircuitPython library documentation here](https://circuitpython.readthedocs.io/projects/hid/en/latest/api.html#adafruit-hid-keycode-keycode).
410 |
411 | As well as sending a single keypress, you can send multiple keypresses at once,
412 | simply by adding them as additional arguments to `keyboard.send()`, e.g.
413 | `keyboard.send(Keycode.A, Keycode.B)` and so on.
414 |
415 | ## Sending strings of text
416 |
417 | Rather than the inconvenience of sending multiple keycodes using
418 | `keyboard.send()`, there's a different method to send whole strings of text at
419 | once, using the `layout` object we created.
420 |
421 | ```
422 | import board
423 | from rgbkeypad import RgbKeypad
424 |
425 | import usb_hid
426 | from adafruit_hid.keyboard import Keyboard
427 | from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
428 | from adafruit_hid.keycode import Keycode
429 |
430 | keypad = RgbKeypad()
431 | keys = keypad.keys
432 |
433 | keyboard = Keyboard(usb_hid.devices)
434 | layout = KeyboardLayoutUS(keyboard)
435 |
436 | key = keys[0]
437 |
438 | @keypad.on_press(key)
439 | def press_handler(key):
440 | layout.write("Pack my box with five dozen liquor jugs.")
441 |
442 | while True:
443 | keypad.update()
444 | ```
445 |
446 | A press of key 0 will send that whole string of text at once!
447 |
448 | Be aware that strings sent like that take a little while to virtually "type",
449 | so you might want to incorporate a delay using `keypad.time_of_last_press`,
450 | and then check against a `time_elapsed` variable created with
451 | `time_elapsed = time.monotonic() - keypad.time_of_last_press`.
452 |
453 | Also, be aware that the Adafruit HID CircuitPython library only currently
454 | supports US Keyboard layouts, so you'll have to work around that and map any
455 | keycodes that differ from their US counterpart to whatever your is.
456 |
457 | There is ongoing work on that subject, see https://github.com/adafruit/Adafruit_CircuitPython_HID/pull/61
458 |
459 | # USB MIDI
460 |
461 | This covers basic MIDI note messages and how to link them to key presses.
462 |
463 | ## Setup
464 |
465 | USB MIDI requires the `adafruit_midi` CircuitPython library. Download it from
466 | the link below and then drop the `adafruit_midi` folder into the `lib` folder on
467 | your `CIRCUITPY` drive.
468 |
469 | [Download the Adafruit MIDI CircuitPython library](https://github.com/adafruit/Adafruit_CircuitPython_MIDI)
470 |
471 | You'll need to connect your Keypad with a USB cable to a computer running a
472 | software synth or DAW like Ableton Live, to a hardware synth that accepts USB
473 | MIDI, or through a MIDI interface that will convert the USB MIDI messages to
474 | regular serial MIDI through a DIN connector.
475 |
476 | ## Sending MIDI notes
477 |
478 | Here's a complete, minimal example of how to send a single MIDI note (middle C,
479 | or MIDI note number 60) when key 0 is pressed, sending a note on message when
480 | pressed and a note off message when released.
481 |
482 | ```
483 | from rgbkeypad import RgbKeypad
484 |
485 | import usb_midi
486 | import adafruit_midi
487 | from adafruit_midi.note_off import NoteOff
488 | from adafruit_midi.note_on import NoteOn
489 |
490 | keypad = RgbKeypad()
491 | keys = keypad.keys
492 |
493 | midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)
494 |
495 | key = keys[0]
496 | note = 60
497 | velocity = 127
498 |
499 | was_pressed = False
500 |
501 | while True:
502 | keypad.update()
503 |
504 | if key.pressed:
505 | midi.send(NoteOn(note, velocity))
506 | was_pressed = True
507 | elif not key.pressed and was_pressed:
508 | midi.send(NoteOff(note, 0))
509 | was_pressed = False
510 | ```
511 |
512 | There'a more complete example of how to set up all of RgbKeypad's keys with
513 | associated MIDI notes using decorators in the original Keybow Library.
514 |
515 |
--------------------------------------------------------------------------------
/demos/demo1.py:
--------------------------------------------------------------------------------
1 | """
2 | Random colors at every keypress
3 |
4 | Save as code.py on your circuitpy drive.
5 | """
6 |
7 | from rgbkeypad import RgbKeypad
8 |
9 |
10 | def key_pressed(a_key):
11 | print(f"Pressed {a_key}")
12 | keypad.random_colors(x_range=4, y_range=4)
13 |
14 |
15 | if __name__ == "__main__":
16 | # No need for params, the object will instanciate its I2C and SPI port drivers.
17 | keypad = RgbKeypad()
18 | keypad.random_colors(x_range=4, y_range=4)
19 |
20 | # Attach the on press handlers
21 | for key in keypad.keys:
22 | @keypad.on_press(key)
23 | def press_handler(a_key):
24 | key_pressed(a_key.number)
25 |
26 | # Required loop so the lib scans for touch events
27 | while True:
28 | keypad.update()
29 |
--------------------------------------------------------------------------------
/examples/colour-picker.py:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2021 Sandy Macdonald
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | # This example demonstrates the use of a modifier key to pick the colour of the
6 | # keys' LEDs, as well as the LED sleep functionality.
7 |
8 | # Drop the rgbkeypad.py file into your `lib` folder on your `CIRCUITPY` drive.
9 |
10 | from rgbkeypad import RgbKeypad, hsv_to_rgb
11 |
12 | MODIFIER_KEY = 0
13 |
14 | # Set up keypad
15 | keypad = RgbKeypad()
16 |
17 | keys = keypad.keys
18 |
19 | # Enable LED sleep and set a time of 5 seconds before the LEDs turn off.
20 | # They'll turn back on with a tap of any key!
21 | keypad.led_sleep_enabled = True
22 | keypad.led_sleep_time = 5
23 |
24 | # Set up the modifier key. It's 0, or the bottom left key.
25 | modifier_key = keys[MODIFIER_KEY]
26 | modifier_key.modifier = True
27 |
28 | # The starting colour (black/off)
29 | rgb = (0, 0, 0)
30 |
31 | while True:
32 | # Always remember to call keybow.update()!
33 | keypad.update()
34 |
35 | # If the modifier key and any other key are pressed, then set all the
36 | # keys to the selected colour. The second key pressed picks the colour.
37 | if modifier_key.held and keypad.any_pressed:
38 | if len(keypad.get_pressed()) > 1:
39 | hue = max(keypad.get_pressed()) / 15.0
40 | rgb = hsv_to_rgb(hue, 1.0, 1.0)
41 |
42 | keypad.set_all(*rgb)
43 |
--------------------------------------------------------------------------------
/examples/decorators.py:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2021 Sandy Macdonald
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | # This example demonstrates attaching functions to keys using decorators, and
6 | # the ability to turn the LEDs off with led_sleep_enabled and led_sleep_time.
7 |
8 | # Drop the rgbkeypad.py file into your `lib` folder on your `CIRCUITPY` drive.
9 |
10 | from rgbkeypad import RgbKeypad
11 |
12 | # Set up keypad
13 | keypad = RgbKeypad()
14 | keys = keypad.keys
15 |
16 | # Enable LED sleep and set a time of 5 seconds before the LEDs turn off.
17 | # They'll turn back on with a tap of any key!
18 | keypad.led_sleep_enabled = True
19 | keypad.led_sleep_time = 5
20 |
21 | # Loop through the keys and set the RGB colour for the keys to magenta.
22 | for key in keys:
23 | key.rgb = (255, 0, 255)
24 |
25 | # Attach a `on_hold` decorator to the key that toggles the key's LED when
26 | # the key is held (the default hold time is 0.75 seconds).
27 | @keypad.on_hold(key)
28 | def hold_handler(key):
29 | key.toggle_led()
30 |
31 | while True:
32 | # Always remember to call keypad.update()!
33 | keypad.update()
34 |
--------------------------------------------------------------------------------
/examples/hid-keys-simple.py:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2021 Sandy Macdonald
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | # Original file was done for Keybow 2040
6 | # Author: Sandy Macdonald
7 |
8 | # Adaptation for Pimoroni RGB Keypad
9 | # Author: Thierry Chantier
10 |
11 | # A simple example of how to set up a keymap and HID keyboard on Pimoroni RGB Keypad.
12 |
13 | # You'll need to connect Pimoroni RGB Keypad to a computer, as you would with a regular
14 | # USB keyboard.
15 |
16 | # Drop the rgbkeypad.py file into your `lib` folder on your `CIRCUITPY` drive.
17 |
18 | # NOTE! Requires the adafruit_hid CircuitPython library also!
19 |
20 | from rgbkeypad import RgbKeypad
21 |
22 | import usb_hid
23 | from adafruit_hid.keyboard import Keyboard
24 | from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
25 | from adafruit_hid.keycode import Keycode
26 |
27 | # Set up Keypad
28 | keypad = RgbKeypad()
29 | keys = keypad.keys
30 |
31 | # Set up the keyboard and layout
32 | keyboard = Keyboard(usb_hid.devices)
33 | layout = KeyboardLayoutUS(keyboard)
34 |
35 | # A map of keycodes that will be mapped sequentially to each of the keys, 0-15
36 | keymap = [Keycode.ZERO,
37 | Keycode.ONE,
38 | Keycode.TWO,
39 | Keycode.THREE,
40 | Keycode.FOUR,
41 | Keycode.FIVE,
42 | Keycode.SIX,
43 | Keycode.SEVEN,
44 | Keycode.EIGHT,
45 | Keycode.NINE,
46 | Keycode.A,
47 | Keycode.B,
48 | Keycode.C,
49 | Keycode.D,
50 | Keycode.E,
51 | Keycode.F]
52 |
53 | # The colour to set the keys when pressed, yellow.
54 | rgb = (255, 255, 0)
55 |
56 | # Attach handler functions to all of the keys
57 | for key in keys:
58 | # A press handler that sends the keycode and turns on the LED
59 | @keypad.on_press(key)
60 | def press_handler(key):
61 | keycode = keymap[key.number]
62 | print(keycode)
63 | keyboard.send(keycode)
64 | key.set_led(*rgb)
65 |
66 | # A release handler that turns off the LED
67 | @keypad.on_release(key)
68 | def release_handler(key):
69 | key.led_off()
70 |
71 | while True:
72 | # Always remember to call keypad.update()
73 | keypad.update()
--------------------------------------------------------------------------------
/examples/rainbow.py:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2021 Sandy Macdonald
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | # This example displays a rainbow animation on Keybow 2040's keys.
6 |
7 | # Drop the rgbkeypad.py file into your `lib` folder on your `CIRCUITPY` drive.
8 |
9 | import math
10 | from rgbkeypad import RgbKeypad, number_to_xy, hsv_to_rgb
11 |
12 | # Set up keypad
13 | keypad = RgbKeypad()
14 | keys = keypad.keys
15 |
16 | # Increment step to shift animation across keys.
17 | step = 0
18 |
19 | while True:
20 | # Always remember to call keybow.update() on every iteration of your loop!
21 | keypad.update()
22 |
23 | step += 1
24 |
25 | for i in range(16):
26 | # Convert the key number to an x/y coordinate to calculate the hue
27 | # in a matrix style-y.
28 | x, y = number_to_xy(i)
29 |
30 | # Calculate the hue.
31 | hue = (x + y + (step / 20)) / 8
32 | hue = hue - int(hue)
33 | hue = hue - math.floor(hue)
34 |
35 | # Convert the hue to RGB values.
36 | r, g, b = hsv_to_rgb(hue, 1, 1)
37 |
38 | # Display it on the key!
39 | keys[i].set_led(r, g, b)
40 |
--------------------------------------------------------------------------------
/examples/reactive-press.py:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2021 Sandy Macdonald
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | # This example demonstrates how to light keys when pressed.
6 |
7 | # Drop the rgbkeypad.py file into your `lib` folder on your `CIRCUITPY` drive.
8 |
9 | from rgbkeypad import RgbKeypad
10 |
11 | # Set up keypad
12 | keypad = RgbKeypad()
13 | keys = keypad.keys
14 |
15 | # Use cyan as the colour.
16 | rgb = (0, 255, 255)
17 |
18 | while True:
19 | # Always remember to call keypad.update() on every iteration of your loop!
20 | keypad.update()
21 |
22 | # Loop through the keys and set the LED to cyan if pressed, otherwise turn
23 | # it off (set it to black).
24 | for key in keys:
25 | if key.pressed:
26 | key.set_led(*rgb)
27 | else:
28 | key.set_led(0, 0, 0)
29 |
--------------------------------------------------------------------------------
/rgbkeypad-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/rgbkeypad-circuitpython/564f2f4bba3a335abe2c6316c32245b966d5eaa1/rgbkeypad-1.jpg
--------------------------------------------------------------------------------
/rgbkeypad.py:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2021 Sandy Macdonald
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | """
6 | `Pimoroni's Pico RGB Keypad CircuitPython library`
7 | ====================================================
8 |
9 | CircuitPython driver for the Pimoroni Pico RGB Keypad.
10 | From Sandy Macdonald's Keybow 2040 library.
11 |
12 | Drop the rgbkeypad.py file into your `lib` folder on your `CIRCUITPY` drive.
13 |
14 | * Author: Sandy Macdonald
15 | * Author: Angainor Dev
16 |
17 | Notes
18 | --------------------
19 |
20 | **Hardware:**
21 |
22 | * Pimoroni Pico RGB Keypad
23 | _
24 |
25 | **Software and Dependencies:**
26 |
27 | * Adafruit CircuitPython firmware for Raspberry Pi Pico:
28 | _
29 |
30 | * Adafruit Dotstar circuit python library
31 |
32 | """
33 |
34 | import time
35 | import board
36 | import busio
37 | from random import randint
38 | from digitalio import DigitalInOut, Direction, Pull
39 | from adafruit_bus_device.i2c_device import I2CDevice
40 | import adafruit_dotstar
41 |
42 |
43 | NUM_KEYS = 16
44 |
45 | # These are the 16 switches on keypad, with their value.
46 | _PINS = [2**i for i in range(NUM_KEYS)]
47 |
48 |
49 | class RgbKeypad(object):
50 | """
51 | Represents a keypad and hence a set of Key instances with
52 | associated LEDs and key behaviours.
53 |
54 |
55 | """
56 | def __init__(self):
57 | self.pins = _PINS
58 | self.cs = DigitalInOut(board.GP17)
59 | self.cs.direction = Direction.OUTPUT
60 | self.cs.value = 0
61 |
62 | self.pixels = adafruit_dotstar.DotStar(board.GP18, board.GP19, 16, brightness=0.1, auto_write=True)
63 |
64 | i2c = busio.I2C(board.GP5, board.GP4)
65 | self.expander = I2CDevice(i2c, 0x20)
66 |
67 | self.keys = []
68 | self.time_of_last_press = time.monotonic()
69 | self.time_since_last_press = None
70 | self.led_sleep_enabled = False
71 | self.led_sleep_time = 60
72 | self.sleeping = False
73 | self.was_asleep = False
74 | self.last_led_states = None
75 | # self.rotation = 0
76 | self.full_state = [0]
77 | for i in range(len(self.pins)):
78 | _key = Key(i, self.pins[i], self.pixels, self.full_state)
79 | self.keys.append(_key)
80 |
81 | def update(self):
82 | # Call this in each iteration of your while loop to update
83 | # to update everything's state, e.g. `keybow.update()`
84 |
85 | with self.expander:
86 | self.expander.write(bytes([0x0]))
87 | result = bytearray(2)
88 | self.expander.readinto(result)
89 | self.full_state[0] = result[0] | result[1] << 8
90 |
91 | # print("u", self.full_state[0])
92 | for _key in self.keys:
93 | _key.update()
94 |
95 | # Used to work out the sleep behaviour, by keeping track
96 | # of the time of the last key press.
97 | if self.any_pressed():
98 | self.time_of_last_press = time.monotonic()
99 | self.sleeping = False
100 |
101 | self.time_since_last_press = time.monotonic() - self.time_of_last_press
102 |
103 | # If LED sleep is enabled, but not engaged, check if enough time
104 | # has elapsed to engage sleep. If engaged, record the state of the
105 | # LEDs, so it can be restored on wake.
106 | if self.led_sleep_enabled and not self.sleeping:
107 | if time.monotonic() - self.time_of_last_press > self.led_sleep_time:
108 | self.sleeping = True
109 | self.last_led_states = [k.rgb if k.lit else [0, 0, 0] for k in self.keys]
110 | self.set_all(0, 0, 0)
111 | self.was_asleep = True
112 |
113 | # If it was sleeping, but is no longer, then restore LED states.
114 | if not self.sleeping and self.was_asleep:
115 | for k in range(len(self.keys)):
116 | self.keys[k].set_led(*self.last_led_states[k])
117 | self.was_asleep = False
118 |
119 | def set_led(self, number, r, g, b):
120 | # Set an individual key's LED to an RGB value by its number.
121 |
122 | self.keys[number].set_led(r, g, b)
123 |
124 | def set_all(self, r, g, b):
125 | # Set all of Keybow's LEDs to an RGB value.
126 |
127 | if not self.sleeping:
128 | for _key in self.keys:
129 | _key.set_led(r, g, b)
130 | else:
131 | for _key in self.keys:
132 | _key.led_off()
133 |
134 | def random_colors(self, x_range=3, y_range=3):
135 | for x in range(x_range):
136 | for y in range(y_range):
137 | i = x * 4 + y
138 | self.keys[i].set_led(randint(0, 255), randint(0, 255), randint(0, 255))
139 |
140 | def clear_all(self):
141 | for _key in self.keys:
142 | _key.led_off()
143 |
144 | def get_states(self):
145 | # Returns a Boolean list of Keybow's key states
146 | # (0=not pressed, 1=pressed).
147 |
148 | _states = [_key.state for _key in self.keys]
149 | return _states
150 |
151 | def get_pressed(self):
152 | # Returns a list of key numbers currently pressed.
153 |
154 | _pressed = [_key.number for _key in self.keys if _key.state]
155 | return _pressed
156 |
157 | def any_pressed(self):
158 | # Returns True if any key is pressed, False if none are pressed.
159 |
160 | if any(self.get_states()):
161 | return True
162 | else:
163 | return False
164 |
165 | def none_pressed(self):
166 | # Returns True if none of the keys are pressed, False is any key
167 | # is pressed.
168 |
169 | if not any(self.get_states()):
170 | return True
171 | else:
172 | return False
173 |
174 | @staticmethod
175 | def on_press(_key, handler=None):
176 | # Attaches a press function to a key, via a decorator. This is stored as
177 | # `key.press_function` in the key's attributes, and run if necessary
178 | # as part of the key's update function (and hence Keybow's update
179 | # function). It can be attached as follows:
180 |
181 | # @keybow.on_press(key)
182 | # def press_handler(key, pressed):
183 | # if pressed:
184 | # do something
185 | # else:
186 | # do something else
187 |
188 | if _key is None:
189 | return
190 |
191 | def attach_handler(a_handler):
192 | _key.press_function = a_handler
193 |
194 | if handler is not None:
195 | attach_handler(handler)
196 | else:
197 | return attach_handler
198 |
199 | @staticmethod
200 | def on_release(_key, handler=None):
201 | # Attaches a release function to a key, via a decorator. This is stored
202 | # as `key.release_function` in the key's attributes, and run if
203 | # necessary as part of the key's update function (and hence Keybow's
204 | # update function). It can be attached as follows:
205 |
206 | # @keybow.on_release(key)
207 | # def release_handler(key):
208 | # do something
209 |
210 | if _key is None:
211 | return
212 |
213 | def attach_handler(a_handler):
214 | _key.release_function = a_handler
215 |
216 | if handler is not None:
217 | attach_handler(handler)
218 | else:
219 | return attach_handler
220 |
221 | @staticmethod
222 | def on_hold(_key, handler=None):
223 | # Attaches a hold unction to a key, via a decorator. This is stored as
224 | # `key.hold_function` in the key's attributes, and run if necessary
225 | # as part of the key's update function (and hence Keybow's update
226 | # function). It can be attached as follows:
227 |
228 | # @keybow.on_hold(key)
229 | # def hold_handler(key):
230 | # do something
231 |
232 | if _key is None:
233 | return
234 |
235 | def attach_handler(a_handler):
236 | _key.hold_function = a_handler
237 |
238 | if handler is not None:
239 | attach_handler(handler)
240 | else:
241 | return attach_handler
242 |
243 |
244 | class Key:
245 | """
246 | Represents a key on Keypad, with associated value and
247 | LED behaviours.
248 |
249 | :param number: the key number (0-15) to associate with the key
250 | :param mask: the value when pressed (2**key number)
251 | :param pixels: the dotstar instance for the LEDs
252 | :param full_state: a list of the keypad full keys state (int)
253 | """
254 | def __init__(self, number, mask, pixels, full_state):
255 | self.mask = mask
256 | self.number = number
257 | self.full_state = full_state
258 |
259 | self.state = 0
260 | self.pressed = 0
261 | self.last_state = None
262 | self.time_of_last_press = time.monotonic()
263 | self.time_since_last_press = None
264 | self.time_held_for = 0
265 | self.held = False
266 | self.hold_time = 0.75
267 | self.modifier = False
268 | self.rgb = [0, 0, 0]
269 | self.lit = False
270 | self.xy = self.get_xy()
271 | self.x, self.y = self.xy
272 | self.pixels = pixels
273 | self.led_off()
274 | self.press_function = None
275 | self.release_function = None
276 | self.hold_function = None
277 | self.press_func_fired = False
278 | self.hold_func_fired = False
279 | self.debounce = 0.125
280 | self.key_locked = False
281 |
282 | def get_state(self):
283 | # Returns the state of the key (0=not pressed, 1=pressed).
284 | res = 0 if self.full_state[0] & self.mask else 1
285 | return res
286 |
287 | def update(self):
288 | # Updates the state of the key and updates all of its
289 | # attributes.
290 |
291 | self.time_since_last_press = time.monotonic() - self.time_of_last_press
292 |
293 | # Keys get locked during the debounce time.
294 | if self.time_since_last_press < self.debounce:
295 | self.key_locked = True
296 | else:
297 | self.key_locked = False
298 |
299 | self.state = self.get_state()
300 | self.pressed = self.state
301 | update_time = time.monotonic()
302 |
303 | # If there's a `press_function` attached, then call it,
304 | # returning the key object and the pressed state.
305 | if self.press_function is not None and self.pressed and not self.press_func_fired and not self.key_locked:
306 | self.press_function(self)
307 | self.press_func_fired = True
308 | # time.sleep(0.05) # A little debounce
309 |
310 | # If the key has been pressed and releases, then call
311 | # the `release_function`, if one is attached.
312 | if not self.pressed and self.last_state:
313 | if self.release_function is not None:
314 | self.release_function(self)
315 | self.last_state = False
316 | self.press_func_fired = False
317 |
318 | if not self.pressed:
319 | self.time_held_for = 0
320 | self.last_state = False
321 |
322 | # If the key has just been pressed, then record the
323 | # `time_of_last_press`, and update last_state.
324 | elif self.pressed and not self.last_state:
325 | self.time_of_last_press = update_time
326 | self.last_state = True
327 |
328 | # If the key is pressed and held, then update the
329 | # `time_held_for` variable.
330 | elif self.pressed and self.last_state:
331 | self.time_held_for = update_time - self.time_of_last_press
332 | self.last_state = True
333 |
334 | # If the `hold_time` threshold is crossed, then call the
335 | # `hold_function` if one is attached. The `hold_func_fired`
336 | # ensures that the function is only called once.
337 | if self.time_held_for > self.hold_time:
338 | self.held = True
339 | if self.hold_function is not None and not self.hold_func_fired:
340 | self.hold_function(self)
341 | self.hold_func_fired = True
342 | else:
343 | self.held = False
344 | self.hold_func_fired = False
345 |
346 | def get_xy(self):
347 | # Returns the x/y coordinate of a key from 0,0 to 3,3.
348 |
349 | return number_to_xy(self.number)
350 |
351 | def get_number(self):
352 | # Returns the key number, from 0 to 15.
353 |
354 | return xy_to_number(self.x, self.y)
355 |
356 | def is_modifier(self):
357 | # Designates a modifier key, so you can hold the modifier
358 | # and tap another key to trigger additional behaviours.
359 |
360 | if self.modifier:
361 | return True
362 | else:
363 | return False
364 |
365 | def set_led(self, r, g, b):
366 | # Set this key's LED to an RGB value.
367 |
368 | if [r, g, b] == [0, 0, 0]:
369 | self.lit = False
370 | else:
371 | self.lit = True
372 | self.rgb = [r, g, b]
373 |
374 | self.pixels[self.number] = (r, g, b)
375 |
376 | def led_on(self):
377 | # Turn the LED on, using its current RGB value.
378 |
379 | r, g, b = self.rgb
380 | self.set_led(r, g, b)
381 |
382 | def led_off(self):
383 | # Turn the LED off.
384 |
385 | self.set_led(0, 0, 0)
386 |
387 | def led_state(self, state):
388 | # Set the LED's state (0=off, 1=on)
389 |
390 | state = int(state)
391 |
392 | if state == 0:
393 | self.led_off()
394 | elif state == 1:
395 | self.led_on()
396 | else:
397 | return
398 |
399 | def toggle_led(self, rgb=None):
400 | # Toggle the LED's state, retaining its RGB value for when it's toggled
401 | # back on. Can also be passed an RGB tuple to set the colour as part of
402 | # the toggle.
403 |
404 | if rgb is not None:
405 | self.rgb = rgb
406 | if self.lit:
407 | self.led_off()
408 | else:
409 | self.led_on()
410 |
411 | def __str__(self):
412 | # When printed, show the key's state (0 or 1).
413 | return self.state
414 |
415 |
416 | def xy_to_number(x, y):
417 | # Convert an x/y coordinate to key number.
418 | return x + (y * 4)
419 |
420 |
421 | def number_to_xy(number):
422 | # Convert a number to an x/y coordinate.
423 | x = number % 4
424 | y = number // 4
425 | return x, y
426 |
427 |
428 | def hsv_to_rgb(h, s, v):
429 | # Convert an HSV (0.0-1.0) colour to RGB (0-255)
430 | rgb = [v, v, v] # s = 0, default value
431 |
432 | i = int(h * 6.0)
433 |
434 | f = (h * 6.) - i
435 | p, q, t = v * (1. - s), v * (1. - s * f), v * (1. - s * (1. - f))
436 | i %= 6
437 |
438 | if i == 0:
439 | rgb = [v, t, p]
440 | if i == 1:
441 | rgb = [q, v, p]
442 | if i == 2:
443 | rgb = [p, v, t]
444 | if i == 3:
445 | rgb = [p, q, v]
446 | if i == 4:
447 | rgb = [t, p, v]
448 | if i == 5:
449 | rgb = [v, p, q]
450 |
451 | rgb = tuple(int(c * 255) for c in rgb)
452 |
453 | return rgb
454 |
--------------------------------------------------------------------------------