Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[do not merge] Add nullbitsco tidbit board with pin mapping; doc updates; hex composer #915

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions boards/nullbitsco/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://nullbits.co/
1 change: 1 addition & 0 deletions boards/nullbitsco/tidbit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://nullbits.co/tidbit/
61 changes: 61 additions & 0 deletions boards/nullbitsco/tidbit/kb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import board

from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
from kmk.quickpin.pro_micro.bitc_promicro import pinout as pins

from kmk.scanners import DiodeOrientation
from kmk.modules.encoder import EncoderHandler


encoder_pinout = [
(pins[13], pins[14], None), # enc 0, button mapped in matrix
(pins[10], pins[11], None), # enc 1 (optional)
(pins[5], pins[4], None), # enc 2 (optional)
(pins[0], pins[1], None), # enc 3 (optional)
]


class KMKKeyboard(_KMKKeyboard):
""""
Create a nullbits tidbit keyboard.
optional constructor arguments:

active_encoders=[0, 2] to list installed encoder positions (first=0)
then declare keyboard.encoders.map = [(KC.<left> , KC.<right>, None), (...)]
enable_rgb=False (default True) to add rgb module (requires neopixel.py)
landscape_layout=True to orient USB port top right rather than left (default)
"""
# led = digitalio.DigitalInOut(board.D21)
# led.direction = digitalio.Direction.OUTPUT
# led.value = False
row_pins = (
pins[15],
pins[9],
pins[8],
pins[7],
pins[6],
)
col_pins = (
pins[19],
pins[18],
pins[17],
pins[16],
)
pixel_pin = pins[12]
diode_orientation = DiodeOrientation.ROW2COL
i2c = board.I2C #TODO ??

def __init__(self, active_encoders=[], landscape_layout=False):
super().__init__()

if landscape_layout:
self.coord_mapping = [
row * len(self.col_pins) + col
for col in range(len(self.col_pins))
for row in reversed(range(len(self.row_pins)))
]

if active_encoders:
self.encoders = EncoderHandler()
self.encoders.pins = tuple([encoder_pinout[i] for i in active_encoders])
self.modules.append(self.encoders)
20 changes: 20 additions & 0 deletions boards/nullbitsco/tidbit/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from kb import KMKKeyboard
from kmk.keys import KC

# add active_encoders=[0, 2] to constructor if first and third encoders installed
keyboard = KMKKeyboard()

XXXXX = KC.NO

keyboard.keymap = [
[
XXXXX, KC.PSLS, KC_PAST, KC_PMNS,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These will all need to be converted from QMK syntax (underscore) to KMK (dot):

keyboard.keymap = [
    [
        XXXXX,  KC.PSLS, KC.PAST, KC.PMNS,
        KC.P7,  KC.P8,   KC.P9,   KC.PPLS,
        KC.P4,  KC.P5,   KC.P6,   KC.PPLS,
        KC.P1,  KC.P2,   KC.P3,   KC.PENT,
        KC.P0,  KC.P0,   KC.PDOT, KC.PENT,
    ]
]

KC_P7, KC_P8, KC_P9, KC_PPLS,
KC_P4, KC_P5, KC_P6, KC_PPLS,
KC_P1, KC_P2, KC_P3, KC_PENT,
KC_P0, KC_P0, KC_PDOT, KC_PENT,
]
]

if __name__ == '__main__':
keyboard.go()
135 changes: 135 additions & 0 deletions boards/nullbitsco/tidbit/main_hexpad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
from kb import KMKKeyboard
from kmk.keys import KC, make_key
from kmk.modules.holdtap import HoldTap
from kmk.modules.layers import Layers
from kmk.modules.hex_compose import HexCompose
from kmk.modules.tapdance import TapDance
from kmk.modules.oneshot import OneShot
from kmk.extensions.rgb import RGB, AnimationModes
from kmk.consts import UnicodeMode

from kmk.handlers.sequences import unicode_string_sequence

keyboard = KMKKeyboard(active_encoders=[0], landscape_layout=True)

keyboard.unicode_mode = UnicodeMode.RALT

keyboard.modules += [
Layers(),
OneShot(),
HoldTap(),
TapDance(),
HexCompose(encoding='utf8')
]

rgb = RGB(
pixel_pin=keyboard.pixel_pin,
num_pixels=8,
animation_mode=AnimationModes.BREATHING,
animation_speed=3,
breathe_center=2,
)
keyboard.extensions.append(rgb)


def set_backlight(hsv):
rgb.hue, rgb.sat, rgb.val = hsv


XXXXX = KC.NO
_____ = KC.TRANSPARENT

