Compare commits

...

8 Commits

9 changed files with 305 additions and 104 deletions

View File

@ -1 +1,20 @@
This code is experimental.
## NeoPixel
Put [this](https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_NeoPixel/main/neopixel.py) in `lib` folder of CircuitPython disk (compile to `mpy` for nice!nano.
## Compile mpy files for nice!nano kmk
``` bash
docker run -v /scratch/kmk_firmware:/opt/kmk_firmware --rm -it --entrypoint=/bin/bash python:latest
cd /opt
wget https://adafruit-circuit-python.s3.amazonaws.com/bin/mpy-cross/mpy-cross.static-amd64-linux-8.0.5
chmod a+x mpy-cross.static-amd64-linux-8.0.5
./mpy-cross.static-amd64-linux-8.0.5 --help
mv mpy-cross.static-amd64-linux-8.0.5 /usr/bin/mpy-cross
mpy-cross --help
cd /opt/kmk_firmware
python util/compile.py
cp ~.compiled/kmk~ folder -> mcu
```

0
ardux/__init__.py Normal file
View File

27
ardux/chord.py Normal file
View File

@ -0,0 +1,27 @@
from kmk.modules.combos import Chord
# TODO: fix up pass by reference trick (wrapped in array) thats used below
# likely deserves a global but KmN couldnt figure it out
class ArduxChord(Chord):
# Override default constructor to allow passing of required fields
def __init__(
self,
match: Tuple[Union[Key, int], ...],
result: Key,
fast_reset=None,
per_key_timeout=None,
timeout=None,
match_coord=None,
ardux_keyboard=[],
layers=[],
):
super().__init__(match, result, fast_reset, per_key_timeout, match_coord)
self.ardux_keyboard = ardux_keyboard
self.layers = layers
# Override standard kmk match logic to first check active vs allowed layers
def matches(self, key: Key, int_coord: int):
if self.ardux_keyboard is None or len(self.ardux_keyboard) == 0 or len(self.layers) == 0 or any(i in self.ardux_keyboard[0].active_layers for i in self.layers):
return super().matches(key, int_coord)
return False

55
ardux/kb.py Normal file
View File

@ -0,0 +1,55 @@
import os
import board
from kmk.quickpin.pro_micro.kb2040 import pinout as pins
from kmk.kmk_keyboard import KMKKeyboard
from kmk.scanners.keypad import KeysScanner
from kmk.modules.layers import Layers
from kmk.modules.combos import Combos, Chord
from ardux.chord import ArduxChord
from kmk.keys import KC
class ArduxKeyboard(KMKKeyboard):
coord_mapping = [
0, 1, 2, 3,
4, 5, 6, 7,
]
keymap = [
[KC.S, KC.T, KC.R, KC.A,
KC.O, KC.I, KC.Y, KC.E]
]
# Init / constructor / setup
def __init__(self):
# Enable debugging if appropriate
if os.getenv('ARDUX_KMK_DEBUGGING'):
self.debug_enabled = True
# setup modules/extensions arrays
self.modules = []
self.extensions = []
# Direct wire & matrix setup
self.matrix = KeysScanner([pins[16], pins[17], pins[18], pins[19], pins[12], pins[13], pins[14], pins[15]])
# Layers
self.modules.append(Layers())
# Combos
self.combo_module = Combos()
self.modules.append(self.combo_module)
self.setup_combos()
# Define combos for ardux
def setup_combos(self):
self.combo_module.combos = []
combo_enter = ArduxChord((KC.A, KC.E), KC.ENTER, ardux_keyboard=[self], layers=[0])
self.combo_module.combos.append(combo_enter)
combo_space = ArduxChord((KC.O, KC.I, KC.Y, KC.E), KC.SPACE, ardux_keyboard=[self], layers=[1])
self.combo_module.combos.append(combo_space)

34
boot.py
View File

@ -1,17 +1,8 @@
print('START boot.py')
# Used http://kmkfw.io/docs/boot/ as starting point
# Used http://kmkfw.ioy/docs/boot/ as starting point
import supervisor
import board
import digitalio
import os
import storage
import usb_cdc
import usb_hid
from kmk.quickpin.pro_micro.kb2040 import pinout as pins
from usb_hid import Device
# Print env vars if debugging enabled
if os.getenv('ARDUX_KMK_DEBUGGING'):
@ -27,8 +18,10 @@ if os.getenv('ARDUX_KMK_DEBUGGING'):
else:
print('debugging disabled')
# If this key is held during boot, don't run the code which hides the storage and disables serial
# If this/these key(s) is/are held during boot, don't run the code which hides the storage and disables serial
# bottom row, index finger key / bottom row pinky key
import digitalio
from kmk.quickpin.pro_micro.kb2040 import pinout as pins
key_1 = digitalio.DigitalInOut(pins[12])
key_2 = digitalio.DigitalInOut(pins[15])
@ -36,22 +29,29 @@ key_2 = digitalio.DigitalInOut(pins[15])
key_1.switch_to_input(pull=digitalio.Pull.UP)
key_2.switch_to_input(pull=digitalio.Pull.UP)
# Pull up means 'active low' so invert pin values for positive logic below
# Pull up means 'active low' so invert pin values for less convoluted logic below
key_1_val = not (key_1.value)
key_2_val = not (key_2.value)
# Check for key hold and disable any dangerous features
# Check for key hold and disable any dangerous features if not held
if not (key_1_val or key_2_val) and not os.getenv('ARDUX_KMK_DEBUGGING'):
# dont expose storage by default
if not os.getenv('ARDUX_KMK_USB_DISK_ALWAYS'):
import storage
storage.disable_usb_drive()
# disable usb cdc stuff thats only useful when debugging
import usb_cdc
usb_cdc.disable() # Equivalent to usb_cdc.enable(console=False, data=False)
# Enable use w/ bios when not debugging (serial device from debug messes things up)
# this only works if *both* cdc and storage are disabled above ; add added logic to avoid crash on boot
import usb_hid
from usb_hid import Device
if not os.getenv('ARDUX_KMK_USB_DISK_ALWAYS'):
usb_hid.enable((Device.KEYBOARD,), boot_device=1)
# Deinit pins so they can be setup per the kmk keymap post-boot
key_1.deinit()
key_2.deinit()
# Enable use w/ BIOS
usb_hid.enable(boot_device=1)
print('END boot.py')

27
kb.py
View File

@ -1,27 +0,0 @@
import board
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
from kmk.quickpin.pro_micro.kb2040 import pinout as pins
from kmk.scanners.keypad import KeysScanner
# Direct wire config
_KEY_CFG = [
pins[16],
pins[17],
pins[18],
pins[19],
pins[12],
pins[13],
pins[14],
pins[15]
]
class KMKKeyboard(_KMKKeyboard):
def __init__(self):
# create and register the scanner for direct wire
self.matrix = KeysScanner(_KEY_CFG)
coord_mapping = [
0, 1, 2, 3,
4, 5, 6, 7,
]

180
lib/neopixel.py Normal file
View File

@ -0,0 +1,180 @@
# SPDX-FileCopyrightText: 2016 Damien P. George
# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries
# SPDX-FileCopyrightText: 2019 Carter Nelson
# SPDX-FileCopyrightText: 2019 Roy Hooper
#
# SPDX-License-Identifier: MIT
"""
`neopixel` - NeoPixel strip driver
====================================================
* Author(s): Damien P. George, Scott Shawcroft, Carter Nelson, Rose Hooper
"""
import sys
import board
import digitalio
from neopixel_write import neopixel_write
import adafruit_pixelbuf
try:
# Used only for typing
from typing import Optional, Type
from types import TracebackType
import microcontroller
except ImportError:
pass
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel.git"
# Pixel color order constants
RGB = "RGB"
"""Red Green Blue"""
GRB = "GRB"
"""Green Red Blue"""
RGBW = "RGBW"
"""Red Green Blue White"""
GRBW = "GRBW"
"""Green Red Blue White"""
class NeoPixel(adafruit_pixelbuf.PixelBuf):
"""
A sequence of neopixels.
:param ~microcontroller.Pin pin: The pin to output neopixel data on.
:param int n: The number of neopixels in the chain
:param int bpp: Bytes per pixel. 3 for RGB and 4 for RGBW pixels.
:param float brightness: Brightness of the pixels between 0.0 and 1.0 where 1.0 is full
brightness
:param bool auto_write: True if the neopixels should immediately change when set. If False,
`show` must be called explicitly.
:param str pixel_order: Set the pixel color channel order. GRBW is set by default.
Example for Circuit Playground Express:
.. code-block:: python
import neopixel
from board import *
RED = 0x100000 # (0x10, 0, 0) also works
pixels = neopixel.NeoPixel(NEOPIXEL, 10)
for i in range(len(pixels)):
pixels[i] = RED
Example for Circuit Playground Express setting every other pixel red using a slice:
.. code-block:: python
import neopixel
from board import *
import time
RED = 0x100000 # (0x10, 0, 0) also works
# Using ``with`` ensures pixels are cleared after we're done.
with neopixel.NeoPixel(NEOPIXEL, 10) as pixels:
pixels[::2] = [RED] * (len(pixels) // 2)
time.sleep(2)
.. py:method:: NeoPixel.show()
Shows the new colors on the pixels themselves if they haven't already
been autowritten.
The colors may or may not be showing after this function returns because
it may be done asynchronously.
.. py:method:: NeoPixel.fill(color)
Colors all pixels the given ***color***.
.. py:attribute:: brightness
Overall brightness of the pixel (0 to 1.0)
"""
def __init__(
self,
pin: microcontroller.Pin,
n: int,
*,
bpp: int = 3,
brightness: float = 1.0,
auto_write: bool = True,
pixel_order: str = None
):
if not pixel_order:
pixel_order = GRB if bpp == 3 else GRBW
elif isinstance(pixel_order, tuple):
order_list = [RGBW[order] for order in pixel_order]
pixel_order = "".join(order_list)
self._power = None
if (
sys.implementation.version[0] >= 7
and getattr(board, "NEOPIXEL", None) == pin
):
power = getattr(board, "NEOPIXEL_POWER_INVERTED", None)
polarity = power is None
if not power:
power = getattr(board, "NEOPIXEL_POWER", None)
if power:
try:
self._power = digitalio.DigitalInOut(power)
self._power.switch_to_output(value=polarity)
except ValueError:
pass
super().__init__(
n, brightness=brightness, byteorder=pixel_order, auto_write=auto_write
)
self.pin = digitalio.DigitalInOut(pin)
self.pin.direction = digitalio.Direction.OUTPUT
def deinit(self) -> None:
"""Blank out the NeoPixels and release the pin."""
self.fill(0)
self.show()
self.pin.deinit()
if self._power:
self._power.deinit()
def __enter__(self):
return self
def __exit__(
self,
exception_type: Optional[Type[BaseException]],
exception_value: Optional[BaseException],
traceback: Optional[TracebackType],
):
self.deinit()
def __repr__(self):
return "[" + ", ".join([str(x) for x in self]) + "]"
@property
def n(self) -> int:
"""
The number of neopixels in the chain (read-only)
"""
return len(self)
def write(self) -> None:
""".. deprecated: 1.0.0
Use ``show`` instead. It matches Micro:Bit and Arduino APIs."""
self.show()
def _transmit(self, buffer: bytearray) -> None:
neopixel_write(self.pin, buffer)

65
main.py
View File

@ -1,15 +1,10 @@
import board
import os
from kb import KMKKeyboard
from kmk.keys import KC
keyboard = KMKKeyboard()
#####
# Enable debugging
if os.getenv('ARDUX_KMK_DEBUGGING'):
keyboard.debug_enabled = True
# Main keyboard object
from ardux.kb import ArduxKeyboard
ardux_keyboard = ArduxKeyboard()
#####
# NeoPixel on kb2040 (tune accordingly / remove if different mcu)
@ -19,60 +14,12 @@ rgb_ext = RGB(
num_pixels=1,
val_limit=100,
val_default=25,
animation_mode=AnimationModes.RAINBOW
animation_mode=AnimationModes.BREATHING_RAINBOW
)
keyboard.extensions.append(rgb_ext)
#####
# Layers
from kmk.modules.layers import Layers
keyboard.modules.append(Layers())
#####
# Combos
from kmk.modules.combos import Combos, Chord
combos = Combos()
keyboard.modules.append(combos)
class KmNChord(Chord):
def __init__(
self,
match: Tuple[Union[Key, int], ...],
result: Key,
fast_reset=None,
per_key_timeout=None,
timeout=None,
match_coord=None,
keyboard=None,
layers=[],
):
super().__init__(match, result, fast_reset, per_key_timeout, match_coord)
self.keyboard = keyboard
self.layers = layers
def matches(self, key: Key, int_coord: int):
if keyboard is None or len(self.layers) == 0 or any(i in self.keyboard.active_layers for i in self.layers):
return super().matches(key, int_coord)
return False
combos.combos = []
combo_enter = KmNChord((KC.A, KC.E), KC.ENTER, keyboard=keyboard, layers=[0])
combos.combos.append(combo_enter)
combo_space = KmNChord((KC.O, KC.I, KC.Y, KC.E), KC.SPACE, keyboard=keyboard, layers=[1])
combos.combos.append(combo_space)
#####
# Keymap
keyboard.keymap = [
[KC.S, KC.T, KC.R, KC.A,
KC.O, KC.I, KC.Y, KC.E]
]
ardux_keyboard.extensions.append(rgb_ext)
#####
# Main
if __name__ == '__main__':
keyboard.go()
ardux_keyboard.go()

View File

@ -1,5 +1,5 @@
CIRCUITPY_BLE_NAME = "ARDUX [L|R] [board]"
ARDUX_KMK_DEBUGGING = 1 # Code only looks for value ; Uncomment/Comment to enable/disable
#ARDUX_KMK_DEBUGGING = 1 # Code only looks for value ; Uncomment/Comment to enable/disable
ARDUX_KMK_USB_DISK_ALWAYS = 1 # Code only looks for value ; Uncomment/Comment to enable/disable
ARDUX_SIZE = "[STANDARD|BIG|40%]"
ARDUX_HAND = "[LEFT|RIGHT]"