kmk_firmware/kmk/matrix.py
Josh Klar 2d1290a12c
Add LeaderMode.TIMEOUT (QMK default Leader mode)
This allows leader sequences to "time out" rather than requiring an
Enter keypress to end.

This also rolls back some unnecessary changes from #72 to the matrix
scanner for performance reasons.

In theory we can use this in the future for Tap Dance support (#40)

Resolves #1
Resolves #37
2018-10-19 01:49:37 -07:00

111 lines
3.8 KiB
Python

import digitalio
from kmk.consts import DiodeOrientation
from kmk.util import intify_coordinate
class MatrixScanner:
def __init__(
self, cols, rows,
diode_orientation=DiodeOrientation.COLUMNS,
rollover_cols_every_rows=None,
swap_indicies=None,
):
# A pin cannot be both a row and column, detect this by combining the
# two tuples into a set and validating that the length did not drop
#
# repr() hackery is because CircuitPython Pin objects are not hashable
unique_pins = {repr(c) for c in cols} | {repr(r) for r in rows}
if len(unique_pins) != len(cols) + len(rows):
raise ValueError('Cannot use a pin as both a column and row')
self.cols = cols
self.rows = rows
self.len_cols = len(cols)
self.len_rows = len(rows)
self.diode_orientation = diode_orientation
if self.diode_orientation == DiodeOrientation.COLUMNS:
self.outputs = self.cols
self.inputs = self.rows
self.translate_coords = True
elif self.diode_orientation == DiodeOrientation.ROWS:
self.outputs = self.rows
self.inputs = self.cols
self.translate_coords = False
else:
raise ValueError('Invalid DiodeOrientation: {}'.format(
self.diode_orientation,
))
for pin in self.outputs:
pin.switch_to_output()
for pin in self.inputs:
pin.switch_to_input(pull=digitalio.Pull.DOWN)
self.swap_indicies = {}
if swap_indicies is not None:
for k, v in swap_indicies.items():
self.swap_indicies[intify_coordinate(*k)] = v
self.swap_indicies[intify_coordinate(*v)] = k
self.rollover_cols_every_rows = rollover_cols_every_rows
if self.rollover_cols_every_rows is None:
self.rollover_cols_every_rows = self.len_rows
self.len_state_arrays = self.len_cols * self.len_rows
self.state = bytearray(self.len_state_arrays)
self.report = bytearray(3)
def scan_for_changes(self):
'''
Poll the matrix for changes and return either None (if nothing updated)
or a bytearray (reused in later runs so copy this if you need the raw
array itself for some crazy reason) consisting of (row, col, pressed)
which are (int, int, bool)
'''
ba_idx = 0
any_changed = False
for oidx, opin in enumerate(self.outputs):
opin.value(True)
for iidx, ipin in enumerate(self.inputs):
old_val = self.state[ba_idx]
new_val = ipin.value()
if old_val != new_val:
if self.translate_coords:
new_oidx = oidx + self.len_cols * (iidx // self.rollover_cols_every_rows)
new_iidx = iidx - self.rollover_cols_every_rows * (
iidx // self.rollover_cols_every_rows
)
self.report[0] = new_iidx
self.report[1] = new_oidx
else:
self.report[0] = oidx
self.report[1] = iidx
swap_src = intify_coordinate(self.report[0], self.report[1])
if swap_src in self.swap_indicies:
tgt_row, tgt_col = self.swap_indicies[swap_src]
self.report[0] = tgt_row
self.report[1] = tgt_col
self.report[2] = new_val
self.state[ba_idx] = new_val
any_changed = True
break
ba_idx += 1
opin.value(False)
if any_changed:
break
if any_changed:
return self.report