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 # Encoder module
Add twist control to your keyboard! Volume, zoom, anything you want. Add twist control to your keyboard! Volume, zoom, anything you want
## Enabling the extension ## 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, 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
and an encoder_map. The encoder_map is modeled after the keymap and works the 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 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.
layers that you don't require any action. The encoder supports a velocity mode The encoder supports a velocity mode if you desire to make something for video or sound editing.
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.
## How to use
## Configuration How to use this module in your main / code file
There is a complete example in the Atreus62 [main.py](/boards/atreus62/main.py) 1. load the module
```python
Create the encoder_map. from kmk.modules.encoder import EncoderHandler
encoder_handler = EncoderHandler()
> Anatomy of an encoder_map tuple: (increment_key, decrement_key, keys presses per encoder click) keyboard.modules = [layers, modtap, encoder_handler]
> `encoder_map` is a Nested List with a Tuple as the list element (`List[List[Tuple(Key,Key,Int)]]`) ```
```python 2. Define the pins for each encoder (pin_a, pin_b, pin_button, True for an inversed encoder)
from kmk.modules.encoder import EncoderHandler # import the Encoder module ```python
encoder_handler.pins = ((board.GP17, board.GP15, board.GP14, False), (encoder 2 definition), etc. )
Zoom_in = KC.LCTRL(KC.EQUAL) ```
Zoom_out = KC.LCTRL(KC.MINUS)
3. Define the mapping of keys to be called (1 / layer)
# create the encoder map, modeled after the keymap ```python
encoder_map = [ # You can optionally predefine combo keys as for your layout
[ Zoom_in = KC.LCTRL(KC.EQUAL)
# Only 1 encoder is being used, so only one tuple per layer is required Zoom_out = KC.LCTRL(KC.MINUS)
# 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), 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
# only one key press sent per encoder click ((KC.NO, KC.NO, KC.NO),(encoder 2 definition), etc. ), # Layer 4
(Zoom_in, Zoom_out,1), ]
], ```
[
# No action keys sent here, the resolution is a dummy number, to be
# removed in the future.
(_______,_______,1),# 4. Encoder methods on_move_do and on_button_do can be overwritten for complex use cases
]
] ## Full example (with 1 encoder)
# create the encoder instance, and pass in a list of pad_a pins, a list of pad_b ```python
# pins, and the encoder map created above import board
encoder_ext = EncoderHandler([board.D40],[board.D41], encoder_map)
from kmk.kmk_keyboard import KMKKeyboard
# if desired, you can flip the incrfement/decrement direction of the knob by from kmk.consts import UnicodeMode
# setting the is_inerted flag to True. If you turn the knob to the right and from kmk.keys import KC
# the volume goes down, setting this flag will make it go up. It's default from kmk.matrix import DiodeOrientation
# setting is False from kmk.modules.layers import Layers
encoder_ext.encoders[0].is_inverted = True from kmk.modules.encoder import EncoderHandler
# Make sure to add the encoder_ext to the modules list
keyboard.modules = [encoder_ext] 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 # See docs/encoder.md for how to use
from supervisor import ticks_ms
import digitalio
from supervisor import ticks_ms
from kmk.modules import Module from kmk.modules import Module
# NB : not using rotaryio as it requires the pins to be consecutive
class EncoderPadState:
OFF = False
ON = True
class EndcoderDirection:
Left = False
Right = True
class Encoder: 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): VELOCITY_MODE = True
return 'ENCODER_{}({})'.format(idx, self._to_dict())
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 { return {
'Encoder_State': self.encoder_state, 'direction': self.is_inverted and -self._direction or self._direction,
'Direction': self.encoder_direction, 'position': self.is_inverted and -self._pos or self._pos,
'Value': self.encoder_value, 'is_pressed': not self._button_state,
'Position_Change': self.position_change, 'velocity': self._velocity,
'Speed': self.encoder_speed,
'Button_State': self.button_state,
} }
# adapted for CircuitPython from raspi # Called in a loop to refresh encoder state
def PreparePin(self, num): def update_state(self):
if num is not None: # Rotation events
pad = digitalio.DigitalInOut(num) new_state = (self.pin_a.get_value(), self.pin_b.get_value())
pad.direction = digitalio.Direction.INPUT
pad.pull = digitalio.Pull.UP
return pad
else:
return None
# checks encoder pins, reports encoder data if new_state != self._state:
def report(self): # it moves !
new_encoder_state = ( self._movement += 1
self.encoder_pad_lookup[int(self.pad_a.value)], # false / false and true / true are common half steps
self.encoder_pad_lookup[int(self.pad_b.value)], # 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 # when the encoder settles on a position (every 2 steps)
if new_encoder_state == (self.eps.ON, self.eps.OFF): # Turned right 1 if new_state == (True, True):
self.encoder_direction = self.edr.Right if self._movement > 2:
elif new_encoder_state == (self.eps.OFF, self.eps.ON): # Turned left 1 # 1 full step is 4 movements, however, when rotated quickly,
self.encoder_direction = self.edr.Left # some steps may be missed. This makes it behaves more
elif self.encoder_state == (self.eps.ON, self.eps.OFF): # R1 or L3 position # naturally
if new_encoder_state == (self.eps.OFF, self.eps.OFF): # Turned right 1 real_movement = round(self._movement / 4)
self.encoder_direction = self.edr.Right self._pos += self._direction * real_movement
elif new_encoder_state == (self.eps.ON, self.eps.ON): # Turned left 1 if self.on_move_do is not None:
if self.encoder_direction == self.edr.Left: for i in range(real_movement):
self.encoder_value = self.encoder_value - 1 self.on_move_do(self.get_state())
elif self.encoder_state == (self.eps.OFF, self.eps.ON): # R3 or L1 # Reinit to properly identify new movement
if new_encoder_state == (self.eps.OFF, self.eps.OFF): # Turned left 1 self._movement = 0
self.encoder_direction = self.edr.Left self._direction = 0
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
self.encoder_state = new_encoder_state self._state = new_state
if self.vel_mode: # Velocity
self.vel_ts = ticks_ms() 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: # Button events
self.position_change = self.invert_rotation( new_button_state = self.pin_button.get_value()
self.encoder_value, self.last_encoder_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 # returnd knob velocity as milliseconds between position changes (detents)
self.last_encoder_value = self.encoder_value # for backwards compatibility
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)
def vel_report(self): def vel_report(self):
self.encoder_speed = self.vel_ts - self.last_vel_ts print(self._velocity)
self.last_vel_ts = self.vel_ts return self._velocity
return self.encoder_speed
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): class EncoderHandler(Module):
def __init__(self):
encoders = [] self.encoders = []
debug_enabled = False # not working as inttended, do not use for now self.pins = None
self.map = None
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 on_runtime_enable(self, keyboard): def on_runtime_enable(self, keyboard):
return return
@ -175,13 +124,41 @@ class EncoderHandler(Module):
return return
def during_bootup(self, keyboard): 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 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): def before_matrix_scan(self, keyboard):
''' '''
Return value will be injected as an extra matrix update 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): def after_matrix_scan(self, keyboard):
''' '''
@ -200,31 +177,3 @@ class EncoderHandler(Module):
def on_powersave_disable(self, keyboard): def on_powersave_disable(self, keyboard):
return 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 import digitalio
from kmk.kmktime import tick_ms
from kmk.modules import Module from kmk.modules import Module
# NB : not using rotaryio as it requires the pins to be consecutive # NB : not using rotaryio as it requires the pins to be consecutive
@ -26,9 +27,7 @@ from kmk.modules import Module
class Encoder: class Encoder:
_debug = False VELOCITY_MODE = True
_debug_counter = 0
STATES = { STATES = {
# old_pos_a, old_pos_b, new_pos_a, new_pos_b # old_pos_a, old_pos_b, new_pos_a, new_pos_b
# -1 : Left ; 1 : Right ; 0 : we don't care # -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.pin_button = EncoderPin(pin_button, button_type=True)
self.is_inverted = is_inverted self.is_inverted = is_inverted
self._actual_state = (self.pin_a.get_value(), self.pin_b.get_value()) self._state = (self.pin_a.get_value(), self.pin_b.get_value())
self._actual_direction = None self._direction = None
self._actual_pos = 0 self._pos = 0
self._actual_button_state = True self._button_state = True
self._movement_counter = 0 self._velocity = 0
self._movement = 0
self._timestamp = tick_ms()
# callback functions on events. Need to be defined externally # callback functions on events. Need to be defined externally
self.on_move_do = None self.on_move_do = None
@ -65,45 +67,59 @@ class Encoder:
def get_state(self): def get_state(self):
return { return {
'direction': self.is_inverted 'direction': self.is_inverted
and -self._actual_direction and -self._direction
or self._actual_direction, or self._direction,
'position': self.is_inverted and -self._actual_pos or self._actual_pos, 'position': self.is_inverted and -self._pos or self._pos,
'is_pressed': not self._actual_button_state, '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): def update_state(self):
# Rotation events # Rotation events
new_state = (self.pin_a.get_value(), self.pin_b.get_value()) new_state = (self.pin_a.get_value(), self.pin_b.get_value())
if new_state != self._actual_state: if new_state != self._state:
if self._debug: self._movement += 1
print(' ', new_state) new_direction = self.STATES[(self._state, new_state)]
self._movement_counter += 1
new_direction = self.STATES[(self._actual_state, new_state)]
if new_direction != 0: 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 ( 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 ): # if < 2 state changes, it is a misstep
self._movement_counter = 0 self._movement = 0
self._actual_pos += self._actual_direction self._pos += self._direction
if self._debug:
self._debug_counter += 1
print(self._debug_counter, self.get_state())
if self.on_move_do is not None: if self.on_move_do is not None:
self.on_move_do(self.get_state()) 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 # Button events
new_button_state = self.pin_button.get_value() new_button_state = self.pin_button.get_value()
if new_button_state != self._actual_button_state: if new_button_state != self._button_state:
self._actual_button_state = new_button_state self._button_state = new_button_state
if self.on_button_do is not None: if self.on_button_do is not None:
self.on_button_do(self.get_state()) 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: class EncoderPin:
def __init__(self, pin, button_type=False): def __init__(self, pin, button_type=False):
@ -140,7 +156,7 @@ class EncoderHandler(Module):
for idx, pins in enumerate(self.pins): for idx, pins in enumerate(self.pins):
gpio_pins = pins[:3] gpio_pins = pins[:3]
new_encoder = Encoder(*gpio_pins) 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_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) new_encoder.on_button_do = lambda x: self.on_button_do(keyboard, idx, x)
self.encoders.append(new_encoder) self.encoders.append(new_encoder)