New encoder module, with button action support
This commit is contained in:
parent
1222477866
commit
7993a4e415
189
kmk/modules/new_encoder.py
Normal file
189
kmk/modules/new_encoder.py
Normal file
@ -0,0 +1,189 @@
|
||||
# How to use this module in your main / code file
|
||||
#
|
||||
# 1. load the module
|
||||
# from kmk.modules.new_encoder import EncoderHandler
|
||||
# encoder_handler = EncoderHandler()
|
||||
# keyboard.modules = [layers, modtap, encoder_handler]
|
||||
#
|
||||
# 2. Define the pins for each encoder (pin_a, pin_b, pin_button, True for an inversed encoder)
|
||||
# encoder_handler.pins = ((board.GP17, board.GP15, board.GP14, False), (encoder 2 definition), etc. )
|
||||
#
|
||||
# 3. Define the mapping of keys to be called (1 / layer)
|
||||
# encoder_handler.map = [(( KC.A, KC.Z, KC.E),(encoder 2 mapping), (etc.)), # Layer 1
|
||||
# ((KC.A, KC.Z, KC.N1),(encoder 2 mapping), (etc.)), # Layer 2
|
||||
# ((KC.A, KC.Z, KC.N1),(encoder 2 mapping), (etc.)), # Layer 3
|
||||
# ((KC.A, KC.Z, KC.N1),(encoder 2 mapping), (etc.)), # Layer 4
|
||||
# ]
|
||||
# 4. Encoder methods on_move_do and on_button_do can be overwritten for complex use cases
|
||||
#
|
||||
|
||||
import board
|
||||
import digitalio
|
||||
from kmk.modules import Module
|
||||
# NB : not using rotaryio as it requires the pins to be consecutive
|
||||
|
||||
class Encoder:
|
||||
|
||||
_debug = False
|
||||
_debug_counter = 0
|
||||
|
||||
STATES = {
|
||||
# old_pos_a, old_pos_b, new_pos_a, new_pos_b
|
||||
# -1 : Left ; 1 : Right ; 0 : we don't care
|
||||
((True, True), (True, False)): -1,
|
||||
((True, True), (False, True)): 1,
|
||||
((True, False), (False, False)): -1,
|
||||
((True, False), (True, True)): 0,
|
||||
((False, True), (False, False)): 1,
|
||||
((False, True), (True, True)): 0,
|
||||
((False, False), (True, False)): 0,
|
||||
((False, False), (False, True)): 0,
|
||||
((False, False), (True, True)): 0,
|
||||
((False, True), (True, False)): 0,
|
||||
((True, False), (False, True)): 0,
|
||||
((True, True), (False, False)): 0,
|
||||
}
|
||||
|
||||
|
||||
def __init__(self, pin_a, pin_b, pin_button=None, is_inverted=False):
|
||||
self.pin_a = EncoderPin(pin_a)
|
||||
self.pin_b = EncoderPin(pin_b)
|
||||
self.pin_button = EncoderPin(pin_button, button_type=True)
|
||||
self.is_inverted = is_inverted
|
||||
|
||||
self._actual_state = (self.pin_a.get_value(), self.pin_b.get_value())
|
||||
self._actual_direction = None
|
||||
self._actual_pos = 0
|
||||
self._actual_button_state = True
|
||||
self._movement_counter = 0
|
||||
|
||||
# callback functions on events. Need to be defined externally
|
||||
self.on_move_do = None
|
||||
self.on_button_do = None
|
||||
|
||||
|
||||
|
||||
def get_state(self):
|
||||
return({'direction':self.is_inverted and -self._actual_direction or self._actual_direction,
|
||||
'position':self.is_inverted and -self._actual_pos or self._actual_pos,
|
||||
'is_pressed':not self._actual_button_state})
|
||||
|
||||
# to be called in a loop
|
||||
def update_state(self):
|
||||
# Rotation events
|
||||
new_state = (self.pin_a.get_value(), self.pin_b.get_value())
|
||||
if new_state != self._actual_state:
|
||||
if self._debug : print(" ", new_state)
|
||||
self._movement_counter += 1
|
||||
new_direction = self.STATES[(self._actual_state, new_state)]
|
||||
if new_direction != 0:
|
||||
self._actual_direction = new_direction
|
||||
|
||||
# when the encoder settles on a position
|
||||
if new_state == (True, True) and self._movement_counter > 2 : # if < 2 state changes, it is a misstep
|
||||
self._movement_counter = 0
|
||||
self._actual_pos += self._actual_direction
|
||||
if self._debug :
|
||||
self._debug_counter += 1
|
||||
print(self._debug_counter, self.get_state())
|
||||
if self.on_move_do is not None:
|
||||
self.on_move_do(self.get_state())
|
||||
|
||||
self._actual_state = new_state
|
||||
|
||||
# Button events
|
||||
new_button_state = self.pin_button.get_value()
|
||||
if new_button_state != self._actual_button_state:
|
||||
self._actual_button_state = new_button_state
|
||||
if self.on_button_do is not None:
|
||||
self.on_button_do(self.get_state())
|
||||
|
||||
|
||||
|
||||
class EncoderPin:
|
||||
|
||||
def __init__(self, pin, button_type=False):
|
||||
self.pin = pin
|
||||
self.button_type = button_type
|
||||
self.prepare_pin()
|
||||
|
||||
def prepare_pin(self):
|
||||
if self.pin is not None:
|
||||
self.io = digitalio.DigitalInOut(self.pin)
|
||||
self.io.direction = digitalio.Direction.INPUT
|
||||
self.io.pull = digitalio.Pull.UP
|
||||
else:
|
||||
self.io = None
|
||||
|
||||
def get_value(self):
|
||||
return self.io.value
|
||||
|
||||
|
||||
class EncoderHandler(Module):
|
||||
|
||||
def __init__(self):
|
||||
self.encoders = []
|
||||
self.pins = None
|
||||
self.map = None
|
||||
|
||||
def on_runtime_enable(self, keyboard):
|
||||
return
|
||||
|
||||
def on_runtime_disable(self, keyboard):
|
||||
return
|
||||
|
||||
def during_bootup(self, keyboard):
|
||||
if self.pins and self.map:
|
||||
for idx, pins in enumerate(self.pins):
|
||||
gpio_pins = pins[:3]
|
||||
new_encoder = Encoder(*gpio_pins)
|
||||
# In our case, we need to fix keybord and encoder_id for the callback
|
||||
new_encoder.on_move_do = lambda x: self.on_move_do(keyboard, idx, x)
|
||||
new_encoder.on_button_do = lambda x: self.on_button_do(keyboard, idx, x)
|
||||
self.encoders.append(new_encoder)
|
||||
return
|
||||
|
||||
def on_move_do(self, keyboard, encoder_id, state):
|
||||
if self.map:
|
||||
layer_id = keyboard.active_layers[0]
|
||||
# if Left, key index 0 else key index 1
|
||||
if state['direction'] == -1:
|
||||
key_index = 0
|
||||
else:
|
||||
key_index =1
|
||||
key = self.map[layer_id][encoder_id][key_index]
|
||||
keyboard.tap_key(key)
|
||||
|
||||
def on_button_do(self, keyboard, encoder_id, state):
|
||||
if state['is_pressed'] is True:
|
||||
layer_id = keyboard.active_layers[0]
|
||||
key = self.map[layer_id][encoder_id][2]
|
||||
keyboard.tap_key(key)
|
||||
|
||||
|
||||
def before_matrix_scan(self, keyboard):
|
||||
'''
|
||||
Return value will be injected as an extra matrix update
|
||||
'''
|
||||
for encoder in self.encoders:
|
||||
encoder.update_state()
|
||||
|
||||
return keyboard
|
||||
|
||||
def after_matrix_scan(self, keyboard):
|
||||
'''
|
||||
Return value will be replace matrix update if supplied
|
||||
'''
|
||||
return
|
||||
|
||||
def before_hid_send(self, keyboard):
|
||||
return
|
||||
|
||||
def after_hid_send(self, keyboard):
|
||||
return
|
||||
|
||||
def on_powersave_enable(self, keyboard):
|
||||
return
|
||||
|
||||
def on_powersave_disable(self, keyboard):
|
||||
return
|
Loading…
x
Reference in New Issue
Block a user