Merge pull request #6 from klardotsh/topic-event-loop

The basic "Redux" style event loop to unify all state
This commit is contained in:
Josh Klar 2018-09-03 15:34:20 -07:00 committed by GitHub
commit a2742984ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 525 additions and 72 deletions

5
.gitmodules vendored
View File

@ -2,10 +2,7 @@
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"
ignore = dirty
[submodule "upy-lib"]
path = vendor/upy-lib
url = https://github.com/micropython/micropython-lib.git

View File

@ -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,26 +43,26 @@ 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
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
@ -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?"

View File

@ -4,11 +4,18 @@ verify_ssl = true
name = "pypi"
[packages]
pydux = "*"
[dev-packages]
adafruit-ampy = "*"
"flake8" = "*"
"flake8-comprehensions" = "*"
ipython = "*"
ipdb = "*"
"flake8-commas" = "*"
isort = "*"
"flake8-isort" = "*"
neovim = "*"
[requires]
python_version = "3.7"

210
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "e246783475c5feb51445494a3f30c397ad6ddaed35c45decc9846970d001ad50"
"sha256": "44e8c37b94a71b7f47fc43f2c98bf17d546f4a5ef7ad1cad5076d4a47fc4515a"
},
"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": [
@ -24,6 +33,13 @@
"index": "pypi",
"version": "==1.0.5"
},
"backcall": {
"hashes": [
"sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4",
"sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"
],
"version": "==0.1.0"
},
"click": {
"hashes": [
"sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
@ -31,6 +47,13 @@
],
"version": "==6.7"
},
"decorator": {
"hashes": [
"sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82",
"sha256:c39efa13fbdeb4506c476c9b3babf6a718da943dab7811c206005a4a956c080c"
],
"version": "==4.3.0"
},
"flake8": {
"hashes": [
"sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0",
@ -39,6 +62,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 +78,76 @@
"index": "pypi",
"version": "==1.4.1"
},
"flake8-isort": {
"hashes": [
"sha256:298d7904ac3a46274edf4ce66fd7e272c2a60c34c3cc999dea000608d64e5e6e",
"sha256:5992850626ce96547b1f1c7e8a7f0ef49ab2be44eca2177934566437b636fa3c"
],
"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"
],
"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 +155,70 @@
],
"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",
"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 +233,13 @@
],
"version": "==1.6.0"
},
"pygments": {
"hashes": [
"sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
"sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
],
"version": "==2.2.0"
},
"pyserial": {
"hashes": [
"sha256:6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627",
@ -81,6 +253,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"
}
}
}

View File

@ -1,23 +1,28 @@
from logging import DEBUG
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,
log_level=DEBUG,
)
firmware.go()

View File

@ -1,5 +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)

View File

@ -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 = []

View File

@ -1,22 +1,17 @@
import time
import board
import digitalio
import time
import sys
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)

View File

View File

@ -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')

33
kmk/common/event_defs.py Normal file
View File

@ -0,0 +1,33 @@
from micropython import const
KEY_UP_EVENT = const(1)
KEY_DOWN_EVENT = const(2)
INIT_FIRMWARE_EVENT = const(3)
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, row, col):
return {
'type': KEY_DOWN_EVENT,
'keycode': keycode,
'row': row,
'col': col,
}

View File

@ -0,0 +1,133 @@
import logging
import sys
from kmk.common.consts import DiodeOrientation
from kmk.common.event_defs import (INIT_FIRMWARE_EVENT, 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)
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({})'.format(self.__dict__)
def copy(self, **kwargs):
new_state = InternalState()
for k, v in self.__dict__.items():
if hasattr(new_state, k):
setattr(new_state, k, v)
for k, v in kwargs.items():
if hasattr(new_state, k):
setattr(new_state, k, v)
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']
),
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']}
),
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']
],
)

View File

@ -1,18 +1,25 @@
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):
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]:
print('{}: {}'.format(
'KEYDOWN' if col else 'KEYUP',
self.map[ridx][cidx],
))
self.state = matrix
if col != state.matrix[ridx][cidx]:
if col:
store.dispatch(key_down_event(
row=ridx,
col=cidx,
keycode=self.map[ridx][cidx],
))
else:
store.dispatch(key_up_event(
row=ridx,
col=cidx,
keycode=self.map[ridx][cidx],
))

46
kmk/firmware.py Normal file
View File

@ -0,0 +1,46 @@
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
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,
log_level=logging.NOTSET,
):
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:
self.keymap.parse(self.matrix.raw_scan(), store=self.store)

View File

@ -1,2 +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

View File

@ -1,4 +1 @@
vendor/upy-lib/__future__/__future__.py
vendor/upy-lib/functools/functools.py
vendor/upy-lib/string/string.py
vendor/pydux/pydux
vendor/upy-lib/logging/logging.py

1
vendor/pydux vendored

@ -1 +0,0 @@
Subproject commit 943ca1c75357b9289f55f17ff2d997a66a3313a4