Merge pull request #45 from KMKfw/topic-feather-express-m4

Add support for Adafruit Feather+ItsyBitsy M4 Express boards through CircuitPython
This commit is contained in:
Josh Klar 2018-10-07 19:00:04 -07:00 committed by GitHub
commit 914b305fee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 999 additions and 553 deletions

View File

@ -2,19 +2,26 @@ version: 2
jobs:
lint:
docker:
- image: 'python:3.7'
- image: 'ubuntu:bionic'
environment:
KMK_TEST: 1
PIPENV_VENV_IN_PROJECT: 1
steps:
- run: apt-get update && apt-get install -y software-properties-common git ssh wget
- run: add-apt-repository ppa:deadsnakes/ppa
- run: apt-get update && apt-get install -y python3.7 python3.7-dev build-essential pkg-config libffi-dev
- run: wget https://bootstrap.pypa.io/get-pip.py -O /tmp/get-pip.py
- run: python3.7 /tmp/get-pip.py
- run: python3.7 -m pip install pip==18.0 # downgrade pip to work around https://github.com/pypa/pipenv/issues/2924
- checkout
- restore_cache:
keys:
- v1-kmk-venv-{{ checksum "Pipfile.lock" }}
- run: pip install pipenv==2018.7.1
- run: python3.7 -m pip install pipenv==2018.7.1
- run: make lint
- save_cache:
@ -24,60 +31,107 @@ jobs:
test:
docker:
- image: 'python:3.7'
- image: 'ubuntu:bionic'
environment:
KMK_TEST: 1
PIPENV_VENV_IN_PROJECT: 1
steps:
- run: apt-get update && apt-get install -y software-properties-common git ssh
- run: add-apt-repository ppa:team-gcc-arm-embedded/ppa
- run: add-apt-repository ppa:deadsnakes/ppa
- run: apt-get update && apt-get install -y gcc-arm-embedded gettext wget unzip rsync python3.7 python3.7-dev build-essential pkg-config libffi-dev
- run: wget https://bootstrap.pypa.io/get-pip.py -O /tmp/get-pip.py
- run: python3.7 /tmp/get-pip.py
- run: python3.7 -m pip install pip==18.0 # downgrade pip to work around https://github.com/pypa/pipenv/issues/2924
- checkout
- restore_cache:
keys:
- v1-kmk-venv-{{ checksum "Pipfile.lock" }}
- run: pip install pipenv==2018.7.1
- run: apt-get update && apt-get install -y gcc-arm-none-eabi gettext wget unzip rsync
- run: python3.7 -m pip install pipenv==2018.7.1
- run: make test
build_feather_m4_express:
docker:
- image: 'ubuntu:bionic'
environment:
KMK_TEST: 1
PIPENV_VENV_IN_PROJECT: 1
steps:
- run: apt-get update && apt-get install -y software-properties-common git ssh
- run: add-apt-repository ppa:team-gcc-arm-embedded/ppa
- run: add-apt-repository ppa:deadsnakes/ppa
- run: apt-get update && apt-get install -y gcc-arm-embedded gettext wget unzip rsync python3.7 python3.7-dev build-essential pkg-config libffi-dev
- run: wget https://bootstrap.pypa.io/get-pip.py -O /tmp/get-pip.py
- run: python3.7 /tmp/get-pip.py
- run: python3.7 -m pip install pip==18.0 # downgrade pip to work around https://github.com/pypa/pipenv/issues/2924
- checkout
- restore_cache:
keys:
- v1-kmk-venv-{{ checksum "Pipfile.lock" }}
- run: python3.7 -m pip install pipenv==2018.7.1
- run: make SKIP_KEYMAP_VALIDATION=1 USER_KEYMAP=user_keymaps/noop.py build-feather-m4-express
build_itsybitsy_m4_express:
docker:
- image: 'ubuntu:bionic'
environment:
KMK_TEST: 1
PIPENV_VENV_IN_PROJECT: 1
steps:
- run: apt-get update && apt-get install -y software-properties-common git ssh
- run: add-apt-repository ppa:team-gcc-arm-embedded/ppa
- run: add-apt-repository ppa:deadsnakes/ppa
- run: apt-get update && apt-get install -y gcc-arm-embedded gettext wget unzip rsync python3.7 python3.7-dev build-essential pkg-config libffi-dev
- run: wget https://bootstrap.pypa.io/get-pip.py -O /tmp/get-pip.py
- run: python3.7 /tmp/get-pip.py
- run: python3.7 -m pip install pip==18.0 # downgrade pip to work around https://github.com/pypa/pipenv/issues/2924
- checkout
- restore_cache:
keys:
- v1-kmk-venv-{{ checksum "Pipfile.lock" }}
- run: python3.7 -m pip install pipenv==2018.7.1
- run: make SKIP_KEYMAP_VALIDATION=1 USER_KEYMAP=user_keymaps/noop.py build-itsybitsy-m4-express
build_pyboard:
docker:
- image: 'python:3.7'
- image: 'ubuntu:bionic'
environment:
KMK_TEST: 1
PIPENV_VENV_IN_PROJECT: 1
steps:
- run: apt-get update && apt-get install -y software-properties-common git ssh
- run: add-apt-repository ppa:team-gcc-arm-embedded/ppa
- run: add-apt-repository ppa:deadsnakes/ppa
- run: apt-get update && apt-get install -y gcc-arm-embedded gettext wget unzip rsync python3.7 python3.7-dev build-essential pkg-config libffi-dev
- run: wget https://bootstrap.pypa.io/get-pip.py -O /tmp/get-pip.py
- run: python3.7 /tmp/get-pip.py
- run: python3.7 -m pip install pip==18.0 # downgrade pip to work around https://github.com/pypa/pipenv/issues/2924
- checkout
- restore_cache:
keys:
- v1-kmk-venv-{{ checksum "Pipfile.lock" }}
- run: pip install pipenv==2018.7.1
- run: apt-get update && apt-get install -y gcc-arm-none-eabi gettext wget unzip rsync
- run: python3.7 -m pip install pipenv==2018.7.1
- run: make SKIP_KEYMAP_VALIDATION=1 USER_KEYMAP=user_keymaps/noop.py build-pyboard
build_teensy_31:
docker:
- image: 'python:3.7'
environment:
KMK_TEST: 1
PIPENV_VENV_IN_PROJECT: 1
steps:
- checkout
- restore_cache:
keys:
- v1-kmk-venv-{{ checksum "Pipfile.lock" }}
- run: pip install pipenv==2018.7.1
- run: apt-get update && apt-get install -y gcc-arm-none-eabi gettext wget unzip rsync
- run: make USER_KEYMAP=user_keymaps/noop.py build-teensy-3.1
workflows:
version: 2
build-deploy:
@ -96,6 +150,22 @@ workflows:
only: /.*/
requires:
- lint
- build_feather_m4_express:
filters:
branches:
only: /.*/
tags:
only: /.*/
requires:
- test
- build_itsybitsy_m4_express:
filters:
branches:
only: /.*/
tags:
only: /.*/
requires:
- test
- build_pyboard:
filters:
branches:
@ -104,11 +174,3 @@ workflows:
only: /.*/
requires:
- test
- build_teensy_31:
filters:
branches:
only: /.*/
tags:
only: /.*/
requires:
- test

1
.gitignore vendored
View File

@ -107,6 +107,7 @@ venv.bak/
.submodules
.circuitpy-deps
.micropython-deps
.devdeps
# Pycharms cruft
.idea

2
.gitmodules vendored
View File

@ -4,7 +4,7 @@
ignore = dirty
[submodule "upy-lib"]
path = vendor/upy-lib
url = https://github.com/micropython/micropython-lib.git
url = https://github.com/kmkfw/micropython-lib.git
ignore = dirty
[submodule "micropython"]
path = vendor/micropython

183
Makefile
View File

