kmk_firmware/kmk/matrix.py
Josh Klar 6baaf5e5d4
Continue to shuffle and burn stuff
- Remove the concept of "mcus". With only one target platform
(CircuitPython), it no longer makes a bunch of sense and has been kept
around for "what if" reasons, complicating our import chains and eating
up RAM for pointless subclasses. If you're a `board`, you derive from
`KeyboardConfig`. If you're a handwire, the user will derive from
`KeyboardConfig`. The end. As part of this, `kmk.hid` was refactored
heavily to emphasize that CircuitPython is our only supported HID stack,
with stubs for future HID implementations (`USB_HID` becomes
`AbstractHID`, probably only usable for testing purposes,
`CircuitPython_USB_HID` becomes `USBHID`, and `BLEHID` is added with an
immediate `NotImplementedError` on instantiation)

- `KeyboardConfig` can now take a HID type at runtime. The NRF52840
boards will happily run in either configuration once CircuitPython
support is in place, and a completely separate `mcu` subclass for each
mode made no sense. This also potentially allows runtime *swaps* of HID
driver down the line, but no code has been added to this effect. The
default, and only functional value, for this is `HIDModes.USB`

- Most consts have been moved to more logical homes - often, the main
or, often only, component that uses them. `DiodeOrientation` moved to
`kmk.matrix`, and anything HID-related moved to `kmk.hid`
2019-07-25 00:58:23 -07:00

128 lines
4.2 KiB
Python

import digitalio
import gc
def intify_coordinate(row, col):
return row << 8 | col
class DiodeOrientation:
'''
Orientation of diodes on handwired boards. You can think of:
COLUMNS = vertical
ROWS = horizontal
'''
COLUMNS = 0
ROWS = 1
class MatrixScanner:
def __init__(
self,
cols,
rows,
diode_orientation=DiodeOrientation.COLUMNS,
rollover_cols_every_rows=None,
):
self.len_cols = len(cols)
self.len_rows = len(rows)
# 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}
assert (
len(unique_pins) == self.len_cols + self.len_rows
), 'Cannot use a pin as both a column and row'
del unique_pins
gc.collect()
self.diode_orientation = diode_orientation
if self.diode_orientation == DiodeOrientation.COLUMNS:
self.outputs = [digitalio.DigitalInOut(x) for x in cols]
self.inputs = [digitalio.DigitalInOut(x) for x in rows]
self.translate_coords = True
elif self.diode_orientation == DiodeOrientation.ROWS:
self.outputs = [digitalio.DigitalInOut(x) for x in rows]
self.inputs = [digitalio.DigitalInOut(x) for x in 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.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):
# cast to int to avoid
#
# >>> xyz = bytearray(3)
# >>> xyz[2] = True
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# OverflowError: value would overflow a 1 byte buffer
#
# I haven't dived too far into what causes this, but it's
# almost certainly because bool types in Python aren't just
# aliases to int values, but are proper pseudo-types
new_val = int(ipin.value)
old_val = self.state[ba_idx]
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
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