Files
Pico-RGB-Keypad/code.py
T
2026-01-18 11:54:22 +00:00

334 lines
11 KiB
Python

# RGB Keypad Multi Deck
# V4.1 Author CJ Games Live, V4 Author Gareth Naylor, adapted from code posted on the Pimoroni Forums
# Right row of buttons assigns the function set of the keypad (with the pico to the right of the board)
# 0 - Microsoft Teams / Media
# 1 - General
# 2 - Number Pad
# 3 - F13 - F24
# Import libraries
import time
import board
import busio
import usb_hid
import random
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
# Configure I2C keypad
from digitalio import DigitalInOut, Direction
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.3, auto_write=True)
i2c = busio.I2C(board.GP5, board.GP4)
device = I2CDevice(i2c, 0x20)
kbd = Keyboard(usb_hid.devices)
layout = KeyboardLayoutUS(kbd)
consumer_control = ConsumerControl(usb_hid.devices)
# Map integer values to ConsumerControlCode
INT_TO_CONSUMER_CONTROL = {
178: ConsumerControlCode.RECORD,
179: ConsumerControlCode.FAST_FORWARD,
180: ConsumerControlCode.REWIND,
181: ConsumerControlCode.SCAN_NEXT_TRACK,
182: ConsumerControlCode.SCAN_PREVIOUS_TRACK,
183: ConsumerControlCode.STOP,
184: ConsumerControlCode.EJECT,
205: ConsumerControlCode.PLAY_PAUSE,
233: ConsumerControlCode.VOLUME_INCREMENT,
234: ConsumerControlCode.VOLUME_DECREMENT,
266: ConsumerControlCode.MUTE,
# Add more mappings if needed, see https://docs.circuitpython.org/projects/hid/en/latest/api.html#adafruit_hid.consumer_control_code.ConsumerControlCode
}
# Program functions
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
def toggle(this):
return 0 if this == 1 else 1
def debounce(amount):
time.sleep(amount) # Just wait a bit for the pressed signals to settle down
def handle_led(id, colour):
latch[id] = toggle(latch[id]) # Toggle the latch
if latch[id] == 1:
pixels[id] = colour # Map pixel index to 0-255 range
else:
pixels[id] = _base
def set_set(set_id):
latch[set_id] = 1 # Set the key to latched
pixels[set_id] = _green # Set control set LED to green
for i in range(4):
if i != set_id:
pixels[i] = (0, 0, 0)
latch[i] = 0
# Set all defined keys in the set to the base colour
# Keys not defined are set to off (0,0,0)
for i in range(4, 16):
if button_set[i + (set_id * 16)][0] == "empty":
pixels[i] = (0, 0, 0)
else:
pixels[i] = _base
def startup(base_colour):
for i in range(16):
if i < 4:
pixels[i] = base_colour
else:
pixels[i] = (0, 0, 0)
def send_keycodes(k, x):
codes = button_set[x][0] # Codes to send
latch = button_set[x][2] # Latch boolean (0 or 1)
col = button_set[x][3] # Get the colour
handle_led(k, col) # Set the LED to the correct colour
print(f"Button index: {x}")
print(f"Codes: {codes}")
print(f"Type of codes: {type(codes)}")
if isinstance(codes, int) and codes in INT_TO_CONSUMER_CONTROL:
# Handle ConsumerControlCode based on integer mapping
print("Sending ConsumerControlCode from INT_TO_CONSUMER_CONTROL mapping")
consumer_control.send(INT_TO_CONSUMER_CONTROL[codes])
elif isinstance(codes, ConsumerControlCode):
# Directly send if it is already a ConsumerControlCode
print("Sending ConsumerControlCode directly")
consumer_control.send(codes)
elif isinstance(codes, int): # Handle as a standard HID code
print("Sending HID code directly")
kbd.send(codes)
elif isinstance(codes, str):
# Handle string-based codes (e.g., "Keycode.A,Keycode.B,Keycode.C")
print("Processing string codes")
symbols = codes.split(",")
if len(symbols) == 3:
kbd.send(eval(symbols[0]), eval(symbols[1]), eval(symbols[2]))
elif len(symbols) == 2:
kbd.send(eval(symbols[0]), eval(symbols[1]))
else:
raise ValueError("String code format is invalid. Expected format: 'keycode1,keycode2,keycode3'")
else:
raise TypeError("Button set code must be either a string, int (HID), or ConsumerControlCode.")
if latch == 0:
pixels[k] = _base # Set LED back to base colour if not latched
def send_text(k, x):
text = button_set[x][0] # Text to send
col = button_set[x][3] # Get the colour
handle_led(k, col) # Set the LED to the correct colour
if text == "_random_":
r = random.randint(0, len(messages) - 1)
text = messages[r]
layout.write(text) # Send the text
# Check if the text is "Thanks for a good meeting." and send Enter
if text == "Thanks for a good meeting.":
kbd.send(Keycode.ENTER) # Send Enter key press
# Define colours
_red = (255, 0, 0)
_green = (0, 255, 0)
_blue = (0, 0, 255)
_yellow = (255, 255, 0)
_orange = (255, 103, 0)
_purple = (84, 22, 250)
_base = (64, 64, 64)
_black = (0, 0, 0) # Colour to light the key if defined
# Manually define F13 to F24 using their HID keycodes
F13 = 0x68
F14 = 0x69
F15 = 0x6A
F16 = 0x6B
F17 = 0x6C
F18 = 0x6D
F19 = 0x6E
F20 = 0x6F
F21 = 0x70
F22 = 0x71
F23 = 0x72
F24 = 0x73
# Define button set array
button_set = {}
for i in range(64):
button_set[i] = ["empty", 0, 0, _red]
# ---- 1st BUTTON SET ---- 4 - 15
# (keycodes or text, flag 0=kc 1=text, latch flag 0=no 1=yes, colour)
button_set[12] = ["Keycode.CONTROL,Keycode.SHIFT,Keycode.M", 0, 1, _red] # Mute toggle in Teams
button_set[8] = ["Keycode.CONTROL,Keycode.SHIFT,Keycode.O", 0, 1, _purple] # Video toggle in Teams
button_set[4] = ["Keycode.CONTROL,Keycode.SHIFT,Keycode.K", 0, 1, _orange] # Raise hand toggle in Teams
button_set[13] = [234, 0, 0, _blue] # Volume Up
button_set[9] = [266, 0, 1, _orange] # Mute Volume
button_set[5] = [233, 0, 0, _blue] # Volume Down
button_set[14] = [180, 0, 0, _blue] # Rewind
button_set[10] = [205, 0, 1, _orange] # Play/Pause
button_set[6] = [179, 0, 0, _blue] # Fast Forward
button_set[15] = ["Thanks for a good meeting.", 1, 0, _green] # End of meeting chat
button_set[11] = ["Keycode.CONTROL,Keycode.SHIFT,Keycode.S", 0, 0, _green] # Accept audio call
button_set[7] = ["Keycode.CONTROL,Keycode.SHIFT,Keycode.H", 0, 0, _red] # Leave teams meeting
# ---- 2nd BUTTON SET ---- 20 - 31
# (keycodes or text, flag 0=kc 1=text, latch flag 0=no 1=yes, colour)
#button_set[28]
#button_set[24]
#button_set[20]
#button_set[29]
#button_set[25]
#button_set[21]
button_set[30] = ["Keycode.CONTROL,Keycode.Z", 0, 0, _blue] # Undo
#button_set[26]
button_set[22] = ["Keycode.CONTROL,Keycode.Y", 0, 0, _blue] # Redo
button_set[31] = ["Keycode.CONTROL,Keycode.C", 0, 0, _blue] # Copy
button_set[27] = ["Keycode.CONTROL,Keycode.X", 0, 0, _blue] # Cut
button_set[23] = ["Keycode.CONTROL,Keycode.V", 0, 0, _blue] # Paste
# ---- 3rd BUTTON SET ---- 36 - 47
# (keycodes or text, flag 0=kc 1=text, latch flag 0=no 1=yes, colour)
button_set[44] = [Keycode.SEVEN, 0, 0, _red] # 7
button_set[40] = [Keycode.EIGHT, 0, 0, _red] # 8
button_set[36] = [Keycode.NINE, 0, 0, _red] # 9
button_set[45] = [Keycode.FOUR, 0, 0, _red] # 4
button_set[41] = [Keycode.FIVE, 0, 0, _red] # 5
button_set[37] = [Keycode.SIX, 0, 0, _red] # 6
button_set[46] = [Keycode.ONE, 0, 0, _red] # 1
button_set[42] = [Keycode.TWO, 0, 0, _red] # 2
button_set[38] = [Keycode.THREE, 0, 0, _red] # 3
button_set[47] = [Keycode.PERIOD, 0, 0, _red] # .
button_set[43] = [Keycode.ZERO, 0, 0, _red] # 0
button_set[39] = [Keycode.RETURN, 0, 0, _red] # Enter
# ---- 4th BUTTON SET ---- 52 - 63
# (keycodes or text , flag 0=kc 1=text, latch flag 0=no 1=yes, colour)
button_set[60] = [F13, 0, 0, _blue] # F13
button_set[56] = [F14, 0, 0, _blue] # F14
button_set[52] = [F15, 0, 0, _blue] # F15
button_set[61] = [F16, 0, 0, _blue] # F16
button_set[57] = [F17, 0, 0, _blue] # F17
button_set[53] = [F18, 0, 0, _blue] # F18
button_set[62] = [F19, 0, 0, _blue] # F19
button_set[58] = [F20, 0, 0, _blue] # F20
button_set[54] = [F21, 0, 0, _blue] # F21
button_set[63] = [F22, 0, 0, _blue] # F22
button_set[59] = [F23, 0, 0, _blue] # F23
button_set[55] = [F24, 0, 0, _blue] # F24
# Variable setup
held = [0] * 16 # Setup
latch = [0] * 16 # Setup
_set = 5 # Set the default active button set to > 4 so i.e. no active set!
todo = False
# Main program starts here
startup(_base) # Light up the top row with the base colour as the keys are defined
# Main loop starts here
while True:
pressed = read_button_states(0, 16)
if pressed[0]:
_set = 0
set_set(_set)
debounce(0.25) # Debounce
kbd.release_all()
debounce(0.4)
held = [0] * 16 # Setup
latch = [0] * 16 # Setup
held[_set] = 1
elif pressed[1]:
_set = 1
set_set(_set)
debounce(0.25) # Debounce
kbd.release_all()
debounce(0.4)
held = [0] * 16 # Setup
latch = [0] * 16 # Setup
held[_set] = 1
elif pressed[2]:
_set = 2
set_set(_set)
debounce(0.25) # Debounce
kbd.release_all()
debounce(0.4)
held = [0] * 16 # Setup
latch = [0] * 16 # Setup
held[_set] = 1
elif pressed[3]:
_set = 3
set_set(_set)
debounce(0.25) # Debounce
kbd.release_all()
debounce(0.4)
held = [0] * 16 # Setup
latch = [0] * 16 # Setup
held[_set] = 1
else:
for i in range(4, 16):
if pressed[i] and button_set[i + (_set * 16)][0] != "empty":
# If we get here we have something to do.
todo = True
index = i + (_set * 16)
if not held[i]:
if button_set[index][1] == 0:
send_keycodes(i, index) # Call the function to send keycodes
debounce(0.25)
else:
send_text(i, index) # Call the function to send text
debounce(0.25)
pixels[i] = _base # Because this is sending text we do not latch the LED
latch[i] = 0
kbd.release_all()
held[i] = 1
debounce(0.4)
if button_set[index][2] == 0: # If the button should not latch
pixels[i] = _base # Set LEDs back to base colour
if todo:
for i in range(16):
held[i] = 0 # Set held states to off
todo = False