Skip to content

Commit fb59680

Browse files
authored
Merge pull request #1593 from adafruit/pb-macropad-macros
Add Macropad_Hotkeys project
2 parents 1f9f69d + 2c868f0 commit fb59680

File tree

4 files changed

+244
-0
lines changed

4 files changed

+244
-0
lines changed

Macropad_Hotkeys/code.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
"""
2+
A fairly straightforward macro/hotkey program for Adafruit MACROPAD.
3+
Macro key setups are stored in the /macros folder (configurable below),
4+
load up just the ones you're likely to use. Plug into computer's USB port,
5+
use dial to select an application macro set, press MACROPAD keys to send
6+
key sequences.
7+
"""
8+
9+
# pylint: disable=import-error, unused-import, too-few-public-methods, eval-used
10+
11+
import os
12+
import time
13+
import board
14+
import digitalio
15+
import displayio
16+
import neopixel
17+
import rotaryio
18+
import terminalio
19+
import usb_hid
20+
from adafruit_display_shapes.rect import Rect
21+
from adafruit_display_text import label
22+
from adafruit_hid.keyboard import Keyboard
23+
from adafruit_hid.keycode import Keycode
24+
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
25+
26+
27+
# CONFIGURABLES ------------------------
28+
29+
MACRO_FOLDER = '/macros'
30+
31+
32+
# CLASSES AND FUNCTIONS ----------------
33+
34+
class Key:
35+
""" Class representing the physical hardware of each MACROPAD key. """
36+
DEBOUNCE_TIME = 1 / 50
37+
38+
def __init__(self, keyname):
39+
self.pin = digitalio.DigitalInOut(keyname)
40+
self.pin.direction = digitalio.Direction.INPUT
41+
self.pin.pull = digitalio.Pull.UP
42+
self.last_value = self.pin.value # Initial state
43+
self.last_time = time.monotonic()
44+
45+
def debounce(self):
46+
""" Read a key's current state (hardware pin value), filtering out
47+
any "bounce" noise. This function needs to be called frequently,
48+
once for each key on pad, plus encoder switch. """
49+
value = self.pin.value
50+
if value != self.last_value:
51+
now = time.monotonic()
52+
elapsed = now - self.last_time
53+
if elapsed >= self.DEBOUNCE_TIME:
54+
self.last_value = value
55+
self.last_time = now
56+
return value
57+
return None
58+
59+
class App:
60+
""" Class representing a host-side application, for which we have a set
61+
of macro sequences. """
62+
def __init__(self, appdata):
63+
self.name = appdata['name']
64+
self.macros = appdata['macros']
65+
66+
def switch(self):
67+
""" Activate application settings; update OLED labels and LED
68+
colors. """
69+
GROUP[13].text = self.name # Application name
70+
for i in range(12):
71+
if i < len(self.macros): # Key in use, set label + LED color
72+
PIXELS[i] = self.macros[i][0]
73+
GROUP[i].text = self.macros[i][1]
74+
else: # Key not in use, no label or LED
75+
PIXELS[i] = 0
76+
GROUP[i].text = ''
77+
PIXELS.show()
78+
DISPLAY.refresh()
79+
80+
81+
# INITIALIZATION -----------------------
82+
83+
DISPLAY = board.DISPLAY
84+
DISPLAY.auto_refresh = False
85+
ENCODER = rotaryio.IncrementalEncoder(board.ENCODER_B, board.ENCODER_A)
86+
PIXELS = neopixel.NeoPixel(board.NEOPIXEL, 12, auto_write=False)
87+
KEYBOARD = Keyboard(usb_hid.devices)
88+
LAYOUT = KeyboardLayoutUS(KEYBOARD)
89+
90+
GROUP = displayio.Group(max_size=14)
91+
for KEY_INDEX in range(12):
92+
x = KEY_INDEX % 3
93+
y = KEY_INDEX // 3
94+
GROUP.append(label.Label(terminalio.FONT, text='', color=0xFFFFFF,
95+
anchored_position=((DISPLAY.width - 1) * x / 2,
96+
DISPLAY.height - 1 -
97+
(3 - y) * 12),
98+
anchor_point=(x / 2, 1.0), max_glyphs=15))
99+
GROUP.append(Rect(0, 0, DISPLAY.width, 12, fill=0xFFFFFF))
100+
GROUP.append(label.Label(terminalio.FONT, text='', color=0x000000,
101+
anchored_position=(DISPLAY.width//2, -2),
102+
anchor_point=(0.5, 0.0), max_glyphs=30))
103+
DISPLAY.show(GROUP)
104+
105+
KEYS = []
106+
for pin in (board.KEY1, board.KEY2, board.KEY3, board.KEY4, board.KEY5,
107+
board.KEY6, board.KEY7, board.KEY8, board.KEY9, board.KEY10,
108+
board.KEY11, board.KEY12, board.ENCODER_SWITCH):
109+
KEYS.append(Key(pin))
110+
111+
# Load all the macro key setups from .py files in MACRO_FOLDER
112+
APPS = []
113+
FILES = os.listdir(MACRO_FOLDER)
114+
FILES.sort()
115+
for FILENAME in FILES:
116+
if FILENAME.endswith('.py'):
117+
module = __import__(MACRO_FOLDER + '/' + FILENAME[:-3])
118+
APPS.append(App(module.app))
119+
120+
if not APPS:
121+
print('No valid macro files found')
122+
while True:
123+
pass
124+
125+
LAST_POSITION = None
126+
APP_INDEX = 0
127+
APPS[APP_INDEX].switch()
128+
129+
130+
# MAIN LOOP ----------------------------
131+
132+
while True:
133+
POSITION = ENCODER.position
134+
if POSITION != LAST_POSITION:
135+
APP_INDEX = POSITION % len(APPS)
136+
APPS[APP_INDEX].switch()
137+
LAST_POSITION = POSITION
138+
139+
for KEY_INDEX, KEY in enumerate(KEYS[0: len(APPS[APP_INDEX].macros)]):
140+
action = KEY.debounce()
141+
if action is not None:
142+
sequence = APPS[APP_INDEX].macros[KEY_INDEX][2]
143+
if action is False: # Macro key pressed
144+
if KEY_INDEX < 12:
145+
PIXELS[KEY_INDEX] = 0xFFFFFF
146+
PIXELS.show()
147+
for item in sequence:
148+
if isinstance(item, int):
149+
if item >= 0:
150+
KEYBOARD.press(item)
151+
else:
152+
KEYBOARD.release(item)
153+
else:
154+
LAYOUT.write(item)
155+
elif action is True: # Macro key released
156+
# Release any still-pressed modifier keys
157+
for item in sequence:
158+
if isinstance(item, int) and item >= 0:
159+
KEYBOARD.release(item)
160+
if KEY_INDEX < 12:
161+
PIXELS[KEY_INDEX] = APPS[APP_INDEX].macros[KEY_INDEX][0]
162+
PIXELS.show()
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values
2+
3+
app = { # REQUIRED dict, must be named 'app'
4+
'name' : 'Illustrator', # Application name
5+
'macros' : [ # List of button macros...
6+
# COLOR LABEL KEY SEQUENCE
7+
# 1st row ----------
8+
(0x004000, 'Undo', [Keycode.COMMAND, 'z']),
9+
(0x004000, 'Redo', [Keycode.COMMAND, 'Z']),
10+
(0x303000, 'Pen', 'p'), # Path-drawing tool
11+
# 2nd row ----------
12+
13+
(0x101010, 'Select', 'v'), # Select (path)
14+
(0x400000, 'Reflect', 'o'), # Reflect selection
15+
(0x303000, 'Rect', 'm'), # Draw rectangle
16+
# 3rd row ----------
17+
(0x101010, 'Direct', 'a'), # Direct (point) selection
18+
(0x400000, 'Rotate', 'r'), # Rotate selection
19+
(0x303000, 'Oval', 'l'), # Draw ellipse
20+
# 4th row ----------
21+
(0x101010, 'Eyedrop', 'i'), # Cycle eyedropper/measure modes
22+
(0x400000, 'Scale', 's'), # Scale selection
23+
(0x303000, 'Text', 't'), # Type tool
24+
# Encoder button ---
25+
(0x000000, '', [Keycode.COMMAND, Keycode.OPTION, 'S']) # Save for web
26+
]
27+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values
2+
3+
app = { # REQUIRED dict, must be named 'app'
4+
'name' : 'Photoshop', # Application name
5+
'macros' : [ # List of button macros...
6+
# COLOR LABEL KEY SEQUENCE
7+
# 1st row ----------
8+
(0x004000, 'Undo', [Keycode.COMMAND, 'z']),
9+
(0x004000, 'Redo', [Keycode.COMMAND, 'Z']),
10+
(0x000040, 'Brush', 'B'), # Cycle brush modes
11+
# 2nd row ----------
12+
(0x101010, 'B&W', 'd'), # Default colors
13+
(0x101010, 'Marquee', 'M'), # Cycle rect/ellipse marquee (select)
14+
(0x000040, 'Eraser', 'E'), # Cycle eraser modes
15+
# 3rd row ----------
16+
(0x101010, 'Swap', 'x'), # Swap foreground/background colors
17+
(0x101010, 'Move', 'v'), # Move layer
18+
(0x000040, 'Fill', 'G'), # Cycle fill/gradient modes
19+
# 4th row ----------
20+
(0x101010, 'Eyedrop', 'I'), # Cycle eyedropper/measure modes
21+
(0x101010, 'Wand', 'W'), # Cycle "magic wand" (selection) modes
22+
(0x000040, 'Heal', 'J'), # Cycle "healing" modes
23+
# Encoder button ---
24+
(0x000000, '', [Keycode.COMMAND, Keycode.OPTION, 'S']) # Save for web
25+
]
26+
}

Macropad_Hotkeys/macros/mac-safari.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values
2+
3+
app = { # REQUIRED dict, must be named 'app'
4+
'name' : 'Safari', # Application name
5+
'macros' : [ # List of button macros...
6+
# COLOR LABEL KEY SEQUENCE
7+
# 1st row ----------
8+
(0x004000, '< Back', [Keycode.COMMAND, '[']),
9+
(0x004000, 'Fwd >', [Keycode.COMMAND, ']']),
10+
(0x400000, 'Up', [Keycode.SHIFT, ' ']), # Scroll up
11+
# 2nd row ----------
12+
(0x202000, '< Tab', [Keycode.CONTROL, Keycode.SHIFT, Keycode.TAB]),
13+
(0x202000, 'Tab >', [Keycode.CONTROL, Keycode.TAB]),
14+
(0x400000, 'Down', ' '), # Scroll down
15+
# 3rd row ----------
16+
(0x000040, 'Reload', [Keycode.COMMAND, 'r']),
17+
(0x000040, 'Home', [Keycode.COMMAND, 'H']),
18+
(0x000040, 'Private', [Keycode.COMMAND, 'N']),
19+
# 4th row ----------
20+
(0x000000, 'Ada', [Keycode.COMMAND, 'n', -Keycode.COMMAND,
21+
'www.adafruit.com\n']), # Adafruit in new window
22+
(0x800000, 'Digi', [Keycode.COMMAND, 'n', -Keycode.COMMAND,
23+
'www.digikey.com\n']), # Digi-Key in new window
24+
(0x101010, 'Hacks', [Keycode.COMMAND, 'n', -Keycode.COMMAND,
25+
'www.hackaday.com\n']), # Hack-a-Day in new win
26+
# Encoder button ---
27+
(0x000000, '', [Keycode.COMMAND, 'w']) # Close window/tab
28+
]
29+
}

0 commit comments

Comments
 (0)