Merge Encoder and NewEncoder. Enhance missteps handling

This commit is contained in:
elric91 2021-10-09 17:18:17 +02:00 committed by Kyle Brown
parent d9fb351448
commit ed68ddb79d
3 changed files with 309 additions and 266 deletions

View File

@ -1,59 +1,137 @@
# Encoder
Add twist control to your keyboard! Volume, zoom, anything you want.
## Enabling the extension
The constructor(`EncoderHandler` class) takes a minimum of 3 arguments: a list of pad_a pins, a list of pad_b pins,
and an encoder_map. The encoder_map is modeled after the keymap and works the
same way. It should have as many layers as your keymap, and use KC.NO keys for
layers that you don't require any action. The encoder supports a velocity mode
if you desire to make something for video or sound editing. The direction of
increment/decrement can be changed to make sense for the direction the knob is
turning by setting the is_inverted flag.
## Configuration
There is a complete example in the Atreus62 [main.py](/boards/atreus62/main.py)
Create the encoder_map.
> Anatomy of an encoder_map tuple: (increment_key, decrement_key, keys presses per encoder click)
> `encoder_map` is a Nested List with a Tuple as the list element (`List[List[Tuple(Key,Key,Int)]]`)
```python
from kmk.modules.encoder import EncoderHandler # import the Encoder module
Zoom_in = KC.LCTRL(KC.EQUAL)
Zoom_out = KC.LCTRL(KC.MINUS)
# create the encoder map, modeled after the keymap
encoder_map = [
[
# Only 1 encoder is being used, so only one tuple per layer is required
# Increment key is volume up, decrement key is volume down, and sends 2
# key presses for every "click" felt while turning the encoder.
(KC.VOLU,KC.VOLD,2),
],
[
# only one key press sent per encoder click
(Zoom_in, Zoom_out,1),
],
[
# No action keys sent here, the resolution is a dummy number, to be
# removed in the future.
(_______,_______,1),#
]
]
# create the encoder instance, and pass in a list of pad_a pins, a list of pad_b
# pins, and the encoder map created above
encoder_ext = EncoderHandler([board.D40],[board.D41], encoder_map)
# if desired, you can flip the incrfement/decrement direction of the knob by
# setting the is_inerted flag to True. If you turn the knob to the right and
# the volume goes down, setting this flag will make it go up. It's default
# setting is False
encoder_ext.encoders[0].is_inverted = True
# Make sure to add the encoder_ext to the modules list
keyboard.modules = [encoder_ext]
```
# Encoder module
Add twist control to your keyboard! Volume, zoom, anything you want
## Enabling the extension
The constructor(`EncoderHandler` class) takes a list of encoders, each one defined as a list of pad_a pin, pad_b pin, button_pin and optionnally a flag set to True is youwant it to be reversed
The encoder_map is modeled after the keymap and works the
same way. It should have as many layers (key pressed on "turned left", key pressed on "turned right", key pressed on "knob pressed") as your keymap, and use KC.NO keys for layers that you don't require any action.
The encoder supports a velocity mode if you desire to make something for video or sound editing.
## How to use
How to use this module in your main / code file
1. load the module
```python
from kmk.modules.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)
```python
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)
```python
# You can optionally predefine combo keys as for your layout
Zoom_in = KC.LCTRL(KC.EQUAL)
Zoom_out = KC.LCTRL(KC.MINUS)
encoder_handler.map = [(( KC.VOLD, KC.VOLU, KC.MUTE),(encoder 2 definition), etc. ), # Layer 1
((KC.Zoom_out, KC.Zoom_in, KC.NO),(encoder 2 definition), etc. ), # Layer 2
((KC.A, KC.Z, KC.N1),(encoder 2 definition), etc. ), # Layer 3
((KC.NO, KC.NO, KC.NO),(encoder 2 definition), etc. ), # Layer 4
]
```
4. Encoder methods on_move_do and on_button_do can be overwritten for complex use cases
## Full example (with 1 encoder)
```python
import board
from kmk.kmk_keyboard import KMKKeyboard
from kmk.consts import UnicodeMode
from kmk.keys import KC
from kmk.matrix import DiodeOrientation
from kmk.modules.layers import Layers
from kmk.modules.encoder import EncoderHandler
keyboard = KMKKeyboard()
layers = Layers()
encoder_handler = EncoderHandler()
keyboard.modules = [layers, encoder_handler]
keyboard.col_pins = (
board.GP0, board.GP1, board.GP2, board.GP3, board.GP4, board.GP5,
board.GP6, board.GP7, board.GP8, board.GP9, board.GP10, board.GP11,
board.GP12, board.GP13,
)
keyboard.row_pins = (board.GP28, board.GP27, board.GP22, board.GP26, board.GP21)
keyboard.diode_orientation = DiodeOrientation.COLUMNS
encoder_handler.pins = ((board.GP17, board.GP15, board.GP14, False),)
keyboard.tap_time = 250
keyboard.debug_enabled = False
# Filler keys
_______ = KC.TRNS
xxxxxxx = KC.NO
tbdtbd = KC.A
# Layers
LYR_STD, LYR_EXT, LYR_NUM, LYR_GAME = 0, 1, 2, 3
TO_STD = KC.DF(LYR_STD)
MT_EXT = KC.MO(LYR_EXT)
TO_NUM = KC.MO(LYR_NUM)
TO_GAME = KC.DF(LYR_GAME)
# Keymap
keyboard.keymap = [
# Standard (ISO) Layer
[
KC.ESC , KC.N1 , KC.N2 , KC.N3 , KC.N4 , KC.N5 , KC.N6 , KC.N7 , KC.N8 , KC.N9 , KC.N0 , KC.MINS, KC.EQL , KC.BSPC,
KC.TAB , KC.Q , KC.W , KC.E , KC.R , KC.T , KC.Y , KC.U , KC.I , KC.O , KC.P , KC.LBRC, KC.RBRC, KC.DEL ,
xxxxxxx, KC.A , KC.S , KC.D , KC.F , KC.G , KC.H , KC.J , KC.K , KC.L , KC.SCLN, KC.QUOT, KC.NUHS, xxxxxxx,
KC.LSFT, KC.NUBS, KC.Z , KC.X , KC.C , KC.V , KC.B , KC.N , KC.M , KC.COMM, KC.DOT , KC.SLSH, KC.UP , KC.ENT ,
KC.LCTL, KC.LGUI, xxxxxxx, KC.LALT, MT_EXT , xxxxxxx, KC.SPC , xxxxxxx, KC.RALT, TO_NUM , KC.RSFT, KC.LEFT, KC.DOWN, KC.RGHT,
],
# Extra Keys Layer
[
TO_STD , KC.F1 , KC.F2 , KC.F3 , KC.F4 , KC.F5 , KC.F6 , KC.F7 , KC.F8 , KC.F9 , KC.F10 , KC.F11 , KC.F12 , KC.RESET,
_______, KC.N1 , KC.N2 , KC.N3 , KC.N4 , KC.N5 , KC.N6 , KC.N7 , KC.N8 , KC.N9 , KC.N0 , KC.MINS, KC.EQL , _______,
xxxxxxx, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, xxxxxxx,
KC.LSFT, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC.PGUP, _______,
KC.LCTL, KC.LGUI, xxxxxxx, KC.LALT, MT_EXT , xxxxxxx, _______, xxxxxxx, _______, TO_NUM , _______, KC.HOME, KC.PGDN, KC.END ,
],
# NumPad Layer
[
TO_STD , xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.P7 , KC.P8 , KC.P9 , KC.PSLS, xxxxxxx, xxxxxxx, KC.BSPC,
xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.P4 , KC.P5 , KC.P6 , KC.PAST, xxxxxxx, xxxxxxx, KC.DEL ,
xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.LPRN, KC.P1 , KC.P2 , KC.P3 , KC.PPLS, xxxxxxx, xxxxxxx, xxxxxxx,
xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.RPRN, KC.P0 , KC.PDOT, _______, KC.PMNS, xxxxxxx, xxxxxxx, KC.PENT,
xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, MT_EXT , xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, TO_NUM , xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx,
],
# Gaming Layer
[
TO_STD , xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx,
xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx,
xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx,
xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx,
xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, MT_EXT , xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, TO_NUM , xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx,
],
]
# Rotary Encoder (1 encoder / 1 definition per layer)
encoder_handler.map = ( ((KC.UP, KC.DOWN, KC.MUTE),), # Standard
((KC.VOLD, KC.VOLU, KC.MUTE),), # Extra
((KC.A, KC.Z, KC.N1),), # NumPad not yet properly configured
((KC.A, KC.Z, KC.N1),), # Gaming not yet properly configured
)
if __name__ == "__main__":
keyboard.go()

View File

@ -1,172 +1,121 @@
import digitalio
from supervisor import ticks_ms
# See docs/encoder.md for how to use
import digitalio
from supervisor import ticks_ms
from kmk.modules import Module
class EncoderPadState:
OFF = False
ON = True
class EndcoderDirection:
Left = False
Right = True
# NB : not using rotaryio as it requires the pins to be consecutive
class Encoder:
def __init__(
self,
pad_a,
pad_b,
button_pin=None,
):
self.pad_a = self.PreparePin(pad_a) # board pin for enc pin a
self.pad_a_state = False
self.pad_b = self.PreparePin(pad_b) # board pin for enc pin b
self.pad_b_state = False
self.button_pin = self.PreparePin(button_pin) # board pin for enc btn
self.button_state = None # state of pushbutton on encoder if enabled
self.encoder_value = 0 # clarify what this value is
self.encoder_state = (
self.pad_a_state,
self.pad_b_state,
) # quaderature encoder state
self.encoder_direction = None # arbitrary, tells direction of knob
self.last_encoder_state = None # not used yet
self.resolution = 2 # number of keys sent per position change
self.revolution_count = 20 # position changes per revolution
self.has_button = False # enable/disable button functionality
self.encoder_data = None # 6tuple containing all encoder data
self.position_change = None # revolution count, inc/dec as knob turns
self.last_encoder_value = 0 # not used
self.is_inverted = False # switch to invert knob direction
self.vel_mode = False # enable the velocity output
self.vel_ts = None # velocity timestamp
self.last_vel_ts = 0 # last velocity timestamp
self.encoder_speed = None # ms per position change(4 states)
self.encoder_map = None
self.eps = EncoderPadState()
self.encoder_pad_lookup = {
False: self.eps.OFF,
True: self.eps.ON,
}
self.edr = EndcoderDirection() # lookup for current encoder direction
self.encoder_dir_lookup = {
False: self.edr.Left,
True: self.edr.Right,
}
def __repr__(self, idx):
return 'ENCODER_{}({})'.format(idx, self._to_dict())
VELOCITY_MODE = True
def _to_dict(self):
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._state = (self.pin_a.get_value(), self.pin_b.get_value())
self._direction = None
self._pos = 0
self._button_state = True
self._velocity = 0
self._movement = 0
self._timestamp = ticks_ms()
# callback functions on events. Need to be defined externally
self.on_move_do = None
self.on_button_do = None
def get_state(self):
return {
'Encoder_State': self.encoder_state,
'Direction': self.encoder_direction,
'Value': self.encoder_value,
'Position_Change': self.position_change,
'Speed': self.encoder_speed,
'Button_State': self.button_state,
'direction': self.is_inverted and -self._direction or self._direction,
'position': self.is_inverted and -self._pos or self._pos,
'is_pressed': not self._button_state,
'velocity': self._velocity,
}
# adapted for CircuitPython from raspi
def PreparePin(self, num):
if num is not None:
pad = digitalio.DigitalInOut(num)
pad.direction = digitalio.Direction.INPUT
pad.pull = digitalio.Pull.UP
return pad
else:
return None
# Called in a loop to refresh encoder state
def update_state(self):
# Rotation events
new_state = (self.pin_a.get_value(), self.pin_b.get_value())
# checks encoder pins, reports encoder data
def report(self):
new_encoder_state = (
self.encoder_pad_lookup[int(self.pad_a.value)],
self.encoder_pad_lookup[int(self.pad_b.value)],
)
if new_state != self._state:
# it moves !
self._movement += 1
# false / false and true / true are common half steps
# looking on the step just before helps determining
# the direction
if new_state[0] == new_state[1] and self._state[0] != self._state[1]:
if new_state[1] == self._state[0]:
self._direction = 1
else:
self._direction = -1
if self.encoder_state == (self.eps.ON, self.eps.ON): # Resting position
if new_encoder_state == (self.eps.ON, self.eps.OFF): # Turned right 1
self.encoder_direction = self.edr.Right
elif new_encoder_state == (self.eps.OFF, self.eps.ON): # Turned left 1
self.encoder_direction = self.edr.Left
elif self.encoder_state == (self.eps.ON, self.eps.OFF): # R1 or L3 position
if new_encoder_state == (self.eps.OFF, self.eps.OFF): # Turned right 1
self.encoder_direction = self.edr.Right
elif new_encoder_state == (self.eps.ON, self.eps.ON): # Turned left 1
if self.encoder_direction == self.edr.Left:
self.encoder_value = self.encoder_value - 1
elif self.encoder_state == (self.eps.OFF, self.eps.ON): # R3 or L1
if new_encoder_state == (self.eps.OFF, self.eps.OFF): # Turned left 1
self.encoder_direction = self.edr.Left
elif new_encoder_state == (self.eps.ON, self.eps.ON): # Turned right 1
if self.encoder_direction == self.edr.Right:
self.encoder_value = self.encoder_value + 1
else: # self.encoder_state == '11'
if new_encoder_state == (self.eps.ON, self.eps.OFF): # Turned left 1
self.encoder_direction = self.edr.Left
elif new_encoder_state == (self.eps.OFF, self.eps.ON): # Turned right 1
self.encoder_direction = self.edr.Right # 'R'
elif new_encoder_state == (
self.eps.ON,
self.eps.ON,
): # Skipped intermediate 01 or 10 state, however turn completed
if self.encoder_direction == self.edr.Left:
self.encoder_value = self.encoder_value - 1
elif self.encoder_direction == self.edr.Right:
self.encoder_value = self.encoder_value + 1
# when the encoder settles on a position (every 2 steps)
if new_state == (True, True):
if self._movement > 2:
# 1 full step is 4 movements, however, when rotated quickly,
# some steps may be missed. This makes it behaves more
# naturally
real_movement = round(self._movement / 4)
self._pos += self._direction * real_movement
if self.on_move_do is not None:
for i in range(real_movement):
self.on_move_do(self.get_state())
# Reinit to properly identify new movement
self._movement = 0
self._direction = 0
self.encoder_state = new_encoder_state
self._state = new_state
if self.vel_mode:
self.vel_ts = ticks_ms()
# Velocity
if VELOCITY_MODE:
new_timestamp = ticks_ms()
self._velocity = new_timestamp - self._timestamp
self._timestamp = new_timestamp
if self.encoder_state != self.last_encoder_state:
self.position_change = self.invert_rotation(
self.encoder_value, self.last_encoder_value
)
# Button events
new_button_state = self.pin_button.get_value()
if new_button_state != self._button_state:
self._button_state = new_button_state
if self.on_button_do is not None:
self.on_button_do(self.get_state())
self.last_encoder_state = self.encoder_state
self.last_encoder_value = self.encoder_value
if self.position_change > 0:
self._to_dict()
# return self.increment_key
return 0
elif self.position_change < 0:
self._to_dict()
# return self.decrement_key
return 1
else:
return None
# invert knob direction if encoder pins are soldered backwards
def invert_rotation(self, new, old):
if self.is_inverted:
return -(new - old)
else:
return new - old
# returns knob velocity as milliseconds between position changes(detents)
# returnd knob velocity as milliseconds between position changes (detents)
# for backwards compatibility
def vel_report(self):
self.encoder_speed = self.vel_ts - self.last_vel_ts
self.last_vel_ts = self.vel_ts
return self.encoder_speed
print(self._velocity)
return self._velocity
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):
encoders = []
debug_enabled = False # not working as inttended, do not use for now
def __init__(self, pad_a, pad_b, encoder_map):
self.pad_a = pad_a
self.pad_b = pad_b
self.encoder_count = len(self.pad_a)
self.encoder_map = encoder_map
self.make_encoders()
def __init__(self):
self.encoders = []
self.pins = None
self.map = None
def on_runtime_enable(self, keyboard):
return
@ -175,13 +124,41 @@ class EncoderHandler(Module):
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 define keybord and encoder_id for callbacks
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
'''
return self.get_reports(keyboard)
for encoder in self.encoders:
encoder.update_state()
return keyboard
def after_matrix_scan(self, keyboard):
'''
@ -200,31 +177,3 @@ class EncoderHandler(Module):
def on_powersave_disable(self, keyboard):
return
def make_encoders(self):
for i in range(self.encoder_count):
self.encoders.append(
Encoder(
self.pad_a[i], # encoder pin a
self.pad_b[i], # encoder pin b
)
)
def send_encoder_keys(self, keyboard, encoder_key, encoder_idx):
# position in the encoder map tuple
encoder_resolution = 2
for _ in range(
self.encoder_map[keyboard.active_layers[0]][encoder_idx][encoder_resolution]
):
keyboard.tap_key(
self.encoder_map[keyboard.active_layers[0]][encoder_idx][encoder_key]
)
return keyboard
def get_reports(self, keyboard):
for idx in range(self.encoder_count):
if self.debug_enabled: # not working as inttended, do not use for now
print(self.encoders[idx].__repr__(idx))
encoder_key = self.encoders[idx].report()
if encoder_key is not None:
return self.send_encoder_keys(keyboard, encoder_key, idx)

View File

@ -19,6 +19,7 @@
import digitalio
from kmk.kmktime import tick_ms
from kmk.modules import Module
# NB : not using rotaryio as it requires the pins to be consecutive
@ -26,9 +27,7 @@ from kmk.modules import Module
class Encoder:
_debug = False
_debug_counter = 0
VELOCITY_MODE = True
STATES = {
# old_pos_a, old_pos_b, new_pos_a, new_pos_b
# -1 : Left ; 1 : Right ; 0 : we don't care
@ -52,11 +51,14 @@ class Encoder:
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
self._state = (self.pin_a.get_value(), self.pin_b.get_value())
self._direction = None
self._pos = 0
self._button_state = True
self._velocity = 0
self._movement = 0
self._timestamp = tick_ms()
# callback functions on events. Need to be defined externally
self.on_move_do = None
@ -65,45 +67,59 @@ class Encoder:
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,
and -self._direction
or self._direction,
'position': self.is_inverted and -self._pos or self._pos,
'is_pressed': not self._button_state,
'velocity': self.velocity
}
# to be called in a loop
# Called in a loop to refresh encoder state
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_state != self._state:
self._movement += 1
new_direction = self.STATES[(self._state, new_state)]
if new_direction != 0:
self._actual_direction = new_direction
self._direction = new_direction
# when the encoder settles on a position
# when the encoder settles on a position (every 2 steps)
if (
new_state == (True, True) and self._movement_counter > 2
new_state == (True, True) and self._movement > 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())
self._movement = 0
self._pos += self._direction
if self.on_move_do is not None:
self.on_move_do(self.get_state())
self._actual_state = new_state
self._state = new_state
# Velocity
if VELOCITY_MODE:
new_timestamp = tick_ms()
self._velocity = new_timestamp - self._timestamp
self._timestamp = new_timestamp
# 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 new_button_state != self._button_state:
self._button_state = new_button_state
if self.on_button_do is not None:
self.on_button_do(self.get_state())
# returnd knob velocity as milliseconds between position changes (detents)
def vel_report(self):
return self._velocity
# callback for actions on move (set up externally)
def on_move_do(self, :
return
# callback for actions on button press (set up externally)
def on_button_do:
return
class EncoderPin:
def __init__(self, pin, button_type=False):
@ -140,7 +156,7 @@ class EncoderHandler(Module):
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
# In our case, we need to define keybord and encoder_id for callbacks
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)