@ -12,8 +12,11 @@ AMPY_DELAY ?= 1.5
ARDUINO ?= /usr/share/arduino
PIPENV ?= $(shell which pipenv)
devdeps: Pipfile.lock
.devdeps: Pipfile.lock
@$(PIPENV) install --dev --ignore-pipfile
@touch .devdeps
devdeps: .devdeps
lint: devdeps
@$(PIPENV) run flake8
@ -42,20 +45,21 @@ test: micropython-build-unix
.submodules: .gitmodules submodules.toml
@echo "===> Pulling dependencies, this may take several minutes"
@git submodule sync
@git submodule update --init --recursive
@rsync -avh vendor/ build/
@rsync -ah vendor/ build/
@touch .submodules
.circuitpy-deps: .submodules
@echo "===> Building circuitpython/mpy-cross"
@make -C build/circuitpython/mpy-cross
@pipenv run $(MAKE) -C build/circuitpython/mpy-cross
@echo "===> Pulling Nordic BLE stack"
@cd build/circuitpython/ports/nrf && ./drivers/bluetooth/download_ble_stack.sh 2>/dev/null >/dev/null
@touch .circuitpy-deps
.micropython-deps: .submodules
@echo "===> Building micropython/mpy-cross"
@make -C build/micropython/mpy-cross
@pipenv run $(MAKE) -C build/micropython/mpy-cross
@touch .micropython-deps
submodules: .submodules
@ -63,74 +67,93 @@ circuitpy-deps: .circuitpy-deps
micropython-deps: .micropython-deps
build/micropython/ports/unix/micropython: micropython-deps build/micropython/ports/unix/modules/.kmk_frozen
@make -j4 -C build/micropython/ports/unix
@pipenv run $(MAKE) -j4 -C build/micropython/ports/unix
micropython-build-unix: build/micropython/ports/unix/micropython
freeze-atmel-samd-build-deps: build/circuitpython/ports/atmel-samd/modules/.kmk_frozen
freeze-nrf-build-deps: build/circuitpython/ports/nrf/freeze/.kmk_frozen
freeze-teensy3.1-build-deps: build/micropython/ports/teensy/freeze/.kmk_frozen
freeze-stm32-build-deps: build/micropython/ports/stm32/freeze/.kmk_frozen
build/micropython/ports/unix/modules/.kmk_frozen: upy-freeze.txt
build/micropython/ports/unix/modules/.kmk_frozen: upy-freeze.txt submodules.toml
@echo "===> Preparing builded dependencies for local development"
@rm -rf build/micropython/ports/unix/freeze/*
@cat $< | xargs -I '{}' cp -a {} build/micropython/ports/unix/modules/
@rm -rf build/micropython/ports/unix/modules/*
@cat upy-freeze.txt | egrep -v '(^#|^\s*$|^\s*\t*#)' | grep MICROPY | cut -d'|' -f2- | \
xargs -I '{}' cp -a {} build/micropython/ports/unix/modules/
@touch $@
build/circuitpython/ports/nrf/freeze/.kmk_frozen: upy-freeze.txt
build/circuitpython/ports/atmel-samd/modules/.kmk_frozen: upy-freeze.txt submodules.toml
@echo "===> Preparing builded dependencies for bundling"
@rm -rf build/circuitpython/ports/atmel-samd/modules/*
@cat upy-freeze.txt | egrep -v '(^#|^\s*$|^\s*\t*#)' | grep CIRCUITPY | cut -d'|' -f2- | \
xargs -I '{}' cp -a {} build/circuitpython/ports/atmel-samd/modules/
@touch $@
build/circuitpython/ports/nrf/freeze/.kmk_frozen: upy-freeze.txt submodules.toml
@echo "===> Preparing builded dependencies for bundling"
@rm -rf build/circuitpython/ports/nrf/freeze/*
@cat $< | xargs -I '{}' cp -a {} build/circuitpython/ports/nrf/freeze/
@cat upy-freeze.txt | egrep -v '(^#|^\s*$|^\s*\t*#)' | grep CIRCUITPY | cut -d'|' -f2- | \
xargs -I '{}' cp -a {} build/circuitpython/ports/nrf/freeze/
@touch $@
build/micropython/ports/teensy/freeze/.kmk_frozen: upy-freeze.txt
@echo "===> Preparing builded dependencies for bundling"
@mkdir -p build/micropython/ports/teensy/freeze/
@rm -rf build/micropython/ports/teensy/freeze/*
@cat $< | xargs -I '{}' cp -a {} build/micropython/ports/teensy/freeze/
@touch $@
build/micropython/ports/stm32/freeze/.kmk_frozen: upy-freeze.txt
build/micropython/ports/stm32/freeze/.kmk_frozen: upy-freeze.txt submodules.toml
@echo "===> Preparing builded dependencies for bundling"
@mkdir -p build/micropython/ports/stm32/freeze/
@rm -rf build/micropython/ports/stm32/freeze/*
@cat $< | xargs -I '{}' cp -a {} build/micropython/ports/stm32/freeze/
@cat upy-freeze.txt | egrep -v '(^#|^\s*$|^\s*\t*#)' | grep MICROPY | cut -d'|' -f2- | \
xargs -I '{}' cp -a {} build/micropython/ports/stm32/freeze/
@touch $@
circuitpy-freeze-kmk-atmel-samd: freeze-atmel-samd-build-deps
@echo "===> Preparing KMK source for bundling into CircuitPython"
@rm -rf build/circuitpython/ports/atmel-samd/modules/kmk*
@rsync -ah kmk build/circuitpython/ports/atmel-samd/modules/
circuitpy-freeze-kmk-nrf: freeze-nrf-build-deps
@echo "===> Preparing KMK source for bundling into CircuitPython"
@rm -rf build/circuitpython/ports/nrf/kmk*
@cp -av kmk build/circuitpython/ports/nrf/freeze/
micropython-freeze-kmk-teensy3.1: freeze-teensy3.1-build-deps
@echo "===> Preparing KMK source for bundling into MicroPython"
@rm -rf build/micropython/ports/teensy/kmk*
@cp -av kmk build/micropython/ports/teensy/memzip_files/
@rsync -ah kmk build/circuitpython/ports/nrf/freeze/
micropython-freeze-kmk-stm32: freeze-stm32-build-deps
@echo "===> Preparing KMK source for bundling into MicroPython"
@rm -rf build/micropython/ports/stm32/freeze/kmk*
@cp -av kmk build/micropython/ports/stm32/freeze/
@rsync -ah kmk build/micropython/ports/stm32/freeze/
circuitpy-build-feather-m4-express:
@echo "===> Building CircuitPython"
@pipenv run $(MAKE) -C build/circuitpython/ports/atmel-samd BOARD=feather_m4_express FROZEN_MPY_DIRS="modules" clean all
circuitpy-build-itsybitsy-m4-express:
@echo "===> Building CircuitPython"
@pipenv run $(MAKE) -C build/circuitpython/ports/atmel-samd BOARD=itsybitsy_m4_express FROZEN_MPY_DIRS="modules" clean all
circuitpy-build-nrf:
@echo "===> Building CircuitPython"
@make -C build/circuitpython/ports/nrf BOARD=feather_nrf52832 SERIAL=${AMPY_PORT} SD=s132 FROZEN_MPY_DIR=freeze clean all
@pipenv run $(MAKE) -C build/circuitpython/ports/nrf BOARD=feather_nrf52832 SERIAL=${AMPY_PORT} SD=s132 FROZEN_MPY_DIR=freeze clean all
circuitpy-flash-feather-m4-express:
@echo "Flashing not available for Feather M4 Express over bossa right now"
@echo "First, double tap the reset button on the Feather. You should see a red light near the USB port"
@echo "Then, find and (if necessary) mount the USB drive that will show up (should be about 4MB)"
@echo "Copy build/circuitpython/ports/atmel-samd/build-feather_m4_express/firmware.uf2 to this device"
@echo "The device will auto-reboot. You may need to forcibly unmount the drive on Linuxes, with umount -f path/to/mountpoint"
circuitpy-flash-itsybitsy-m4-express:
@echo "Flashing not available for ItsyBitsy M4 Express over bossa right now"
@echo "First, double tap the reset button on the ItsyBitsy. You should see a red light near the USB port"
@echo "Then, find and (if necessary) mount the USB drive that will show up (should be about 4MB)"
@echo "Copy build/circuitpython/ports/atmel-samd/build-itsybitsy_m4_express/firmware.uf2 to this device"
@echo "The device will auto-reboot. You may need to forcibly unmount the drive on Linuxes, with umount -f path/to/mountpoint"
circuitpy-flash-nrf: circuitpy-build-nrf
@echo "===> Flashing CircuitPython with KMK and your keymap"
@make -C build/circuitpython/ports/nrf BOARD=feather_nrf52832 SERIAL=${AMPY_PORT} SD=s132 FROZEN_MPY_DIR=freeze dfu-gen dfu-flash
micropython-build-teensy3.1:
@make -C build/micropython/ports/teensy/ BOARD=TEENSY_3.1 all
micropython-flash-teensy3.1: micropython-build-teensy3.1
@make -C build/micropython/ports/teensy/ BOARD=TEENSY_3.1 deploy
@pipenv run $(MAKE) -C build/circuitpython/ports/nrf BOARD=feather_nrf52832 SERIAL=${AMPY_PORT} SD=s132 FROZEN_MPY_DIR=freeze dfu-gen dfu-flash
micropython-build-pyboard:
@make -j4 -C build/micropython/ports/stm32/ BOARD=PYBV11 FROZEN_MPY_DIR=freeze all
@pipenv run $(MAKE) -j4 -C build/micropython/ports/stm32/ BOARD=PYBV11 FROZEN_MPY_DIR=freeze all
micropython-flash-pyboard: micropython-build-pyboard
@make -j4 -C build/micropython/ports/stm32/ BOARD=PYBV11 FROZEN_MPY_DIR=freeze deploy
@pipenv run $(MAKE) -j4 -C build/micropython/ports/stm32/ BOARD=PYBV11 FROZEN_MPY_DIR=freeze deploy
circuitpy-flash-nrf-entrypoint:
@echo "===> Flashing entrypoint if it doesn't already exist"
@ -139,6 +162,29 @@ circuitpy-flash-nrf-entrypoint:
@-timeout -k 5s 10s $(PIPENV) run ampy -p ${AMPY_PORT} -d ${AMPY_DELAY} -b ${AMPY_BAUD} put entrypoints/feather_nrf52832.py main.py
@echo "===> Flashed keyboard successfully!"
ifndef USER_KEYMAP
build-feather-m4-express:
@echo "===> Must provide a USER_KEYMAP (usually from user_keymaps/...) to build!" && exit 1
flash-feather-m4-express:
@echo "===> Must provide a USER_KEYMAP (usually from user_keymaps/...) to build!" && exit 1
else
ifndef SKIP_KEYMAP_VALIDATION
build-feather-m4-express: lint devdeps circuitpy-deps circuitpy-freeze-kmk-atmel-samd
else
build-feather-m4-express: lint devdeps circuitpy-deps circuitpy-freeze-kmk-atmel-samd micropython-build-unix
endif
@echo "===> Preparing keyboard script for bundling into CircuitPython"
ifndef SKIP_KEYMAP_VALIDATION
@MICROPYPATH=./ ./bin/micropython.sh bin/keymap_sanity_check.py ${USER_KEYMAP}
endif
@rsync -ah ${USER_KEYMAP} build/circuitpython/ports/atmel-samd/modules/kmk_keyboard_user.py
@rsync -ah kmk/entrypoints/global.py build/circuitpython/ports/atmel-samd/modules/_main.py
@$(MAKE) circuitpy-build-feather-m4-express
flash-feather-m4-express: build-feather-m4-express circuitpy-flash-feather-m4-express
endif
ifndef USER_KEYMAP
build-feather-nrf52832:
@echo "===> Must provide a USER_KEYMAP (usually from user_keymaps/...) to build!" && exit 1
@ -148,31 +194,33 @@ flash-feather-nrf52832:
else
build-feather-nrf52832: lint devdeps circuitpy-deps circuitpy-freeze-kmk-nrf
@echo "===> Preparing keyboard script for bundling into CircuitPython"
@cp -av ${USER_KEYMAP} build/circuitpython/ports/nrf/freeze/kmk_keyboard_user.py
@rsync -ah ${USER_KEYMAP} build/circuitpython/ports/nrf/freeze/kmk_keyboard_user.py
@$(MAKE) circuitpy-build-nrf
flash-feather-nrf52832: lint devdeps circuitpy-deps circuitpy-freeze-kmk-nrf
@echo "===> Preparing keyboard script for bundling into CircuitPython"
@cp -av ${USER_KEYMAP} build/circuitpython/ports/nrf/freeze/kmk_keyboard_user.py
@$(MAKE) circuitpy-flash-nrf circuitpy-flash-nrf-entrypoint
flash-feather-nrf52832: build-feather-nrf52832 circuitpy-flash-nrf circuitpy-flash-nrf-endpoint
endif
ifndef USER_KEYMAP
build-teensy-3.1:
build-itsybitsy-m4-express:
@echo "===> Must provide a USER_KEYMAP (usually from user_keymaps/...) to build!" && exit 1
flash-teensy-3.1:
flash-itsybitsy-m4-express:
@echo "===> Must provide a USER_KEYMAP (usually from user_keymaps/...) to build!" && exit 1
else
build-teensy-3.1: lint devdeps micropython-deps micropython-freeze-kmk-teensy3.1
@echo "===> Preparing keyboard script for bundling into MicroPython"
@cp -av ${USER_KEYMAP} build/micropython/ports/teensy/freeze/kmk_keyboard_user.py
@$(MAKE) ARDUINO=${ARDUINO} micropython-build-teensy3.1
ifndef SKIP_KEYMAP_VALIDATION
build-itsybitsy-m4-express: lint devdeps circuitpy-deps circuitpy-freeze-kmk-atmel-samd
else
build-itsybitsy-m4-express: lint devdeps circuitpy-deps circuitpy-freeze-kmk-atmel-samd micropython-build-unix
endif
@echo "===> Preparing keyboard script for bundling into CircuitPython"
ifndef SKIP_KEYMAP_VALIDATION
@MICROPYPATH=./ ./bin/micropython.sh bin/keymap_sanity_check.py ${USER_KEYMAP}
endif
@rsync -ah ${USER_KEYMAP} build/circuitpython/ports/atmel-samd/modules/kmk_keyboard_user.py
@rsync -ah kmk/entrypoints/global.py build/circuitpython/ports/atmel-samd/modules/_main.py
@$(MAKE) circuitpy-build-itsybitsy-m4-express
flash-teensy-3.1: lint devdeps micropython-deps micropython-freeze-kmk-teensy3.1
@echo "===> Preparing keyboard script for bundling into MicroPython"
@cp -av ${USER_KEYMAP} build/micropython/ports/teensy/freeze/kmk_keyboard_user.py
@$(MAKE) ARDUINO=${ARDUINO} micropython-flash-teensy3.1
flash-itsybitsy-m4-express: build-itsybitsy-m4-express circuitpy-flash-itsybitsy-m4-express
endif
ifndef USER_KEYMAP
@ -191,15 +239,12 @@ endif
ifndef SKIP_KEYMAP_VALIDATION
@MICROPYPATH=./ ./bin/micropython.sh bin/keymap_sanity_check.py ${USER_KEYMAP}
endif
@cp -av ${USER_KEYMAP} build/micropython/ports/stm32/freeze/kmk_keyboard_user.py
@rsync -ah ${USER_KEYMAP} build/micropython/ports/stm32/freeze/kmk_keyboard_user.py
@rsync -ah kmk/entrypoints/global.py build/micropython/ports/stm32/freeze/_main.py
@rsync -ah kmk/entrypoints/handwire/pyboard_boot.py build/micropython/ports/stm32/freeze/_boot.py
@$(MAKE) AMPY_PORT=/dev/ttyACM0 AMPY_BAUD=115200 micropython-build-pyboard
flash-pyboard: lint devdeps micropython-deps micropython-freeze-kmk-stm32
@echo "===> Preparing keyboard script for bundling into MicroPython"
@cp -av ${USER_KEYMAP} build/micropython/ports/stm32/freeze/kmk_keyboard_user.py
@cp -av kmk/entrypoints/global.py build/micropython/ports/stm32/freeze/_main.py
@cp -av kmk/entrypoints/handwire/pyboard_boot.py build/micropython/ports/stm32/freeze/_boot.py
@$(MAKE) micropython-flash-pyboard
flash-pyboard: build-pyboard micropython-flash-pyboard
endif
reset-bootloader:
@ -207,23 +252,3 @@ reset-bootloader:
reset-board:
@-timeout -k 5s 10s $(PIPENV) run ampy -p /dev/ttyACM0 -d ${AMPY_DELAY} -b ${AMPY_BAUD} run util/reset.py
# Fully wipe the board with only stock CircuitPython
burn-it-all-with-fire: lint devdeps
@echo "===> Flashing STOCK CircuitPython with no KMK or user keyboard scripts, and wiping entrypoint"
@echo "===> This is the nuclear option. Ctrl-C to cancel, or any key to continue"
@read
@echo "===> Pulling dependencies, this may take several minutes"
@git submodule update --init --recursive
@echo "===> Building circuitpython/mpy-cross"
@make -C build/circuitpython/mpy-cross
@echo "===> Pulling Nordic BLE stack"
@cd build/circuitpython/ports/nrf && ./drivers/bluetooth/download_ble_stack.sh 2>/dev/null >/dev/null
@echo "===> Preparing KMK source for bundling into CircuitPython"
@rm -rf build/circuitpython/ports/nrf/*
@echo "===> Building CircuitPython WITHOUT KMK or user keyboard script to induce ImportError"
@$(MAKE) circuitpy-flash-nrf
@echo "===> Wiping keyboard config"
@sleep 2
@-timeout -k 5s 10s $(PIPENV) run ampy -p ${AMPY_PORT} -d ${AMPY_DELAY} -b ${AMPY_BAUD} rm main.py 2>/dev/null
@echo "===> Wiped! Probably safe to flash keyboard, try Python serial REPL to verify?"

View File

@ -2,31 +2,54 @@
[![CircleCI](https://circleci.com/gh/KMKfw/kmk_firmware/tree/master.svg?style=svg)](https://circleci.com/gh/KMKfw/kmk_firmware/tree/master)[![CLA assistant](https://cla-assistant.io/readme/badge/KMKfw/kmk_firmware)](https://cla-assistant.io/KMKfw/kmk_firmware)
KMK is a work-in-progress and proof-of-concept firmware for (usually mechanical)
keyboards, written in [MicroPython](https://micropython.org/) and
[CircuitPython](https://github.com/adafruit/circuitpython). This allows for
high-level and expressive keyboard programming and creature comforts that C
simply doesn't make easy. KMK was heavily inspired by QMK - in fact, KMK was
only created because QMK didn't correctly support some hardware I bought, and
hacking support in was going to be a heavy uphill battle.
KMK is a firmware for (usually mechanical) keyboards, written in
[MicroPython](https://micropython.org/) and
[CircuitPython](https://github.com/adafruit/circuitpython), heavily inspired by
QMK (and with some additions of our own). Python may not be the fastest thing on
the planet, but it's a joy to write, and bringing that ease of maintainership to
keyboard firmware (often a world of C and all the crazy error states C can
provide) opens up custom keyboards to whole new demographics. KMK currently only
supports handwired keyboards (see "Supported Devices" below), but work has begun
on both ports to existing keyboards, as well as converter devices to allow
existing keyboards with Pro Micro pinouts to use KMK-supported microcontrollers.
As always in open-source, KMK is a work in progress, and help is welcome!
This project is currently written and maintained by:
- [Josh Klar](https://github.com/klardotsh)
- [Kyle Brown](https://github.com/kdb424)
- [Josh Klar (@klardotsh)](https://github.com/klardotsh)
- [Kyle Brown (@kdb424)](https://github.com/kdb424)
This project also owes a `$BEVERAGE_OF_CHOICE` to some wonderful people in the
ecosystem:
- [Jack Humbert (@jackhumbert)](https://jackhumbert.com/), for writing QMK.
Without QMK, I'd have never been exposed to the wonderful world of
programmable keyboards. He's also just an awesometastic human in general, if
you ever catch him on Discord/Reddit/etc.
- [Dan Halbert (@dhalbert)](https://danhalbert.org/), for his amazing and
unjudgemental support of two random dudes on Github asking all sorts of
bizzare (okay... and occasionally dumb) questions on the MicroPython and
CircuitPython Github projects and the Adafruit Discord. Dan, without your help
and pointers (even when those pointers are "Remember you're working with a
microcontroller with a few MHz of processing speed and a few KB of RAM"), this
project would have never gotten off the ground. Thank you, and an extended
thanks to Adafruit.
## Supported Devices
| Board | Chipset | Python Platform | Notes |
| ----- | ------- | --------------- | ----- |
| [pyboard v1.1](https://www.adafruit.com/product/2390) | STM32F405RG (Cortex M4F) | MicroPython | A very basic keyboard has been written for this, see `boards/klardotsh/threethree_matrix_pyboard.py` |
| [pyboard v1.1](https://www.adafruit.com/product/2390) | STM32F405RG (Cortex M4F) | MicroPython | Our reference board for basic USB keyboards |
| [Adafruit Feather M4 Express](https://www.adafruit.com/product/3857) | Atmel SAMD52 (Cortex M4F) | CircuitPython | A more economical solution for basic USB keyboards |
| [Adafruit ItsyBitsy M4 Express](https://www.adafruit.com/product/3800) | Atmel SAMD52 (Cortex M4F) | CircuitPython | An EVEN MORE economical solution for basic USB keyboards |
### Support Planned/WIP
| Board | Chipset | Python Platform | Notes |
| ----- | ------- | --------------- | ----- |
| [Seeed nRF52840 Micro Dev Kit](https://www.seeedstudio.com/nRF52840-Micro-Development-Kit-p-3079.html) | nRF52840 | [MicroPython](https://github.com/klardotsh/micropython/commit/4eac11a6d1ba2d269b4cdc663d4b5b788b288804) | This is basically as bleeding edge as it gets. Linked is my very unstable and somewhat broken uPy port to this device, WIP. Supports BLE HID and, allegedly, USB HID if I can figure it out. Another option is CircuitPython, since AdaFruit is working on a Feather Express with the same chipset, and commits have been made there to support USB HID. |
| [Planck rev6 Keyboard](https://olkb.com/planck) | STM32 of some sort | Probably MicroPython? | I have one on the way! We'll see what happens. |
| [Seeed nRF52840 Micro Dev Kit](https://www.seeedstudio.com/nRF52840-Micro-Development-Kit-p-3079.html) | nRF52840 | [CircuitPython](https://github.com/KMKfw/circuitpython/tree/topic-nrf52840-mdk) | This is basically as bleeding edge as it gets. Will support BLE HID to PC as well as BLE split boards |
| [Planck rev6 Keyboard](https://olkb.com/planck) | STM32 of some sort | MicroPython | Requires porting MicroPython to STM32F3, this work has begun but I'm pretty terrible at it. |
| [Proton C Controller?](https://www.reddit.com/r/MechanicalKeyboards/comments/87cw36/render_of_the_qmk_proton_c_qmkpowered_pro_micro/) | ??? | ??? | Does not exist yet, the controller from a Planck rev6 in a Pro Micro pin-compat controller chip |
@ -49,34 +72,6 @@ are currently not, due to some deficiency uncovered in development/testing:
| [Teensy 3.2 Controller](https://www.adafruit.com/product/2756) | | MicroPython | Lack of USB HID (SW - MP) |
## The Great Hackaround
While it is required that at least the device talking over USB/BLE HID (the
"primary brain") be from the Supported Devices list and running the primary
component of KMK, it will soon be possible to build split keyboards with other,
otherwise unsupported devices (currently this means a Pro Micro), either to
reduce costs or to convert existing QMK boards to KMK. You'll need to flash
"dummy" firmware to each Pro Micro which simply scans a matrix and passes the
values over I2C to the "brain" device, which does the heavy lifting from there
(including actually sending HID events).
The obvious downsides of this method are increased number of moving parts,
increased number of things to flash (though the Pro Micros only need flashed
when matricies change, which should almost never happen once a board is built),
and all downsides that go with those points (increased power usage, etc.) The
upside is that it can be a _ton_ cheaper to build a split keyboard this way -
cheapo Pro Micro clones can be had for as little as $4 CAD at time of writing,
whereas a HUZZAH32, for example, is closer to $26 CAD, and to build the
"traditional" way, you'd need N of them (where N is the number of split sections
of your keyboard).
It is also possible to convert many QMK boards through this fashion - while
untested for now, just about anything with a TRRS jack should work (Ergodoxen,
just about anything from keeb.io, etc.)
This hackaround is almost certainly pointless for non-split boards.
## License, Copyright, and Legal
This project, and all source code within (even if the file is missing headers),

View File

@ -59,8 +59,6 @@ assert len(user_keymap.keymap), 'Keymap must contain at least one layer'
for lidx, layer in enumerate(user_keymap.keymap):
assert len(layer), 'Layer {} must contain at least one row'.format(lidx)
assert all(len(row) for row in layer), 'Layer {} must not contain empty rows'.format(lidx)
assert all(len(row) == len(layer[0]) for row in user_keymap.keymap), \
'All rows in layer {} must be of the same length'.format(lidx)
for ridx, row in enumerate(layer):
for cidx, key in enumerate(row):

39
kmk/circuitpython/hid.py Normal file
View File

@ -0,0 +1,39 @@
import usb_hid
from kmk.common.abstract.hid import AbstractHidHelper
from kmk.common.consts import (HID_REPORT_SIZES, HIDReportTypes, HIDUsage,
HIDUsagePage)
class HIDHelper(AbstractHidHelper):
REPORT_BYTES = 9
def post_init(self):
self.devices = {}
for device in usb_hid.devices:
if device.usage_page == HIDUsagePage.CONSUMER and device.usage == HIDUsage.CONSUMER:
self.devices[HIDReportTypes.CONSUMER] = device
continue
if device.usage_page == HIDUsagePage.KEYBOARD and device.usage == HIDUsage.KEYBOARD:
self.devices[HIDReportTypes.KEYBOARD] = device
continue
if device.usage_page == HIDUsagePage.MOUSE and device.usage == HIDUsage.MOUSE:
self.devices[HIDReportTypes.MOUSE] = device
continue
if (
device.usage_page == HIDUsagePage.SYSCONTROL and
device.usage == HIDUsage.SYSCONTROL
):
self.devices[HIDReportTypes.SYSCONTROL] = device
continue
def hid_send(self, evt):
# int, can be looked up in HIDReportTypes
reporting_device_const = self.report_device[0]
return self.devices[reporting_device_const].send_report(
evt[1:HID_REPORT_SIZES[reporting_device_const] + 1],
)

View File

@ -2,6 +2,7 @@ import digitalio
from kmk.common.abstract.matrix_scanner import AbstractMatrixScanner
from kmk.common.consts import DiodeOrientation
from kmk.common.event_defs import matrix_changed
class MatrixScanner(AbstractMatrixScanner):
@ -17,6 +18,7 @@ class MatrixScanner(AbstractMatrixScanner):
self.cols = [digitalio.DigitalInOut(pin) for pin in cols]
self.rows = [digitalio.DigitalInOut(pin) for pin in rows]
self.diode_orientation = diode_orientation
self.last_pressed_len = 0
if self.diode_orientation == DiodeOrientation.COLUMNS:
self.outputs = self.cols
@ -35,15 +37,22 @@ class MatrixScanner(AbstractMatrixScanner):
for pin in self.inputs:
pin.switch_to_input(pull=digitalio.Pull.DOWN)
def _normalize_matrix(self, matrix):
return super()._normalize_matrix(matrix)
def scan_for_pressed(self):
pressed = []
def raw_scan(self):
matrix = []
for opin in self.outputs:
for oidx, opin in enumerate(self.outputs):
opin.value = True
matrix.append([ipin.value for ipin in self.inputs])
for iidx, ipin in enumerate(self.inputs):
if ipin.value:
pressed.append(
(oidx, iidx) if self.diode_orientation == DiodeOrientation.ROWS else (iidx, oidx) # noqa
)
opin.value = False
return self._normalize_matrix(matrix)
if len(pressed) != self.last_pressed_len:
self.last_pressed_len = len(pressed)
return matrix_changed(pressed)
return None # The default, but for explicitness

155
kmk/common/abstract/hid.py Normal file
View File

@ -0,0 +1,155 @@
import logging
from kmk.common.consts import HIDReportTypes
from kmk.common.event_defs import HID_REPORT_EVENT
from kmk.common.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, ConsumerKeycode,
ModifierKeycode)
class AbstractHidHelper:
REPORT_BYTES = 8
def __init__(self, store, log_level=logging.NOTSET):
self.logger = logging.getLogger(__name__)
self.logger.setLevel(log_level)
self.store = store
self.store.subscribe(
lambda state, action: self._subscription(state, action),
)
self._evt = bytearray(self.REPORT_BYTES)
self.report_device = memoryview(self._evt)[0:1]
self.report_device[0] = HIDReportTypes.KEYBOARD
# Landmine alert for HIDReportTypes.KEYBOARD: byte index 1 of this view
# is "reserved" and evidently (mostly?) unused. However, other modes (or
# at least consumer, so far) will use this byte, which is the main reason
# this view exists. For KEYBOARD, use report_mods and report_non_mods
self.report_keys = memoryview(self._evt)[1:]
self.report_mods = memoryview(self._evt)[1:2]
self.report_non_mods = memoryview(self._evt)[3:]
self.post_init()
def post_init(self):
pass
def _subscription(self, state, action):
if action.type == HID_REPORT_EVENT:
self.clear_all()
consumer_key = None
for key in state.keys_pressed:
if isinstance(key, ConsumerKeycode):
consumer_key = key
break
reporting_device = self.report_device[0]
needed_reporting_device = HIDReportTypes.KEYBOARD
if consumer_key:
needed_reporting_device = HIDReportTypes.CONSUMER
if reporting_device != needed_reporting_device:
# If we are about to change reporting devices, release
# all keys and close our proverbial tab on the existing
# device, or keys will get stuck (mostly when releasing
# media/consumer keys)
self.send()
self.report_device[0] = needed_reporting_device
if consumer_key:
self.add_key(consumer_key)
else:
for key in state.keys_pressed:
if key.code >= FIRST_KMK_INTERNAL_KEYCODE:
continue
if isinstance(key, ModifierKeycode):
self.add_modifier(key)
else:
self.add_key(key)
if key.has_modifiers:
for mod in key.has_modifiers:
self.add_modifier(mod)
self.send()
def hid_send(self, evt):
raise NotImplementedError('hid_send(evt) must be implemented')
def send(self):
self.logger.debug('Sending HID report: {}'.format(self._evt))
self.hid_send(self._evt)
return self
def clear_all(self):
for idx, _ in enumerate(self.report_keys):
self.report_keys[idx] = 0x00
return self
def clear_non_modifiers(self):
for idx, _ in enumerate(self.report_non_mods):
self.report_non_mods[idx] = 0x00
return self
def add_modifier(self, modifier):
if isinstance(modifier, ModifierKeycode):
self.report_mods[0] |= modifier.code
else:
self.report_mods[0] |= modifier
return self
def remove_modifier(self, modifier):
if isinstance(modifier, ModifierKeycode):
self.report_mods[0] ^= modifier.code
else:
self.report_mods[0] ^= modifier
return self
def add_key(self, key):
# Try to find the first empty slot in the key report, and fill it
placed = False
where_to_place = self.report_non_mods
if self.report_device[0] == HIDReportTypes.CONSUMER:
where_to_place = self.report_keys
for idx, _ in enumerate(where_to_place):
if where_to_place[idx] == 0x00:
where_to_place[idx] = key.code
placed = True
break
if not placed:
self.logger.warning('Out of space in HID report, could not add key')
return self
def remove_key(self, key):
removed = False
where_to_place = self.report_non_mods
if self.report_device[0] == HIDReportTypes.CONSUMER:
where_to_place = self.report_keys
for idx, _ in enumerate(where_to_place):
if where_to_place[idx] == key.code:
where_to_place[idx] = 0x00
removed = True
if not removed:
self.logger.warning('Tried to remove key that was not added')
return self

View File

@ -5,10 +5,33 @@ class HIDReportTypes:
SYSCONTROL = 4
class HIDUsage:
KEYBOARD = 0x06
MOUSE = 0x02
CONSUMER = 0x01
SYSCONTROL = 0x80
class HIDUsagePage:
CONSUMER = 0x0C
KEYBOARD = MOUSE = SYSCONTROL = 0x01
# Currently only used by the CircuitPython HIDHelper because CircuitPython
# actually enforces these limits with a ValueError. Unused on PyBoard because
# we can happily send full reports there and it magically works.
HID_REPORT_SIZES = {
HIDReportTypes.KEYBOARD: 8,
HIDReportTypes.MOUSE: 4,
HIDReportTypes.CONSUMER: 2,
HIDReportTypes.SYSCONTROL: 8, # TODO find the correct value for this
}
HID_REPORT_STRUCTURE = bytes([
# Regular keyboard
0x05, 0x01, # Usage Page (Generic Desktop)
0x09, 0x06, # Usage (Keyboard)
0x05, HIDUsagePage.KEYBOARD, # Usage Page (Generic Desktop)
0x09, HIDUsage.KEYBOARD, # Usage (Keyboard)
0xA1, 0x01, # Collection (Application)
0x85, HIDReportTypes.KEYBOARD, # Report ID (1)
0x05, 0x07, # Usage Page (Keyboard)
@ -39,8 +62,8 @@ HID_REPORT_STRUCTURE = bytes([
0x91, 0x01, # Output (Constant)
0xC0, # End Collection
# Regular mouse
0x05, 0x01, # Usage Page (Generic Desktop)
0x09, 0x02, # Usage (Mouse)
0x05, HIDUsagePage.MOUSE, # Usage Page (Generic Desktop)
0x09, HIDUsage.MOUSE, # Usage (Mouse)
0xA1, 0x01, # Collection (Application)
0x09, 0x01, # Usage (Pointer)
0xA1, 0x00, # Collection (Physical)
@ -73,8 +96,8 @@ HID_REPORT_STRUCTURE = bytes([
0xC0, # End Collection
0xC0, # End Collection
# Consumer ("multimedia") keys
0x05, 0x0C, # Usage Page (Consumer)
0x09, 0x01, # Usage (Consumer Control)
0x05, HIDUsagePage.CONSUMER, # Usage Page (Consumer)
0x09, HIDUsage.CONSUMER, # Usage (Consumer Control)
0xA1, 0x01, # Collection (Application)
0x85, HIDReportTypes.CONSUMER, # Report ID (n)
0x75, 0x10, # Report Size (16)
@ -86,8 +109,8 @@ HID_REPORT_STRUCTURE = bytes([
0x81, 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, # End Collection
# Power controls
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x80, # Usage (Sys Control)
0x05, HIDUsagePage.SYSCONTROL, # Usage Page (Generic Desktop Ctrls)
0x09, HIDUsage.SYSCONTROL, # Usage (Sys Control)
0xA1, 0x01, # Collection (Application)
0x85, HIDReportTypes.SYSCONTROL, # Report ID (n)
0x75, 0x02, # Report Size (2)

View File

@ -4,6 +4,7 @@ from collections import namedtuple
from micropython import const
from kmk.common.keycodes import Keycodes
from kmk.common.util import reset_bootloader
KEY_UP_EVENT = const(1)
KEY_DOWN_EVENT = const(2)
@ -117,11 +118,7 @@ def matrix_changed(new_pressed):
dispatch(hid_report_event())
if Keycodes.KMK.KC_RESET in state.keys_pressed:
try:
import machine
machine.bootloader()
except ImportError:
logger.warning('Tried to reset to bootloader, but not supported on this chip?')
reset_bootloader()
if state.pending_keys:
for key in state.pending_keys:

View File

@ -1,183 +0,0 @@
import logging
from kmk.common import kmktime
from kmk.common.event_defs import KEY_DOWN_EVENT, KEY_UP_EVENT
from kmk.common.keycodes import Keycodes, RawKeycodes
GESC_TRIGGERS = {
Keycodes.Modifiers.KC_LSHIFT, Keycodes.Modifiers.KC_RSHIFT,
Keycodes.Modifiers.KC_LGUI, Keycodes.Modifiers.KC_RGUI,
}
def process_internal_key_event(state, action_type, changed_key, logger=None):
if logger is None:
logger = logging.getLogger(__name__)
# Since the key objects can be chained into new objects
# with, for example, no_press set, always check against
# the underlying code rather than comparing Keycode
# objects
if changed_key.code == RawKeycodes.KC_DF:
return df(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_MO:
return mo(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_LM:
return lm(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_LT:
return lt(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_TG:
return tg(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_TO:
return to(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_TT:
return tt(state, action_type, changed_key, logger=logger)
elif changed_key.code == Keycodes.KMK.KC_GESC.code:
return grave_escape(state, action_type, logger=logger)
elif changed_key.code == RawKeycodes.KC_UC_MODE:
return unicode_mode(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_MACRO:
return macro(state, action_type, changed_key, logger=logger)
else:
return state
def grave_escape(state, action_type, logger):
if action_type == KEY_DOWN_EVENT:
if any(key in GESC_TRIGGERS for key in state.keys_pressed):
# if Shift is held, KC_GRAVE will become KC_TILDE on OS level
state.keys_pressed.add(Keycodes.Common.KC_GRAVE)
return state
# else return KC_ESC
state.keys_pressed.add(Keycodes.Common.KC_ESCAPE)
return state
elif action_type == KEY_UP_EVENT:
state.keys_pressed.discard(Keycodes.Common.KC_ESCAPE)
state.keys_pressed.discard(Keycodes.Common.KC_GRAVE)
return state
return state
def df(state, action_type, changed_key, logger):
"""Switches the default layer"""
if action_type == KEY_DOWN_EVENT:
state.active_layers[0] = changed_key.layer
return state
def mo(state, action_type, changed_key, logger):
"""Momentarily activates layer, switches off when you let go"""
if action_type == KEY_UP_EVENT:
state.active_layers = [
layer for layer in state.active_layers
if layer != changed_key.layer
]
elif action_type == KEY_DOWN_EVENT:
state.active_layers.append(changed_key.layer)
return state
def lm(state, action_type, changed_key, logger):
"""As MO(layer) but with mod active"""
if action_type == KEY_DOWN_EVENT:
# Sets the timer start and acts like MO otherwise
state.start_time['lm'] = kmktime.ticks_ms()
state.keys_pressed.add(changed_key.kc)
state = mo(state, action_type, changed_key, logger)
elif action_type == KEY_UP_EVENT:
state.keys_pressed.discard(changed_key.kc)
state.start_time['lm'] = None
state = mo(state, action_type, changed_key)
return state
def lt(state, action_type, changed_key, logger):
"""Momentarily activates layer if held, sends kc if tapped"""
if action_type == KEY_DOWN_EVENT:
# Sets the timer start and acts like MO otherwise
state.start_time['lt'] = kmktime.ticks_ms()
state = mo(state, action_type, changed_key, logger)
elif action_type == KEY_UP_EVENT:
# On keyup, check timer, and press key if needed.
if state.start_time['lt'] and (
kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['lt']) < state.tap_time
):
state.pending_keys.add(changed_key.kc)
state.start_time['lt'] = None
state = mo(state, action_type, changed_key, logger)
return state
def tg(state, action_type, changed_key, logger):
"""Toggles the layer (enables it if not active, and vise versa)"""
if action_type == KEY_DOWN_EVENT:
if changed_key.layer in state.active_layers:
state.active_layers = [
layer for layer in state.active_layers
if layer != changed_key.layer
]
else:
state.active_layers.append(changed_key.layer)
return state
def to(state, action_type, changed_key, logger):
"""Activates layer and deactivates all other layers"""
if action_type == KEY_DOWN_EVENT:
state.active_layers = [changed_key.layer]
return state
def tt(state, action_type, changed_key, logger):
"""Momentarily activates layer if held, toggles it if tapped repeatedly"""
# TODO Make this work with tap dance to function more correctly, but technically works.
if action_type == KEY_DOWN_EVENT:
if state.start_time['tt'] is None:
# Sets the timer start and acts like MO otherwise
state.start_time['tt'] = kmktime.ticks_ms()
state = mo(state, action_type, changed_key, logger)
elif kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['tt']) < state.tap_time:
state.start_time['tt'] = None
state = tg(state, action_type, changed_key, logger)
elif action_type == KEY_UP_EVENT and (
state.start_time['tt'] is None or
kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['tt']) >= state.tap_time
):
# On first press, works like MO. On second press, does nothing unless let up within
# time window, then acts like TG.
state.start_time['tt'] = None
state = mo(state, action_type, changed_key, logger)
return state
def unicode_mode(state, action_type, changed_key, logger):
if action_type == KEY_DOWN_EVENT:
state.unicode_mode = changed_key.mode
return state
def macro(state, action_type, changed_key, logger):
if action_type == KEY_UP_EVENT:
if changed_key.keyup:
state.macro_pending = changed_key.keyup
return state
elif action_type == KEY_DOWN_EVENT:
if changed_key.keydown:
state.macro_pending = changed_key.keydown
return state
return state

View File

@ -1,17 +1,31 @@
import logging
import sys
from kmk.common import kmktime
from kmk.common.consts import DiodeOrientation, UnicodeModes
from kmk.common.event_defs import (HID_REPORT_EVENT, INIT_FIRMWARE_EVENT,
KEY_DOWN_EVENT, KEY_UP_EVENT,
KEYCODE_DOWN_EVENT, KEYCODE_UP_EVENT,
MACRO_COMPLETE_EVENT, NEW_MATRIX_EVENT,
PENDING_KEYCODE_POP_EVENT)
from kmk.common.internal_keycodes import process_internal_key_event
from kmk.common.keycodes import FIRST_KMK_INTERNAL_KEYCODE, Keycodes
from kmk.common.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, Keycodes,
RawKeycodes)
GESC_TRIGGERS = {
Keycodes.Modifiers.KC_LSHIFT, Keycodes.Modifiers.KC_RSHIFT,
Keycodes.Modifiers.KC_LGUI, Keycodes.Modifiers.KC_RGUI,
}
class ReduxStore:
class Store:
'''
A data store very loosely inspired by Redux, but with most of the fancy
functional and immutable abilities unavailable because microcontrollers.
This serves as the event dispatcher at the heart of KMK. All changes to the
state of the keyboard should be triggered by events (see event_defs.py)
dispatched through this store, and listened to (for side-effects or other
handling) by subscription functions.
'''
def __init__(self, reducer, log_level=logging.NOTSET):
self.reducer = reducer
self.logger = logging.getLogger(__name__)
@ -38,7 +52,7 @@ class ReduxStore:
cb(self.state, action)
except Exception as e:
self.logger.error('Callback failed, moving on')
print(sys.print_exception(e), file=sys.stderr)
sys.print_exception(e)
def get_state(self):
return self.state
@ -223,3 +237,176 @@ def kmk_reducer(state=None, action=None, logger=None):
# On unhandled events, log and do not mutate state
logger.warning('Unhandled event! Returning state unmodified.')
return state
def process_internal_key_event(state, action_type, changed_key, logger=None):
if logger is None:
logger = logging.getLogger(__name__)
# Since the key objects can be chained into new objects
# with, for example, no_press set, always check against
# the underlying code rather than comparing Keycode
# objects
if changed_key.code == RawKeycodes.KC_DF:
return df(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_MO:
return mo(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_LM:
return lm(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_LT:
return lt(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_TG:
return tg(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_TO:
return to(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_TT:
return tt(state, action_type, changed_key, logger=logger)
elif changed_key.code == Keycodes.KMK.KC_GESC.code:
return grave_escape(state, action_type, logger=logger)
elif changed_key.code == RawKeycodes.KC_UC_MODE:
return unicode_mode(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_MACRO:
return macro(state, action_type, changed_key, logger=logger)
else:
return state
def grave_escape(state, action_type, logger):
if action_type == KEY_DOWN_EVENT:
if any(key in GESC_TRIGGERS for key in state.keys_pressed):
# if Shift is held, KC_GRAVE will become KC_TILDE on OS level
state.keys_pressed.add(Keycodes.Common.KC_GRAVE)
return state
# else return KC_ESC
state.keys_pressed.add(Keycodes.Common.KC_ESCAPE)
return state
elif action_type == KEY_UP_EVENT:
state.keys_pressed.discard(Keycodes.Common.KC_ESCAPE)
state.keys_pressed.discard(Keycodes.Common.KC_GRAVE)
return state
return state
def df(state, action_type, changed_key, logger):
"""Switches the default layer"""
if action_type == KEY_DOWN_EVENT:
state.active_layers[0] = changed_key.layer
return state
def mo(state, action_type, changed_key, logger):
"""Momentarily activates layer, switches off when you let go"""
if action_type == KEY_UP_EVENT:
state.active_layers = [
layer for layer in state.active_layers
if layer != changed_key.layer
]
elif action_type == KEY_DOWN_EVENT:
state.active_layers.append(changed_key.layer)
return state
def lm(state, action_type, changed_key, logger):
"""As MO(layer) but with mod active"""
if action_type == KEY_DOWN_EVENT:
# Sets the timer start and acts like MO otherwise
state.start_time['lm'] = kmktime.ticks_ms()
state.keys_pressed.add(changed_key.kc)
state = mo(state, action_type, changed_key, logger)
elif action_type == KEY_UP_EVENT:
state.keys_pressed.discard(changed_key.kc)
state.start_time['lm'] = None
state = mo(state, action_type, changed_key)
return state
def lt(state, action_type, changed_key, logger):
"""Momentarily activates layer if held, sends kc if tapped"""
if action_type == KEY_DOWN_EVENT:
# Sets the timer start and acts like MO otherwise
state.start_time['lt'] = kmktime.ticks_ms()
state = mo(state, action_type, changed_key, logger)
elif action_type == KEY_UP_EVENT:
# On keyup, check timer, and press key if needed.
if state.start_time['lt'] and (
kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['lt']) < state.tap_time
):
state.pending_keys.add(changed_key.kc)
state.start_time['lt'] = None
state = mo(state, action_type, changed_key, logger)
return state
def tg(state, action_type, changed_key, logger):
"""Toggles the layer (enables it if not active, and vise versa)"""
if action_type == KEY_DOWN_EVENT:
if changed_key.layer in state.active_layers:
state.active_layers = [
layer for layer in state.active_layers
if layer != changed_key.layer
]
else:
state.active_layers.append(changed_key.layer)
return state
def to(state, action_type, changed_key, logger):
"""Activates layer and deactivates all other layers"""
if action_type == KEY_DOWN_EVENT:
state.active_layers = [changed_key.layer]
return state
def tt(state, action_type, changed_key, logger):
"""Momentarily activates layer if held, toggles it if tapped repeatedly"""
# TODO Make this work with tap dance to function more correctly, but technically works.
if action_type == KEY_DOWN_EVENT:
if state.start_time['tt'] is None:
# Sets the timer start and acts like MO otherwise
state.start_time['tt'] = kmktime.ticks_ms()
state = mo(state, action_type, changed_key, logger)
elif kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['tt']) < state.tap_time:
state.start_time['tt'] = None
state = tg(state, action_type, changed_key, logger)
elif action_type == KEY_UP_EVENT and (
state.start_time['tt'] is None or
kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['tt']) >= state.tap_time
):
# On first press, works like MO. On second press, does nothing unless let up within
# time window, then acts like TG.
state.start_time['tt'] = None
state = mo(state, action_type, changed_key, logger)
return state
def unicode_mode(state, action_type, changed_key, logger):
if action_type == KEY_DOWN_EVENT:
state.unicode_mode = changed_key.mode
return state
def macro(state, action_type, changed_key, logger):
if action_type == KEY_UP_EVENT:
if changed_key.keyup:
state.macro_pending = changed_key.keyup
return state
elif action_type == KEY_DOWN_EVENT:
if changed_key.keydown:
state.macro_pending = changed_key.keydown
return state
return state

View File

@ -1,13 +1,33 @@
import math
try:
import utime
import utime as time
USE_UTIME = True
except ImportError:
pass
import time
USE_UTIME = False
def sleep_ms(ms):
'''
Tries to sleep for a number of milliseconds in a cross-implementation
way. Will raise an ImportError if time is not available on the platform.
'''
if USE_UTIME:
return time.sleep_ms(ms)
else:
return time.sleep(ms / 1000)
def ticks_ms():
return utime.ticks_ms()
if USE_UTIME:
return time.ticks_ms()
else:
return math.floor(time.monotonic() * 1000)
def ticks_diff(new, old):
return utime.ticks_diff(new, old)
if USE_UTIME:
return time.ticks_diff(new, old)
else:
return new - old

View File

@ -3,7 +3,7 @@ import string
from kmk.common.event_defs import (hid_report_event, keycode_down_event,
keycode_up_event)
from kmk.common.keycodes import Keycodes, Macro, RawKeycodes, char_lookup
from kmk.common.util import sleep_ms
from kmk.common.kmktime import sleep_ms
def simple_key_sequence(seq):

40
kmk/common/pins.py Normal file
View File

@ -0,0 +1,40 @@
try:
import board
PLATFORM = 'CircuitPython'
PIN_SOURCE = board
except ImportError:
import machine
PLATFORM = 'MicroPython'
PIN_SOURCE = machine.Pin.board
except ImportError:
from kmk.common.types import Passthrough
PLATFORM = 'Unit Testing'
PIN_SOURCE = Passthrough()
def get_pin(pin):
'''
Cross-platform method to find a pin by string.
The pin definitions are platform-dependent, but this provides
a way to say "I'm using pin D20" without rolling a D20 and
having to actually learn MicroPython/CircuitPython and the
differences in how they handle pinouts.
This also makes the keymap sanity checker actually work for
CircuitPython boards, since it's not possible in CPY to
define a module stub for `board` that uses Passthrough
natively (which is how the MicroPython stub worked originally)
'''
return getattr(PIN_SOURCE, pin)
class PinLookup:
def __getattr__(self, attr):
return get_pin(attr)
Pin = PinLookup()

View File

@ -8,3 +8,19 @@ class AttrDict(dict):
'''
def __getattr__(self, key):
return self[key]
class Anything:
'''
A stub class which will repr as a provided name
'''
def __init__(self, name):
self.name = name
def __repr__(self):
return 'Anything<{}>'.format(self.name)
class Passthrough:
def __getattr__(self, attr):
return Anything(attr)

View File

@ -29,16 +29,3 @@ def reset_bootloader():
import microcontroller
microcontroller.on_next_reset(microcontroller.RunMode.BOOTLOADER)
microcontroller.reset()
def sleep_ms(ms):
'''
Tries to sleep for a number of milliseconds in a cross-implementation
way. Will raise an ImportError if time is not available on the platform.
'''
try:
import time
time.sleep_ms(ms)
except AttributeError:
import time
time.sleep(ms / 1000)

View File

@ -0,0 +1,33 @@
import sys
from logging import WARNING
from kmk.circuitpython.hid import HIDHelper
from kmk.circuitpython.matrix import MatrixScanner
from kmk.common.consts import UnicodeModes
from kmk.firmware import Firmware
def main():
from kmk_keyboard_user import cols, diode_orientation, keymap, rows
try:
from kmk_keyboard_user import unicode_mode
except Exception:
unicode_mode = UnicodeModes.NOOP
try:
firmware = Firmware(
keymap=keymap,
row_pins=rows,
col_pins=cols,
diode_orientation=diode_orientation,
unicode_mode=unicode_mode,
log_level=WARNING,
matrix_scanner=MatrixScanner,
hid=HIDHelper,
)
firmware.go()
except Exception as e:
sys.print_exception(e)
sys.exit(1)

View File

@ -0,0 +1,33 @@
import sys
from logging import WARNING
from kmk.circuitpython.hid import HIDHelper
from kmk.circuitpython.matrix import MatrixScanner
from kmk.common.consts import UnicodeModes
from kmk.firmware import Firmware
def main():
from kmk_keyboard_user import cols, diode_orientation, keymap, rows
try:
from kmk_keyboard_user import unicode_mode
except Exception:
unicode_mode = UnicodeModes.NOOP
try:
firmware = Firmware(
keymap=keymap,
row_pins=rows,
col_pins=cols,
diode_orientation=diode_orientation,
unicode_mode=unicode_mode,
log_level=WARNING,
matrix_scanner=MatrixScanner,
hid=HIDHelper,
)
firmware.go()
except Exception as e:
sys.print_exception(e)
sys.exit(1)

View File

@ -3,6 +3,7 @@ from logging import DEBUG
from kmk.common.consts import UnicodeModes
from kmk.firmware import Firmware
from kmk.micropython.matrix import MatrixScanner
from kmk.micropython.pyb_hid import HIDHelper
@ -23,6 +24,7 @@ def main():
unicode_mode=unicode_mode,
hid=HIDHelper,
log_level=DEBUG,
matrix_scanner=MatrixScanner,
)
firmware.go()

View File

@ -1,12 +1,7 @@
import logging
from kmk.common.event_defs import init_firmware
from kmk.common.internal_state import ReduxStore, kmk_reducer
try:
from kmk.circuitpython.matrix import MatrixScanner
except ImportError:
from kmk.micropython.matrix import MatrixScanner
from kmk.common.internal_state import Store, kmk_reducer
class Firmware:
@ -14,25 +9,29 @@ class Firmware:
self, keymap, row_pins, col_pins,
diode_orientation, unicode_mode=None,
hid=None, log_level=logging.NOTSET,
matrix_scanner=None,
):
assert matrix_scanner is not None
self.matrix_scanner = matrix_scanner
logger = logging.getLogger(__name__)
logger.setLevel(log_level)
self.hydrated = False
self.store = ReduxStore(kmk_reducer, log_level=log_level)
self.store = Store(kmk_reducer, log_level=log_level)
self.store.subscribe(
lambda state, action: self._subscription(state, action),
)
if not hid:
if hid:
self.hid = hid(store=self.store, log_level=log_level)
else:
logger.warning(
"Must provide a HIDHelper (arg: hid), disabling HID\n"
"Board will run in debug mode",
)
self.hid = hid(store=self.store, log_level=log_level)
self.store.dispatch(init_firmware(
keymap=keymap,
row_pins=row_pins,
@ -43,7 +42,7 @@ class Firmware:
def _subscription(self, state, action):
if not self.hydrated:
self.matrix = MatrixScanner(
self.matrix = self.matrix_scanner(
state.col_pins,
state.row_pins,
state.diode_orientation,

View File

@ -1,11 +1,7 @@
import logging
from pyb import USB_HID, delay, hid_keyboard
from kmk.common.consts import HID_REPORT_STRUCTURE, HIDReportTypes
from kmk.common.event_defs import HID_REPORT_EVENT
from kmk.common.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, ConsumerKeycode,
ModifierKeycode)
from kmk.common.abstract.hid import AbstractHidHelper
from kmk.common.consts import HID_REPORT_STRUCTURE
def generate_pyb_hid_descriptor():
@ -14,80 +10,20 @@ def generate_pyb_hid_descriptor():
return tuple(existing_keyboard)
class HIDHelper:
def __init__(self, store, log_level=logging.NOTSET):
self.logger = logging.getLogger(__name__)
self.logger.setLevel(log_level)
self.store = store
self.store.subscribe(
lambda state, action: self._subscription(state, action),
)
class HIDHelper(AbstractHidHelper):
# For some bizarre reason this can no longer be 8, it'll just fail to send
# anything. This is almost certainly a bug in the report descriptor sent
# over in the boot process. For now the sacrifice is that we only support
# 5KRO until I figure this out, rather than the 6KRO HID defines.
REPORT_BYTES = 7
def post_init(self):
self._hid = USB_HID()
# For some bizarre reason this can no longer be 8, it'll just fail to
# send anything. This is almost certainly a bug in the report descriptor
# sent over in the boot process. For now the sacrifice is that we only
# support 5KRO until I figure this out, rather than the 6KRO HID defines.
self._evt = bytearray(7)
self.report_device = memoryview(self._evt)[0:1]
# Landmine alert for HIDReportTypes.KEYBOARD: byte index 1 of this view
# is "reserved" and evidently (mostly?) unused. However, other modes (or
# at least consumer, so far) will use this byte, which is the main reason
# this view exists. For KEYBOARD, use report_mods and report_non_mods
self.report_keys = memoryview(self._evt)[1:]
self.report_mods = memoryview(self._evt)[1:2]
self.report_non_mods = memoryview(self._evt)[3:]
def _subscription(self, state, action):
if action.type == HID_REPORT_EVENT:
self.clear_all()
consumer_key = None
for key in state.keys_pressed:
if isinstance(key, ConsumerKeycode):
consumer_key = key
break
reporting_device = self.report_device[0]
needed_reporting_device = HIDReportTypes.KEYBOARD
if consumer_key:
needed_reporting_device = HIDReportTypes.CONSUMER
if reporting_device != needed_reporting_device:
# If we are about to change reporting devices, release
# all keys and close our proverbial tab on the existing
# device, or keys will get stuck (mostly when releasing
# media/consumer keys)
self.send()
self.report_device[0] = needed_reporting_device
if consumer_key:
self.add_key(consumer_key)
else:
for key in state.keys_pressed:
if key.code >= FIRST_KMK_INTERNAL_KEYCODE:
continue
if isinstance(key, ModifierKeycode):
self.add_modifier(key)
else:
self.add_key(key)
if key.has_modifiers:
for mod in key.has_modifiers:
self.add_modifier(mod)
self.send()
self.hid_send = self._hid.send
def send(self):
self.logger.debug('Sending HID report: {}'.format(self._evt))
self._hid.send(self._evt)
self.hid_send(self._evt)
# Without this delay, events get clobbered and you'll likely end up with
# a string like `heloooooooooooooooo` rather than `hello`. This number
@ -101,69 +37,3 @@ class HIDHelper:
delay(5)
return self
def clear_all(self):
for idx, _ in enumerate(self.report_keys):
self.report_keys[idx] = 0x00
return self
def clear_non_modifiers(self):
for idx, _ in enumerate(self.report_non_mods):
self.report_non_mods[idx] = 0x00
return self
def add_modifier(self, modifier):
if isinstance(modifier, ModifierKeycode):
self.report_mods[0] |= modifier.code
else:
self.report_mods[0] |= modifier
return self
def remove_modifier(self, modifier):
if isinstance(modifier, ModifierKeycode):
self.report_mods[0] ^= modifier.code
else:
self.report_mods[0] ^= modifier
return self
def add_key(self, key):
# Try to find the first empty slot in the key report, and fill it
placed = False
where_to_place = self.report_non_mods
if self.report_device[0] == HIDReportTypes.CONSUMER:
where_to_place = self.report_keys
for idx, _ in enumerate(where_to_place):
if where_to_place[idx] == 0x00:
where_to_place[idx] = key.code
placed = True
break
if not placed:
self.logger.warning('Out of space in HID report, could not add key')
return self
def remove_key(self, key):
removed = False
where_to_place = self.report_non_mods
if self.report_device[0] == HIDReportTypes.CONSUMER:
where_to_place = self.report_keys
for idx, _ in enumerate(where_to_place):
if where_to_place[idx] == key.code:
where_to_place[idx] = 0x00
removed = True
if not removed:
self.logger.warning('Tried to remove key that was not added')
return self

View File

@ -1,4 +1,4 @@
[submodules]
"vendor/circuitpython" = "d751740"
"vendor/circuitpython" = "5c606de"
"vendor/micropython" = "65a49fa"
"vendor/upy-lib" = "f20d89c"
"vendor/upy-lib" = "451b1c0"

View File

@ -1,3 +1,5 @@
vendor/upy-lib/collections/collections
vendor/upy-lib/logging/logging.py
vendor/upy-lib/string/string.py
# CircuitPython provides collections, don't overwrite it
MICROPY|vendor/upy-lib/collections/collections
MICROPYCIRCUITPY|vendor/upy-lib/logging/logging.py
MICROPYCIRCUITPY|vendor/upy-lib/string/string.py

View File

@ -0,0 +1,3 @@
class DigitalInOut:
def __init__(self, *args, **kwargs):
pass

View File

@ -0,0 +1 @@
devices = []

View File

@ -1,14 +1,11 @@
import machine
from kmk.common.consts import DiodeOrientation, UnicodeModes
from kmk.common.keycodes import KC
from kmk.common.macros.unicode import unicode_sequence
from kmk.common.pins import Pin as P
from kmk.entrypoints.handwire.pyboard import main
p = machine.Pin.board
cols = (p.Y12, p.Y11, p.Y10, p.Y9, p.X8, p.X7, p.X6, p.X5, p.X4, p.X3, p.X2, p.X1)
rows = (p.Y1, p.Y2, p.Y3, p.Y4)
cols = (P.Y12, P.Y11, P.Y10, P.Y9, P.X8, P.X7, P.X6, P.X5, P.X4, P.X3, P.X2, P.X1)
rows = (P.Y1, P.Y2, P.Y3, P.Y4)
diode_orientation = DiodeOrientation.COLUMNS

View File

@ -0,0 +1,73 @@
from kmk.common.consts import DiodeOrientation, UnicodeModes
from kmk.common.keycodes import KC
from kmk.common.macros.simple import send_string, simple_key_sequence
from kmk.common.macros.unicode import unicode_sequence
from kmk.common.pins import Pin as P
from kmk.entrypoints.handwire.feather_m4_express import main
from kmk.firmware import Firmware
cols = (P.D11, P.D10, P.D9)
rows = (P.A2, P.A3, P.A4, P.A5)
diode_orientation = DiodeOrientation.COLUMNS
unicode_mode = UnicodeModes.LINUX
MACRO_TEST_SIMPLE = simple_key_sequence([
KC.LSHIFT(KC.H),
KC.E,
KC.L,
KC.L,
KC.O,
KC.SPACE,
KC.MACRO_SLEEP_MS(500),
KC.LSHIFT(KC.K),
KC.LSHIFT(KC.M),
KC.LSHIFT(KC.K),
KC.EXCLAIM,
])
MACRO_TEST_STRING = send_string("Hello! from, uhhhh, send_string | and some other WEIRD STUFF` \\ like this' \"\t[]")
ANGRY_TABLE_FLIP = unicode_sequence([
"28",
"30ce",
"ca0",
"75ca",
"ca0",
"29",
"30ce",
"5f61",
"253b",
"2501",
"253b",
])
keymap = [
[
[KC.GESC, KC.A, KC.RESET],
[KC.MO(1), KC.B, KC.MUTE],
[KC.LT(2, KC.EXCLAIM), KC.HASH, KC.ENTER],
[KC.TT(3), KC.SPACE, ANGRY_TABLE_FLIP],
],
[
[KC.TRNS, KC.B, KC.C],
[KC.NO, KC.D, KC.E],
[KC.F, KC.G, KC.H],
[KC.I, KC.J, KC.K],
],
[
[KC.VOLU, KC.MUTE, ANGRY_TABLE_FLIP],
[KC.TRNS, KC.PIPE, MACRO_TEST_SIMPLE],
[KC.VOLD, KC.P, MACRO_TEST_STRING],
[KC.L, KC.M, KC.N],
],
[
[KC.NO, KC.UC_MODE_NOOP, KC.C],
[KC.NO, KC.UC_MODE_LINUX, KC.E],
[KC.TRNS, KC.UC_MODE_MACOS, KC.H],
[KC.O, KC.P, KC.Q],
],
]

View File

@ -0,0 +1,64 @@
from kmk.common.consts import DiodeOrientation, UnicodeModes
from kmk.common.keycodes import KC
from kmk.common.macros.simple import send_string, simple_key_sequence
from kmk.common.macros.unicode import unicode_sequence
from kmk.common.pins import Pin as P
from kmk.entrypoints.handwire.itsybitsy_m4_express import main
from kmk.firmware import Firmware
cols = (P.A4, P.A5, P.D7)
rows = (P.D12, P.D11, P.D10)
diode_orientation = DiodeOrientation.COLUMNS
unicode_mode = UnicodeModes.LINUX
MACRO_TEST_SIMPLE = simple_key_sequence([
KC.LSHIFT(KC.H),
KC.E,
KC.L,
KC.L,
KC.O,
KC.SPACE,
KC.MACRO_SLEEP_MS(500),
KC.LSHIFT(KC.K),
KC.LSHIFT(KC.M),
KC.LSHIFT(KC.K),
KC.EXCLAIM,
])
MACRO_TEST_STRING = send_string("Hello! from, uhhhh, send_string | and some other WEIRD STUFF` \\ like this' \"\t[]")
ANGRY_TABLE_FLIP = unicode_sequence([
"28",
"30ce",
"ca0",
"75ca",
"ca0",
"29",
"30ce",
"5f61",
"253b",
"2501",
"253b",
])
keymap = [
[
[KC.GESC, KC.A, KC.RESET],
[KC.MO(1), KC.B, KC.MUTE],
[KC.LT(2, KC.EXCLAIM), KC.HASH, KC.ENTER],
],
[
[KC.MUTE, KC.B, KC.C],
[KC.TRNS, KC.D, KC.E],
[KC.F, KC.G, KC.H],
],
[
[KC.VOLU, KC.MUTE, ANGRY_TABLE_FLIP],
[KC.NO, KC.PIPE, MACRO_TEST_SIMPLE],
[KC.TRNS, KC.P, MACRO_TEST_STRING],
],
]

View File

@ -1,14 +1,12 @@
import machine
from kmk.common.consts import DiodeOrientation, UnicodeModes
from kmk.common.keycodes import KC
from kmk.common.macros.simple import send_string, simple_key_sequence
from kmk.common.macros.unicode import unicode_sequence
from kmk.common.pins import Pin as P
from kmk.entrypoints.handwire.pyboard import main
p = machine.Pin.board
cols = (p.X10, p.X11, p.X12)
rows = (p.X1, p.X2, p.X3)
cols = (P.X10, P.X11, P.X12)
rows = (P.X1, P.X2, P.X3)
diode_orientation = DiodeOrientation.COLUMNS
unicode_mode = UnicodeModes.LINUX

@ -1 +1 @@
Subproject commit d7517409e93be2b098b463cf5430c8dbc76cef07
Subproject commit 5c606deecc93c573516c46787486cee846a6d599

2
vendor/upy-lib vendored

@ -1 +1 @@
Subproject commit f20d89c6aad9443a696561ca2a01f7ef0c8fb302
Subproject commit 451b1c07567de85062ef35b672b2101647285e9a