SPC_ENTER = KC.HT(KC.SPACE, KC.ENTER)
SHFT_CTL = KC.TD(KC.LSHIFT, KC.LCTL)

OS_NUM = KC.OS(KC.MO(2), tap_time=None)
OS_ALPHA = KC.OS(KC.MO(4), tap_time=None)
OS_SYM = KC.OS(KC.MO(6), tap_time=None)

TO_HEX = KC.TO(0)
TO_NUM = KC.TO(2)
TO_ALPHA = KC.TO(4)

colors = dict(
hex=(180,255,50),
num=(265,255,50),
alpha=(95,255,50)
)
set_backlight(colors['hex'])
TO_HEX.after_press_handler(lambda *_: set_backlight(colors['hex']))
TO_NUM.after_press_handler(lambda *_: set_backlight(colors['num']))
TO_ALPHA.after_press_handler(lambda *_: set_backlight(colors['alpha']))

# encoder direction does left/right or up/down with alt
keyboard.encoders.map = [
[(KC.LEFT, KC.RIGHT, XXXXX)],
[(KC.UP, KC.DOWN, XXXXX)],
] * 4

# encoder button cycles modes (hex, numpad, alpha)
keyboard.keymap = [
# -----------------------------------------------------------
# 0: hex
[
KC.HEX7, KC.HEX8, KC.HEX9, TO_NUM, XXXXX,
KC.HEX4, KC.HEX5, KC.HEX6, KC.HEXA, KC.HEXB,
KC.HEX1, KC.HEX2, KC.HEX3, KC.HEXC, KC.HEXD,
KC.LT(1, OS_SYM), KC.HEX0, SPC_ENTER, KC.HEXE, KC.HEXF,
],
# 1: hex alt
[
XXXXX, XXXXX, XXXXX, XXXXX, XXXXX,
XXXXX, XXXXX, XXXXX, OS_ALPHA, XXXXX,
XXXXX, XXXXX, XXXXX, XXXXX, XXXXX,
XXXXX, OS_NUM, XXXXX, XXXXX, KC.BSPC
],
# -----------------------------------------------------------
# 2: numpad
[
KC.N7, KC.N8, KC.N9, TO_ALPHA, XXXXX,
KC.N4, KC.N5, KC.N6, KC.SLSH, KC.ASTR,
KC.N1, KC.N2, KC.N3, KC.MINUS, KC.PLUS,
KC.LT(3, OS_SYM), KC.N0, SPC_ENTER, KC.EQUAL, KC.BSPC,
],
# 3: numpad alt
[
KC.HOME, KC.UP, KC.PGUP, XXXXX, XXXXX,
KC.LEFT, KC.COMMA, KC.RIGHT, OS_ALPHA, XXXXX,
KC.END, KC.DOWN, KC.PGDN, XXXXX, XXXXX,
XXXXX, KC.DOT, KC.TAB, XXXXX, KC.ESC,
],
# -----------------------------------------------------------
# 4: alpha
[
KC.S, KC.T, KC.U, TO_HEX, XXXXX,
KC.I, KC.L, KC.N, KC.O, KC.R,
KC.A, KC.C, KC.D, KC.E, KC.H,
KC.LT(5, OS_SYM), SHFT_CTL, SPC_ENTER, KC.COMM, KC.BSPC,
],
# 5: alpha alt
[
KC.X, KC.Y, KC.Z, XXXXX, XXXXX,
KC.M, KC.P, KC.Q, KC.V, KC.W,
KC.B, KC.F, KC.G, KC.J, KC.K,
XXXXX, OS_NUM, KC.TAB, KC.DOT, KC.ESC,
],
# -----------------------------------------------------------
# 6: symbols
[
KC.QUOT, KC.LPRN, KC.RPRN, XXXXX, XXXXX,
KC.DLR, KC.PERC, KC.AMPR, KC.LBRC, KC.RBRC,
KC.EXLM, KC.DQT, KC.HASH, KC.SCLN, KC.EQUAL,
KC.MO(7), KC.SLSH, SPC_ENTER, KC.COMM, KC.DOT,
],
# 7: symbols alt
[
KC.GRV, KC.ASTR, KC.UNDS, XXXXX, XXXXX,
KC.BSLS, KC.PIPE, KC.CIRC, KC.LCBR, KC.RCBR,
KC.TILD, KC.AT, KC.MINUS, KC.COLN, KC.PLUS,
XXXXX, KC.QUES, KC.TAB, KC.LABK, KC.RABK,
],
]


