2022-10-09 13:17:05 -07:00

202 lines
6.1 KiB
Python

'''Enables splitting keyboards wirelessly or wired'''
from micropython import const
from supervisor import runtime
from keypad import Event as KeyEvent
from storage import getmount
from kmk.modules import Module
class SplitSide:
LEFT = const(1)
RIGHT = const(2)
class SplitType:
UART = const(1)
I2C = const(2) # unused
ONEWIRE = const(3) # unused
BLE = const(4)
PIO_UART = const(5)
class Split(Module):
'''Enables splitting keyboards wirelessly, or wired'''
def __init__(
self,
split_flip=True,
split_side=None,
split_type=SplitType.UART,
split_target_left=True,
uart_interval=20,
data_pin=None,
data_pin2=None,
uart_flip=True,
use_pio=False,
debug_enabled=False,
):
self._is_target = True
self._uart_buffer = []
self.split_flip = split_flip
self.split_side = split_side
self.split_type = split_type
self.split_target_left = split_target_left
self.split_offset = None
self.data_pin = data_pin
self.data_pin2 = data_pin2
self.uart_flip = uart_flip
self._use_pio = use_pio
self._transport = None
self._uart_interval = uart_interval
self._debug_enabled = debug_enabled
self.uart_header = bytearray([0xB2]) # Any non-zero byte should work
if split_type == SplitType.UART and use_pio:
split_type = SplitType.PIO_UART
def during_bootup(self, keyboard):
# Set up name for target side detection and BLE advertisment
if not self.data_pin:
self.data_pin = keyboard.data_pin
self._get_side()
if not self._is_target:
keyboard._hid_send_enabled = False
if self.split_offset is None:
self.split_offset = keyboard.matrix[-1].coord_mapping[-1] + 1
self._init_transport(keyboard)
# Attempt to sanely guess a coord_mapping if one is not provided.
if not keyboard.coord_mapping:
self._guess_coord_mapping()
if self.split_side == SplitSide.RIGHT:
offset = self.split_offset
for matrix in keyboard.matrix:
matrix.offset = offset
offset += matrix.key_count
def before_matrix_scan(self, keyboard):
if self._can_receive(keyboard):
keyboard.secondary_matrix_update = self._transport.receive(keyboard)
def after_matrix_scan(self, keyboard):
if keyboard.matrix_update:
self._transport.write(keyboard, keyboard.matrix_update)
def before_hid_send(self, keyboard):
if not self._is_target:
keyboard.hid_pending = False
return
def after_hid_send(self, keyboard):
return
def on_powersave_enable(self, keyboard):
self._transport.powersave(True)
def on_powersave_disable(self, keyboard):
self._transport.powersave(False)
def _serialize_update(self, update):
buffer = bytearray(2)
buffer[0] = update.key_number
buffer[1] = update.pressed
return buffer
def _deserialize_update(self, update):
kevent = KeyEvent(key_number=update[0], pressed=update[1])
return kevent
def _checksum(self, update):
checksum = bytes([sum(update) & 0xFF])
return checksum
def _get_side(self):
name = str(getmount('/').label)
# if split side was given, find target from split_side.
if self.split_side == SplitSide.LEFT:
self._is_target = bool(self.split_target_left)
elif self.split_side == SplitSide.RIGHT:
self._is_target = not bool(self.split_target_left)
else:
# Detect split side from name
if (
self.split_type == SplitType.UART
or self.split_type == SplitType.ONEWIRE
):
self._is_target = runtime.usb_connected
if name.endswith('L'):
self.split_side = SplitSide.LEFT
elif name.endswith('R'):
self.split_side = SplitSide.RIGHT
def _init_transport(self, keyboard):
if self.split_type == SplitType.UART:
from kmk.transports.uart import UART
elif self.split_type == SplitType.PIO_UART:
from kmk.transports.pio_uart import PIO_UART
elif self.split_type == SplitType.BLE:
from kmk.transports.ble import BLE_UART
if self._is_target or not self.uart_flip:
tx_pin = self.data_pin2
rx_pin = self.data_pin
else:
tx_pin = self.data_pin
rx_pin = self.data_pin2
if self.split_type == SplitType.UART:
self._transport = UART(
tx=tx_pin,
rx=rx_pin,
target=self.is_target,
uart_interval=self._uart_interval,
)
elif self.split_type == SplitType.PIO_UART:
self._transport = PIO_UART(tx=tx_pin, rx=rx_pin)
elif self.split_type == SplitType.BLE:
self._transport = BLE_UART(
target=self._is_target, uart_interval=self.uart_interval
)
else:
raise NotImplementedError
def _guess_coord_mapping(self, keyboard):
cm = []
rows_to_calc = len(keyboard.row_pins)
cols_to_calc = len(keyboard.col_pins)
# Flips the col order if PCB is the same but flipped on right
cols_rhs = list(range(cols_to_calc))
if self.split_flip:
cols_rhs = list(reversed(cols_rhs))
for ridx in range(rows_to_calc):
for cidx in range(cols_to_calc):
cm.append(cols_to_calc * ridx + cidx)
for cidx in cols_rhs:
cm.append(cols_to_calc * (rows_to_calc + ridx) + cidx)
keyboard.coord_mapping = tuple(cm)
def _can_receive(self, keyboard) -> bool:
if self.split_type == SplitType.BLE:
self._transport.check_connection(keyboard)
return True
elif self.split_type == SplitType.UART:
if self._is_target or self.data_pin2:
return True
return False