Cruft cleaned, timers more accurate
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
'''Enables splitting keyboards wirelessly'''
|
||||||
from adafruit_ble import BLERadio
|
from adafruit_ble import BLERadio
|
||||||
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
|
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
|
||||||
from adafruit_ble.services.nordic import UARTService
|
from adafruit_ble.services.nordic import UARTService
|
||||||
@@ -8,6 +9,8 @@ from kmk.matrix import intify_coordinate
|
|||||||
|
|
||||||
|
|
||||||
class BLE_Split(Extension):
|
class BLE_Split(Extension):
|
||||||
|
'''Enables splitting keyboards wirelessly'''
|
||||||
|
|
||||||
def __init__(self, split_flip=True, split_side=None, hid_type=HIDModes.BLE):
|
def __init__(self, split_flip=True, split_side=None, hid_type=HIDModes.BLE):
|
||||||
self._is_target = True
|
self._is_target = True
|
||||||
self._uart_buffer = []
|
self._uart_buffer = []
|
||||||
@@ -28,7 +31,20 @@ class BLE_Split(Extension):
|
|||||||
return f'BLE_SPLIT({self._to_dict()})'
|
return f'BLE_SPLIT({self._to_dict()})'
|
||||||
|
|
||||||
def _to_dict(self):
|
def _to_dict(self):
|
||||||
return f'BLE_Split( _ble={self._ble} _ble_last_scan={self._ble_last_scan} _is_target={self._is_target} _uart_buffer={self._uart_buffer} _split_flip={self.split_flip} _split_side={self.split_side} )'
|
return f'''
|
||||||
|
BLE_Split( _ble={self._ble}
|
||||||
|
_ble_last_scan={self._ble_last_scan}
|
||||||
|
_is_target={self._is_target}
|
||||||
|
_uart_buffer={self._uart_buffer}
|
||||||
|
_split_flip={self.split_flip}
|
||||||
|
_split_side={self.split_side} )
|
||||||
|
'''
|
||||||
|
|
||||||
|
def on_runtime_enable(self, keyboard):
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_runtime_disable(self, keyboard):
|
||||||
|
return
|
||||||
|
|
||||||
def during_bootup(self, keyboard):
|
def during_bootup(self, keyboard):
|
||||||
self._is_target = bool(self.split_side == 'Left')
|
self._is_target = bool(self.split_side == 'Left')
|
||||||
@@ -49,32 +65,32 @@ class BLE_Split(Extension):
|
|||||||
for cidx in range(cols_to_calc):
|
for cidx in range(cols_to_calc):
|
||||||
keyboard.coord_mapping.append(intify_coordinate(ridx, cidx))
|
keyboard.coord_mapping.append(intify_coordinate(ridx, cidx))
|
||||||
|
|
||||||
def before_matrix_scan(self, keyboard_state):
|
def before_matrix_scan(self, keyboard):
|
||||||
self.check_all_connections()
|
self._check_all_connections()
|
||||||
return self._receive()
|
return self._receive()
|
||||||
|
|
||||||
def after_matrix_scan(self, keyboard_state, matrix_update):
|
def after_matrix_scan(self, keyboard, matrix_update):
|
||||||
if matrix_update:
|
if matrix_update:
|
||||||
matrix_update = self._send(matrix_update)
|
matrix_update = self._send(matrix_update)
|
||||||
return matrix_update
|
return matrix_update
|
||||||
|
return None
|
||||||
|
|
||||||
def check_all_connections(self):
|
def before_hid_send(self, keyboard):
|
||||||
|
return
|
||||||
|
|
||||||
|
def after_hid_send(self, keyboard):
|
||||||
|
return
|
||||||
|
|
||||||
|
def _check_all_connections(self):
|
||||||
|
'''Validates the correct number of BLE connections'''
|
||||||
self._connection_count = len(self._ble.connections)
|
self._connection_count = len(self._ble.connections)
|
||||||
if self._is_target and self._connection_count < 2:
|
if self._is_target and self._connection_count < 2:
|
||||||
self.target_advertise()
|
self._target_advertise()
|
||||||
elif not self._is_target and self._connection_count < 1:
|
elif not self._is_target and self._connection_count < 1:
|
||||||
self.initiator_scan()
|
self._initiator_scan()
|
||||||
|
|
||||||
def connect(self):
|
def _initiator_scan(self):
|
||||||
if not self.check_all_connections() and self.ble_rescan_timer:
|
'''Scans for target device'''
|
||||||
if self.split_side == 'Left':
|
|
||||||
self._is_target = True
|
|
||||||
self.target_advertise()
|
|
||||||
elif self.split_side == 'Right':
|
|
||||||
self._is_target = False
|
|
||||||
self.initiator_scan()
|
|
||||||
|
|
||||||
def initiator_scan(self):
|
|
||||||
self._uart = None
|
self._uart = None
|
||||||
self._uart_connection = None
|
self._uart_connection = None
|
||||||
# See if any existing connections are providing UARTService.
|
# See if any existing connections are providing UARTService.
|
||||||
@@ -83,6 +99,7 @@ class BLE_Split(Extension):
|
|||||||
for connection in self._ble.connections:
|
for connection in self._ble.connections:
|
||||||
if UARTService in connection:
|
if UARTService in connection:
|
||||||
self._uart_connection = connection
|
self._uart_connection = connection
|
||||||
|
self._uart_connection.connection_interval = 11.25
|
||||||
self._uart = self._uart_connection[UARTService]
|
self._uart = self._uart_connection[UARTService]
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -93,14 +110,15 @@ class BLE_Split(Extension):
|
|||||||
print('Scanning')
|
print('Scanning')
|
||||||
if UARTService in adv.services:
|
if UARTService in adv.services:
|
||||||
self._uart_connection = self._ble.connect(adv)
|
self._uart_connection = self._ble.connect(adv)
|
||||||
|
self._uart_connection.connection_interval = 11.25
|
||||||
self._uart = self._uart_connection[UARTService]
|
self._uart = self._uart_connection[UARTService]
|
||||||
self._ble.stop_scan()
|
self._ble.stop_scan()
|
||||||
print('Scan complete')
|
print('Scan complete')
|
||||||
break
|
break
|
||||||
self._ble.stop_scan()
|
self._ble.stop_scan()
|
||||||
return
|
|
||||||
|
|
||||||
def target_advertise(self):
|
def _target_advertise(self):
|
||||||
|
'''Advertises the target for the initiator to find'''
|
||||||
self._ble.stop_advertising()
|
self._ble.stop_advertising()
|
||||||
print('Advertising')
|
print('Advertising')
|
||||||
# Uart must not change on this connection if reconnecting
|
# Uart must not change on this connection if reconnecting
|
||||||
@@ -108,10 +126,8 @@ class BLE_Split(Extension):
|
|||||||
self._uart = UARTService()
|
self._uart = UARTService()
|
||||||
advertisement = ProvideServicesAdvertisement(self._uart)
|
advertisement = ProvideServicesAdvertisement(self._uart)
|
||||||
|
|
||||||
try:
|
self._ble.stop_advertising()
|
||||||
self._ble.start_advertising(advertisement)
|
self._ble.start_advertising(advertisement)
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
self.ble_time_reset()
|
self.ble_time_reset()
|
||||||
while not self.ble_rescan_timer():
|
while not self.ble_rescan_timer():
|
||||||
@@ -123,11 +139,12 @@ class BLE_Split(Extension):
|
|||||||
self._ble.stop_advertising()
|
self._ble.stop_advertising()
|
||||||
|
|
||||||
def ble_rescan_timer(self):
|
def ble_rescan_timer(self):
|
||||||
|
'''If true, the rescan timer is up'''
|
||||||
return bool(ticks_diff(ticks_ms(), self._ble_last_scan) > 5000)
|
return bool(ticks_diff(ticks_ms(), self._ble_last_scan) > 5000)
|
||||||
|
|
||||||
def ble_time_reset(self):
|
def ble_time_reset(self):
|
||||||
|
'''Resets the rescan timer'''
|
||||||
self._ble_last_scan = ticks_ms()
|
self._ble_last_scan = ticks_ms()
|
||||||
return self
|
|
||||||
|
|
||||||
def _send(self, update):
|
def _send(self, update):
|
||||||
if self._uart:
|
if self._uart:
|
||||||
@@ -154,4 +171,3 @@ class BLE_Split(Extension):
|
|||||||
return update
|
return update
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@@ -1,8 +1,20 @@
|
|||||||
|
from micropython import const
|
||||||
|
|
||||||
import kmk.handlers.modtap as modtap
|
import kmk.handlers.modtap as modtap
|
||||||
from kmk.extensions import Extension
|
from kmk.extensions import Extension
|
||||||
from kmk.key_validators import layer_key_validator, mod_tap_validator
|
from kmk.key_validators import layer_key_validator, mod_tap_validator
|
||||||
from kmk.keys import make_argumented_key
|
from kmk.keys import make_argumented_key
|
||||||
from kmk.kmktime import ticks_diff, ticks_ms
|
from kmk.kmktime import accurate_ticks, accurate_ticks_diff
|
||||||
|
|
||||||
|
|
||||||
|
class LayerType:
|
||||||
|
# These number must be reserved for layer timers.
|
||||||
|
MO = const(0)
|
||||||
|
DF = const(1)
|
||||||
|
LM = const(2)
|
||||||
|
LT = const(3)
|
||||||
|
TG = const(4)
|
||||||
|
TT = const(5)
|
||||||
|
|
||||||
|
|
||||||
class Layers(Extension):
|
class Layers(Extension):
|
||||||
@@ -49,6 +61,13 @@ class Layers(Extension):
|
|||||||
on_release=modtap.mt_released,
|
on_release=modtap.mt_released,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
start_time = {
|
||||||
|
LayerType.LT: None,
|
||||||
|
LayerType.TG: None,
|
||||||
|
LayerType.TT: None,
|
||||||
|
LayerType.LM: None,
|
||||||
|
}
|
||||||
|
|
||||||
def df_pressed(self, key, state, *args, **kwargs):
|
def df_pressed(self, key, state, *args, **kwargs):
|
||||||
'''
|
'''
|
||||||
Switches the default layer
|
Switches the default layer
|
||||||
@@ -86,7 +105,6 @@ class Layers(Extension):
|
|||||||
'''
|
'''
|
||||||
state._hid_pending = True
|
state._hid_pending = True
|
||||||
# Sets the timer start and acts like MO otherwise
|
# Sets the timer start and acts like MO otherwise
|
||||||
state._start_time['lm'] = ticks_ms()
|
|
||||||
state._keys_pressed.add(key.meta.kc)
|
state._keys_pressed.add(key.meta.kc)
|
||||||
return self.mo_pressed(key, state, *args, **kwargs)
|
return self.mo_pressed(key, state, *args, **kwargs)
|
||||||
|
|
||||||
@@ -96,24 +114,25 @@ class Layers(Extension):
|
|||||||
'''
|
'''
|
||||||
state._hid_pending = True
|
state._hid_pending = True
|
||||||
state._keys_pressed.discard(key.meta.kc)
|
state._keys_pressed.discard(key.meta.kc)
|
||||||
state._start_time['lm'] = None
|
|
||||||
return self.mo_released(key, state, *args, **kwargs)
|
return self.mo_released(key, state, *args, **kwargs)
|
||||||
|
|
||||||
def lt_pressed(self, key, state, *args, **kwargs):
|
def lt_pressed(self, key, state, *args, **kwargs):
|
||||||
# Sets the timer start and acts like MO otherwise
|
# Sets the timer start and acts like MO otherwise
|
||||||
state._start_time['lt'] = ticks_ms()
|
self.start_time[LayerType.LT] = accurate_ticks()
|
||||||
return self.mo_pressed(key, state, *args, **kwargs)
|
return self.mo_pressed(key, state, *args, **kwargs)
|
||||||
|
|
||||||
def lt_released(self, key, state, *args, **kwargs):
|
def lt_released(self, key, state, *args, **kwargs):
|
||||||
# On keyup, check timer, and press key if needed.
|
# On keyup, check timer, and press key if needed.
|
||||||
if state._start_time['lt'] and (
|
if self.start_time[LayerType.LT] and (
|
||||||
ticks_diff(ticks_ms(), state._start_time['lt']) < state.tap_time
|
accurate_ticks_diff(
|
||||||
|
accurate_ticks(), self.start_time[LayerType.LT], state.tap_time
|
||||||
|
)
|
||||||
):
|
):
|
||||||
state._hid_pending = True
|
state._hid_pending = True
|
||||||
state._tap_key(key.meta.kc)
|
state._tap_key(key.meta.kc)
|
||||||
|
|
||||||
self.mo_released(key, state, *args, **kwargs)
|
self.mo_released(key, state, *args, **kwargs)
|
||||||
state._start_time['lt'] = None
|
self.start_time[LayerType.LT] = None
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def tg_pressed(self, key, state, *args, **kwargs):
|
def tg_pressed(self, key, state, *args, **kwargs):
|
||||||
@@ -143,22 +162,23 @@ class Layers(Extension):
|
|||||||
Momentarily activates layer if held, toggles it if tapped repeatedly
|
Momentarily activates layer if held, toggles it if tapped repeatedly
|
||||||
'''
|
'''
|
||||||
# TODO Make this work with tap dance to function more correctly, but technically works.
|
# TODO Make this work with tap dance to function more correctly, but technically works.
|
||||||
if state._start_time['tt'] is None:
|
if self.start_time[LayerType.TT] is None:
|
||||||
# Sets the timer start and acts like MO otherwise
|
# Sets the timer start and acts like MO otherwise
|
||||||
state._start_time['tt'] = ticks_ms()
|
self.start_time[LayerType.TT] = accurate_ticks()
|
||||||
return self.mo_pressed(key, state, *args, **kwargs)
|
return self.mo_pressed(key, state, *args, **kwargs)
|
||||||
elif ticks_diff(ticks_ms(), state._start_time['tt']) < state.tap_time:
|
elif accurate_ticks_diff(
|
||||||
state._start_time['tt'] = None
|
accurate_ticks(), self.start_time[LayerType.TT], state.tap_time
|
||||||
|
):
|
||||||
|
self.start_time[LayerType.TT] = None
|
||||||
return self.tg_pressed(key, state, *args, **kwargs)
|
return self.tg_pressed(key, state, *args, **kwargs)
|
||||||
|
|
||||||
def tt_released(self, key, state, *args, **kwargs):
|
def tt_released(self, key, state, *args, **kwargs):
|
||||||
tap_timed_out = (
|
if self.start_time[LayerType.TT] is None or not accurate_ticks_diff(
|
||||||
ticks_diff(ticks_ms(), state._start_time['tt']) >= state.tap_time
|
accurate_ticks(), self.start_time[LayerType.TT], state.tap_time
|
||||||
)
|
):
|
||||||
if state._start_time['tt'] is None or tap_timed_out:
|
|
||||||
# On first press, works like MO. On second press, does nothing unless let up within
|
# On first press, works like MO. On second press, does nothing unless let up within
|
||||||
# time window, then acts like TG.
|
# time window, then acts like TG.
|
||||||
state._start_time['tt'] = None
|
self.start_time[LayerType.TT] = None
|
||||||
return self.mo_released(key, state, *args, **kwargs)
|
return self.mo_released(key, state, *args, **kwargs)
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
@@ -45,7 +45,6 @@ class KMKKeyboard:
|
|||||||
# overhead (the underlying list was never used anyway)
|
# overhead (the underlying list was never used anyway)
|
||||||
_active_layers = [0]
|
_active_layers = [0]
|
||||||
|
|
||||||
_start_time = {'lt': None, 'tg': None, 'tt': None, 'lm': None}
|
|
||||||
_timeouts = {}
|
_timeouts = {}
|
||||||
_tapping = False
|
_tapping = False
|
||||||
_tap_dance_counts = {}
|
_tap_dance_counts = {}
|
||||||
@@ -68,7 +67,6 @@ class KMKKeyboard:
|
|||||||
'coord_keys_pressed={} '
|
'coord_keys_pressed={} '
|
||||||
'hid_pending={} '
|
'hid_pending={} '
|
||||||
'active_layers={} '
|
'active_layers={} '
|
||||||
'start_time={} '
|
|
||||||
'timeouts={} '
|
'timeouts={} '
|
||||||
'tapping={} '
|
'tapping={} '
|
||||||
'tap_dance_counts={} '
|
'tap_dance_counts={} '
|
||||||
@@ -90,7 +88,6 @@ class KMKKeyboard:
|
|||||||
self._coord_keys_pressed,
|
self._coord_keys_pressed,
|
||||||
self._hid_pending,
|
self._hid_pending,
|
||||||
self._active_layers,
|
self._active_layers,
|
||||||
self._start_time,
|
|
||||||
self._timeouts,
|
self._timeouts,
|
||||||
self._tapping,
|
self._tapping,
|
||||||
self._tap_dance_counts,
|
self._tap_dance_counts,
|
||||||
@@ -407,7 +404,6 @@ class KMKKeyboard:
|
|||||||
|
|
||||||
if self._old_timeouts_len != self._new_timeouts_len:
|
if self._old_timeouts_len != self._new_timeouts_len:
|
||||||
self._state_changed = True
|
self._state_changed = True
|
||||||
|
|
||||||
if self._hid_pending:
|
if self._hid_pending:
|
||||||
self._send_hid()
|
self._send_hid()
|
||||||
|
|
||||||
|
@@ -1,29 +1,23 @@
|
|||||||
import math
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
USE_UTIME = False
|
|
||||||
|
|
||||||
|
|
||||||
def sleep_ms(ms):
|
def sleep_ms(ms):
|
||||||
'''
|
|
||||||
Tries to sleep for a number of milliseconds in a cross-implementation
|
|
||||||
way. Will raise an ImportError if time is not available on the platform.
|
|
||||||
'''
|
|
||||||
if USE_UTIME:
|
|
||||||
return time.sleep_ms(ms)
|
|
||||||
else:
|
|
||||||
return time.sleep(ms / 1000)
|
return time.sleep(ms / 1000)
|
||||||
|
|
||||||
|
|
||||||
def ticks_ms():
|
def ticks_ms():
|
||||||
if USE_UTIME:
|
'''Has .25s granularity, but is cheap'''
|
||||||
return time.ticks_ms()
|
return time.monotonic() * 1000
|
||||||
else:
|
|
||||||
return math.floor(time.monotonic() * 1000)
|
|
||||||
|
|
||||||
|
|
||||||
def ticks_diff(new, old):
|
def ticks_diff(new, old):
|
||||||
if USE_UTIME:
|
|
||||||
return time.ticks_diff(new, old)
|
|
||||||
else:
|
|
||||||
return new - old
|
return new - old
|
||||||
|
|
||||||
|
|
||||||
|
def accurate_ticks():
|
||||||
|
'''Is more expensive, but good for time critical things'''
|
||||||
|
return time.monotonic_ns()
|
||||||
|
|
||||||
|
|
||||||
|
def accurate_ticks_diff(new, old, ms):
|
||||||
|
return bool(new - old < ms * 1000000)
|
||||||
|
@@ -23,7 +23,7 @@ LT2_SP = KC.LT(3, KC.SPC)
|
|||||||
TAB_SB = KC.LT(5, KC.TAB)
|
TAB_SB = KC.LT(5, KC.TAB)
|
||||||
SUPER_L = KC.LM(4, KC.LGUI)
|
SUPER_L = KC.LM(4, KC.LGUI)
|
||||||
|
|
||||||
keyboard.tap_time = 500
|
keyboard.tap_time = 320
|
||||||
keyboard.debug_enabled = False
|
keyboard.debug_enabled = False
|
||||||
|
|
||||||
# TODO Get this out of here
|
# TODO Get this out of here
|
||||||
@@ -58,7 +58,7 @@ keyboard.keymap = [
|
|||||||
KC.GESC, KC.QUOT, KC.COMM, KC.DOT, KC.P, KC.Y, KC.F, KC.G, KC.C, KC.R, KC.L, KC.BSPC, \
|
KC.GESC, KC.QUOT, KC.COMM, KC.DOT, KC.P, KC.Y, KC.F, KC.G, KC.C, KC.R, KC.L, KC.BSPC, \
|
||||||
TAB_SB, KC.A, KC.O, KC.E, KC.U, KC.I, KC.D, KC.H, KC.T, KC.N, KC.S, KC.ENT, \
|
TAB_SB, KC.A, KC.O, KC.E, KC.U, KC.I, KC.D, KC.H, KC.T, KC.N, KC.S, KC.ENT, \
|
||||||
KC.LSFT, KC.SCLN, KC.Q, KC.J, KC.K, KC.X, KC.B, KC.M, KC.W, KC.V, KC.Z, KC.SLSH, \
|
KC.LSFT, KC.SCLN, KC.Q, KC.J, KC.K, KC.X, KC.B, KC.M, KC.W, KC.V, KC.Z, KC.SLSH, \
|
||||||
KC.LALT, SUPER_L, LT1_SP, LT2_SP, KC.LCTL, XXXXXXX,
|
KC.LALT, SUPER_L, LT1_SP, LT2_SP, KC.LCTL, KC.N0
|
||||||
],
|
],
|
||||||
|
|
||||||
# GAMING
|
# GAMING
|
||||||
|
Reference in New Issue
Block a user