if __name__ == '__main__':
keyboard.go()
8 changes: 8 additions & 0 deletions docs/en/config_and_keymap.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ print(dir(board))
keyboard.keymap = [[KC.A, KC.B]]
```

- The keymap contains a flat list of `Key` objects for each layer of the keyboard.
The list of keys in each layer are stored as a single list that folllows the
grid of row and column pins in the keyboard matrix. This list starts with keys
in the first row from left to right, then the second row, and so on.
The row x column matrix structure doesn't appear explicitly
in the keymap. Use `KC.NO` to mark grid positions without a physical key.
For very sparse grids `keyboard.coord_mapping` can be useful to avoid `KC.NO`.

You can further define a bunch of other stuff:

- `keyboard.debug_enabled` which will spew a ton of debugging information to the serial
Expand Down
16 changes: 14 additions & 2 deletions docs/en/holdtap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
# HoldTap Keycodes
Enabling HoldTap will give you access to the following keycodes and can simply be
added to the modules list.
The HoldTap module lets keys do double duty:
tap the key to do one thing,
hold it longer than the configurable `tap_time` to do another.

HoldTap is often used with modifier keys.
For example `KC.HT(KC.ESCAPE, KC.LCTRL)` configures
a key that sends Escape when tapped and
left control when held.
It can be used with regular keys as well
like `KC.HT(KC.SPACE, KC.ENTER)` to send space on tab
and enter on hold.

Simply import HoldTap and add it to the modules list.
This lets you use `KC.HT` actions like those below.

```python
from kmk.modules.holdtap import HoldTap
Expand Down
119 changes: 119 additions & 0 deletions kmk/modules/hex_compose.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from kmk.modules import Module
from kmk.keys import KC, make_key
from kmk.handlers.sequences import unicode_string_sequence


_ascii7_keys = []
for c in range(64, 96):
_ascii7_keys.append(KC.LCTRL(KC.get(chr(c))))
for c in range(32, 127):
k = KC.get(chr(c))
_ascii7_keys.append(KC.LSHIFT(k) if 65 <= c <= 90 else k)
_ascii7_keys.append(KC.DEL)

assert len(_ascii7_keys) == 128, "Failed to enumerate 7-bit ascii keys"


def _utf8_length(first_byte):
# utf8 encodes length in first byte see https://en.wikipedia.org/wiki/UTF-8
length = 0
if first_byte & 0x80 == 0: # 0xxx xxxx
length = 1
elif first_byte & 0xe0 == 0xc0: # 110x xxxx
length = 2
elif first_byte & 0xf0 == 0xe0: # 1110 xxxx
length = 3
elif first_byte & 0xf8 == 0xf0: # 1111 0xxx
length = 4
return length or None


class HexCompose(Module):
def __init__(self, encoding='utf8'):
self.encoding = encoding
self._hex_keys = [
make_key(
names=(f'HEX{digit:x}'.upper(), ),
meta=digit,
)
for digit in range(16)
]
self.reset()

def reset(self):
self._bytes_needed = 1 # ascii always needs 1, uft8 sets dynamically
self._hi_nibble = None # two hex digits => one byte
self._bytes = b''

def during_bootup(self, keyboard):
self.reset()

def before_matrix_scan(self, keyboard):
return

def after_matrix_scan(self, keyboard):
return

def before_hid_send(self, keyboard):
return

def after_hid_send(self, keyboard):
return

def on_powersave_enable(self, keyboard):
return

def on_powersave_disable(self, keyboard):
return

def process_key(self, keyboard, key, is_pressed, int_coord):
if key not in self._hex_keys:
self.reset()
return key
elif not is_pressed:
return None

digit = key.meta
print("HexCompose: got hex key", digit)

# half way thru a byte?
if self._hi_nibble is None:
self._hi_nibble = digit << 4
return None

# otherwise add a byte
self._bytes += bytes([self._hi_nibble + digit])
self._hi_nibble = None

# validate length and continuation for utf-8
if self.encoding == 'utf8':
if len(self._bytes) == 1:
self._bytes_needed = _utf8_length(self._bytes[0])
# valid starting byte?
if not self._bytes_needed:
print(f'HexCompose: invalid utf-8 length coding from {self._bytes}')
self.reset()
return None
else:
# valid continuation byte?
if self._bytes[-1] & 0xc0 != 0x80:
print(f'HexCompose: invalid utf-8 continuation in {self._bytes}')
self.reset()
return None

if len(self._bytes) != self._bytes_needed:
return None

print('HexCompose: completed bytes', self._bytes)
character = self._bytes.decode(self.encoding)[0]
c = ord(character)
if c < 128:
composed_key = _ascii7_keys[c]
print('HexCompose: sending ascii7', composed_key)
else:
composed_key = unicode_string_sequence(character)
print('HexCompose: sending unicode for', character, composed_key)

self.reset()

return composed_key
Loading