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:
commit
914b305fee
@ -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
1
.gitignore
vendored
@ -107,6 +107,7 @@ venv.bak/
|
||||
.submodules
|
||||
.circuitpy-deps
|
||||
.micropython-deps
|
||||
.devdeps
|
||||
|
||||
# Pycharms cruft
|
||||
.idea
|
||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -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
183
Makefile
@ -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?"
|
||||
|
75
README.md
75
README.md
@ -2,31 +2,54 @@
|
||||
|
||||
[](https://circleci.com/gh/KMKfw/kmk_firmware/tree/master)[](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),
|
||||
|
@ -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
39
kmk/circuitpython/hid.py
Normal 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],
|
||||
)
|
@ -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
155
kmk/common/abstract/hid.py
Normal 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
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
40
kmk/common/pins.py
Normal 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()
|
@ -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)
|
||||
|
@ -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)
|
||||
|
33
kmk/entrypoints/handwire/feather_m4_express.py
Normal file
33
kmk/entrypoints/handwire/feather_m4_express.py
Normal 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)
|
33
kmk/entrypoints/handwire/itsybitsy_m4_express.py
Normal file
33
kmk/entrypoints/handwire/itsybitsy_m4_express.py
Normal 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)
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -1,4 +1,4 @@
|
||||
[submodules]
|
||||
"vendor/circuitpython" = "d751740"
|
||||
"vendor/circuitpython" = "5c606de"
|
||||
"vendor/micropython" = "65a49fa"
|
||||
"vendor/upy-lib" = "f20d89c"
|
||||
"vendor/upy-lib" = "451b1c0"
|
||||
|
@ -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
|
||||
|
3
upy-unix-stubs/digitalio.py
Normal file
3
upy-unix-stubs/digitalio.py
Normal file
@ -0,0 +1,3 @@
|
||||
class DigitalInOut:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
1
upy-unix-stubs/usb_hid.py
Normal file
1
upy-unix-stubs/usb_hid.py
Normal file
@ -0,0 +1 @@
|
||||
devices = []
|
@ -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
|
||||
|
||||
|
73
user_keymaps/klardotsh/feather_m4_express/fourfour.py
Normal file
73
user_keymaps/klardotsh/feather_m4_express/fourfour.py
Normal 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],
|
||||
],
|
||||
]
|
64
user_keymaps/klardotsh/itsybitsy_m4_express/threethree.py
Normal file
64
user_keymaps/klardotsh/itsybitsy_m4_express/threethree.py
Normal 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],
|
||||
],
|
||||
]
|
@ -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
|
||||
|
2
vendor/circuitpython
vendored
2
vendor/circuitpython
vendored
@ -1 +1 @@
|
||||
Subproject commit d7517409e93be2b098b463cf5430c8dbc76cef07
|
||||
Subproject commit 5c606deecc93c573516c46787486cee846a6d599
|
2
vendor/upy-lib
vendored
2
vendor/upy-lib
vendored
@ -1 +1 @@
|
||||
Subproject commit f20d89c6aad9443a696561ca2a01f7ef0c8fb302
|
||||
Subproject commit 451b1c07567de85062ef35b672b2101647285e9a
|
Loading…
x
Reference in New Issue
Block a user