From d5de2601d9173b4935d845f1f73291d8a573f1b4 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Mon, 3 Sep 2018 03:22:11 -0700 Subject: [PATCH 01/11] Prepare things for the event loop, also abstract gross stuff from end users --- Makefile | 14 ++++++++----- boards/klardotsh/twotwo_matrix_feather.py | 22 +++++++++++--------- entrypoints/feather_nrf52832.py | 1 - kmk/circuitpython/matrix.py | 18 +++------------- kmk/circuitpython/util.py | 5 +++-- kmk/common/abstract/__init__.py | 0 kmk/common/abstract/matrix_scanner.py | 25 +++++++++++++++++++++++ kmk/common/keymap.py | 4 ++-- kmk/firmware.py | 18 ++++++++++++++++ 9 files changed, 72 insertions(+), 35 deletions(-) create mode 100644 kmk/common/abstract/__init__.py create mode 100644 kmk/common/abstract/matrix_scanner.py create mode 100644 kmk/firmware.py diff --git a/Makefile b/Makefile index fb9f393..c114676 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,10 @@ freeze-nrf-vendor-deps \ lint +NRF_DFU_PORT ?= /dev/ttyUSB0 +NRF_DFU_BAUD ?= 115200 +NRF_DFU_DELAY ?= 1.5 + devdeps: Pipfile.lock @pipenv install --dev --ignore-pipfile @@ -39,15 +43,15 @@ circuitpy-freeze-kmk-nrf: freeze-nrf-vendor-deps @rm -rf vendor/circuitpython/ports/nrf/kmk* @cp -av kmk vendor/circuitpython/ports/nrf/freeze/ -circuitpy-flash-nrf: circuitpy-freeze-kmk-nrf +circuitpy-flash-nrf: @echo "===> Building and flashing CircuitPython with KMK and your keymap" - @make -C vendor/circuitpython/ports/nrf BOARD=feather_nrf52832 SERIAL=/dev/ttyUSB0 SD=s132 FROZEN_MPY_DIR=freeze clean dfu-gen dfu-flash + @make -C vendor/circuitpython/ports/nrf BOARD=feather_nrf52832 SERIAL=${NRF_DFU_PORT} SD=s132 FROZEN_MPY_DIR=freeze clean dfu-gen dfu-flash circuitpy-flash-nrf-entrypoint: @echo "===> Flashing entrypoint if it doesn't already exist" @sleep 2 - @-timeout -k 5s 10s pipenv run ampy rm main.py 2>/dev/null - @-timeout -k 5s 10s pipenv run ampy put entrypoints/feather_nrf52832.py main.py + @-timeout -k 5s 10s pipenv run ampy -p ${NRF_DFU_PORT} -d ${NRF_DFU_DELAY} -b ${NRF_DFU_BAUD} rm main.py 2>/dev/null + @-timeout -k 5s 10s pipenv run ampy -p ${NRF_DFU_PORT} -d ${NRF_DFU_DELAY} -b ${NRF_DFU_BAUD} put entrypoints/feather_nrf52832.py main.py @echo "===> Flashed keyboard successfully!" build-feather-test: lint devdeps circuitpy-deps circuitpy-freeze-kmk-nrf @@ -77,5 +81,5 @@ burn-it-all-with-fire: lint devdeps @$(MAKE) circuitpy-flash-nrf @echo "===> Wiping keyboard config" @sleep 2 - @-pipenv run ampy rm main.py 2>/dev/null + @-timeout -k 5s 10s pipenv run ampy -p ${NRF_DFU_PORT} -d ${NRF_DFU_DELAY} -b ${NRF_DFU_BAUD} rm main.py 2>/dev/null @echo "===> Wiped! Probably safe to flash keyboard, try Python serial REPL to verify?" diff --git a/boards/klardotsh/twotwo_matrix_feather.py b/boards/klardotsh/twotwo_matrix_feather.py index e8b23eb..84c9cb8 100644 --- a/boards/klardotsh/twotwo_matrix_feather.py +++ b/boards/klardotsh/twotwo_matrix_feather.py @@ -1,23 +1,25 @@ import board -from kmk.circuitpython.matrix import MatrixScanner from kmk.common.consts import DiodeOrientation -from kmk.common.keymap import Keymap +from kmk.firmware import Firmware def main(): cols = (board.A4, board.A5) rows = (board.D27, board.A6) - matrix = MatrixScanner( - cols=cols, rows=rows, - diode_orientation=DiodeOrientation.COLUMNS, - ) + diode_orientation = DiodeOrientation.COLUMNS - keymap = Keymap([ + keymap = [ ['A', 'B'], ['C', 'D'], - ]) + ] - while True: - keymap.parse(matrix.raw_scan()) + firmware = Firmware( + keymap=keymap, + row_pins=rows, + col_pins=cols, + diode_orientation=diode_orientation, + ) + + firmware.go() diff --git a/entrypoints/feather_nrf52832.py b/entrypoints/feather_nrf52832.py index 1f52211..c07d83c 100644 --- a/entrypoints/feather_nrf52832.py +++ b/entrypoints/feather_nrf52832.py @@ -1,5 +1,4 @@ from kmk_keyboard_user import main - if __name__ == '__main__': main() diff --git a/kmk/circuitpython/matrix.py b/kmk/circuitpython/matrix.py index 5203cc2..666a2ca 100644 --- a/kmk/circuitpython/matrix.py +++ b/kmk/circuitpython/matrix.py @@ -1,9 +1,10 @@ import digitalio +from kmk.common.abstract.matrix_scanner import AbstractMatrixScanner from kmk.common.consts import DiodeOrientation -class MatrixScanner: +class MatrixScanner(AbstractMatrixScanner): def __init__(self, cols, rows, diode_orientation=DiodeOrientation.COLUMNS): # 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 @@ -35,20 +36,7 @@ class MatrixScanner: pin.switch_to_input(pull=digitalio.Pull.DOWN) def _normalize_matrix(self, matrix): - ''' - We always want to internally look at a keyboard as a list of rows, - where a "row" is a list of keycodes (columns). - - This will convert DiodeOrientation.COLUMNS matrix scans into a - ROWS scan, so we never have to think about these things again. - ''' - if self.diode_orientation == DiodeOrientation.ROWS: - return matrix - - return [ - [col[col_entry] for col in matrix] - for col_entry in range(max(len(col) for col in matrix)) - ] + return super()._normalize_matrix(matrix) def raw_scan(self): matrix = [] diff --git a/kmk/circuitpython/util.py b/kmk/circuitpython/util.py index 01d63d5..0b822b1 100644 --- a/kmk/circuitpython/util.py +++ b/kmk/circuitpython/util.py @@ -1,7 +1,8 @@ +import sys +import time + import board import digitalio -import time -import sys def feather_signal_error_with_led_flash(rate=0.5): diff --git a/kmk/common/abstract/__init__.py b/kmk/common/abstract/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kmk/common/abstract/matrix_scanner.py b/kmk/common/abstract/matrix_scanner.py new file mode 100644 index 0000000..4ef71dc --- /dev/null +++ b/kmk/common/abstract/matrix_scanner.py @@ -0,0 +1,25 @@ +from kmk.common.consts import DiodeOrientation + + +class AbstractMatrixScanner(): + def __init__(self, cols, rows, diode_orientation=DiodeOrientation.COLUMNS): + raise NotImplementedError('Abstract implementation') + + def _normalize_matrix(self, matrix): + ''' + We always want to internally look at a keyboard as a list of rows, + where a "row" is a list of keycodes (columns). + + This will convert DiodeOrientation.COLUMNS matrix scans into a + ROWS scan, so we never have to think about these things again. + ''' + if self.diode_orientation == DiodeOrientation.ROWS: + return matrix + + return [ + [col[col_entry] for col in matrix] + for col_entry in range(max(len(col) for col in matrix)) + ] + + def raw_scan(self): + raise NotImplementedError('Abstract implementation') diff --git a/kmk/common/keymap.py b/kmk/common/keymap.py index cdcfc9c..815c082 100644 --- a/kmk/common/keymap.py +++ b/kmk/common/keymap.py @@ -10,9 +10,9 @@ class Keymap: for ridx, row in enumerate(matrix): for cidx, col in enumerate(row): if col != self.state[ridx][cidx]: - print('{}: {}'.format( + yield '{}: {}'.format( 'KEYDOWN' if col else 'KEYUP', self.map[ridx][cidx], - )) + ) self.state = matrix diff --git a/kmk/firmware.py b/kmk/firmware.py new file mode 100644 index 0000000..7e5fa22 --- /dev/null +++ b/kmk/firmware.py @@ -0,0 +1,18 @@ +from kmk.common.keymap import Keymap + +try: + from kmk.circuitpython.matrix import MatrixScanner +except ImportError: + from kmk.micropython.matrix import MatrixScanner + + +class Firmware: + def __init__(self, keymap, row_pins, col_pins, diode_orientation): + self.raw_keymap = keymap + self.keymap = Keymap(keymap) + self.matrix = MatrixScanner(col_pins, row_pins, diode_orientation) + + def go(self): + while True: + for event in self.keymap.parse(self.matrix.raw_scan()): + print(event) From c641903d61da61c71628086f9da936bb8549c8c9 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Mon, 3 Sep 2018 03:22:31 -0700 Subject: [PATCH 02/11] Enforce that dependencies are imported in correct order at lint time --- Pipfile | 5 ++ Pipfile.lock | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++- setup.cfg | 3 ++ 3 files changed, 155 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index dfdfabf..4c7f879 100644 --- a/Pipfile +++ b/Pipfile @@ -9,6 +9,11 @@ name = "pypi" adafruit-ampy = "*" "flake8" = "*" "flake8-comprehensions" = "*" +ipython = "*" +ipdb = "*" +"flake8-commas" = "*" +isort = "*" +"flake8-isort" = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 09ac140..806f359 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e246783475c5feb51445494a3f30c397ad6ddaed35c45decc9846970d001ad50" + "sha256": "13025d2428b2f3830c77567460f41ae840f254378a26bd01c3832d20a6239bf0" }, "pipfile-spec": 6, "requires": { @@ -24,6 +24,13 @@ "index": "pypi", "version": "==1.0.5" }, + "backcall": { + "hashes": [ + "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", + "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2" + ], + "version": "==0.1.0" + }, "click": { "hashes": [ "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", @@ -31,6 +38,13 @@ ], "version": "==6.7" }, + "decorator": { + "hashes": [ + "sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82", + "sha256:c39efa13fbdeb4506c476c9b3babf6a718da943dab7811c206005a4a956c080c" + ], + "version": "==4.3.0" + }, "flake8": { "hashes": [ "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0", @@ -39,6 +53,14 @@ "index": "pypi", "version": "==3.5.0" }, + "flake8-commas": { + "hashes": [ + "sha256:d3005899466f51380387df7151fb59afec666a0f4f4a2c6a8995b975de0f44b7", + "sha256:ee2141a3495ef9789a3894ed8802d03eff1eaaf98ce6d8653a7c573ef101935e" + ], + "index": "pypi", + "version": "==2.0.0" + }, "flake8-comprehensions": { "hashes": [ "sha256:b83891fec0e680b07aa1fd92e53eb6993be29a0f3673a09badbe8da307c445e0", @@ -47,6 +69,52 @@ "index": "pypi", "version": "==1.4.1" }, + "flake8-isort": { + "hashes": [ + "sha256:298d7904ac3a46274edf4ce66fd7e272c2a60c34c3cc999dea000608d64e5e6e", + "sha256:5992850626ce96547b1f1c7e8a7f0ef49ab2be44eca2177934566437b636fa3c" + ], + "index": "pypi", + "version": "==2.5" + }, + "ipdb": { + "hashes": [ + "sha256:7081c65ed7bfe7737f83fa4213ca8afd9617b42ff6b3f1daf9a3419839a2a00a" + ], + "index": "pypi", + "version": "==0.11" + }, + "ipython": { + "hashes": [ + "sha256:007dcd929c14631f83daff35df0147ea51d1af420da303fd078343878bd5fb62", + "sha256:b0f2ef9eada4a68ef63ee10b6dde4f35c840035c50fd24265f8052c98947d5a4" + ], + "index": "pypi", + "version": "==6.5.0" + }, + "ipython-genutils": { + "hashes": [ + "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", + "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" + ], + "version": "==0.2.0" + }, + "isort": { + "hashes": [ + "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af", + "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8", + "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497" + ], + "index": "pypi", + "version": "==4.3.4" + }, + "jedi": { + "hashes": [ + "sha256:b409ed0f6913a701ed474a614a3bb46e6953639033e31f769ca7581da5bd1ec1", + "sha256:c254b135fb39ad76e78d4d8f92765ebc9bf92cbc76f49e97ade1d5f5121e1f6f" + ], + "version": "==0.12.1" + }, "mccabe": { "hashes": [ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", @@ -54,6 +122,43 @@ ], "version": "==0.6.1" }, + "parso": { + "hashes": [ + "sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2", + "sha256:895c63e93b94ac1e1690f5fdd40b65f07c8171e3e53cbd7793b5b96c0e0a7f24" + ], + "version": "==0.3.1" + }, + "pexpect": { + "hashes": [ + "sha256:2a8e88259839571d1251d278476f3eec5db26deb73a70be5ed5dc5435e418aba", + "sha256:3fbd41d4caf27fa4a377bfd16fef87271099463e6fa73e92a52f92dfee5d425b" + ], + "markers": "sys_platform != 'win32'", + "version": "==4.6.0" + }, + "pickleshare": { + "hashes": [ + "sha256:84a9257227dfdd6fe1b4be1319096c20eb85ff1e82c7932f36efccfe1b09737b", + "sha256:c9a2541f25aeabc070f12f452e1f2a8eae2abd51e1cd19e8430402bdf4c1d8b5" + ], + "version": "==0.7.4" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:1df952620eccb399c53ebb359cc7d9a8d3a9538cb34c5a1344bdbeb29fbcc381", + "sha256:3f473ae040ddaa52b52f97f6b4a493cfa9f5920c255a12dc56a7d34397a398a4", + "sha256:858588f1983ca497f1cf4ffde01d978a3ea02b01c8a26a8bbc5cd2e66d816917" + ], + "version": "==1.0.15" + }, + "ptyprocess": { + "hashes": [ + "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", + "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f" + ], + "version": "==0.6.0" + }, "pycodestyle": { "hashes": [ "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", @@ -68,6 +173,13 @@ ], "version": "==1.6.0" }, + "pygments": { + "hashes": [ + "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", + "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" + ], + "version": "==2.2.0" + }, "pyserial": { "hashes": [ "sha256:6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627", @@ -81,6 +193,40 @@ "sha256:4a205787bc829233de2a823aa328e44fd9996fedb954989a21f1fc67c13d7a77" ], "version": "==0.9.1" + }, + "simplegeneric": { + "hashes": [ + "sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173" + ], + "version": "==0.8.1" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "testfixtures": { + "hashes": [ + "sha256:7e4df89a8bf8b8905464160f08aff131a36f0b33654fe4f9e4387afe546eae25", + "sha256:bcadbad77526cc5fc38bfb2ab80da810d7bde56ffe4c7fdb8e2bba122ded9620" + ], + "version": "==6.2.0" + }, + "traitlets": { + "hashes": [ + "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", + "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9" + ], + "version": "==4.3.2" + }, + "wcwidth": { + "hashes": [ + "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", + "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + ], + "version": "==0.1.7" } } } diff --git a/setup.cfg b/setup.cfg index 1d9a970..49656ae 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ [flake8] exclude = .git,__pycache__,vendor + +[isort] +known_third_party = analogio,bitbangio,bleio,board,busio,digitalio,framebuf,gamepad,gc,microcontroller,micropython,pulseio,pyb,pydux,uio,ubluepy From d9b909d8411794e5a2f384d17a7ec152258b20e3 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Mon, 3 Sep 2018 04:22:52 -0700 Subject: [PATCH 03/11] Event dispatching, super simply --- .gitmodules | 4 --- Pipfile | 1 + Pipfile.lock | 13 ++++++-- kmk/common/keymap.py | 28 ++++++++++++++---- kmk/firmware.py | 70 ++++++++++++++++++++++++++++++++++++++++++-- upy-freeze.txt | 4 --- 6 files changed, 102 insertions(+), 18 deletions(-) diff --git a/.gitmodules b/.gitmodules index ce7fb1a..0d46a64 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,10 +2,6 @@ path = vendor/circuitpython url = https://github.com/adafruit/circuitpython.git branch = "9b98ad779468676c3d5f1efdc06b454aaed7c407" -[submodule "pydux"] - path = vendor/pydux - url = https://github.com/usrlocalben/pydux.git - branch = "943ca1c75357b9289f55f17ff2d997a66a3313a4" [submodule "upy-lib"] path = vendor/upy-lib url = https://github.com/micropython/micropython-lib.git diff --git a/Pipfile b/Pipfile index 4c7f879..e45b392 100644 --- a/Pipfile +++ b/Pipfile @@ -4,6 +4,7 @@ verify_ssl = true name = "pypi" [packages] +pydux = "*" [dev-packages] adafruit-ampy = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 806f359..1fe5584 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "13025d2428b2f3830c77567460f41ae840f254378a26bd01c3832d20a6239bf0" + "sha256": "adeed87e2092ae72a6de1ba825163f5e9fd803059aea3cc42a461aa729914b23" }, "pipfile-spec": 6, "requires": { @@ -15,7 +15,16 @@ } ] }, - "default": {}, + "default": { + "pydux": { + "hashes": [ + "sha256:5cb9217be9d8c7ff79b028f6f02597bbb055b107ce8eecbe5f631f3fc76d793f", + "sha256:bed123b5255d566f792b9ceebad87e3f9c1d2d85abed4b9a9475ffc831035879" + ], + "index": "pypi", + "version": "==0.2.2" + } + }, "develop": { "adafruit-ampy": { "hashes": [ diff --git a/kmk/common/keymap.py b/kmk/common/keymap.py index 815c082..3f3ba09 100644 --- a/kmk/common/keymap.py +++ b/kmk/common/keymap.py @@ -1,3 +1,21 @@ +KEY_UP_EVENT = 'KEY_UP' +KEY_DOWN_EVENT = 'KEY_DOWN' + + +def key_up_event(keycode): + return { + 'type': KEY_UP_EVENT, + 'keycode': keycode, + } + + +def key_down_event(keycode): + return { + 'type': KEY_DOWN_EVENT, + 'keycode': keycode, + } + + class Keymap: def __init__(self, map): self.map = map @@ -6,13 +24,13 @@ class Keymap: for row in self.map ] - def parse(self, matrix): + def parse(self, matrix, store): for ridx, row in enumerate(matrix): for cidx, col in enumerate(row): if col != self.state[ridx][cidx]: - yield '{}: {}'.format( - 'KEYDOWN' if col else 'KEYUP', - self.map[ridx][cidx], - ) + if col: + store.dispatch(key_down_event(self.map[ridx][cidx])) + else: + store.dispatch(key_up_event(self.map[ridx][cidx])) self.state = matrix diff --git a/kmk/firmware.py b/kmk/firmware.py index 7e5fa22..2f6b9ab 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -1,4 +1,4 @@ -from kmk.common.keymap import Keymap +from kmk.common.keymap import KEY_DOWN_EVENT, KEY_UP_EVENT, Keymap try: from kmk.circuitpython.matrix import MatrixScanner @@ -6,13 +6,77 @@ except ImportError: from kmk.micropython.matrix import MatrixScanner +class ReduxStore: + def __init__(self, reducer): + self.reducer = reducer + self.state = self.reducer() + + def dispatch(self, action): + self.state = self.reducer(self.state, action) + + def get_state(self): + return self.state + + +class InternalState: + modifiers_pressed = frozenset() + keys_pressed = frozenset() + + def __repr__(self): + return 'InternalState(mods={}, keys={})'.format( + self.modifiers_pressed, + self.keys_pressed, + ) + + def copy(self, modifiers_pressed=None, keys_pressed=None): + new_state = InternalState() + + if modifiers_pressed is None: + new_state.modifiers_pressed = self.modifiers_pressed.copy() + else: + new_state.modifiers_pressed = modifiers_pressed + + if keys_pressed is None: + new_state.keys_pressed = self.keys_pressed.copy() + else: + new_state.keys_pressed = keys_pressed + + return new_state + + +def reducer(state=None, action=None): + if state is None: + state = InternalState() + + if action is None: + return state + + if action['type'] == KEY_UP_EVENT: + new_state = state.copy(keys_pressed=frozenset( + key for key in state.keys_pressed if key != action['keycode'] + )) + + print(new_state) + + return new_state + + if action['type'] == KEY_DOWN_EVENT: + new_state = state.copy(keys_pressed=( + state.keys_pressed | {action['keycode']} + )) + + print(new_state) + + return new_state + + class Firmware: def __init__(self, keymap, row_pins, col_pins, diode_orientation): self.raw_keymap = keymap self.keymap = Keymap(keymap) self.matrix = MatrixScanner(col_pins, row_pins, diode_orientation) + self.store = ReduxStore(reducer) def go(self): while True: - for event in self.keymap.parse(self.matrix.raw_scan()): - print(event) + self.keymap.parse(self.matrix.raw_scan(), store=self.store) diff --git a/upy-freeze.txt b/upy-freeze.txt index 97cb5b7..e69de29 100644 --- a/upy-freeze.txt +++ b/upy-freeze.txt @@ -1,4 +0,0 @@ -vendor/upy-lib/__future__/__future__.py -vendor/upy-lib/functools/functools.py -vendor/upy-lib/string/string.py -vendor/pydux/pydux From ef639f5292a78e93004e5e917ba08d2e196ce747 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Mon, 3 Sep 2018 13:50:12 -0700 Subject: [PATCH 04/11] Add debug hooks to the event dispatcher, remove explicit prints --- kmk/common/event_defs.py | 18 +++++++++ kmk/common/internal_state.py | 69 ++++++++++++++++++++++++++++++++ kmk/common/keymap.py | 17 +------- kmk/firmware.py | 76 +++++------------------------------- upy-freeze.txt | 1 + vendor/pydux | 1 - 6 files changed, 98 insertions(+), 84 deletions(-) create mode 100644 kmk/common/event_defs.py create mode 100644 kmk/common/internal_state.py delete mode 160000 vendor/pydux diff --git a/kmk/common/event_defs.py b/kmk/common/event_defs.py new file mode 100644 index 0000000..12cb1de --- /dev/null +++ b/kmk/common/event_defs.py @@ -0,0 +1,18 @@ +from micropython import const + +KEY_UP_EVENT = const(1) +KEY_DOWN_EVENT = const(2) + + +def key_up_event(keycode): + return { + 'type': KEY_UP_EVENT, + 'keycode': keycode, + } + + +def key_down_event(keycode): + return { + 'type': KEY_DOWN_EVENT, + 'keycode': keycode, + } diff --git a/kmk/common/internal_state.py b/kmk/common/internal_state.py new file mode 100644 index 0000000..0a408ed --- /dev/null +++ b/kmk/common/internal_state.py @@ -0,0 +1,69 @@ +import logging + +from kmk.common.event_defs import KEY_DOWN_EVENT, KEY_UP_EVENT + + +class ReduxStore: + def __init__(self, reducer, log_level=logging.NOTSET): + self.reducer = reducer + self.logger = logging.getLogger(__name__) + self.logger.setLevel(log_level) + self.state = self.reducer(logger=self.logger) + + def dispatch(self, action): + self.logger.debug('Dispatching action: {}'.format(action)) + self.state = self.reducer(self.state, action) + self.logger.debug('Dispatching complete: {}'.format(action)) + + def get_state(self): + return self.state + + +class InternalState: + modifiers_pressed = frozenset() + keys_pressed = frozenset() + + def __repr__(self): + return 'InternalState(mods={}, keys={})'.format( + self.modifiers_pressed, + self.keys_pressed, + ) + + def copy(self, modifiers_pressed=None, keys_pressed=None): + new_state = InternalState() + + if modifiers_pressed is None: + new_state.modifiers_pressed = self.modifiers_pressed.copy() + else: + new_state.modifiers_pressed = modifiers_pressed + + if keys_pressed is None: + new_state.keys_pressed = self.keys_pressed.copy() + else: + new_state.keys_pressed = keys_pressed + + return new_state + + +def kmk_reducer(state=None, action=None, logger=None): + if state is None: + state = InternalState() + + if logger is not None: + logger.debug('Reducer received state of None, creating new') + + if action is None: + if logger is not None: + logger.debug('No action received, returning state unmodified') + + return state + + if action['type'] == KEY_UP_EVENT: + return state.copy(keys_pressed=frozenset( + key for key in state.keys_pressed if key != action['keycode'] + )) + + if action['type'] == KEY_DOWN_EVENT: + return state.copy(keys_pressed=( + state.keys_pressed | {action['keycode']} + )) diff --git a/kmk/common/keymap.py b/kmk/common/keymap.py index 3f3ba09..da018ee 100644 --- a/kmk/common/keymap.py +++ b/kmk/common/keymap.py @@ -1,19 +1,4 @@ -KEY_UP_EVENT = 'KEY_UP' -KEY_DOWN_EVENT = 'KEY_DOWN' - - -def key_up_event(keycode): - return { - 'type': KEY_UP_EVENT, - 'keycode': keycode, - } - - -def key_down_event(keycode): - return { - 'type': KEY_DOWN_EVENT, - 'keycode': keycode, - } +from kmk.common.event_defs import key_down_event, key_up_event class Keymap: diff --git a/kmk/firmware.py b/kmk/firmware.py index 2f6b9ab..b9071fa 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -1,4 +1,7 @@ -from kmk.common.keymap import KEY_DOWN_EVENT, KEY_UP_EVENT, Keymap +import logging + +from kmk.common.internal_state import ReduxStore, kmk_reducer +from kmk.common.keymap import Keymap try: from kmk.circuitpython.matrix import MatrixScanner @@ -6,76 +9,15 @@ except ImportError: from kmk.micropython.matrix import MatrixScanner -class ReduxStore: - def __init__(self, reducer): - self.reducer = reducer - self.state = self.reducer() - - def dispatch(self, action): - self.state = self.reducer(self.state, action) - - def get_state(self): - return self.state - - -class InternalState: - modifiers_pressed = frozenset() - keys_pressed = frozenset() - - def __repr__(self): - return 'InternalState(mods={}, keys={})'.format( - self.modifiers_pressed, - self.keys_pressed, - ) - - def copy(self, modifiers_pressed=None, keys_pressed=None): - new_state = InternalState() - - if modifiers_pressed is None: - new_state.modifiers_pressed = self.modifiers_pressed.copy() - else: - new_state.modifiers_pressed = modifiers_pressed - - if keys_pressed is None: - new_state.keys_pressed = self.keys_pressed.copy() - else: - new_state.keys_pressed = keys_pressed - - return new_state - - -def reducer(state=None, action=None): - if state is None: - state = InternalState() - - if action is None: - return state - - if action['type'] == KEY_UP_EVENT: - new_state = state.copy(keys_pressed=frozenset( - key for key in state.keys_pressed if key != action['keycode'] - )) - - print(new_state) - - return new_state - - if action['type'] == KEY_DOWN_EVENT: - new_state = state.copy(keys_pressed=( - state.keys_pressed | {action['keycode']} - )) - - print(new_state) - - return new_state - - class Firmware: - def __init__(self, keymap, row_pins, col_pins, diode_orientation): + def __init__( + self, keymap, row_pins, col_pins, diode_orientation, + log_level=logging.NOTSET, + ): self.raw_keymap = keymap self.keymap = Keymap(keymap) self.matrix = MatrixScanner(col_pins, row_pins, diode_orientation) - self.store = ReduxStore(reducer) + self.store = ReduxStore(kmk_reducer, log_level=log_level) def go(self): while True: diff --git a/upy-freeze.txt b/upy-freeze.txt index e69de29..b06922b 100644 --- a/upy-freeze.txt +++ b/upy-freeze.txt @@ -0,0 +1 @@ +vendor/upy-lib/logging/logging.py diff --git a/vendor/pydux b/vendor/pydux deleted file mode 160000 index 943ca1c..0000000 --- a/vendor/pydux +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 943ca1c75357b9289f55f17ff2d997a66a3313a4 From 5ba6f15335353c3e2d7a675d2b3e5711f3314e99 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Mon, 3 Sep 2018 13:50:27 -0700 Subject: [PATCH 05/11] Use the new debug mode --- boards/klardotsh/twotwo_matrix_feather.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/boards/klardotsh/twotwo_matrix_feather.py b/boards/klardotsh/twotwo_matrix_feather.py index 84c9cb8..9a814c8 100644 --- a/boards/klardotsh/twotwo_matrix_feather.py +++ b/boards/klardotsh/twotwo_matrix_feather.py @@ -1,3 +1,5 @@ +from logging import DEBUG + import board from kmk.common.consts import DiodeOrientation @@ -20,6 +22,7 @@ def main(): row_pins=rows, col_pins=cols, diode_orientation=diode_orientation, + log_level=DEBUG, ) firmware.go() From f0957227ff201a462426f14e731e1786a4b664ae Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Mon, 3 Sep 2018 13:50:53 -0700 Subject: [PATCH 06/11] Niceties --- Pipfile | 1 + Pipfile.lock | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index e45b392..45f1914 100644 --- a/Pipfile +++ b/Pipfile @@ -15,6 +15,7 @@ ipdb = "*" "flake8-commas" = "*" isort = "*" "flake8-isort" = "*" +neovim = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 1fe5584..e92f6e8 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "adeed87e2092ae72a6de1ba825163f5e9fd803059aea3cc42a461aa729914b23" + "sha256": "44e8c37b94a71b7f47fc43f2c98bf17d546f4a5ef7ad1cad5076d4a47fc4515a" }, "pipfile-spec": 6, "requires": { @@ -86,6 +86,30 @@ "index": "pypi", "version": "==2.5" }, + "greenlet": { + "hashes": [ + "sha256:0411b5bf0de5ec11060925fd811ad49073fa19f995bcf408839eb619b59bb9f7", + "sha256:131f4ed14f0fd28d2a9fa50f79a57d5ed1c8f742d3ccac3d773fee09ef6fe217", + "sha256:13510d32f8db72a0b3e1720dbf8cba5c4eecdf07abc4cb631982f51256c453d1", + "sha256:31dc4d77ef04ab0460d024786f51466dbbc274fda7c8aad0885a6df5ff8d642e", + "sha256:35021d9fecea53b21e4defec0ff3ad69a8e2b75aca1ceddd444a5ba71216547e", + "sha256:426a8ef9e3b97c27e841648241c2862442c13c91ec4a48c4a72b262ccf30add9", + "sha256:58217698193fb94f3e6ff57eed0ae20381a8d06c2bc10151f76c06bb449a3a19", + "sha256:5f45adbbb69281845981bb4e0a4efb8a405f10f3cd6c349cb4a5db3357c6bf93", + "sha256:5fdb524767288f7ad161d2182f7ed6cafc0a283363728dcd04b9485f6411547c", + "sha256:71fbee1f7ef3fb42efa3761a8faefc796e7e425f528de536cfb4c9de03bde885", + "sha256:80bd314157851d06f7db7ca527082dbb0ee97afefb529cdcd59f7a5950927ba0", + "sha256:b843c9ef6aed54a2649887f55959da0031595ccfaf7e7a0ba7aa681ffeaa0aa1", + "sha256:c6a05ef8125503d2d282ccf1448e3599b8a6bd805c3cdee79760fa3da0ea090e", + "sha256:deeda2769a52db840efe5bf7bdf7cefa0ae17b43a844a3259d39fb9465c8b008", + "sha256:e66f8b09eec1afdcab947d3a1d65b87b25fde39e9172ae1bec562488335633b4", + "sha256:e8db93045414980dbada8908d49dbbc0aa134277da3ff613b3e548cb275bdd37", + "sha256:f1cc268a15ade58d9a0c04569fe6613e19b8b0345b64453064e2c3c6d79051af", + "sha256:fe3001b6a4f3f3582a865b9e5081cc548b973ec20320f297f5e2d46860e9c703", + "sha256:fe85bf7adb26eb47ad53a1bae5d35a28df16b2b93b89042a3a28746617a4738d" + ], + "version": "==0.4.14" + }, "ipdb": { "hashes": [ "sha256:7081c65ed7bfe7737f83fa4213ca8afd9617b42ff6b3f1daf9a3419839a2a00a" @@ -131,6 +155,33 @@ ], "version": "==0.6.1" }, + "msgpack": { + "hashes": [ + "sha256:0b3b1773d2693c70598585a34ca2715873ba899565f0a7c9a1545baef7e7fbdc", + "sha256:0bae5d1538c5c6a75642f75a1781f3ac2275d744a92af1a453c150da3446138b", + "sha256:0ee8c8c85aa651be3aa0cd005b5931769eaa658c948ce79428766f1bd46ae2c3", + "sha256:1369f9edba9500c7a6489b70fdfac773e925342f4531f1e3d4c20ac3173b1ae0", + "sha256:22d9c929d1d539f37da3d1b0e16270fa9d46107beab8c0d4d2bddffffe895cee", + "sha256:2ff43e3247a1e11d544017bb26f580a68306cec7a6257d8818893c1fda665f42", + "sha256:31a98047355d34d047fcdb55b09cb19f633cf214c705a765bd745456c142130c", + "sha256:8767eb0032732c3a0da92cbec5ac186ef89a3258c6edca09161472ca0206c45f", + "sha256:8acc8910218555044e23826980b950e96685dc48124a290c86f6f41a296ea172", + "sha256:ab189a6365be1860a5ecf8159c248f12d33f79ea799ae9695fa6a29896dcf1d4", + "sha256:cfd6535feb0f1cf1c7cdb25773e965cc9f92928244a8c3ef6f8f8a8e1f7ae5c4", + "sha256:e274cd4480d8c76ec467a85a9c6635bbf2258f0649040560382ab58cabb44bcf", + "sha256:f86642d60dca13e93260187d56c2bef2487aa4d574a669e8ceefcf9f4c26fd00", + "sha256:f8a57cbda46a94ed0db55b73e6ab0c15e78b4ede8690fa491a0e55128d552bb0", + "sha256:fcea97a352416afcbccd7af9625159d80704a25c519c251c734527329bb20d0e" + ], + "version": "==0.5.6" + }, + "neovim": { + "hashes": [ + "sha256:6ce58a742e0427491c0e1c8108556ee72ba33844209bd9e226b8da9538299276" + ], + "index": "pypi", + "version": "==0.2.6" + }, "parso": { "hashes": [ "sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2", From cb923096b62a22d49499fd3aa8b95f64eded5d44 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Mon, 3 Sep 2018 13:51:04 -0700 Subject: [PATCH 07/11] Clean up NRF builds big time --- Makefile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index c114676..69ade93 100644 --- a/Makefile +++ b/Makefile @@ -54,15 +54,15 @@ circuitpy-flash-nrf-entrypoint: @-timeout -k 5s 10s pipenv run ampy -p ${NRF_DFU_PORT} -d ${NRF_DFU_DELAY} -b ${NRF_DFU_BAUD} put entrypoints/feather_nrf52832.py main.py @echo "===> Flashed keyboard successfully!" -build-feather-test: lint devdeps circuitpy-deps circuitpy-freeze-kmk-nrf +ifndef BOARD +build-feather-nrf52832: + @echo "===> Must provide a board (usually from boards/...) to build!" +else +build-feather-nrf52832: lint devdeps circuitpy-deps circuitpy-freeze-kmk-nrf @echo "===> Preparing keyboard script for bundling into CircuitPython" - @cp -av boards/klardotsh/twotwo_matrix_feather.py vendor/circuitpython/ports/nrf/freeze/kmk_keyboard_user.py - @$(MAKE) circuitpy-flash-nrf circuitpy-flash-nrf-entrypoint - -build-feather-noop: lint devdeps circuitpy-deps circuitpy-freeze-kmk-nrf - @echo "===> Preparing keyboard script for bundling into CircuitPython" - @cp -av boards/noop.py vendor/circuitpython/ports/nrf/freeze/kmk_keyboard_user.py + @cp -av ${BOARD} vendor/circuitpython/ports/nrf/freeze/kmk_keyboard_user.py @$(MAKE) circuitpy-flash-nrf circuitpy-flash-nrf-entrypoint +endif # Fully wipe the board with only stock CircuitPython burn-it-all-with-fire: lint devdeps From 96368c46328df6e12e72a6714c8bdd97d0cc3063 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Mon, 3 Sep 2018 13:53:45 -0700 Subject: [PATCH 08/11] Stop complaining about changed files in the vendor folder we knowingly do terribad things to --- .gitmodules | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitmodules b/.gitmodules index 0d46a64..2f10435 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,6 +2,7 @@ path = vendor/circuitpython url = https://github.com/adafruit/circuitpython.git branch = "9b98ad779468676c3d5f1efdc06b454aaed7c407" + ignore = dirty [submodule "upy-lib"] path = vendor/upy-lib url = https://github.com/micropython/micropython-lib.git From ea18655a210592f04af2c8070f980936b1e22907 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Mon, 3 Sep 2018 15:20:27 -0700 Subject: [PATCH 09/11] On unhandled exceptions, blink the feather LED repeatedly --- entrypoints/feather_nrf52832.py | 10 +++++++++- kmk/circuitpython/util.py | 12 +++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/entrypoints/feather_nrf52832.py b/entrypoints/feather_nrf52832.py index c07d83c..a21b146 100644 --- a/entrypoints/feather_nrf52832.py +++ b/entrypoints/feather_nrf52832.py @@ -1,4 +1,12 @@ +import sys + +from kmk.circuitpython.util import feather_red_led_flash from kmk_keyboard_user import main if __name__ == '__main__': - main() + try: + main() + except Exception as e: + sys.print_exception(e) + feather_red_led_flash(duration=10, rate=0.5) + sys.exit(1) diff --git a/kmk/circuitpython/util.py b/kmk/circuitpython/util.py index 0b822b1..a0575f7 100644 --- a/kmk/circuitpython/util.py +++ b/kmk/circuitpython/util.py @@ -1,23 +1,17 @@ -import sys import time import board import digitalio -def feather_signal_error_with_led_flash(rate=0.5): +def feather_red_led_flash(duration=10, rate=0.5): ''' - Flash the red LED for 10 seconds, alternating every $rate - Could be useful as an uncaught exception handler later on, - but is for now unused + Flash the red LED for $duration seconds, alternating every $rate ''' rled = digitalio.DigitalInOut(board.LED1) rled.direction = digitalio.Direction.OUTPUT - # blink for 5 seconds and exit - for cycle in range(10): + for cycle in range(duration / rate): rled.value = cycle % 2 time.sleep(rate) - - sys.exit(1) From b9dfffd2b355e403ee98af3d0ac6ee3b3b206e10 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Mon, 3 Sep 2018 15:20:48 -0700 Subject: [PATCH 10/11] More sane linter rules --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 49656ae..fc463b5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,6 @@ [flake8] exclude = .git,__pycache__,vendor +max_line_length = 99 [isort] known_third_party = analogio,bitbangio,bleio,board,busio,digitalio,framebuf,gamepad,gc,microcontroller,micropython,pulseio,pyb,pydux,uio,ubluepy From ffd47c478fa49b8afb20ee4fcdc03e7e0ad40940 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Mon, 3 Sep 2018 15:21:34 -0700 Subject: [PATCH 11/11] Move all remaining state into the single store, woot! --- kmk/common/event_defs.py | 19 ++++++- kmk/common/internal_state.py | 104 ++++++++++++++++++++++++++++------- kmk/common/keymap.py | 22 +++++--- kmk/firmware.py | 28 +++++++++- 4 files changed, 139 insertions(+), 34 deletions(-) diff --git a/kmk/common/event_defs.py b/kmk/common/event_defs.py index 12cb1de..04a1f7a 100644 --- a/kmk/common/event_defs.py +++ b/kmk/common/event_defs.py @@ -2,17 +2,32 @@ from micropython import const KEY_UP_EVENT = const(1) KEY_DOWN_EVENT = const(2) +INIT_FIRMWARE_EVENT = const(3) -def key_up_event(keycode): +def init_firmware(keymap, row_pins, col_pins, diode_orientation): + return { + 'type': INIT_FIRMWARE_EVENT, + 'keymap': keymap, + 'row_pins': row_pins, + 'col_pins': col_pins, + 'diode_orientation': diode_orientation, + } + + +def key_up_event(keycode, row, col): return { 'type': KEY_UP_EVENT, 'keycode': keycode, + 'row': row, + 'col': col, } -def key_down_event(keycode): +def key_down_event(keycode, row, col): return { 'type': KEY_DOWN_EVENT, 'keycode': keycode, + 'row': row, + 'col': col, } diff --git a/kmk/common/internal_state.py b/kmk/common/internal_state.py index 0a408ed..a7790b0 100644 --- a/kmk/common/internal_state.py +++ b/kmk/common/internal_state.py @@ -1,6 +1,9 @@ import logging +import sys -from kmk.common.event_defs import KEY_DOWN_EVENT, KEY_UP_EVENT +from kmk.common.consts import DiodeOrientation +from kmk.common.event_defs import (INIT_FIRMWARE_EVENT, KEY_DOWN_EVENT, + KEY_UP_EVENT) class ReduxStore: @@ -9,38 +12,69 @@ class ReduxStore: self.logger = logging.getLogger(__name__) self.logger.setLevel(log_level) self.state = self.reducer(logger=self.logger) + self.callbacks = [] def dispatch(self, action): self.logger.debug('Dispatching action: {}'.format(action)) self.state = self.reducer(self.state, action) self.logger.debug('Dispatching complete: {}'.format(action)) + self.logger.debug('Calling subscriptions') + + for cb in self.callbacks: + if cb is not None: + try: + cb(self.state, action) + except Exception as e: + self.logger.error('Callback failed, moving on') + print(sys.print_exception(e), file=sys.stderr) + + self.logger.debug('Callbacks complete') + def get_state(self): return self.state + def subscribe(self, callback): + self.callbacks.append(callback) + return len(self.callbacks) - 1 + + def unsubscribe(self, idx): + self.callbacks[idx] = None + class InternalState: modifiers_pressed = frozenset() keys_pressed = frozenset() + keymap = [] + row_pins = [] + col_pins = [] + matrix = [] + diode_orientation = DiodeOrientation.COLUMNS + + @property + def __dict__(self): + return { + 'keys_pressed': self.keys_pressed, + 'modifiers_pressed': self.modifiers_pressed, + 'keymap': self.keymap, + 'col_pins': self.col_pins, + 'row_pins': self.row_pins, + 'diode_orientation': self.diode_orientation, + } def __repr__(self): - return 'InternalState(mods={}, keys={})'.format( - self.modifiers_pressed, - self.keys_pressed, - ) + return 'InternalState({})'.format(self.__dict__) - def copy(self, modifiers_pressed=None, keys_pressed=None): + def copy(self, **kwargs): new_state = InternalState() - if modifiers_pressed is None: - new_state.modifiers_pressed = self.modifiers_pressed.copy() - else: - new_state.modifiers_pressed = modifiers_pressed + for k, v in self.__dict__.items(): + if hasattr(new_state, k): + setattr(new_state, k, v) - if keys_pressed is None: - new_state.keys_pressed = self.keys_pressed.copy() - else: - new_state.keys_pressed = keys_pressed + for k, v in kwargs.items(): + if hasattr(new_state, k): + setattr(new_state, k, v) return new_state @@ -59,11 +93,41 @@ def kmk_reducer(state=None, action=None, logger=None): return state if action['type'] == KEY_UP_EVENT: - return state.copy(keys_pressed=frozenset( - key for key in state.keys_pressed if key != action['keycode'] - )) + return state.copy( + keys_pressed=frozenset( + key for key in state.keys_pressed if key != action['keycode'] + ), + matrix=[ + r if ridx != action['row'] else [ + c if cidx != action['col'] else False + for cidx, c in enumerate(r) + ] + for ridx, r in enumerate(state.matrix) + ], + ) if action['type'] == KEY_DOWN_EVENT: - return state.copy(keys_pressed=( - state.keys_pressed | {action['keycode']} - )) + return state.copy( + keys_pressed=( + state.keys_pressed | {action['keycode']} + ), + matrix=[ + r if ridx != action['row'] else [ + c if cidx != action['col'] else True + for cidx, c in enumerate(r) + ] + for ridx, r in enumerate(state.matrix) + ], + ) + + if action['type'] == INIT_FIRMWARE_EVENT: + return state.copy( + keymap=action['keymap'], + row_pins=action['row_pins'], + col_pins=action['col_pins'], + diode_orientation=action['diode_orientation'], + matrix=[ + [False for c in action['col_pins']] + for r in action['row_pins'] + ], + ) diff --git a/kmk/common/keymap.py b/kmk/common/keymap.py index da018ee..c135552 100644 --- a/kmk/common/keymap.py +++ b/kmk/common/keymap.py @@ -4,18 +4,22 @@ from kmk.common.event_defs import key_down_event, key_up_event class Keymap: def __init__(self, map): self.map = map - self.state = [ - [False for _ in row] - for row in self.map - ] def parse(self, matrix, store): + state = store.get_state() + for ridx, row in enumerate(matrix): for cidx, col in enumerate(row): - if col != self.state[ridx][cidx]: + if col != state.matrix[ridx][cidx]: if col: - store.dispatch(key_down_event(self.map[ridx][cidx])) + store.dispatch(key_down_event( + row=ridx, + col=cidx, + keycode=self.map[ridx][cidx], + )) else: - store.dispatch(key_up_event(self.map[ridx][cidx])) - - self.state = matrix + store.dispatch(key_up_event( + row=ridx, + col=cidx, + keycode=self.map[ridx][cidx], + )) diff --git a/kmk/firmware.py b/kmk/firmware.py index b9071fa..907919c 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -1,5 +1,6 @@ import logging +from kmk.common.event_defs import init_firmware from kmk.common.internal_state import ReduxStore, kmk_reducer from kmk.common.keymap import Keymap @@ -14,10 +15,31 @@ class Firmware: self, keymap, row_pins, col_pins, diode_orientation, log_level=logging.NOTSET, ): - self.raw_keymap = keymap - self.keymap = Keymap(keymap) - self.matrix = MatrixScanner(col_pins, row_pins, diode_orientation) + self.cached_state = None self.store = ReduxStore(kmk_reducer, log_level=log_level) + self.store.subscribe( + lambda state, action: self._subscription(state, action), + ) + self.store.dispatch(init_firmware( + keymap=keymap, + row_pins=row_pins, + col_pins=col_pins, + diode_orientation=diode_orientation, + )) + + def _subscription(self, state, action): + if self.cached_state is None or self.cached_state.keymap != state.keymap: + self.keymap = Keymap(state.keymap) + + if self.cached_state is None or any( + getattr(self.cached_state, k) != getattr(state, k) + for k in state.__dict__.keys() + ): + self.matrix = MatrixScanner( + state.col_pins, + state.row_pins, + state.diode_orientation, + ) def go(self): while True: