Skip to content

Instantly share code, notes, and snippets.

@thundernixon
Last active February 2, 2019 01:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thundernixon/6b77fef43f57a814e21542ba8de1af1b to your computer and use it in GitHub Desktop.
Save thundernixon/6b77fef43f57a814e21542ba8de1af1b to your computer and use it in GitHub Desktop.
MiniKbd TwoEncoder – System Music + Video Speed Control

miniKbd Two Encoder setup for System Music + Video Speed Control

This is my code for the miniKbd, customized from the TwoEncoder-Multimode starter setup.

In mode 1, it controls macOS system music – play/pause, up/down volume, and skip/prev song.

In mode 2, it controls the Chrome extension Video Speed Controller – play/pause, skip forward/back by 5 seconds, and speed increase/decrease.

To avoid existing shortcut keys on Vimeo, I have separately configured the Video Speed Controller settings to use W for decrease speed and E for increase speed.

CIRCUITPY lib files required for this setup:

- lib
  - adafruit_hid
    - __init__.py
    - consumer_control_code.mpy
    - consumer_control.mpy
    - keyboard_layout_us.mpy
    - keyboard.mpy
    - keycode.mpy
  - adafruit_dotstar.mpy
- ._encoder.py
- ._main.py

...and the files inside this Gist. The most important one is main.py

