225 lines
7.3 KiB
Python
225 lines
7.3 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
|
|
|
|
if split_type == SplitType.UART:
|
|
import busio
|
|
elif split_type == SplitType.PIO_UART:
|
|
from kmk.transports.pio_uart import PIO_UART
|
|
elif split_type == SplitType.BLE:
|
|
from kmk.transports.ble import BLE_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.split_type == SplitType.BLE:
|
|
self._transport.check_connection(keyboard)
|
|
|
|
if self.split_type == SplitType.UART:
|
|
if self._is_target or self.data_pin2:
|
|
self._receive_uart(keyboard)
|
|
else:
|
|
self._receive_uart(keyboard)
|
|
|
|
def after_matrix_scan(self, keyboard):
|
|
if keyboard.matrix_update:
|
|
if self.split_type == SplitType.UART:
|
|
if not self._is_target or self.data_pin2:
|
|
self._send_uart(keyboard.matrix_update)
|
|
else:
|
|
pass # explicit pass just for dev sanity...
|
|
else:
|
|
self._send_uart(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):
|
|
if self.split_type == SplitType.BLE:
|
|
self._transport.enable_powersave(True)
|
|
|
|
def on_powersave_disable(self, keyboard):
|
|
if self.split_type == SplitType.BLE:
|
|
self._transport.enable_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 _send_uart(self, update):
|
|
# Change offsets depending on where the data is going to match the correct
|
|
# matrix location of the receiever
|
|
update = self._serialize_update(update)
|
|
self._transport.write(self.uart_header)
|
|
self._transport.write(update)
|
|
self._transport.write(self._checksum(update))
|
|
|
|
def _receive_uart(self, keyboard):
|
|
if self._transport.in_waiting > 0 or self._uart_buffer:
|
|
if self._transport.in_waiting >= 60:
|
|
# This is a dirty hack to prevent crashes in unrealistic cases
|
|
import microcontroller
|
|
|
|
microcontroller.reset()
|
|
|
|
while self._transport.in_waiting >= 4:
|
|
# Check the header
|
|
if self._transport.read(1) == self.uart_header:
|
|
update = self._transport.read(2)
|
|
|
|
# check the checksum
|
|
if self._checksum(update) == self._transport.read(1):
|
|
self._uart_buffer.append(self._deserialize_update(update))
|
|
if self._uart_buffer:
|
|
keyboard.secondary_matrix_update = self._uart_buffer.pop(0)
|
|
|
|
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._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 split_type == SplitType.UART:
|
|
self._transport = busio.UART(tx=tx_pin, rx=rx_pin, timeout=self._uart_interval)
|
|
elif split_type == SplitType.PIO_UART:
|
|
self._transport = PIO_UART(tx=tx_pin, rx=rx_pin)
|
|
elif split_type == SplitType.BLE:
|
|
self._transport = BLE_UART(self._is_target, 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)
|