Created
September 15, 2022 08:30
-
-
Save brouberol/f6b784cd08dc578760adec7cf7722393 to your computer and use it in GitHub Desktop.
D&D Ambiance Keypad
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import time | |
import math | |
from pimoroni_rgbkeypad import RGBKeypad | |
# From red to violet | |
## Row1 | |
DARK_RED = (220, 20, 60) | |
RED = (255, 0, 0) | |
ORANGE_RED = (255, 69, 0) | |
ORANGE = (255, 165, 0) | |
## Row 2 | |
GOLD = (255, 215, 0) | |
# YELLOW = (255, 255, 0) | |
GREEN_YELLOW = (173, 255, 47) | |
GREEN = (0, 255, 0) | |
LIME_GREEN = (50, 205, 50) | |
## Row 3 | |
AQUAMARINE = (102, 205, 170) | |
DARK_CYAN = (32, 178, 170) | |
TURQUOISE = (0, 206, 209) | |
SKY_BLUE = (0, 191, 255) | |
## Row4 | |
DARK_BLUE = (65, 105, 225) | |
BLUE_VIOLET = (138, 43, 226) | |
PURPLE = (186, 85, 211) | |
DEEP_PINK = (255, 20, 147) | |
COLORS = [ | |
DARK_RED, | |
RED, | |
ORANGE_RED, | |
ORANGE, | |
GOLD, | |
GREEN_YELLOW, | |
GREEN, | |
LIME_GREEN, | |
AQUAMARINE, | |
DARK_CYAN, | |
TURQUOISE, | |
SKY_BLUE, | |
DARK_BLUE, | |
BLUE_VIOLET, | |
PURPLE, | |
DEEP_PINK, | |
] | |
PAUSE_KEY_INDEX = 15 | |
ACTIVATED_KEY_BRIGHTNESS = 0.5 | |
DEACTIVATED_KEY_BRIGHTNESS = 0.05 | |
BRIGHTNESS_FLUCTUATION_CYCLE_MS = 3000 | |
def fluctuating_brightness(t, cycle): | |
brightness = abs(math.cos(math.pi * t / cycle)) | |
return flatten(value=brightness, min_value=0.05, max_value=0.80) | |
def flatten(value, min_value, max_value): | |
if value < min_value: | |
return min_value | |
elif value > max_value: | |
return max_value | |
return value | |
def initialize_keys(keypad): | |
for i, key in enumerate(keypad.keys): | |
key.color = COLORS[i] | |
key.brightness = DEACTIVATED_KEY_BRIGHTNESS | |
def main(): | |
start_time = time.monotonic() | |
activated_keys = {} | |
keys_being_pressed = {} | |
keypad = RGBKeypad() | |
initialize_keys(keypad) | |
while True: | |
# This is faster than iterating over all the keys everytime | |
# BUT while the operator presses on the key, the key will be | |
# marked as pressed multiple times. We need to keep track of | |
# the keys that are _being_ pressed and only light them up once, | |
# to avoid a flicker effect | |
keys_pressed = keypad.get_keys_pressed() | |
for key_index, key_pressed in enumerate(keys_pressed): | |
# De-register a key that was being pressed if their state indicates | |
# that they are not being pressed. | |
if not key_pressed: | |
if key_index in keys_being_pressed: | |
keys_being_pressed.pop(key_index) | |
# When a key was activated, make its brightness fluctuate, | |
# except if the pause button is activated itself. In that case, | |
# only make the pause button fluctuate and deactivate all other | |
# activated keys, while keeping their activated state, to make it | |
# easy to restore | |
if PAUSE_KEY_INDEX in activated_keys and key_index != PAUSE_KEY_INDEX: | |
key = keypad.keys[key_index] | |
key.brightness = DEACTIVATED_KEY_BRIGHTNESS | |
elif key_index in activated_keys: | |
elapsed_ms = (time.monotonic() - start_time) * 1000 | |
key = keypad.keys[key_index] | |
key.brightness = fluctuating_brightness( | |
elapsed_ms, cycle=BRIGHTNESS_FLUCTUATION_CYCLE_MS | |
) | |
continue | |
# Don't modify a key that is still being pressed, to avoid making it flicker | |
if key_index in keys_being_pressed: | |
continue | |
# When a key was pressed, send the associated keyboard event from the | |
# keypad to the computed it is connected to | |
key = keypad.keys[key_index] | |
keys_being_pressed[key_index] = True | |
# keyboard.send(KEY_INDEX_TO_KEYBOARD_KEY[key_index]) | |
# Toggle the key activation state after it was pressed | |
if key_index in activated_keys: | |
activated_keys.pop(key_index) | |
key.brightness = DEACTIVATED_KEY_BRIGHTNESS | |
state = "off" | |
else: | |
activated_keys[key_index] = True | |
key.brightness = ACTIVATED_KEY_BRIGHTNESS | |
state = "on" | |
message = '{"key": "%s", "state": "%s"}\n' % (str(key_index), state) | |
print(message) # That sends the message over the usb port | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import contextlib | |
with contextlib.redirect_stdout(None): | |
import pygame | |
pygame.init() | |
import json | |
import sys | |
from tqdm import tqdm as progress | |
from pygame import mixer | |
from serial import Serial | |
from serial.tools.list_ports import grep as list_ports | |
PAUSE_KEY = "15" | |
paused = False | |
config = json.load(open("config.json")) | |
cached_sounds = {} | |
progress_bar = progress(config.items()) | |
for key_name, sound_path in progress_bar: | |
progress_bar.set_description(f"Processing {sound_path}") | |
cached_sounds[key_name] = mixer.Sound(sound_path) | |
def process_message(message): | |
global paused | |
key_name = message["key"] | |
if key_name == PAUSE_KEY: | |
if not paused: | |
paused = True | |
pygame.mixer.pause() | |
else: | |
paused = False | |
pygame.mixer.unpause() | |
elif key_name in config: | |
key_channel = int(key_name) | |
try: | |
channel = mixer.Channel(key_channel) | |
except IndexError: | |
return | |
if message["state"] == "on": | |
sound_path = config.get(key_name) | |
if not sound_path: | |
return | |
channel.play(cached_sounds[key_name], loops=-1) | |
else: | |
channel.stop() | |
def main(): | |
mixer.set_num_channels(len(config.keys())) | |
usb_ports = list(list_ports(r"^/dev/cu\.usbmodem.*$")) | |
if not usb_ports: | |
print("No USB-plugged keypad was found") | |
sys.exit(1) | |
usb_device = Serial(usb_ports[0].device) | |
while True: | |
line = usb_device.readline().strip() | |
line = line.decode("utf-8") | |
if not line.startswith("{"): | |
continue | |
try: | |
message = json.loads(line) | |
except ValueError: | |
continue | |
else: | |
process_message(message) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment