From 57ba0fe8b29910de73f5105d6a8fe4deb9c7fd60 Mon Sep 17 00:00:00 2001 From: Ellie Date: Mon, 4 Oct 2021 01:40:18 +1100 Subject: [PATCH] Custom matrix scanners - Introduce Scanner base class for MatrixScanner - Create new Scanner using built-in keypad module - Allow overriding the scanner used by KMKKeyboard --- docs/scanners.md | 66 +++++++++++++++++++++++++++ kmk/kmk_keyboard.py | 18 +++++--- kmk/matrix.py | 4 +- kmk/scanners/__init__.py | 12 +++++ kmk/scanners/native_keypad_scanner.py | 60 ++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 docs/scanners.md create mode 100644 kmk/scanners/__init__.py create mode 100644 kmk/scanners/native_keypad_scanner.py diff --git a/docs/scanners.md b/docs/scanners.md new file mode 100644 index 0000000..3bdde56 --- /dev/null +++ b/docs/scanners.md @@ -0,0 +1,66 @@ +# Scanners + +Smaller boards and macro pads sometimes assign a GPIO pin to each key, rather +than using a full matrix. Boards like this aren't compatible with the default +matrix scanner, so you will need to swap it out with an alternative scanner. + +Beside the default `Matrix` scanner, KMK includes the following: + + +## keypad.Keys + +The `keypad.Keys` scanner treats individual GPIO pins as discrete keys. To use +this scanner, provide a sequence of pins that describes the layout of your +board then include it in the initialisation sequence of your keyboard class. + +Since the `_init_sanity_check` method in the `KMKKeyboard` class will attempt +to validate a number of settings that are no longer needed, you need to +override it with a modified version that doesn't fail when they aren't present. + + +```python +import board +from kmk.kmk_keyboard import KMKKeyboard +from kmk.scanners.native_keypad_scanner import keys_scanner + + +# GPIO to key mapping - each line is a new row. +_KEY_CFG = [ + [board.SW3, board.SW7, board.SW11, board.SW15], + [board.SW2, board.SW6, board.SW10, board.SW14], + [board.SW1, board.SW5, board.SW9, board.SW13], + [board.SW0, board.SW4, board.SW8, board.SW12], +] + + +# Keyboard implementation class +class MyKeyboard(KMKKeyboard): + def __init__(self): + # create and register the scanner + self.matrix = keys_scanner(_KEY_CFG) + + # copy the key coordinates from the scanner + self.coord_mapping = self.matrix.coord_mapping + + def _init_sanity_check(self): + return self +``` + + +## keypad.KeyMatrix + +The `keypad.KeyMatrix` scanner is an alternative implementation of the default +matrix scanner using CircuitPython's builtin keypad objects. This is currently +experimental and ***not recommended for use***. + +Using this scanner is similar to the `keypad.Keys` scanner. Create the scanner +using `keypad_matrix()` instead of `keys_scanner()`. + + +## `Scanner` base class + +If you require a different type of scanner, you can create your own by +providing a subclass of `Scanner`. This is a very simple interface, it only +contains a single method, `scan_for_changes(self)` which returns a key report +if one exists, or `None` otherwise. + diff --git a/kmk/kmk_keyboard.py b/kmk/kmk_keyboard.py index 8142449..4648710 100644 --- a/kmk/kmk_keyboard.py +++ b/kmk/kmk_keyboard.py @@ -290,12 +290,18 @@ class KMKKeyboard: self._hid_send_enabled = True def _init_matrix(self): - self.matrix = self.matrix_scanner( - cols=self.col_pins, - rows=self.row_pins, - diode_orientation=self.diode_orientation, - rollover_cols_every_rows=getattr(self, 'rollover_cols_every_rows', None), - ) + if self.matrix is None: + print('Initialising matrix scanner from self.matrix_scanner') + self.matrix = self.matrix_scanner( + cols=self.col_pins, + rows=self.row_pins, + diode_orientation=self.diode_orientation, + rollover_cols_every_rows=getattr( + self, 'rollover_cols_every_rows', None + ), + ) + else: + print('Matrix scanner already set, not overwriting.') return self diff --git a/kmk/matrix.py b/kmk/matrix.py index b5daa65..0273c38 100644 --- a/kmk/matrix.py +++ b/kmk/matrix.py @@ -1,5 +1,7 @@ import digitalio +from kmk.scanners import Scanner + def intify_coordinate(row, col, len_cols): return len_cols * row + col @@ -26,7 +28,7 @@ class KeyEvent: self.pressed = pressed -class MatrixScanner: +class MatrixScanner(Scanner): def __init__( self, cols, diff --git a/kmk/scanners/__init__.py b/kmk/scanners/__init__.py new file mode 100644 index 0000000..348bda8 --- /dev/null +++ b/kmk/scanners/__init__.py @@ -0,0 +1,12 @@ +class Scanner: + ''' + Base class for scanners. + ''' + + def scan_for_changes(self): + ''' + Scan for key events and return a key report if an event exists. + + The key report is a byte array with contents [row, col, True if pressed else False] + ''' + pass diff --git a/kmk/scanners/native_keypad_scanner.py b/kmk/scanners/native_keypad_scanner.py new file mode 100644 index 0000000..62ca664 --- /dev/null +++ b/kmk/scanners/native_keypad_scanner.py @@ -0,0 +1,60 @@ +import keypad + +from kmk.matrix import DiodeOrientation +from kmk.scanners import Scanner + + +class NativeKeypadScanner(Scanner): + ''' + Translation layer around a CircuitPython 7 keypad scanner. + + :param pin_map: A sequence of (row, column) tuples for each key. + :param kp: An instance of the keypad class. + ''' + + def __init__(self, pin_map, kp): + self.pin_map = pin_map + self.keypad = kp + # self.coord_mapping = [ic(row, col) for (row, col) in self.pin_map] + self.coord_mapping = list(range(len(pin_map))) + + self.curr_event = keypad.Event() + + def scan_for_changes(self): + ''' + Scan for key events and return a key report if an event exists. + + The key report is a byte array with contents [row, col, True if pressed else False] + ''' + ev = self.curr_event + has_event = self.keypad.events.get_into(ev) + if has_event: + return ev + + +def keypad_matrix(row_pins, col_pins, direction=DiodeOrientation.COLUMNS): + ''' + Row/Column matrix using the CircuitPython 7 keypad scanner. + + :param row_pins: A sequence of pins used for rows. + :param col_pins: A sequence of pins used for columns. + :param direction: The diode orientation of the matrix. + ''' + pin_map = [ + (row, col) for row in range(len(row_pins)) for col in range(len(col_pins)) + ] + kp = keypad.KeyMatrix(row_pins, col_pins, direction == DiodeOrientation.COLUMNS) + return NativeKeypadScanner(pin_map, kp) + + +def keys_scanner(pins): + ''' + GPIO-per-key 'matrix' using the native CircuitPython 7 keypad scanner. + + :param pins: An array of arrays of CircuitPython Pin objects, such that pins[r][c] is the pin for row r, column c. + ''' + pin_map = [(row, col) for row in range(len(pins)) for col in range(len(pins[row]))] + kp = keypad.Keys( + [pins[r][c] for (r, c) in pin_map], value_when_pressed=False, pull=True + ) + return NativeKeypadScanner(pin_map, kp)