Adafruit CircuitPython 3.1.1 on 2018-11-02; Adafruit Trinket M0 with samd21e18
from digitalio import DigitalInOut, Direction, Pull
class Encoder:
"""
CircuitPython Rotary Encoder (without interrupts)
2017_10_16 Andy Clymer
"""
def __init__(self, pin1, pin2, upCallback=None, downCallback=None):
# Init pins
self.d1 = DigitalInOut(pin1)
self.d1.direction = Direction.INPUT
self.d1.pull = Pull.UP
self.d2 = DigitalInOut(pin2)
self.d2.direction = Direction.INPUT
self.d2.pull = Pull.UP
# Callbacks
self.upCallback = upCallback
self.downCallback = downCallback
# Values for comparison
self.prev1 = 0
self.prev2 = 0
self.new1 = 0
self.new2 = 0
self.lastFewDirs = [0, 0, 0, 0]
# Encoder truth table
self.encTable = {
(1, 1): {(1, 0):1, (1, 1):0, (0, 1):-1, (0, 0):2},
(1, 0): {(0, 0):1, (1, 0):0, (1, 1):-1, (0, 1):2},
(0, 0): {(0, 1):1, (0, 0):0, (1, 0):-1, (1, 1):2},
(0, 1): {(1, 1):1, (0, 1):0, (0, 0):-1, (1, 0):2}}
def update(self):
self.new1 = self.d1.value
self.new2 = self.d2.value
# Pin values changed:
if not (self.prev1, self.prev2) == (self.new1, self.new2):
# Determine out the dirction
newDir = self.encTable[(self.prev1, self.prev2)][(self.new1, self.new2)]
self.prev1 = self.new1
self.prev2 = self.new2
# Hold on to this new direction with the last three
self.lastFewDirs = self.lastFewDirs[1:] + [newDir]
# A good reading has four values of the same direction.
# If the list adds up as expected, return the direction and rest the list
s = sum(self.lastFewDirs)
if s == 4:
self.lastFewDirs = [0, 0, 0, 0]
if self.upCallback: self.upCallback()
elif s == -4:
self.lastFewDirs = [0, 0, 0, 0]
if self.downCallback: self.downCallback()
return None
import board
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode
import adafruit_dotstar as dotstar
from digitalio import DigitalInOut, Direction, Pull
from analogio import AnalogIn
from encoder import Encoder
import time
import random
kbd = Keyboard()
cc = ConsumerControl()
dot = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.5)
modeColors = [(0, 255, 128), (255, 0, 0)]
mode = 0
dot[0] = modeColors[0]
prevTurn = time.monotonic()
def fastTurn():
global prevTurn
now = time.monotonic()
diff = now - prevTurn
prevTurn = now
if diff < 0.1:
return True
return False
def enc1Up():
global mode
if mode == 0:
cc.send(ConsumerControlCode.SCAN_NEXT_TRACK)
elif mode == 1:
kbd.press(Keycode.RIGHT_ARROW)
kbd.release_all()
def enc1Down():
global mode
if mode == 0:
cc.send(ConsumerControlCode.SCAN_PREVIOUS_TRACK)
elif mode == 1:
kbd.press(Keycode.LEFT_ARROW)
kbd.release_all()
def enc2Up():
print("yo")
global mode
if mode == 0:
if not fastTurn():
kbd.press(Keycode.ALT)
kbd.press(Keycode.SHIFT)
cc.send(ConsumerControlCode.VOLUME_INCREMENT)
elif mode == 1:
kbd.press(Keycode.E)
kbd.release_all()
def enc2Down():
global mode
if mode == 0:
if not fastTurn():
kbd.press(Keycode.ALT)
kbd.press(Keycode.SHIFT)
cc.send(ConsumerControlCode.VOLUME_DECREMENT)
elif mode == 1:
kbd.press(Keycode.W)
kbd.release_all()
def button1():
global mode
if not fastTurn():
mode += 1
if mode >= len(modeColors):
mode = 0
dot[0] = modeColors[mode]
def button2():
global mode
if not fastTurn():
if mode == 0:
if not fastTurn():
print("press")
cc.send(ConsumerControlCode.PLAY_PAUSE)
if mode == 1:
kbd.press(Keycode.SPACE)
# kbd.press(Keycode.ZERO)
kbd.release_all()
e1 = Encoder(board.D4, board.D3, upCallback=enc1Up, downCallback=enc1Down)
e2 = Encoder(board.D1, board.D0, upCallback=enc2Up, downCallback=enc2Down)
buttonPin = AnalogIn(board.D2)
while True:
e1.update()
e2.update()
v = buttonPin.value
if 65535 > v > 54500:
b1 = False
b2 = False
elif 54500 > v > 38500:
b1 = True
b2 = False
elif 38500 > v > 29400:
b1 = False
b2 = True
elif v < 29400:
b1 = True
b2 = True
if b1:
button1()
if b2:
button2()
import board
from digitalio import DigitalInOut, Direction, Pull
class MiniKbdButtons:
def __init__(self, keyDownCallback=None, keyUpCallback=None,):
# Callbacks
self.downCback = keyDownCallback
self.upCback = keyUpCallback
# Button state and map
self.state = []
self.pins = {}
self.btnMap = [
dict(row="D0", col="D2", id=1),
dict(row="D1", col="D2", id=2),
dict(row="D0", col="D4", id=3),
dict(row="D1", col="D4", id=4),
dict(row="D0", col="D3", id=5),
dict(row="D1", col="D3", id=6)]
self.initPins()
def initPins(self):
# Rows
for pn in ["D0", "D1"]:
p = DigitalInOut(getattr(board, pn))
p.direction = Direction.OUTPUT
self.pins[pn] = p
# Columns
for pn in ["D2", "D4", "D3"]:
p = DigitalInOut(getattr(board, pn))
p.direction = Direction.INPUT
p.pull = Pull.DOWN
self.pins[pn] = p
def update(self):
# Compare old and new state
oldSt = self.state
newSt = []
newBtn = None
for btn in self.btnMap:
r = self.pins[btn["row"]]
r.value = True
if self.pins[btn["col"]].value:
newSt += [btn["id"]]
if not btn["id"] in oldSt:
newBtn = btn["id"]
r.value = False
# Callbacks
for oID in oldSt:
if not oID in newSt:
self.upCback(oID)
if newBtn:
self.downCback(newBtn, newSt)
self.state = newSt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment