Raspberry Pi Pico
RGB Keypad Base
The Pico RGB Keypad Base is a Pimoroni product. Like so many of their products, it is absolutely gorgeous. It uses APA102 (dotstar) LEDs to light up a 4x4 silcone keypad of conductive buttons. It is squidgy as far as keypads go but it looks good on your desktop and at a smidgen under £22, is at a reasonable price point.
Pimoroni provide drivers for MicroPython but, to make the most of this, you need to be using the HID library in CircuitPython. Working from the snippets of code that several people had put online, I worked out an approach to using the keypad for keyboard macros. Leon had asked for help with this and was struggling to follow the couple of examples he had found online.
For this project, you need 3 libraries,
What I wanted was a system for defining a set of macros and the colours being used for the buttons. I decided to make this as a list in a separate file. I placed this file in my libraries folder and called it pages.py
The pages file consists of your button layouts. You start by defining the colours you use for not pressed, then pressed. Then you have your shortcut which can be a string, a series of keycodes or a consumer control code. Study the examples to see. You only need to make as many 'pages' as you need up to the limit.
The last button is used to cycle through the pages. Leave the colour and shortcut information blank for this.
from adafruit_hid.keycode import Keycode from adafruit_hid.consumer_control_code import ConsumerControlCode import time # You can have up to 16 different 'pages' of shortcuts. # When you change the page, a button will blink to indicate # which page you are now using. # Each row in these lists is the default button colour (not pressed), # the button colour when pressed, and a # list of the keys that need to be pressed. # You can have any number of keys being pressed. # You can write a string instead of a list of keycodes and have that typed out. # Use \n for a line break. # You can also write a ConsumerControlCode constant for multimedia buttons. btns = [0] * 16 btns[0] = [[(64,0,0), (255,0,0), [Keycode.FIVE,Keycode.ENTER]], [(64,0,0), (255,0,0), "Leon says like all the time."], [(64,0,0), (255,0,0), ConsumerControlCode.VOLUME_INCREMENT], [(64,0,0), (255,0,0), ConsumerControlCode.VOLUME_DECREMENT], [(64,0,0), (255,0,0), "5"], [(64,0,0), (255,0,0), [Keycode.SIX,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.SEVEN,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.EIGHT,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.A,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.B,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.C,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.D,Keycode.ENTER]], [(0,0,64), (255,0,255), [Keycode.E,Keycode.ENTER]], [(0,0,64), (255,0,255), [Keycode.F,Keycode.ENTER]], [(0,0,64), (255,0,255), [Keycode.G,Keycode.ENTER]], [(0,0,0), (255,0,0), []]] btns[1] = [[(64,0,0), (255,0,0), [Keycode.SHIFT,Keycode.L,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.TWO,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.THREE,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.FOUR,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.FIVE,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.SIX,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.SEVEN,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.EIGHT,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.A,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.B,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.C,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.D,Keycode.ENTER]], [(0,0,64), (255,0,255), [Keycode.E,Keycode.ENTER]], [(0,0,64), (255,0,255), [Keycode.F,Keycode.ENTER]], [(0,0,64), (255,0,255), [Keycode.G,Keycode.ENTER]], [(0,0,0), (255,0,0), []]] btns[2] = [[(64,64,0), (255,255,0), [Keycode.SHIFT,Keycode.L]], [(64,64,0), (255,255,0), [Keycode.SHIFT,Keycode.E]], [(64,64,0), (255,255,0), [Keycode.SHIFT,Keycode.O]], [(64,64,0), (255,255,0), [Keycode.SHIFT,Keycode.N,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.FIVE,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.SIX,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.SEVEN,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.EIGHT,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.A,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.B,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.C,Keycode.ENTER]], [(64,0,0), (255,0,0), [Keycode.D,Keycode.ENTER]], [(0,0,64), (255,0,255), [Keycode.E,Keycode.ENTER]], [(0,0,64), (255,0,255), [Keycode.F,Keycode.ENTER]], [(0,0,64), (255,0,255), [Keycode.G,Keycode.ENTER]], [(0,0,0), (255,0,0), []]]
This is the main program. It reads the pages file. When you press the last button to move to the next 'page', a button will blink a few times to show you which page you have moved to.
import time import board import busio import usb_hid from adafruit_bus_device.i2c_device import I2CDevice import adafruit_dotstar 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 from digitalio import DigitalInOut, Direction, Pull from pages import btns cs = DigitalInOut(board.GP17) cs.direction = Direction.OUTPUT cs.value = 0 num_pixels = 16 pixels = adafruit_dotstar.DotStar(board.GP18, board.GP19, num_pixels, brightness=0.1, auto_write=True) i2c = busio.I2C(board.GP5, board.GP4) device = I2CDevice(i2c, 0x20) kbd = Keyboard(usb_hid.devices) layout = KeyboardLayoutUS(kbd) cc = ConsumerControl(usb_hid.devices) def read_button_states(x, y): pressed = [0] * 16 with device: device.write(bytes([0x0])) result = bytearray(2) device.readinto(result) b = result[0] | result[1] << 8 for i in range(x, y): if not (1 << i) & b: pressed[i] = 1 else: pressed[i] = 0 return pressed held = [0] * 16 page = 0 # works out how many pages there are - stops as soon as a page is 'missing' numpages = 0 for i in range(16): if btns[i]==0: break numpages += 1 # subroutine to blink the button indicating which page has been selected. # done whenever a new page is selected def blink_page(p): # turn off all pixels for i in range(16): pixels[i] = (0,0,0) # blink the right button for i in range(10): pixels[p] = (0,0,255) time.sleep(0.1) pixels[p] = (0,0,0) time.sleep(0.1) while True: pressed = read_button_states(0, 16) # find first pressed p = -1 for i in range(16): if pressed[i]: p = i if p>=0 and p<15: # something being pressed pixels[p] = btns[page][p][1] if not held[p]: if type(btns[page][p][2]) is list: kbd.send(*btns[page][p][2]) elif type(btns[page][p][2]) is str: layout.write(btns[page][p][2]) else: cc.send(btns[page][p][2]) held[p] = 1 elif p==15: # change of page if not held[15]: pixels[15] = btns[page][15][1] page = (page + 1) % numpages held[15] = 1 blink_page(page) else: for i in range(16): pixels[i] = btns[page][i][0] held[i] = 0 time.sleep(0.1)