Deleted KMK and Lib Folder

This commit is contained in:
Lawri Darbyshire 2025-02-27 12:01:04 +00:00
parent 3e0e9b42dc
commit 43a7fbce86
507 changed files with 0 additions and 433878 deletions

View file

@ -1,107 +0,0 @@
try:
from typing import Optional
except ImportError:
pass
import digitalio
import microcontroller
import usb_hid
def bootcfg(
sense: Optional[microcontroller.Pin, digitalio.DigitalInOut] = None,
source: Optional[microcontroller.Pin, digitalio.DigitalInOut] = None,
autoreload: bool = True,
boot_device: int = 0,
cdc_console: bool = True,
cdc_data: bool = False,
consumer_control: bool = True,
keyboard: bool = True,
midi: bool = True,
mouse: bool = True,
nkro: bool = False,
pan: bool = False,
storage: bool = True,
usb_id: Optional[tuple[str, str]] = None,
**kwargs,
) -> bool:
if len(kwargs):
print('unknown option', kwargs)
if isinstance(sense, microcontroller.Pin):
sense = digitalio.DigitalInOut(sense)
sense.direction = digitalio.Direction.INPUT
sense.pull = digitalio.Pull.UP
if isinstance(source, microcontroller.Pin):
source = digitalio.DigitalInOut(source)
source.direction = digitalio.Direction.OUTPUT
source.value = False
if not autoreload:
import supervisor
supervisor.runtime.autoreload = False
# configure HID devices
devices = []
if keyboard:
if nkro:
from kmk.hid_reports import nkro_keyboard
devices.append(nkro_keyboard.NKRO_KEYBOARD)
else:
devices.append(usb_hid.Device.KEYBOARD)
if mouse:
if pan:
from kmk.hid_reports import pointer
devices.append(pointer.POINTER)
else:
devices.append(usb_hid.Device.MOUSE)
if consumer_control:
devices.append(usb_hid.Device.CONSUMER_CONTROL)
if devices:
usb_hid.enable(devices, boot_device)
else:
usb_hid.disable()
# configure midi over usb
if not midi:
import usb_midi
usb_midi.disable()
# configure usb vendor and product id
if usb_id is not None:
import supervisor
if hasattr(supervisor, 'set_usb_identification'):
supervisor.set_usb_identification(*usb_id)
# configure data serial
if cdc_data:
import usb_cdc
usb_cdc.enable(data=True)
# sense not provided or pulled low -> Skip boot configuration that may
# disable debug or rescue facilities.
if sense is None or not sense.value:
return False
# Entries for serial console (REPL) and storage are intentionally evaluated
# last to ensure the board is debuggable, mountable and rescueable, in case
# any of the previous code throws an exception.
if not cdc_console:
import usb_cdc
usb_cdc.enable(console=False)
if not storage:
import storage
storage.disable_usb_drive()
return True

View file

@ -1,54 +0,0 @@
class InvalidExtensionEnvironment(Exception):
pass
class Extension:
_enabled = True
def enable(self, keyboard):
self._enabled = True
self.on_runtime_enable(keyboard)
def disable(self, keyboard):
self._enabled = False
self.on_runtime_disable(keyboard)
# The below methods should be implemented by subclasses
def on_runtime_enable(self, keyboard):
raise NotImplementedError
def on_runtime_disable(self, keyboard):
raise NotImplementedError
def during_bootup(self, keyboard):
raise NotImplementedError
def before_matrix_scan(self, keyboard):
'''
Return value will be injected as an extra matrix update
'''
raise NotImplementedError
def after_matrix_scan(self, keyboard):
'''
Return value will be replace matrix update if supplied
'''
raise NotImplementedError
def before_hid_send(self, keyboard):
raise NotImplementedError
def after_hid_send(self, keyboard):
raise NotImplementedError
def on_powersave_enable(self, keyboard):
raise NotImplementedError
def on_powersave_disable(self, keyboard):
raise NotImplementedError
def deinit(self, keyboard):
pass

View file

@ -1,279 +0,0 @@
from supervisor import ticks_ms
import displayio
import terminalio
from adafruit_display_text import label
from kmk.extensions import Extension
from kmk.keys import make_key
from kmk.kmktime import PeriodicTimer, ticks_diff
from kmk.modules.split import Split, SplitSide
from kmk.utils import clamp
class TextEntry:
def __init__(
self,
text='',
x=0,
y=0,
x_anchor='L',
y_anchor='T',
direction='LTR',
line_spacing=0.75,
inverted=False,
layer=None,
side=None,
):
self.text = text
self.direction = direction
self.line_spacing = line_spacing
self.inverted = inverted
self.layer = layer
self.color = 0xFFFFFF
self.background_color = 0x000000
self.x_anchor = 0.0
self.y_anchor = 0.0
if x_anchor == 'L':
self.x_anchor = 0.0
x = x + 1
if x_anchor == 'M':
self.x_anchor = 0.5
if x_anchor == 'R':
self.x_anchor = 1.0
if y_anchor == 'T':
self.y_anchor = 0.0
if y_anchor == 'M':
self.y_anchor = 0.5
if y_anchor == 'B':
self.y_anchor = 1.0
self.anchor_point = (self.x_anchor, self.y_anchor)
self.anchored_position = (x, y)
if inverted:
self.color = 0x000000
self.background_color = 0xFFFFFF
self.side = side
if side == 'L':
self.side = SplitSide.LEFT
if side == 'R':
self.side = SplitSide.RIGHT
class ImageEntry:
def __init__(self, x=0, y=0, image='', layer=None, side=None):
self.x = x
self.y = y
self.image = displayio.OnDiskBitmap(image)
self.layer = layer
self.side = side
if side == 'L':
self.side = SplitSide.LEFT
if side == 'R':
self.side = SplitSide.RIGHT
class DisplayBase:
def __init__(self):
raise NotImplementedError
def during_bootup(self, width, height, rotation):
raise NotImplementedError
def deinit(self):
raise NotImplementedError
def sleep(self):
self.display.sleep()
def wake(self):
self.display.wake()
@property
def brightness(self):
return self.display.brightness
@brightness.setter
def brightness(self, new_brightness):
self.display.brightness = new_brightness
# display.show() is deprecated, so use root_group instead
@property
def root_group(self):
return self.display.root_group
@root_group.setter
def root_group(self, group):
self.display.root_group = group
class Display(Extension):
def __init__(
self,
display=None,
entries=[],
width=128,
height=32,
flip: bool = False,
flip_left: bool = False,
flip_right: bool = False,
brightness=0.8,
brightness_step=0.1,
dim_time=20,
dim_target=0.1,
off_time=60,
powersave_dim_time=10,
powersave_dim_target=0.1,
powersave_off_time=30,
):
self.display = display
self.flip = flip
self.flip_left = flip_left
self.flip_right = flip_right
self.entries = entries
self.width = width
self.height = height
self.prev_layer = None
self.brightness = brightness
self.brightness_step = brightness_step
self.timer_start = ticks_ms()
self.powersave = False
self.dim_time_ms = dim_time * 1000
self.dim_target = dim_target
self.off_time_ms = off_time * 1000
self.powersavedim_time_ms = powersave_dim_time * 1000
self.powersave_dim_target = powersave_dim_target
self.powersave_off_time_ms = powersave_off_time * 1000
self.dim_period = PeriodicTimer(50)
self.split_side = None
make_key(names=('DIS_BRI',), on_press=self.display_brightness_increase)
make_key(names=('DIS_BRD',), on_press=self.display_brightness_decrease)
def render(self, layer):
splash = displayio.Group()
for entry in self.entries:
if entry.layer != layer and entry.layer is not None:
continue
if isinstance(entry, TextEntry):
splash.append(
label.Label(
terminalio.FONT,
text=entry.text,
color=entry.color,
background_color=entry.background_color,
anchor_point=entry.anchor_point,
anchored_position=entry.anchored_position,
label_direction=entry.direction,
line_spacing=entry.line_spacing,
padding_left=1,
)
)
elif isinstance(entry, ImageEntry):
splash.append(
displayio.TileGrid(
entry.image,
pixel_shader=entry.image.pixel_shader,
x=entry.x,
y=entry.y,
)
)
self.display.root_group = splash
def on_runtime_enable(self, sandbox):
return
def on_runtime_disable(self, sandbox):
return
def during_bootup(self, keyboard):
for module in keyboard.modules:
if isinstance(module, Split):
self.split_side = module.split_side
if self.split_side == SplitSide.LEFT:
self.flip = self.flip_left
elif self.split_side == SplitSide.RIGHT:
self.flip = self.flip_right
for idx, entry in enumerate(self.entries):
if entry.side != self.split_side and entry.side is not None:
del self.entries[idx]
self.display.during_bootup(self.width, self.height, 180 if self.flip else 0)
self.display.brightness = self.brightness
def before_matrix_scan(self, sandbox):
if self.dim_period.tick():
self.dim()
if sandbox.active_layers[0] != self.prev_layer:
self.prev_layer = sandbox.active_layers[0]
self.render(sandbox.active_layers[0])
def after_matrix_scan(self, sandbox):
if sandbox.matrix_update or sandbox.secondary_matrix_update:
self.timer_start = ticks_ms()
def before_hid_send(self, sandbox):
return
def after_hid_send(self, sandbox):
return
def on_powersave_enable(self, sandbox):
self.powersave = True
def on_powersave_disable(self, sandbox):
self.powersave = False
def deinit(self, sandbox):
displayio.release_displays()
self.display.deinit()
def display_brightness_increase(self, *args):
self.display.brightness = clamp(
self.display.brightness + self.brightness_step, 0, 1
)
self.brightness = self.display.brightness # Save current brightness
def display_brightness_decrease(self, *args):
self.display.brightness = clamp(
self.display.brightness - self.brightness_step, 0, 1
)
self.brightness = self.display.brightness # Save current brightness
def dim(self):
if self.powersave:
if (
self.powersave_off_time_ms
and ticks_diff(ticks_ms(), self.timer_start)
> self.powersave_off_time_ms
):
self.display.sleep()
elif (
self.powersave_dim_time_ms
and ticks_diff(ticks_ms(), self.timer_start)
> self.powersave_dim_time_ms
):
self.display.brightness = self.powersave_dim_target
else:
self.display.brightness = self.brightness
self.display.wake()
elif (
self.off_time_ms
and ticks_diff(ticks_ms(), self.timer_start) > self.off_time_ms
):
self.display.sleep()
elif (
self.dim_time_ms
and ticks_diff(ticks_ms(), self.timer_start) > self.dim_time_ms
):
self.display.brightness = self.dim_target
else:
self.display.brightness = self.brightness
self.display.wake()

View file

@ -1,24 +0,0 @@
from . import DisplayBase
# Intended for displays with drivers built into CircuitPython
# that can be used directly without manual initialization
class BuiltInDisplay(DisplayBase):
def __init__(self, display=None, sleep_command=None, wake_command=None):
self.display = display
self.sleep_command = sleep_command
self.wake_command = wake_command
self.is_awake = True
def during_bootup(self, width, height, rotation):
self.display.rotation = rotation
return self.display
def deinit(self):
return
def sleep(self):
self.display.bus.send(self.sleep_command, b'')
def wake(self):
self.display.bus.send(self.wake_command, b'')

View file

@ -1,49 +0,0 @@
import busio
import adafruit_displayio_sh1106 # Display-specific library
import displayio
from . import DisplayBase
# Required to initialize this display
displayio.release_displays()
class SH1106(DisplayBase):
def __init__(
self,
spi=None,
sck=None,
mosi=None,
command=None,
chip_select=None,
reset=None,
baudrate=1000000,
):
self.command = command
self.chip_select = chip_select
self.reset = reset
self.baudrate = baudrate
# spi initialization
self.spi = spi
if self.spi is None:
self.spi = busio.SPI(sck, mosi)
def during_bootup(self, width, height, rotation):
self.display = adafruit_displayio_sh1106.SH1106(
displayio.FourWire(
self.spi,
command=self.command,
chip_select=self.chip_select,
reset=self.reset,
baudrate=self.baudrate,
),
width=width,
height=height,
rotation=rotation,
)
return self.display
def deinit(self):
self.spi.deinit()

View file

@ -1,31 +0,0 @@
import busio
import adafruit_displayio_ssd1306 # Display-specific library
import displayio
from . import DisplayBase
# Required to initialize this display
displayio.release_displays()
class SSD1306(DisplayBase):
def __init__(self, i2c=None, sda=None, scl=None, device_address=0x3C):
self.device_address = device_address
# i2c initialization
self.i2c = i2c
if self.i2c is None:
self.i2c = busio.I2C(scl, sda)
def during_bootup(self, width, height, rotation):
self.display = adafruit_displayio_ssd1306.SSD1306(
displayio.I2CDisplay(self.i2c, device_address=self.device_address),
width=width,
height=height,
rotation=rotation,
)
return self.display
def deinit(self):
self.i2c.deinit()

View file

@ -1,63 +0,0 @@
'''Adds international keys'''
from kmk.extensions import Extension
from kmk.keys import KeyboardKey, make_key
class International(Extension):
'''Adds international keys'''
def __init__(self):
# International
codes = (
(50, ('NONUS_HASH', 'NUHS')),
(100, ('NONUS_BSLASH', 'NUBS')),
(101, ('APP', 'APPLICATION', 'SEL', 'WINMENU')),
(135, ('INT1', 'RO')),
(136, ('INT2', 'KANA')),
(137, ('INT3', 'JYEN')),
(138, ('INT4', 'HENK')),
(139, ('INT5', 'MHEN')),
(140, ('INT6',)),
(141, ('INT7',)),
(142, ('INT8',)),
(143, ('INT9',)),
(144, ('LANG1', 'HAEN')),
(145, ('LANG2', 'HAEJ')),
(146, ('LANG3',)),
(147, ('LANG4',)),
(148, ('LANG5',)),
(149, ('LANG6',)),
(150, ('LANG7',)),
(151, ('LANG8',)),
(152, ('LANG9',)),
)
for code, names in codes:
make_key(names=names, constructor=KeyboardKey, code=code)
def on_runtime_enable(self, sandbox):
return
def on_runtime_disable(self, sandbox):
return
def during_bootup(self, sandbox):
return
def before_matrix_scan(self, sandbox):
return
def after_matrix_scan(self, sandbox):
return
def before_hid_send(self, sandbox):
return
def after_hid_send(self, sandbox):
return
def on_powersave_enable(self, sandbox):
return
def on_powersave_disable(self, sandbox):
return

View file

@ -1,34 +0,0 @@
# What's this?
# This is a keycode conversion script. With this, KMK will work as a JIS keyboard.
# Usage
# ```python
# import kmk.extensions.keymap_extras.keymap_jp
# ```
from kmk.keys import KC
KC.CIRC = KC.EQL # ^
KC.AT = KC.LBRC # @
KC.LBRC = KC.RBRC # [
KC.EISU = KC.CAPS # Eisū (英数)
KC.COLN = KC.QUOT # :
KC.LCBR = KC.LSFT(KC.RBRC) # {
KC.RBRC = KC.NUHS # ]
KC.BSLS = KC.INT1 # (backslash)
KC.PLUS = KC.LSFT(KC.SCLN)
KC.TILD = KC.LSFT(KC.EQL) # ~
KC.GRV = KC.LSFT(KC.AT) # `
KC.DQUO = KC.LSFT(KC.N2) # "
KC.AMPR = KC.LSFT(KC.N6) # &
KC.ASTR = KC.LSFT(KC.QUOT) # *
KC.QUOT = KC.LSFT(KC.N7) # '
KC.LPRN = KC.LSFT(KC.N8) # (
KC.RPRN = KC.LSFT(KC.N9) # )
KC.EQL = KC.LSFT(KC.MINS) # =
KC.PIPE = KC.LSFT(KC.INT3) # |
KC.RCBR = KC.LSFT(KC.NUHS) # }
KC.LABK = KC.LSFT(KC.COMM) # <
KC.RABK = KC.LSFT(KC.DOT) # >
KC.QUES = KC.LSFT(KC.SLSH) # ?
KC.UNDS = KC.LSFT(KC.INT1) # _

View file

@ -1,256 +0,0 @@
import pwmio
from math import e, exp, pi, sin
from kmk.extensions import Extension, InvalidExtensionEnvironment
from kmk.keys import Key, make_argumented_key, make_key
from kmk.utils import Debug, clamp
debug = Debug(__name__)
class LEDKey(Key):
def __init__(self, *leds, brightness=None, **kwargs):
super().__init__(**kwargs)
self.leds = leds
self.brightness = None
def led_set_key(brightness, *leds):
return LEDKey(*leds, brightness=brightness)
class AnimationModes:
OFF = 0
STATIC = 1
STATIC_STANDBY = 2
BREATHING = 3
USER = 4
class LED(Extension):
def __init__(
self,
led_pin,
brightness=50,
brightness_step=5,
brightness_limit=100,
breathe_center=1.5,
animation_mode=AnimationModes.STATIC,
animation_speed=1,
user_animation=None,
val=100,
):
try:
pins_iter = iter(led_pin)
except TypeError:
pins_iter = [led_pin]
try:
self._leds = [pwmio.PWMOut(pin) for pin in pins_iter]
except Exception as e:
if debug.enabled:
debug(e)
raise InvalidExtensionEnvironment(
'Unable to create pwmio.PWMOut() instance with provided led_pin'
)
self._brightness = brightness
self._pos = 0
self._effect_init = False
self._enabled = True
self.brightness_step = brightness_step
self.brightness_limit = brightness_limit
self.animation_mode = animation_mode
self.animation_speed = animation_speed
self.breathe_center = breathe_center
self.val = val
if user_animation is not None:
self.user_animation = user_animation
make_argumented_key(
names=('LED_TOG',),
constructor=LEDKey,
on_press=self._key_led_tog,
)
make_argumented_key(
names=('LED_INC',),
constructor=LEDKey,
on_press=self._key_led_inc,
)
make_argumented_key(
names=('LED_DEC',),
constructor=LEDKey,
on_press=self._key_led_dec,
)
make_argumented_key(
names=('LED_SET',),
constructor=led_set_key,
on_press=self._key_led_set,
)
make_key(names=('LED_ANI',), on_press=self._key_led_ani)
make_key(names=('LED_AND',), on_press=self._key_led_and)
make_key(
names=('LED_MODE_PLAIN', 'LED_M_P'), on_press=self._key_led_mode_static
)
make_key(
names=('LED_MODE_BREATHE', 'LED_M_B'), on_press=self._key_led_mode_breathe
)
def __repr__(self):
return f'LED({self._to_dict()})'
def _to_dict(self):
return {
'_brightness': self._brightness,
'_pos': self._pos,
'brightness_step': self.brightness_step,
'brightness_limit': self.brightness_limit,
'animation_mode': self.animation_mode,
'animation_speed': self.animation_speed,
'breathe_center': self.breathe_center,
'val': self.val,
}
def on_runtime_enable(self, sandbox):
return
def on_runtime_disable(self, sandbox):
return
def during_bootup(self, sandbox):
return
def before_matrix_scan(self, sandbox):
return
def after_matrix_scan(self, sandbox):
return
def before_hid_send(self, sandbox):
return
def after_hid_send(self, sandbox):
self.animate()
def on_powersave_enable(self, sandbox):
return
def on_powersave_disable(self, sandbox):
return
def _init_effect(self):
self._pos = 0
self._effect_init = False
return self
def set_brightness(self, percent, leds=None):
leds = leds or range(0, len(self._leds))
for i in leds:
self._leds[i].duty_cycle = int(percent / 100 * 65535)
def step_brightness(self, step, leds=None):
leds = leds or range(0, len(self._leds))
for i in leds:
brightness = int(self._leds[i].duty_cycle / 65535 * 100) + step
self.set_brightness(clamp(brightness), [i])
def increase_brightness(self, step=None, leds=None):
if step is None:
step = self.brightness_step
self.step_brightness(step, leds)
def decrease_brightness(self, step=None, leds=None):
if step is None:
step = self.brightness_step
self.step_brightness(-step, leds)
def off(self):
self.set_brightness(0)
def increase_ani(self):
'''
Increases animation speed by 1 amount stopping at 10
:param step:
'''
if (self.animation_speed + 1) >= 10:
self.animation_speed = 10
else:
self.val += 1
def decrease_ani(self):
'''
Decreases animation speed by 1 amount stopping at 0
:param step:
'''
if (self.val - 1) <= 0:
self.val = 0
else:
self.val -= 1
def effect_breathing(self):
# http://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/
# https://github.com/qmk/qmk_firmware/blob/9f1d781fcb7129a07e671a46461e501e3f1ae59d/quantum/rgblight.c#L806
sined = sin((self._pos / 255.0) * pi)
multip_1 = exp(sined) - self.breathe_center / e
multip_2 = self.brightness_limit / (e - 1 / e)
self._brightness = int(multip_1 * multip_2)
self._pos = (self._pos + self.animation_speed) % 256
self.set_brightness(self._brightness)
def effect_static(self):
self.set_brightness(self._brightness)
# Set animation mode to standby to prevent cycles from being wasted
self.animation_mode = AnimationModes.STATIC_STANDBY
def animate(self):
'''
Activates a "step" in the animation based on the active mode
:return: Returns the new state in animation
'''
if self._effect_init:
self._init_effect()
if self._enabled:
if self.animation_mode == AnimationModes.BREATHING:
return self.effect_breathing()
elif self.animation_mode == AnimationModes.STATIC:
return self.effect_static()
elif self.animation_mode == AnimationModes.STATIC_STANDBY:
pass
elif self.animation_mode == AnimationModes.USER:
return self.user_animation(self)
else:
self.off()
def _key_led_tog(self, *args, **kwargs):
if self.animation_mode == AnimationModes.STATIC_STANDBY:
self.animation_mode = AnimationModes.STATIC
if self._enabled:
self.off()
self._enabled = not self._enabled
def _key_led_inc(self, key, *args, **kwargs):
self.increase_brightness(leds=key.leds)
def _key_led_dec(self, key, *args, **kwargs):
self.decrease_brightness(leds=key.leds)
def _key_led_set(self, key, *args, **kwargs):
self.set_brightness(percent=key.brightness, leds=key.leds)
def _key_led_ani(self, *args, **kwargs):
self.increase_ani()
def _key_led_and(self, *args, **kwargs):
self.decrease_ani()
def _key_led_mode_static(self, *args, **kwargs):
self._effect_init = True
self.animation_mode = AnimationModes.STATIC
def _key_led_mode_breathe(self, *args, **kwargs):
self._effect_init = True
self.animation_mode = AnimationModes.BREATHING

View file

@ -1,69 +0,0 @@
import usb_hid
from micropython import const
from kmk.extensions import Extension
_NUMLOCK = const(0x01)
_CAPSLOCK = const(0x02)
_SCROLLLOCK = const(0x04)
_COMPOSE = const(0x08)
_KANA = const(0x10)
class LockStatus(Extension):
def __init__(self):
self.report = 0
self.hid = None
self._report_updated = False
def __repr__(self):
return f'LockStatus(report={self.report})'
def during_bootup(self, sandbox):
for device in usb_hid.devices:
if device.usage == usb_hid.Device.KEYBOARD.usage:
self.hid = device
if self.hid is None:
raise RuntimeError
def before_matrix_scan(self, sandbox):
return
def after_matrix_scan(self, sandbox):
return
def before_hid_send(self, sandbox):
return
def after_hid_send(self, sandbox):
report = self.hid.get_last_received_report()
if report is None:
self._report_updated = False
else:
self.report = report[0]
self._report_updated = True
def on_powersave_enable(self, sandbox):
return
def on_powersave_disable(self, sandbox):
return
@property
def report_updated(self):
return self._report_updated
def get_num_lock(self):
return bool(self.report & _NUMLOCK)
def get_caps_lock(self):
return bool(self.report & _CAPSLOCK)
def get_scroll_lock(self):
return bool(self.report & _SCROLLLOCK)
def get_compose(self):
return bool(self.report & _COMPOSE)
def get_kana(self):
return bool(self.report & _KANA)

View file

@ -1,61 +0,0 @@
from kmk.extensions import Extension
from kmk.keys import ConsumerKey, make_key
class MediaKeys(Extension):
def __init__(self):
# Consumer ("media") keys. Most known keys aren't supported here. A much
# longer list used to exist in this file, but the codes were almost certainly
# incorrect, conflicting with each other, or otherwise 'weird'. We'll add them
# back in piecemeal as needed. PRs welcome.
#
# A super useful reference for these is http://www.freebsddiary.org/APC/usb_hid_usages.php
# Note that currently we only have the PC codes. Recent MacOS versions seem to
# support PC media keys, so I don't know how much value we would get out of
# adding the old Apple-specific consumer codes, but again, PRs welcome if the
# lack of them impacts you.
codes = (
(0xE2, ('AUDIO_MUTE', 'MUTE')),
(0xE9, ('AUDIO_VOL_UP', 'VOLU')),
(0xEA, ('AUDIO_VOL_DOWN', 'VOLD')),
(0x6F, ('BRIGHTNESS_UP', 'BRIU')),
(0x70, ('BRIGHTNESS_DOWN', 'BRID')),
(0xB5, ('MEDIA_NEXT_TRACK', 'MNXT')),
(0xB6, ('MEDIA_PREV_TRACK', 'MPRV')),
(0xB7, ('MEDIA_STOP', 'MSTP')),
(0xCD, ('MEDIA_PLAY_PAUSE', 'MPLY')),
(0xB8, ('MEDIA_EJECT', 'EJCT')),
(0xB3, ('MEDIA_FAST_FORWARD', 'MFFD')),
(0xB4, ('MEDIA_REWIND', 'MRWD')),
)
for code, names in codes:
make_key(names=names, constructor=ConsumerKey, code=code)
def on_runtime_enable(self, sandbox):
return
def on_runtime_disable(self, sandbox):
return
def during_bootup(self, sandbox):
return
def before_matrix_scan(self, sandbox):
return
def after_matrix_scan(self, sandbox):
return
def before_hid_send(self, sandbox):
return
def after_hid_send(self, sandbox):
return
def on_powersave_enable(self, sandbox):
return
def on_powersave_disable(self, sandbox):
return

View file

@ -1,200 +0,0 @@
import neopixel
from storage import getmount
from kmk.extensions import Extension
from kmk.keys import make_key
from kmk.utils import Debug
debug = Debug(__name__)
class Color:
OFF = [0, 0, 0]
BLACK = OFF
WHITE = [249, 249, 249]
RED = [255, 0, 0]
AZURE = [153, 245, 255]
BLUE = [0, 0, 255]
CYAN = [0, 255, 255]
GREEN = [0, 255, 0]
YELLOW = [255, 247, 0]
MAGENTA = [255, 0, 255]
ORANGE = [255, 77, 0]
PURPLE = [255, 0, 242]
TEAL = [0, 128, 128]
PINK = [255, 0, 255]
class Rgb_matrix_data:
def __init__(self, keys=[], underglow=[]):
if len(keys) == 0:
if debug.enabled:
debug('No colors passed for your keys')
return
if len(underglow) == 0:
if debug.enabled:
debug('No colors passed for your underglow')
return
self.data = keys + underglow
@staticmethod
def generate_led_map(
number_of_keys, number_of_underglow, key_color, underglow_color
):
keys = [key_color] * number_of_keys
underglow = [underglow_color] * number_of_underglow
if debug.enabled:
debug('Rgb_matrix_data(keys=', keys, ', nunderglow=', underglow, ')')
class Rgb_matrix(Extension):
def __init__(
self,
rgb_order=(1, 0, 2), # GRB WS2812
disable_auto_write=False,
ledDisplay=[],
split=False,
rightSide=False,
):
name = str(getmount('/').label)
self.rgb_order = rgb_order
self.disable_auto_write = disable_auto_write
self.split = split
self.rightSide = rightSide
self.brightness_step = 0.1
self.brightness = 0
if name.endswith('L'):
self.rightSide = False
elif name.endswith('R'):
self.rightSide = True
if type(ledDisplay) is Rgb_matrix_data:
self.ledDisplay = ledDisplay.data
else:
self.ledDisplay = ledDisplay
make_key(names=('RGB_TOG',), on_press=self._rgb_tog)
make_key(names=('RGB_BRI',), on_press=self._rgb_bri)
make_key(names=('RGB_BRD',), on_press=self._rgb_brd)
def _rgb_tog(self, *args, **kwargs):
if self.enable:
self.off()
else:
self.on()
self.enable = not self.enable
def _rgb_bri(self, *args, **kwargs):
self.increase_brightness()
def _rgb_brd(self, *args, **kwargs):
self.decrease_brightness()
def on(self):
if self.neopixel:
self.setBasedOffDisplay()
self.neopixel.show()
def off(self):
if self.neopixel:
self.set_rgb_fill((0, 0, 0))
def set_rgb_fill(self, rgb):
if self.neopixel:
self.neopixel.fill(rgb)
if self.disable_auto_write:
self.neopixel.show()
def set_brightness(self, brightness=None):
if brightness is None:
brightness = self.brightness
if self.neopixel:
self.neopixel.brightness = brightness
if self.disable_auto_write:
self.neopixel.show()
def increase_brightness(self, step=None):
if step is None:
step = self.brightness_step
self.brightness = (
self.brightness + step if self.brightness + step <= 1.0 else 1.0
)
self.set_brightness(self.brightness)
def decrease_brightness(self, step=None):
if step is None:
step = self.brightness_step
self.brightness = (
self.brightness - step if self.brightness - step >= 0.0 else 0.0
)
self.set_brightness(self.brightness)
def setBasedOffDisplay(self):
if self.split:
for i, val in enumerate(self.ledDisplay):
if self.rightSide:
if self.keyPos[i] >= (self.num_pixels / 2):
self.neopixel[int(self.keyPos[i] - (self.num_pixels / 2))] = (
val[0],
val[1],
val[2],
)
else:
if self.keyPos[i] <= (self.num_pixels / 2):
self.neopixel[self.keyPos[i]] = (val[0], val[1], val[2])
else:
for i, val in enumerate(self.ledDisplay):
self.neopixel[self.keyPos[i]] = (val[0], val[1], val[2])
def on_runtime_enable(self, sandbox):
return
def on_runtime_disable(self, sandbox):
return
def during_bootup(self, board):
self.neopixel = neopixel.NeoPixel(
board.rgb_pixel_pin,
board.num_pixels,
brightness=board.brightness_limit,
pixel_order=self.rgb_order,
auto_write=not self.disable_auto_write,
)
self.num_pixels = board.num_pixels
self.keyPos = board.led_key_pos
self.brightness = board.brightness_limit
self.on()
return
def before_matrix_scan(self, sandbox):
return
def after_matrix_scan(self, sandbox):
return
def before_hid_send(self, sandbox):
return
def after_hid_send(self, sandbox):
return
def on_powersave_enable(self, sandbox):
if self.neopixel:
self.neopixel.brightness = (
self.neopixel.brightness / 2
if self.neopixel.brightness / 2 > 0
else 0.1
)
if self.disable_auto_write:
self.neopixel.show()
def on_powersave_disable(self, sandbox):
if self.neopixel:
self.neopixel.brightness = self.brightness
if self.disable_auto_write:
self.neopixel.show()

View file

@ -1,551 +0,0 @@
from adafruit_pixelbuf import PixelBuf
from math import e, exp, pi, sin
from kmk.extensions import Extension
from kmk.keys import make_key
from kmk.scheduler import create_task
from kmk.utils import Debug, clamp
debug = Debug(__name__)
rgb_config = {}
def hsv_to_rgb(hue, sat, val):
'''
Converts HSV values, and returns a tuple of RGB values
:param hue:
:param sat:
:param val:
:return: (r, g, b)
'''
if sat == 0:
return (val, val, val)
hue = 6 * (hue & 0xFF)
frac = hue & 0xFF
sxt = hue >> 8
base = (0xFF - sat) * val
color = (val * sat * frac) >> 8
val <<= 8
if sxt == 0:
r = val
g = base + color
b = base
elif sxt == 1:
r = val - color
g = val
b = base
elif sxt == 2:
r = base
g = val
b = base + color
elif sxt == 3:
r = base
g = val - color
b = val
elif sxt == 4:
r = base + color
g = base
b = val
elif sxt == 5:
r = val
g = base
b = val - color
return (r >> 8), (g >> 8), (b >> 8)
def hsv_to_rgbw(hue, sat, val):
'''
Converts HSV values, and returns a tuple of RGBW values
:param hue:
:param sat:
:param val:
:return: (r, g, b, w)
'''
rgb = hsv_to_rgb(hue, sat, val)
return rgb[0], rgb[1], rgb[2], min(rgb)
class AnimationModes:
OFF = 0
STATIC = 1
STATIC_STANDBY = 2
BREATHING = 3
RAINBOW = 4
BREATHING_RAINBOW = 5
KNIGHT = 6
SWIRL = 7
USER = 8
class RGB(Extension):
pos = 0
def __init__(
self,
pixel_pin,
num_pixels=0,
rgb_order=(1, 0, 2), # GRB WS2812
val_limit=255,
hue_default=0,
sat_default=255,
val_default=255,
hue_step=4,
sat_step=13,
val_step=13,
animation_speed=1,
breathe_center=1, # 1.0-2.7
knight_effect_length=3,
animation_mode=AnimationModes.STATIC,
effect_init=False,
reverse_animation=False,
user_animation=None,
pixels=None,
refresh_rate=60,
):
self.pixel_pin = pixel_pin
self.num_pixels = num_pixels
self.rgb_order = rgb_order
self.hue_step = hue_step
self.sat_step = sat_step
self.val_step = val_step
self.hue = hue_default
self.hue_default = hue_default
self.sat = sat_default
self.sat_default = sat_default
self.val = val_default
self.val_default = val_default
self.breathe_center = breathe_center
self.knight_effect_length = knight_effect_length
self.val_limit = val_limit
self.animation_mode = animation_mode
self.animation_speed = animation_speed
self.effect_init = effect_init
self.reverse_animation = reverse_animation
self.user_animation = user_animation
self.pixels = pixels
self.refresh_rate = refresh_rate
self.rgbw = bool(len(rgb_order) == 4)
self._substep = 0
make_key(names=('RGB_TOG',), on_press=self._rgb_tog)
make_key(names=('RGB_HUI',), on_press=self._rgb_hui)
make_key(names=('RGB_HUD',), on_press=self._rgb_hud)
make_key(names=('RGB_SAI',), on_press=self._rgb_sai)
make_key(names=('RGB_SAD',), on_press=self._rgb_sad)
make_key(names=('RGB_VAI',), on_press=self._rgb_vai)
make_key(names=('RGB_VAD',), on_press=self._rgb_vad)
make_key(names=('RGB_ANI',), on_press=self._rgb_ani)
make_key(names=('RGB_AND',), on_press=self._rgb_and)
make_key(names=('RGB_MODE_PLAIN', 'RGB_M_P'), on_press=self._rgb_mode_static)
make_key(names=('RGB_MODE_BREATHE', 'RGB_M_B'), on_press=self._rgb_mode_breathe)
make_key(names=('RGB_MODE_RAINBOW', 'RGB_M_R'), on_press=self._rgb_mode_rainbow)
make_key(
names=('RGB_MODE_BREATHE_RAINBOW', 'RGB_M_BR'),
on_press=self._rgb_mode_breathe_rainbow,
)
make_key(names=('RGB_MODE_SWIRL', 'RGB_M_S'), on_press=self._rgb_mode_swirl)
make_key(names=('RGB_MODE_KNIGHT', 'RGB_M_K'), on_press=self._rgb_mode_knight)
make_key(names=('RGB_RESET', 'RGB_RST'), on_press=self._rgb_reset)
def on_runtime_enable(self, sandbox):
return
def on_runtime_disable(self, sandbox):
return
def during_bootup(self, sandbox):
if self.pixels is None:
import neopixel
self.pixels = neopixel.NeoPixel(
self.pixel_pin,
self.num_pixels,
pixel_order=self.rgb_order,
)
# PixelBuffer are already iterable, can't do the usual `try: iter(...)`
if issubclass(self.pixels.__class__, PixelBuf):
self.pixels = (self.pixels,)
# Turn off auto_write on the backend. We handle the propagation of auto_write
# behaviour.
for pixel in self.pixels:
pixel.auto_write = False
if self.num_pixels == 0:
for pixels in self.pixels:
self.num_pixels += len(pixels)
if debug.enabled:
for n, pixels in enumerate(self.pixels):
debug(f'pixels[{n}] = {pixels.__class__}[{len(pixels)}]')
self._task = create_task(self.animate, period_ms=(1000 // self.refresh_rate))
def before_matrix_scan(self, sandbox):
return
def after_matrix_scan(self, sandbox):
return
def before_hid_send(self, sandbox):
return
def after_hid_send(self, sandbox):
pass
def on_powersave_enable(self, sandbox):
return
def on_powersave_disable(self, sandbox):
self._do_update()
def deinit(self, sandbox):
for pixel in self.pixels:
pixel.deinit()
def set_hsv(self, hue, sat, val, index):
'''
Takes HSV values and displays it on a single LED/Neopixel
:param hue:
:param sat:
:param val:
:param index: Index of LED/Pixel
'''
val = clamp(val, 0, self.val_limit)
if self.rgbw:
self.set_rgb(hsv_to_rgbw(hue, sat, val), index)
else:
self.set_rgb(hsv_to_rgb(hue, sat, val), index)
def set_hsv_fill(self, hue, sat, val):
'''
Takes HSV values and displays it on all LEDs/Neopixels
:param hue:
:param sat:
:param val:
'''
val = clamp(val, 0, self.val_limit)
if self.rgbw:
self.set_rgb_fill(hsv_to_rgbw(hue, sat, val))
else:
self.set_rgb_fill(hsv_to_rgb(hue, sat, val))
def set_rgb(self, rgb, index):
'''
Takes an RGB or RGBW and displays it on a single LED/Neopixel
:param rgb: RGB or RGBW
:param index: Index of LED/Pixel
'''
if 0 <= index <= self.num_pixels - 1:
for pixels in self.pixels:
if index <= (len(pixels) - 1):
pixels[index] = rgb
break
index -= len(pixels)
def set_rgb_fill(self, rgb):
'''
Takes an RGB or RGBW and displays it on all LEDs/Neopixels
:param rgb: RGB or RGBW
'''
for pixels in self.pixels:
pixels.fill(rgb)
def increase_hue(self, step=None):
'''
Increases hue by step amount rolling at 256 and returning to 0
:param step:
'''
if step is None:
step = self.hue_step
self.hue = (self.hue + step) % 256
if self._check_update():
self._do_update()
def decrease_hue(self, step=None):
'''
Decreases hue by step amount rolling at 0 and returning to 256
:param step:
'''
if step is None:
step = self.hue_step
if (self.hue - step) <= 0:
self.hue = (self.hue + 256 - step) % 256
else:
self.hue = (self.hue - step) % 256
if self._check_update():
self._do_update()
def increase_sat(self, step=None):
'''
Increases saturation by step amount stopping at 255
:param step:
'''
if step is None:
step = self.sat_step
self.sat = clamp(self.sat + step, 0, 255)
if self._check_update():
self._do_update()
def decrease_sat(self, step=None):
'''
Decreases saturation by step amount stopping at 0
:param step:
'''
if step is None:
step = self.sat_step
self.sat = clamp(self.sat - step, 0, 255)
if self._check_update():
self._do_update()
def increase_val(self, step=None):
'''
Increases value by step amount stopping at 100
:param step:
'''
if step is None:
step = self.val_step
self.val = clamp(self.val + step, 0, 255)
if self._check_update():
self._do_update()
def decrease_val(self, step=None):
'''
Decreases value by step amount stopping at 0
:param step:
'''
if step is None:
step = self.val_step
self.val = clamp(self.val - step, 0, 255)
if self._check_update():
self._do_update()
def increase_ani(self):
'''
Increases animation speed by 1 amount stopping at 10
:param step:
'''
self.animation_speed = clamp(self.animation_speed + 1, 0, 10)
if self._check_update():
self._do_update()
def decrease_ani(self):
'''
Decreases animation speed by 1 amount stopping at 0
:param step:
'''
self.animation_speed = clamp(self.animation_speed - 1, 0, 10)
if self._check_update():
self._do_update()
def off(self):
'''
Turns off all LEDs/Neopixels without changing stored values
'''
self.set_hsv_fill(0, 0, 0)
self.show()
def show(self):
'''
Turns on all LEDs/Neopixels without changing stored values
'''
for pixels in self.pixels:
pixels.show()
def animate(self):
'''
Activates a "step" in the animation based on the active mode
:return: Returns the new state in animation
'''
if self.effect_init:
self._init_effect()
if self.animation_mode is AnimationModes.STATIC_STANDBY:
return
if not self.enable:
return
self._animation_step()
if self.animation_mode == AnimationModes.STATIC_STANDBY:
return
elif self.animation_mode == AnimationModes.BREATHING:
self.effect_breathing()
elif self.animation_mode == AnimationModes.BREATHING_RAINBOW:
self.effect_breathing_rainbow()
elif self.animation_mode == AnimationModes.KNIGHT:
self.effect_knight()
elif self.animation_mode == AnimationModes.RAINBOW:
self.effect_rainbow()
elif self.animation_mode == AnimationModes.STATIC:
self.effect_static()
elif self.animation_mode == AnimationModes.SWIRL:
self.effect_swirl()
elif self.animation_mode == AnimationModes.USER:
self.user_animation(self)
else:
self.off()
self.show()
def _animation_step(self):
self._substep += self.animation_speed / 4
self._step = int(self._substep)
self._substep -= self._step
def _init_effect(self):
self.pos = 0
self.reverse_animation = False
self.effect_init = False
def _check_update(self):
return bool(self.animation_mode == AnimationModes.STATIC_STANDBY)
def _do_update(self):
if self.animation_mode == AnimationModes.STATIC_STANDBY:
self.animation_mode = AnimationModes.STATIC
def effect_static(self):
self.set_hsv_fill(self.hue, self.sat, self.val)
self.animation_mode = AnimationModes.STATIC_STANDBY
def effect_breathing(self):
# http://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/
# https://github.com/qmk/qmk_firmware/blob/9f1d781fcb7129a07e671a46461e501e3f1ae59d/quantum/rgblight.c#L806
sined = sin((self.pos / 255.0) * pi)
multip_1 = exp(sined) - self.breathe_center / e
multip_2 = clamp(self.val, 0, self.val_limit) / (e - 1 / e)
val = int(multip_1 * multip_2)
self.pos = (self.pos + self._step) % 256
self.set_hsv_fill(self.hue, self.sat, val)
def effect_breathing_rainbow(self):
self.increase_hue(self._step)
self.effect_breathing()
def effect_rainbow(self):
self.increase_hue(self._step)
self.set_hsv_fill(self.hue, self.sat, self.val)
def effect_swirl(self):
self.increase_hue(self._step)
for i in range(0, self.num_pixels):
self.set_hsv(
(self.hue - (i * self.num_pixels)) % 256, self.sat, self.val, i
)
def effect_knight(self):
# Determine which LEDs should be lit up
self.off() # Fill all off
pos = int(self.pos)
# Set all pixels on in range of animation length offset by position
for i in range(pos, (pos + self.knight_effect_length)):
self.set_hsv(self.hue, self.sat, self.val, i)
# Reverse animation when a boundary is hit
if pos >= self.num_pixels:
self.reverse_animation = True
elif 1 - pos > self.knight_effect_length:
self.reverse_animation = False
if self.reverse_animation:
self.pos -= self._step / 2
else:
self.pos += self._step / 2
def _rgb_tog(self, *args, **kwargs):
if self.animation_mode == AnimationModes.STATIC:
self.animation_mode = AnimationModes.STATIC_STANDBY
self._do_update()
if self.animation_mode == AnimationModes.STATIC_STANDBY:
self.animation_mode = AnimationModes.STATIC
self._do_update()
if self.enable:
self.off()
self.enable = not self.enable
def _rgb_hui(self, *args, **kwargs):
self.increase_hue()
def _rgb_hud(self, *args, **kwargs):
self.decrease_hue()
def _rgb_sai(self, *args, **kwargs):
self.increase_sat()
def _rgb_sad(self, *args, **kwargs):
self.decrease_sat()
def _rgb_vai(self, *args, **kwargs):
self.increase_val()
def _rgb_vad(self, *args, **kwargs):
self.decrease_val()
def _rgb_ani(self, *args, **kwargs):
self.increase_ani()
def _rgb_and(self, *args, **kwargs):
self.decrease_ani()
def _rgb_mode_static(self, *args, **kwargs):
self.effect_init = True
self.animation_mode = AnimationModes.STATIC
def _rgb_mode_breathe(self, *args, **kwargs):
self.effect_init = True
self.animation_mode = AnimationModes.BREATHING
def _rgb_mode_breathe_rainbow(self, *args, **kwargs):
self.effect_init = True
self.animation_mode = AnimationModes.BREATHING_RAINBOW
def _rgb_mode_rainbow(self, *args, **kwargs):
self.effect_init = True
self.animation_mode = AnimationModes.RAINBOW
def _rgb_mode_swirl(self, *args, **kwargs):
self.effect_init = True
self.animation_mode = AnimationModes.SWIRL
def _rgb_mode_knight(self, *args, **kwargs):
self.effect_init = True
self.animation_mode = AnimationModes.KNIGHT
def _rgb_reset(self, *args, **kwargs):
self.hue = self.hue_default
self.sat = self.sat_default
self.val = self.val_default
if self.animation_mode == AnimationModes.STATIC_STANDBY:
self.animation_mode = AnimationModes.STATIC
self._do_update()

View file

@ -1,149 +0,0 @@
# Use this extension for showing layer status with three leds
import pwmio
import time
from kmk.extensions import Extension, InvalidExtensionEnvironment
from kmk.keys import make_key
from kmk.utils import Debug
debug = Debug(__name__)
class statusLED(Extension):
def __init__(
self,
led_pins,
brightness=30,
brightness_step=5,
brightness_limit=100,
):
self._leds = []
for led in led_pins:
try:
self._leds.append(pwmio.PWMOut(led))
except Exception as e:
if debug.enabled:
debug(e)
raise InvalidExtensionEnvironment(
'Unable to create pulseio.PWMOut() instance with provided led_pin'
)
self._led_count = len(self._leds)
self.brightness = brightness
self._layer_last = -1
self.brightness_step = brightness_step
self.brightness_limit = brightness_limit
make_key(names=('SLED_INC',), on_press=self._key_led_inc)
make_key(names=('SLED_DEC',), on_press=self._key_led_dec)
def _layer_indicator(self, layer_active, *args, **kwargs):
'''
Indicates layer with leds
For the time being just a simple consecutive single led
indicator. And when there are more layers than leds it
wraps around to the first led again.
(Also works for a single led, which just lights when any
layer is active)
'''
if self._layer_last != layer_active:
led_last = 0 if self._layer_last == 0 else 1 + (self._layer_last - 1) % 3
if layer_active > 0:
led_active = 0 if layer_active == 0 else 1 + (layer_active - 1) % 3
self.set_brightness(self.brightness, led_active)
self.set_brightness(0, led_last)
else:
self.set_brightness(0, led_last)
self._layer_last = layer_active
def __repr__(self):
return f'SLED({self._to_dict()})'
def _to_dict(self):
return {
'brightness': self.brightness,
'brightness_step': self.brightness_step,
'brightness_limit': self.brightness_limit,
}
def on_runtime_enable(self, sandbox):
return
def on_runtime_disable(self, sandbox):
return
def during_bootup(self, sandbox):
'''Light up every single led once for 200 ms'''
for i in range(self._led_count + 2):
if i < self._led_count:
self._leds[i].duty_cycle = int(self.brightness / 100 * 65535)
i_off = i - 2
if i_off >= 0 and i_off < self._led_count:
self._leds[i_off].duty_cycle = int(0)
time.sleep(0.1)
for led in self._leds:
led.duty_cycle = int(0)
return
def before_matrix_scan(self, sandbox):
return
def after_matrix_scan(self, sandbox):
self._layer_indicator(sandbox.active_layers[0])
return
def before_hid_send(self, sandbox):
return
def after_hid_send(self, sandbox):
return
def on_powersave_enable(self, sandbox):
self.set_brightness(0)
return
def on_powersave_disable(self, sandbox):
self.set_brightness(self.brightness)
self._leds[2].duty_cycle = int(50 / 100 * 65535)
time.sleep(0.2)
self._leds[2].duty_cycle = int(0)
return
def set_brightness(self, percent, layer_id=-1):
if layer_id < 0:
for led in self._leds:
led.duty_cycle = int(percent / 100 * 65535)
else:
self._leds[layer_id - 1].duty_cycle = int(percent / 100 * 65535)
def increase_brightness(self, step=None):
if not step:
self.brightness += self.brightness_step
else:
self.brightness += step
if self.brightness > 100:
self.brightness = 100
self.set_brightness(self.brightness, self._layer_last)
def decrease_brightness(self, step=None):
if not step:
self.brightness -= self.brightness_step
else:
self.brightness -= step
if self.brightness < 0:
self.brightness = 0
self.set_brightness(self.brightness, self._layer_last)
def _key_led_inc(self, *args, **kwargs):
self.increase_brightness()
def _key_led_dec(self, *args, **kwargs):
self.decrease_brightness()

View file

@ -1,45 +0,0 @@
from kmk.extensions import Extension
from kmk.keys import KC
class StringyKeymaps(Extension):
#####
# User-configurable
debug_enabled = False
def on_runtime_enable(self, keyboard):
return
def on_runtime_disable(self, keyboard):
return
def during_bootup(self, keyboard):
for _, layer in enumerate(keyboard.keymap):
for key_idx, key in enumerate(layer):
if isinstance(key, str):
replacement = KC.get(key)
if replacement is None:
replacement = KC.NO
if self.debug_enabled:
print(f"Failed replacing '{key}'. Using KC.NO")
elif self.debug_enabled:
print(f"Replacing '{key}' with {replacement}")
layer[key_idx] = replacement
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

View file

@ -1,121 +0,0 @@
from time import sleep
def passthrough(key, keyboard, *args, **kwargs):
return keyboard
def reset(*args, **kwargs):
import microcontroller
microcontroller.reset()
def reload(*args, **kwargs):
import supervisor
supervisor.reload()
def bootloader(*args, **kwargs):
import microcontroller
microcontroller.on_next_reset(microcontroller.RunMode.BOOTLOADER)
microcontroller.reset()
def gesc_pressed(key, keyboard, KC, *args, **kwargs):
GESC_TRIGGERS = {KC.LSHIFT, KC.RSHIFT, KC.LGUI, KC.RGUI}
if GESC_TRIGGERS.intersection(keyboard.keys_pressed):
# First, release GUI if already pressed
keyboard._send_hid()
# if Shift is held, KC_GRAVE will become KC_TILDE on OS level
keyboard.keys_pressed.add(KC.GRAVE)
keyboard.hid_pending = True
return keyboard
# else return KC_ESC
keyboard.keys_pressed.add(KC.ESCAPE)
keyboard.hid_pending = True
return keyboard
def gesc_released(key, keyboard, KC, *args, **kwargs):
keyboard.keys_pressed.discard(KC.ESCAPE)
keyboard.keys_pressed.discard(KC.GRAVE)
keyboard.hid_pending = True
return keyboard
def bkdl_pressed(key, keyboard, KC, *args, **kwargs):
BKDL_TRIGGERS = {KC.LGUI, KC.RGUI}
if BKDL_TRIGGERS.intersection(keyboard.keys_pressed):
keyboard._send_hid()
keyboard.keys_pressed.add(KC.DEL)
keyboard.hid_pending = True
return keyboard
# else return KC_ESC
keyboard.keys_pressed.add(KC.BKSP)
keyboard.hid_pending = True
return keyboard
def bkdl_released(key, keyboard, KC, *args, **kwargs):
keyboard.keys_pressed.discard(KC.BKSP)
keyboard.keys_pressed.discard(KC.DEL)
keyboard.hid_pending = True
return keyboard
def sleep_pressed(key, keyboard, KC, *args, **kwargs):
sleep(key.meta.ms / 1000)
return keyboard
def uc_mode_pressed(key, keyboard, *args, **kwargs):
keyboard.unicode_mode = key.meta.mode
return keyboard
def hid_switch(key, keyboard, *args, **kwargs):
keyboard.hid_type, keyboard.secondary_hid_type = (
keyboard.secondary_hid_type,
keyboard.hid_type,
)
keyboard._init_hid()
return keyboard
def ble_refresh(key, keyboard, *args, **kwargs):
from kmk.hid import HIDModes
if keyboard.hid_type != HIDModes.BLE:
return keyboard
keyboard._hid_helper.stop_advertising()
keyboard._hid_helper.start_advertising()
return keyboard
def ble_disconnect(key, keyboard, *args, **kwargs):
from kmk.hid import HIDModes
if keyboard.hid_type != HIDModes.BLE:
return keyboard
keyboard._hid_helper.clear_bonds()
return keyboard
def any_pressed(key, keyboard, *args, **kwargs):
from random import randint
key.code = randint(4, 56)
keyboard.keys_pressed.add(key)
keyboard.hid_pending = True

View file

@ -1,311 +0,0 @@
import supervisor
import usb_hid
from micropython import const
from struct import pack, pack_into
from kmk.keys import Axis, ConsumerKey, KeyboardKey, ModifierKey, MouseKey
from kmk.scheduler import cancel_task, create_task
from kmk.utils import Debug, clamp
try:
from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.standard.hid import HIDService
from storage import getmount
_BLE_APPEARANCE_HID_KEYBOARD = const(961)
except ImportError:
# BLE not supported on this platform
pass
debug = Debug(__name__)
class HIDModes:
NOOP = 0 # currently unused; for testing?
USB = 1
BLE = 2
_USAGE_PAGE_CONSUMER = const(0x0C)
_USAGE_PAGE_KEYBOARD = const(0x01)
_USAGE_PAGE_MOUSE = const(0x01)
_USAGE_PAGE_SYSCONTROL = const(0x01)
_USAGE_CONSUMER = const(0x01)
_USAGE_KEYBOARD = const(0x06)
_USAGE_MOUSE = const(0x02)
_USAGE_SYSCONTROL = const(0x80)
_REPORT_SIZE_CONSUMER = const(2)
_REPORT_SIZE_KEYBOARD = const(8)
_REPORT_SIZE_KEYBOARD_NKRO = const(16)
_REPORT_SIZE_MOUSE = const(4)
_REPORT_SIZE_MOUSE_HSCROLL = const(5)
_REPORT_SIZE_SYSCONTROL = const(8)
def find_device(devices, usage_page, usage):
for device in devices:
if (
device.usage_page == usage_page
and device.usage == usage
and hasattr(device, 'send_report')
):
return device
class Report:
def __init__(self, size):
self.buffer = bytearray(size)
self.pending = False
def clear(self):
for k, v in enumerate(self.buffer):
if v:
self.buffer[k] = 0x00
self.pending = True
def get_action_map(self):
return {}
class KeyboardReport(Report):
def __init__(self, size=_REPORT_SIZE_KEYBOARD):
self.buffer = bytearray(size)
self.prev_buffer = bytearray(size)
@property
def pending(self):
return self.buffer != self.prev_buffer
@pending.setter
def pending(self, v):
if v is False:
self.prev_buffer[:] = self.buffer[:]
def clear(self):
for idx in range(len(self.buffer)):
self.buffer[idx] = 0x00
def add_key(self, key):
# Find the first empty slot in the key report, and fill it; drop key if
# report is full.
idx = self.buffer.find(b'\x00', 2)
if 0 < idx < _REPORT_SIZE_KEYBOARD:
self.buffer[idx] = key.code
def remove_key(self, key):
idx = self.buffer.find(pack('B', key.code), 2)
if 0 < idx:
self.buffer[idx] = 0x00
def add_modifier(self, modifier):
self.buffer[0] |= modifier.code
def remove_modifier(self, modifier):
self.buffer[0] &= ~modifier.code
def get_action_map(self):
return {KeyboardKey: self.add_key, ModifierKey: self.add_modifier}
class NKROKeyboardReport(KeyboardReport):
def __init__(self):
super().__init__(_REPORT_SIZE_KEYBOARD_NKRO)
def add_key(self, key):
self.buffer[(key.code >> 3) + 1] |= 1 << (key.code & 0x07)
def remove_key(self, key):
self.buffer[(key.code >> 3) + 1] &= ~(1 << (key.code & 0x07))
class ConsumerControlReport(Report):
def __init__(self):
super().__init__(_REPORT_SIZE_CONSUMER)
def add_cc(self, cc):
pack_into('<H', self.buffer, 0, cc.code)
self.pending = True
def remove_cc(self):
if self.buffer != b'\x00\x00':
self.buffer = b'\x00\x00'
self.pending = True
def get_action_map(self):
return {ConsumerKey: self.add_cc}
class PointingDeviceReport(Report):
def __init__(self, size=_REPORT_SIZE_MOUSE):
super().__init__(size)
def add_button(self, key):
self.buffer[0] |= key.code
self.pending = True
def remove_button(self, key):
self.buffer[0] &= ~key.code
self.pending = True
def move_axis(self, axis):
delta = clamp(axis.delta, -127, 127)
axis.delta -= delta
try:
self.buffer[axis.code + 1] = 0xFF & delta
self.pending = True
except IndexError:
if debug.enabled:
debug(axis, ' not supported')
def get_action_map(self):
return {Axis: self.move_axis, MouseKey: self.add_button}
class HSPointingDeviceReport(PointingDeviceReport):
def __init__(self):
super().__init__(_REPORT_SIZE_MOUSE_HSCROLL)
class AbstractHID:
def __init__(self):
self.report_map = {}
self.device_map = {}
self._setup_task = create_task(self.setup, period_ms=100)
def __repr__(self):
return self.__class__.__name__
def create_report(self, keys):
for report in self.device_map.keys():
report.clear()
for key in keys:
if action := self.report_map.get(type(key)):
action(key)
def send(self):
for report in self.device_map.keys():
if report.pending:
self.device_map[report].send_report(report.buffer)
report.pending = False
def setup(self):
if not self.connected:
return
try:
self.setup_keyboard_hid()
self.setup_consumer_control()
self.setup_mouse_hid()
cancel_task(self._setup_task)
self._setup_task = None
if debug.enabled:
self.show_debug()
except OSError as e:
if debug.enabled:
debug(type(e), ':', e)
def setup_keyboard_hid(self):
if device := find_device(self.devices, _USAGE_PAGE_KEYBOARD, _USAGE_KEYBOARD):
# bodgy NKRO autodetect
try:
report = KeyboardReport()
device.send_report(report.buffer)
except ValueError:
report = NKROKeyboardReport()
self.report_map.update(report.get_action_map())
self.device_map[report] = device
def setup_consumer_control(self):
if device := find_device(self.devices, _USAGE_PAGE_CONSUMER, _USAGE_CONSUMER):
report = ConsumerControlReport()
self.report_map.update(report.get_action_map())
self.device_map[report] = device
def setup_mouse_hid(self):
if device := find_device(self.devices, _USAGE_PAGE_MOUSE, _USAGE_MOUSE):
# bodgy pointing device panning autodetect
try:
report = PointingDeviceReport()
device.send_report(report.buffer)
except ValueError:
report = HSPointingDeviceReport()
self.report_map.update(report.get_action_map())
self.device_map[report] = device
def show_debug(self):
for report in self.device_map.keys():
debug('use ', report.__class__.__name__)
class USBHID(AbstractHID):
@property
def connected(self):
return supervisor.runtime.usb_connected
@property
def devices(self):
return usb_hid.devices
class BLEHID(AbstractHID):
def __init__(self, ble_name=None):
super().__init__()
self.ble = BLERadio()
self.ble.name = ble_name if ble_name else getmount('/').label
self.ble_connected = False
self.hid = HIDService()
self.hid.protocol_mode = 0 # Boot protocol
create_task(self.ble_monitor, period_ms=1000)
@property
def connected(self):
return self.ble.connected
@property
def devices(self):
return self.hid.devices
def ble_monitor(self):
if self.ble_connected != self.connected:
self.ble_connected = self.connected
if self._connected:
if debug.enabled:
debug('BLE connected')
else:
# Security-wise this is not right. While you're away someone turns
# on your keyboard and they can pair with it nice and clean and then
# listen to keystrokes.
# On the other hand we don't have LESC so it's like shouting your
# keystrokes in the air
self.start_advertising()
if debug.enabled:
debug('BLE disconnected')
def clear_bonds(self):
import _bleio
_bleio.adapter.erase_bonding()
def start_advertising(self):
if not self.ble.advertising:
advertisement = ProvideServicesAdvertisement(self.hid)
advertisement.appearance = _BLE_APPEARANCE_HID_KEYBOARD
self.ble.start_advertising(advertisement)
def stop_advertising(self):
self.ble.stop_advertising()

View file

@ -1,50 +0,0 @@
import usb_hid
# fmt:off
report_descriptor = bytes(
(
0x05, 0x01, # Usage Page (Generic Desktop Ctrls),
0x09, 0x06, # Usage (Keyboard),
0xA1, 0x01, # Collection (Application),
0x85, 0x01, # Report ID (1)
# modifiers
0x05, 0x07, # Usage Page (Key Codes),
0x19, 0xE0, # Usage Minimum (224),
0x29, 0xE7, # Usage Maximum (231),
0x15, 0x00, # Logical Minimum (0),
0x25, 0x01, # Logical Maximum (1),
0x75, 0x01, # Report Size (1),
0x95, 0x08, # Report Count (8),
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
# LEDs
0x05, 0x08, # Usage Page (LEDs),
0x19, 0x01, # Usage Minimum (1),
0x29, 0x05, # Usage Maximum (5),
0x95, 0x05, # Report Count (5),
0x75, 0x01, # Report Size (1),
0x91, 0x02, # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non- olatile)
0x95, 0x01, # Report Count (1),
0x75, 0x03, # Report Size (3),
0x91, 0x01, # Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,N n-volatile)
# keys
0x05, 0x07, # Usage Page (Kbrd/Keypad),
0x19, 0x00, # Usage Minimum (0),
0x29, 0x77, # Usage Maximum (119),
0x15, 0x00, # Logical Minimum (0),
0x25, 0x01, # Logical Maximum(1),
0x95, 0x78, # Report Count (120),
0x75, 0x01, # Report Size (1),
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, # End Collection
)
)
# fmt:on
NKRO_KEYBOARD = usb_hid.Device(
report_descriptor=report_descriptor,
usage_page=0x01,
usage=0x06,
report_ids=(0x01,),
in_report_lengths=(16,),
out_report_lengths=(1,),
)

View file

@ -1,48 +0,0 @@
import usb_hid
# fmt:off
report_descriptor = bytes(
(
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x02, # Usage (Mouse)
0xA1, 0x01, # Collection (Application)
0x09, 0x01, # Usage (Pointer)
0xA1, 0x00, # Collection (Physical)
0x85, 0x02, # 10, 11 Report ID (2)
0x05, 0x09, # Usage Page (Button)
0x19, 0x01, # Usage Minimum (0x01)
0x29, 0x05, # Usage Maximum (0x05)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x01, # Logical Maximum (1)
0x95, 0x05, # Report Count (5)
0x75, 0x01, # Report Size (1)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, # Report Count (1)
0x75, 0x03, # Report Size (3)
0x81, 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x30, # Usage (X)
0x09, 0x31, # Usage (Y)
0x09, 0x38, # Usage (Wheel)
0x05, 0x0C, # Usage Page (Consumer Devices)
0x0A, 0x38, 0x02, # Usage (AC Pan)
0x15, 0x81, # Logical Minimum (-127)
0x25, 0x7F, # Logical Maximum (127)
0x95, 0x04, # Report Count (4)
0x75, 0x08, # Report Size (8)
0x81, 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0xC0, # End Collection
0xC0, # End Collection
)
)
# fmt:on
POINTER = usb_hid.Device(
report_descriptor=report_descriptor,
usage_page=0x01,
usage=0x02,
report_ids=(0x02,),
in_report_lengths=(5,),
out_report_lengths=(0,),
)

View file

@ -1,575 +0,0 @@
try:
from typing import Callable, Optional, Tuple
except ImportError:
pass
import kmk.handlers.stock as handlers
from kmk.utils import Debug
# Type aliases / forward declaration; can't use the proper types because of circular imports.
Keyboard = object
Key = object
ALL_ALPHAS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ALL_NUMBERS = '1234567890'
# since KC.1 isn't valid Python, alias to KC.N1
ALL_NUMBER_ALIASES = tuple(f'N{x}' for x in ALL_NUMBERS)
debug = Debug(__name__)
class Axis:
def __init__(self, code: int) -> None:
self.code = code
self.delta = 0
def __repr__(self) -> str:
return f'Axis(code={self.code}, delta={self.delta})'
def move(self, keyboard: Keyboard, delta: int):
self.delta += delta
if self.delta:
keyboard.keys_pressed.add(self)
keyboard.hid_pending = True
else:
keyboard.keys_pressed.discard(self)
class AX:
P = Axis(3)
W = Axis(2)
X = Axis(0)
Y = Axis(1)
def maybe_make_key(
names: Tuple[str, ...],
*args,
**kwargs,
) -> Callable[[str], Key]:
def closure(candidate):
if candidate in names:
return make_key(names=names, *args, **kwargs)
return closure
def maybe_make_argumented_key(
names: Tuple[str, ...],
constructor: [Key, Callable[[...], Key]],
**kwargs,
) -> Callable[[str], Key]:
def closure(candidate):
if candidate in names:
return make_argumented_key(
names=names,
constructor=constructor,
**kwargs,
)
return closure
def maybe_make_no_key(candidate: str) -> Optional[Key]:
# NO and TRNS are functionally identical in how they (don't) mutate
# the state, but are tracked semantically separately, so create
# two keys with the exact same functionality
keys = (
('NO', 'XXXXXXX'),
('TRANSPARENT', 'TRNS'),
)
for names in keys:
if candidate in names:
return make_key(
names=names,
on_press=handlers.passthrough,
on_release=handlers.passthrough,
)
def maybe_make_alpha_key(candidate: str) -> Optional[Key]:
if len(candidate) != 1:
return
candidate_upper = candidate.upper()
if candidate_upper in ALL_ALPHAS:
return make_key(
names=(candidate_upper, candidate.lower()),
constructor=KeyboardKey,
code=4 + ALL_ALPHAS.index(candidate_upper),
)
def maybe_make_numeric_key(candidate: str) -> Optional[Key]:
if candidate in ALL_NUMBERS or candidate in ALL_NUMBER_ALIASES:
try:
offset = ALL_NUMBERS.index(candidate)
except ValueError:
offset = ALL_NUMBER_ALIASES.index(candidate)
return make_key(
names=(ALL_NUMBERS[offset], ALL_NUMBER_ALIASES[offset]),
constructor=KeyboardKey,
code=30 + offset,
)
def maybe_make_mod_key(candidate: str) -> Optional[Key]:
# MEH = LCTL | LALT | LSFT
# HYPR = LCTL | LALT | LSFT | LGUI
mods = (
(0x01, ('LEFT_CONTROL', 'LCTRL', 'LCTL')),
(0x02, ('LEFT_SHIFT', 'LSHIFT', 'LSFT')),
(0x04, ('LEFT_ALT', 'LALT', 'LOPT')),
(0x08, ('LEFT_SUPER', 'LGUI', 'LCMD', 'LWIN')),
(0x10, ('RIGHT_CONTROL', 'RCTRL', 'RCTL')),
(0x20, ('RIGHT_SHIFT', 'RSHIFT', 'RSFT')),
(0x40, ('RIGHT_ALT', 'RALT', 'ROPT')),
(0x80, ('RIGHT_SUPER', 'RGUI', 'RCMD', 'RWIN')),
(0x07, ('MEH',)),
(0x0F, ('HYPER', 'HYPR')),
)
for code, names in mods:
if candidate in names:
return make_key(names=names, constructor=ModifierKey, code=code)
def maybe_make_more_ascii(candidate: str) -> Optional[Key]:
codes = (
(40, ('ENTER', 'ENT', '\n')),
(41, ('ESCAPE', 'ESC')),
(42, ('BACKSPACE', 'BSPACE', 'BSPC', 'BKSP')),
(43, ('TAB', '\t')),
(44, ('SPACE', 'SPC', ' ')),
(45, ('MINUS', 'MINS', '-')),
(46, ('EQUAL', 'EQL', '=')),
(47, ('LBRACKET', 'LBRC', '[')),
(48, ('RBRACKET', 'RBRC', ']')),
(49, ('BACKSLASH', 'BSLASH', 'BSLS', '\\')),
(51, ('SEMICOLON', 'SCOLON', 'SCLN', ';')),
(52, ('QUOTE', 'QUOT', "'")),
(53, ('GRAVE', 'GRV', 'ZKHK', '`')),
(54, ('COMMA', 'COMM', ',')),
(55, ('DOT', '.')),
(56, ('SLASH', 'SLSH', '/')),
)
for code, names in codes:
if candidate in names:
return make_key(names=names, constructor=KeyboardKey, code=code)
def maybe_make_fn_key(candidate: str) -> Optional[Key]:
codes = (
(58, ('F1',)),
(59, ('F2',)),
(60, ('F3',)),
(61, ('F4',)),
(62, ('F5',)),
(63, ('F6',)),
(64, ('F7',)),
(65, ('F8',)),
(66, ('F9',)),
(67, ('F10',)),
(68, ('F11',)),
(69, ('F12',)),
(104, ('F13',)),
(105, ('F14',)),
(106, ('F15',)),
(107, ('F16',)),
(108, ('F17',)),
(109, ('F18',)),
(110, ('F19',)),
(111, ('F20',)),
(112, ('F21',)),
(113, ('F22',)),
(114, ('F23',)),
(115, ('F24',)),
)
for code, names in codes:
if candidate in names:
return make_key(names=names, constructor=KeyboardKey, code=code)
def maybe_make_navlock_key(candidate: str) -> Optional[Key]:
codes = (
(57, ('CAPS_LOCK', 'CAPSLOCK', 'CLCK', 'CAPS')),
# FIXME: Investigate whether this key actually works, and
# uncomment when/if it does.
# (130, ('LOCKING_CAPS', 'LCAP')),
(70, ('PRINT_SCREEN', 'PSCREEN', 'PSCR')),
(71, ('SCROLL_LOCK', 'SCROLLLOCK', 'SLCK')),
# FIXME: Investigate whether this key actually works, and
# uncomment when/if it does.
# (132, ('LOCKING_SCROLL', 'LSCRL')),
(72, ('PAUSE', 'PAUS', 'BRK')),
(73, ('INSERT', 'INS')),
(74, ('HOME',)),
(75, ('PGUP',)),
(76, ('DELETE', 'DEL')),
(77, ('END',)),
(78, ('PGDOWN', 'PGDN')),
(79, ('RIGHT', 'RGHT')),
(80, ('LEFT',)),
(81, ('DOWN',)),
(82, ('UP',)),
)
for code, names in codes:
if candidate in names:
return make_key(names=names, constructor=KeyboardKey, code=code)
def maybe_make_numpad_key(candidate: str) -> Optional[Key]:
codes = (
(83, ('NUM_LOCK', 'NUMLOCK', 'NLCK')),
(84, ('KP_SLASH', 'NUMPAD_SLASH', 'PSLS')),
(85, ('KP_ASTERISK', 'NUMPAD_ASTERISK', 'PAST')),
(86, ('KP_MINUS', 'NUMPAD_MINUS', 'PMNS')),
(87, ('KP_PLUS', 'NUMPAD_PLUS', 'PPLS')),
(88, ('KP_ENTER', 'NUMPAD_ENTER', 'PENT')),
(89, ('KP_1', 'P1', 'NUMPAD_1')),
(90, ('KP_2', 'P2', 'NUMPAD_2')),
(91, ('KP_3', 'P3', 'NUMPAD_3')),
(92, ('KP_4', 'P4', 'NUMPAD_4')),
(93, ('KP_5', 'P5', 'NUMPAD_5')),
(94, ('KP_6', 'P6', 'NUMPAD_6')),
(95, ('KP_7', 'P7', 'NUMPAD_7')),
(96, ('KP_8', 'P8', 'NUMPAD_8')),
(97, ('KP_9', 'P9', 'NUMPAD_9')),
(98, ('KP_0', 'P0', 'NUMPAD_0')),
(99, ('KP_DOT', 'PDOT', 'NUMPAD_DOT')),
(103, ('KP_EQUAL', 'PEQL', 'NUMPAD_EQUAL')),
(133, ('KP_COMMA', 'PCMM', 'NUMPAD_COMMA')),
(134, ('KP_EQUAL_AS400', 'NUMPAD_EQUAL_AS400')),
)
for code, names in codes:
if candidate in names:
return make_key(names=names, constructor=KeyboardKey, code=code)
def maybe_make_shifted_key(candidate: str) -> Optional[Key]:
codes = (
('1', ('EXCLAIM', 'EXLM', '!')),
('2', ('AT', '@')),
('3', ('HASH', 'POUND', '#')),
('4', ('DOLLAR', 'DLR', '$')),
('5', ('PERCENT', 'PERC', '%')),
('6', ('CIRCUMFLEX', 'CIRC', '^')),
('7', ('AMPERSAND', 'AMPR', '&')),
('8', ('ASTERISK', 'ASTR', '*')),
('9', ('LEFT_PAREN', 'LPRN', '(')),
('0', ('RIGHT_PAREN', 'RPRN', ')')),
('-', ('UNDERSCORE', 'UNDS', '_')),
('=', ('PLUS', '+')),
('[', ('LEFT_CURLY_BRACE', 'LCBR', '{')),
(']', ('RIGHT_CURLY_BRACE', 'RCBR', '}')),
('\\', ('PIPE', '|')),
(';', ('COLON', 'COLN', ':')),
("'", ('DOUBLE_QUOTE', 'DQUO', 'DQT', '"')),
('`', ('TILDE', 'TILD', '~')),
(',', ('LEFT_ANGLE_BRACKET', 'LABK', '<')),
('.', ('RIGHT_ANGLE_BRACKET', 'RABK', '>')),
('/', ('QUESTION', 'QUES', '?')),
)
for unshifted, names in codes:
if candidate in names:
return make_key(
names=names,
constructor=ModifiedKey,
code=KC[unshifted],
modifier=KC.LSFT,
)
def maybe_make_firmware_key(candidate: str) -> Optional[Key]:
keys = (
((('BLE_REFRESH',), handlers.ble_refresh)),
((('BLE_DISCONNECT',), handlers.ble_disconnect)),
((('BOOTLOADER',), handlers.bootloader)),
((('HID_SWITCH', 'HID'), handlers.hid_switch)),
((('RELOAD', 'RLD'), handlers.reload)),
((('RESET',), handlers.reset)),
((('ANY',), handlers.any_pressed)),
)
for names, handler in keys:
if candidate in names:
return make_key(names=names, on_press=handler)
KEY_GENERATORS = (
maybe_make_no_key,
maybe_make_alpha_key,
maybe_make_numeric_key,
maybe_make_firmware_key,
maybe_make_key(
('BKDL',),
on_press=handlers.bkdl_pressed,
on_release=handlers.bkdl_released,
),
maybe_make_key(
('GESC', 'GRAVE_ESC'),
on_press=handlers.gesc_pressed,
on_release=handlers.gesc_released,
),
maybe_make_mod_key,
# More ASCII standard keys
maybe_make_more_ascii,
# Function Keys
maybe_make_fn_key,
# Lock Keys, Navigation, etc.
maybe_make_navlock_key,
# Numpad
# FIXME: Investigate whether this key actually works, and
# uncomment when/if it does.
# maybe_make_key(131, ('LOCKING_NUM', 'LNUM')),
maybe_make_numpad_key,
# Making life better for folks on tiny keyboards especially: exposes
# the 'shifted' keys as raw keys. Under the hood we're still
# sending Shift+(whatever key is normally pressed) to get these, so
# for example `KC_AT` will hold shift and press 2.
maybe_make_shifted_key,
)
class KeyAttrDict:
# Instead of relying on the uncontrollable availability of a big chunk of
# contiguous memory for key caching, we can manually fragment the cache into
# reasonably small partitions. The partition size is chosen from the magic
# values of CPs hash allocation sizes.
# (https://github.com/adafruit/circuitpython/blob/main/py/map.c, 2023-02)
__partition_size = 37
__cache = [{}]
def __iter__(self):
for partition in self.__cache:
for name in partition:
yield name
def __setitem__(self, name: str, key: Key):
# Overwrite existing reference.
for partition in self.__cache:
if name in partition:
partition[name] = key
return key
# Insert new reference.
if len(self.__cache[-1]) >= self.__partition_size:
self.__cache.append({})
self.__cache[-1][name] = key
return key
def __getattr__(self, name: str):
return self.__getitem__(name)
def get(self, name: str, default: Optional[Key] = None):
try:
return self.__getitem__(name)
except Exception:
return default
def clear(self):
self.__cache.clear()
self.__cache.append({})
def __getitem__(self, name: str):
for partition in self.__cache:
if name in partition:
return partition[name]
for func in KEY_GENERATORS:
maybe_key = func(name)
if maybe_key:
break
if not maybe_key:
if debug.enabled:
debug('Invalid key: ', name)
return KC.NO
return maybe_key
# Global state, will be filled in throughout this file, and
# anywhere the user creates custom keys
KC = KeyAttrDict()
class Key:
'''Generic Key class with assignable handlers.'''
def __init__(
self,
on_press: Callable[[object, Key, Keyboard, ...], None] = handlers.passthrough,
on_release: Callable[[object, Key, Keyboard, ...], None] = handlers.passthrough,
):
self._on_press = on_press
self._on_release = on_release
def __repr__(self):
return self.__class__.__name__
def on_press(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None:
self._on_press(self, keyboard, KC, coord_int)
def on_release(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None:
self._on_release(self, keyboard, KC, coord_int)
class _DefaultKey(Key):
'''Meta class implementing handlers for Keys with HID codes.'''
def __init__(self, code: Optional[int] = None):
self.code = code
def __repr__(self):
return super().__repr__() + '(code=' + str(self.code) + ')'
def on_press(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None:
if keyboard.implicit_modifier is not None:
keyboard.keys_pressed.discard(keyboard.implicit_modifier)
keyboard.implicit_modifier = None
if self in keyboard.keys_pressed:
keyboard.keys_pressed.discard(self)
keyboard.hid_pending = True
keyboard._send_hid()
keyboard.keys_pressed.add(self)
keyboard.hid_pending = True
def on_release(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None:
keyboard.keys_pressed.discard(self)
keyboard.hid_pending = True
class KeyboardKey(_DefaultKey):
pass
class ModifierKey(_DefaultKey):
def __call__(self, key: Key) -> Key:
# don't duplicate when applying the same modifier twice
if (
isinstance(key, ModifiedKey)
and key.modifier.code & self.code == key.modifier.code
):
return key
elif isinstance(key, ModifierKey) and key.code & self.code == key.code:
return key
return ModifiedKey(key, self)
class ModifiedKey(Key):
def __init__(self, code: [Key, int], modifier: [ModifierKey]):
# generate from code by maybe_make_shifted_key
if isinstance(code, int):
key = KeyboardKey(code=code)
else:
key = code
# stack modifier keys
if isinstance(key, ModifierKey):
modifier = ModifierKey(key.code | modifier.code)
key = None
# stack modified keys
elif isinstance(key, ModifiedKey):
modifier = ModifierKey(key.modifier.code | modifier.code)
key = key.key
# clone modifier so it doesn't override explicit mods
else:
modifier = ModifierKey(modifier.code)
self.key = key
self.modifier = modifier
def on_press(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None:
if self.key in keyboard.keys_pressed:
self.key.on_release(keyboard, coord_int)
keyboard._send_hid()
keyboard.keys_pressed.add(self.modifier)
if self.key is not None:
self.key.on_press(keyboard, coord_int)
if keyboard.implicit_modifier is not None:
keyboard.keys_pressed.discard(keyboard.implicit_modifier)
keyboard.implicit_modifier = self.modifier
keyboard.hid_pending = True
def on_release(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None:
keyboard.keys_pressed.discard(self.modifier)
if self.key is not None:
self.key.on_release(keyboard, coord_int)
if keyboard.implicit_modifier == self.modifier:
keyboard.implicit_modifier = None
keyboard.hid_pending = True
def __repr__(self):
return (
super().__repr__()
+ '(key='
+ str(self.key)
+ ', modifier='
+ str(self.modifier)
+ ')'
)
class ConsumerKey(_DefaultKey):
pass
class MouseKey(_DefaultKey):
pass
def make_key(
names: Tuple[str, ...],
constructor: Key = Key,
**kwargs,
) -> Key:
'''
Create a new key, aliased by `names` in the KC lookup table.
Names are globally unique. If a later key is created with
the same name as an existing entry in `KC`, it will overwrite
the existing entry.
Names are case sensitive.
All **kwargs are passed to the Key constructor
'''
key = constructor(**kwargs)
for name in names:
KC[name] = key
return key
# Argumented keys are implicitly internal, so auto-gen of code
# is almost certainly the best plan here
def make_argumented_key(
names: Tuple[str, ...],
constructor: [Key, Callable[[...], Key]],
**_kwargs,
) -> Key:
def argumented_key(*args, **kwargs) -> Key:
# This is a very ugly workaround for missing syntax in mpy-cross 8.x
# and, once EOL, can be replaced by:
# return constructor(*args, **_kwargs, **kwargs)
k = _kwargs.copy()
k.update(**kwargs)
return constructor(*args, **k)
for name in names:
KC[name] = argumented_key
return argumented_key

View file

@ -1,528 +0,0 @@
try:
from typing import Callable, Optional
except ImportError:
pass
from collections import namedtuple
from keypad import Event as KeyEvent
from kmk.hid import BLEHID, USBHID, AbstractHID, HIDModes
from kmk.keys import KC, Axis, Key
from kmk.modules import Module
from kmk.scanners.keypad import MatrixScanner
from kmk.scheduler import Task, cancel_task, create_task, get_due_task
from kmk.utils import Debug
debug = Debug('kmk.keyboard')
KeyBufferFrame = namedtuple(
'KeyBufferFrame', ('key', 'is_pressed', 'int_coord', 'index')
)
def debug_error(module, message: str, error: Exception):
if debug.enabled:
debug(
message, ': ', error.__class__.__name__, ': ', error, name=module.__module__
)
class Sandbox:
matrix_update = None
secondary_matrix_update = None
active_layers = None
class KMKKeyboard:
def __init__(self) -> None:
#####
# User-configurable
self.keymap = []
self.coord_mapping = None
self.row_pins = None
self.col_pins = None
self.diode_orientation = None
self.matrix = None
self.modules = []
self.extensions = []
self.sandbox = Sandbox()
#####
# Internal State
self.keys_pressed = set()
self._coordkeys_pressed = {}
self.implicit_modifier = None
self.hid_type = HIDModes.USB
self.secondary_hid_type = None
self._hid_helper = None
self._hid_send_enabled = False
self.hid_pending = False
self.matrix_update = None
self.secondary_matrix_update = None
self.matrix_update_queue = []
self._trigger_powersave_enable = False
self._trigger_powersave_disable = False
self._go_args = None
self._resume_buffer = []
self._resume_buffer_x = []
# this should almost always be PREpended to, replaces
# former use of reversed_active_layers which had pointless
# overhead (the underlying list was never used anyway)
self.active_layers = [0]
def __repr__(self) -> str:
return self.__class__.__name__
def _send_hid(self) -> None:
if not self._hid_send_enabled:
return
if debug.enabled:
if self.keys_pressed:
debug('keys_pressed=', self.keys_pressed)
self._hid_helper.create_report(self.keys_pressed)
try:
self._hid_helper.send()
except Exception as err:
debug_error(self._hid_helper, 'send', err)
self.hid_pending = False
for key in self.keys_pressed:
if isinstance(key, Axis):
key.move(self, 0)
def _handle_matrix_report(self, kevent: KeyEvent) -> None:
if kevent is not None:
self._on_matrix_changed(kevent)
def _find_key_in_map(self, int_coord: int) -> Key:
try:
idx = self.coord_mapping.index(int_coord)
except ValueError:
if debug.enabled:
debug('no such int_coord: ', int_coord)
return None
key = None
for layer in self.active_layers:
try:
key = self.keymap[layer][idx]
except IndexError:
if debug.enabled:
debug('keymap IndexError: idx=', idx, ' layer=', layer)
if key and key != KC.TRNS:
break
return key
def _on_matrix_changed(self, kevent: KeyEvent) -> None:
int_coord = kevent.key_number
is_pressed = kevent.pressed
key = None
if not is_pressed:
key = self._coordkeys_pressed.pop(int_coord, None)
if key is None:
key = self._find_key_in_map(int_coord)
if key is None:
return
if debug.enabled:
debug(kevent, ': ', key)
self.pre_process_key(key, is_pressed, int_coord)
def _process_resume_buffer(self):
'''
Resume the processing of buffered, delayed, deferred, etc. key events
emitted by modules.
We use a copy of the `_resume_buffer` as a working buffer. The working
buffer holds all key events in the correct order for processing. If
during processing new events are pushed to the `_resume_buffer`, they
are prepended to the working buffer (which may not be emptied), in
order to preserve key event order.
We also double-buffer `_resume_buffer` with `_resume_buffer_x`, only
copying the reference to hopefully safe some time on allocations.
'''
buffer, self._resume_buffer = self._resume_buffer, self._resume_buffer_x
while buffer:
ksf = buffer.pop(0)
key = ksf.key
# Handle any unaccounted-for layer shifts by looking up the key resolution again.
if ksf.int_coord is not None:
if ksf.is_pressed:
key = self._find_key_in_map(ksf.int_coord)
else:
key = self._coordkeys_pressed.pop(ksf.int_coord, key)
# Resume the processing of the key event and update the HID report
# when applicable.
self.pre_process_key(key, ksf.is_pressed, ksf.int_coord, ksf.index)
if self.hid_pending:
self._send_hid()
self.hid_pending = False
# Any newly buffered key events must be prepended to the working
# buffer.
if self._resume_buffer:
self._resume_buffer.extend(buffer)
buffer.clear()
buffer, self._resume_buffer = self._resume_buffer, buffer
self._resume_buffer_x = buffer
def pre_process_key(
self,
key: Key,
is_pressed: bool,
int_coord: Optional[int] = None,
index: int = 0,
) -> None:
for module in self.modules[index:]:
try:
key = module.process_key(self, key, is_pressed, int_coord)
if key is None:
break
except Exception as err:
debug_error(module, 'process_key', err)
if int_coord is not None:
if is_pressed:
self._coordkeys_pressed[int_coord] = key
if debug.enabled:
debug('coordkeys_pressed=', self._coordkeys_pressed)
if key:
self.process_key(key, is_pressed, int_coord)
def process_key(
self, key: Key, is_pressed: bool, int_coord: Optional[int] = None
) -> None:
if is_pressed:
key.on_press(self, int_coord)
else:
key.on_release(self, int_coord)
def resume_process_key(
self,
module: Module,
key: Key,
is_pressed: bool,
int_coord: Optional[int] = None,
reprocess: Optional[bool] = False,
) -> None:
index = self.modules.index(module) + (0 if reprocess else 1)
ksf = KeyBufferFrame(
key=key, is_pressed=is_pressed, int_coord=int_coord, index=index
)
self._resume_buffer.append(ksf)
def remove_key(self, keycode: Key) -> None:
self.process_key(keycode, False)
def add_key(self, keycode: Key) -> None:
self.process_key(keycode, True)
def tap_key(self, keycode: Key) -> None:
self.add_key(keycode)
# On the next cycle, we'll remove the key.
self.set_timeout(0, lambda: self.remove_key(keycode))
def set_timeout(self, after_ticks: int, callback: Callable[[None], None]) -> [Task]:
return create_task(callback, after_ms=after_ticks)
def cancel_timeout(self, timeout_key: int) -> None:
cancel_task(timeout_key)
def _process_timeouts(self) -> None:
for task in get_due_task():
task()
def _init_coord_mapping(self) -> None:
'''
Attempt to sanely guess a coord_mapping if one is not provided. No-op
if `kmk.extensions.split.Split` is used, it provides equivalent
functionality in `on_bootup`
To save RAM on boards that don't use Split, we don't import Split
and do an isinstance check, but instead do string detection
'''
if any(x.__class__.__module__ == 'kmk.modules.split' for x in self.modules):
return
if not self.coord_mapping:
cm = []
for m in self.matrix:
cm.extend(m.coord_mapping)
self.coord_mapping = tuple(cm)
def _init_hid(self) -> None:
if self.hid_type == HIDModes.NOOP:
self._hid_helper = AbstractHID
elif self.hid_type == HIDModes.USB:
self._hid_helper = USBHID
elif self.hid_type == HIDModes.BLE:
self._hid_helper = BLEHID
else:
self._hid_helper = AbstractHID
self._hid_helper = self._hid_helper(**self._go_args)
self._hid_send_enabled = True
if debug.enabled:
debug('hid=', self._hid_helper)
def _deinit_hid(self) -> None:
try:
self._hid_helper.create_report({})
self._hid_helper.send()
except Exception as e:
debug_error(self, '_deinit_hid', e)
def _init_matrix(self) -> None:
if self.matrix is None:
self.matrix = MatrixScanner(
column_pins=self.col_pins,
row_pins=self.row_pins,
columns_to_anodes=self.diode_orientation,
)
try:
self.matrix = tuple(iter(self.matrix))
offset = 0
for matrix in self.matrix:
matrix.offset = offset
offset += matrix.key_count
except TypeError:
self.matrix = (self.matrix,)
if debug.enabled:
debug('matrix=', [_.__class__.__name__ for _ in self.matrix])
def during_bootup(self) -> None:
# Modules and extensions that fail `during_bootup` get removed from
# their respective lists. This serves as a self-check mechanism; any
# modules or extensions that initialize peripherals or data structures
# should do that in `during_bootup`.
for idx, module in enumerate(self.modules):
try:
module.during_bootup(self)
except Exception as err:
debug_error(module, 'during_bootup', err)
self.modules[idx] = None
self.modules[:] = [_ for _ in self.modules if _]
if debug.enabled:
debug('modules=', [_.__class__.__name__ for _ in self.modules])
for idx, ext in enumerate(self.extensions):
try:
ext.during_bootup(self)
except Exception as err:
debug_error(ext, 'during_bootup', err)
self.extensions[idx] = None
self.extensions[:] = [_ for _ in self.extensions if _]
if debug.enabled:
debug('extensions=', [_.__class__.__name__ for _ in self.extensions])
def before_matrix_scan(self) -> None:
for module in self.modules:
try:
module.before_matrix_scan(self)
except Exception as err:
debug_error(module, 'before_matrix_scan', err)
for ext in self.extensions:
try:
ext.before_matrix_scan(self.sandbox)
except Exception as err:
debug_error(ext, 'before_matrix_scan', err)
def after_matrix_scan(self) -> None:
for module in self.modules:
try:
module.after_matrix_scan(self)
except Exception as err:
debug_error(module, 'after_matrix_scan', err)
for ext in self.extensions:
try:
ext.after_matrix_scan(self.sandbox)
except Exception as err:
debug_error(ext, 'after_matrix_scan', err)
def before_hid_send(self) -> None:
for module in self.modules:
try:
module.before_hid_send(self)
except Exception as err:
debug_error(module, 'before_hid_send', err)
for ext in self.extensions:
try:
ext.before_hid_send(self.sandbox)
except Exception as err:
debug_error(ext, 'before_hid_send', err)
def after_hid_send(self) -> None:
for module in self.modules:
try:
module.after_hid_send(self)
except Exception as err:
debug_error(module, 'after_hid_send', err)
for ext in self.extensions:
try:
ext.after_hid_send(self.sandbox)
except Exception as err:
debug_error(ext, 'after_hid_send', err)
def powersave_enable(self) -> None:
for module in self.modules:
try:
module.on_powersave_enable(self)
except Exception as err:
debug_error(module, 'powersave_enable', err)
for ext in self.extensions:
try:
ext.on_powersave_enable(self.sandbox)
except Exception as err:
debug_error(ext, 'powersave_enable', err)
def powersave_disable(self) -> None:
for module in self.modules:
try:
module.on_powersave_disable(self)
except Exception as err:
debug_error(module, 'powersave_disable', err)
for ext in self.extensions:
try:
ext.on_powersave_disable(self.sandbox)
except Exception as err:
debug_error(ext, 'powersave_disable', err)
def deinit(self) -> None:
for module in self.modules:
try:
module.deinit(self)
except Exception as err:
debug_error(module, 'deinit', err)
for ext in self.extensions:
try:
ext.deinit(self.sandbox)
except Exception as err:
debug_error(ext, 'deinit', err)
def go(self, hid_type=HIDModes.USB, secondary_hid_type=None, **kwargs) -> None:
try:
self._init(
hid_type=hid_type,
secondary_hid_type=secondary_hid_type,
**kwargs,
)
while True:
self._main_loop()
except Exception as err:
import traceback
traceback.print_exception(err)
finally:
debug('cleaning up...')
self._deinit_hid()
self.deinit()
debug('...done')
if not debug.enabled:
import supervisor
supervisor.reload()
def _init(
self,
hid_type: HIDModes = HIDModes.USB,
secondary_hid_type: Optional[HIDModes] = None,
**kwargs,
) -> None:
self._go_args = kwargs
self.hid_type = hid_type
self.secondary_hid_type = secondary_hid_type
if debug.enabled:
debug('Initialising ', self)
self._init_hid()
self._init_matrix()
self._init_coord_mapping()
self.during_bootup()
if debug.enabled:
import gc
gc.collect()
debug('mem_info used:', gc.mem_alloc(), ' free:', gc.mem_free())
def _main_loop(self) -> None:
self.sandbox.active_layers = self.active_layers.copy()
self.before_matrix_scan()
self._process_resume_buffer()
for matrix in self.matrix:
update = matrix.scan_for_changes()
if update:
self.matrix_update = update
break
self.sandbox.matrix_update = self.matrix_update
self.sandbox.secondary_matrix_update = self.secondary_matrix_update
self.after_matrix_scan()
if self.secondary_matrix_update:
self.matrix_update_queue.append(self.secondary_matrix_update)
self.secondary_matrix_update = None
if self.matrix_update:
self.matrix_update_queue.append(self.matrix_update)
self.matrix_update = None
# only handle one key per cycle.
if self.matrix_update_queue:
self._handle_matrix_report(self.matrix_update_queue.pop(0))
self.before_hid_send()
if self.hid_pending:
self._send_hid()
self._process_timeouts()
if self.hid_pending:
self._send_hid()
self.after_hid_send()
if self._trigger_powersave_enable:
self.powersave_enable()
if self._trigger_powersave_disable:
self.powersave_disable()

View file

@ -1,34 +0,0 @@
from micropython import const
from supervisor import ticks_ms
_TICKS_PERIOD = const(1 << 29)
_TICKS_MAX = const(_TICKS_PERIOD - 1)
_TICKS_HALFPERIOD = const(_TICKS_PERIOD // 2)
def ticks_diff(new: int, start: int) -> int:
diff = (new - start) & _TICKS_MAX
diff = ((diff + _TICKS_HALFPERIOD) & _TICKS_MAX) - _TICKS_HALFPERIOD
return diff
def ticks_add(ticks: int, delta: int) -> int:
return (ticks + delta) % _TICKS_PERIOD
def check_deadline(new: int, start: int, ms: int) -> int:
return ticks_diff(new, start) < ms
class PeriodicTimer:
def __init__(self, period: int):
self.period = period
self.last_tick = ticks_ms()
def tick(self) -> bool:
now = ticks_ms()
if ticks_diff(now, self.last_tick) >= self.period:
self.last_tick = now
return True
else:
return False

View file

@ -1,46 +0,0 @@
class InvalidExtensionEnvironment(Exception):
pass
class Module:
'''
Modules differ from extensions in that they not only can read the state, but
are allowed to modify the state. The will be loaded on boot, and are not
allowed to be unloaded as they are required to continue functioning in a
consistant manner.
'''
# The below methods should be implemented by subclasses
def during_bootup(self, keyboard):
raise NotImplementedError
def before_matrix_scan(self, keyboard):
'''
Return value will be injected as an extra matrix update
'''
raise NotImplementedError
def after_matrix_scan(self, keyboard):
'''
Return value will be replace matrix update if supplied
'''
raise NotImplementedError
def process_key(self, keyboard, key, is_pressed, int_coord):
return key
def before_hid_send(self, keyboard):
raise NotImplementedError
def after_hid_send(self, keyboard):
raise NotImplementedError
def on_powersave_enable(self, keyboard):
raise NotImplementedError
def on_powersave_disable(self, keyboard):
raise NotImplementedError
def deinit(self, keyboard):
pass

View file

@ -1,230 +0,0 @@
import busio
import digitalio
import microcontroller
import time
from kmk.keys import AX
from kmk.modules import Module
from kmk.modules.adns9800_firmware import firmware
from kmk.utils import Debug
debug = Debug(__name__)
class REG:
Product_ID = 0x0
Revision_ID = 0x1
MOTION = 0x2
DELTA_X_L = 0x3
DELTA_X_H = 0x4
DELTA_Y_L = 0x5
DELTA_Y_H = 0x6
SQUAL = 0x7
PIXEL_SUM = 0x8
Maximum_Pixel = 0x9
Minimum_Pixel = 0xA
Shutter_Lower = 0xB
Shutter_Upper = 0xC
Frame_Period_Lower = 0xD
Frame_Period_Upper = 0xE
Configuration_I = 0xF
Configuration_II = 0x10
Frame_Capture = 0x12
SROM_Enable = 0x13
Run_Downshift = 0x14
Rest1_Rate = 0x15
Rest1_Downshift = 0x16
Rest2_Rate = 0x17
Rest2_Downshift = 0x18
Rest3_Rate = 0x19
Frame_Period_Max_Bound_Lower = 0x1A
Frame_Period_Max_Bound_Upper = 0x1B
Frame_Period_Min_Bound_Lower = 0x1C
Frame_Period_Min_Bound_Upper = 0x1D
Shutter_Max_Bound_Lower = 0x1E
Shutter_Max_Bound_Upper = 0x1F
LASER_CTRL0 = 0x20
Observation = 0x24
Data_Out_Lower = 0x25
Data_Out_Upper = 0x26
SROM_ID = 0x2A
Lift_Detection_Thr = 0x2E
Configuration_V = 0x2F
Configuration_IV = 0x39
Power_Up_Reset = 0x3A
Shutdown = 0x3B
Inverse_Product_ID = 0x3F
Snap_Angle = 0x42
Motion_Burst = 0x50
SROM_Load_Burst = 0x62
Pixel_Burst = 0x64
class ADNS9800(Module):
tswr = tsww = 120
tsrw = tsrr = 20
tsrad = 100
tbexit = 1
baud = 2000000
cpol = 1
cpha = 1
DIR_WRITE = 0x80
DIR_READ = 0x7F
def __init__(self, cs, sclk, miso, mosi, invert_x=False, invert_y=False):
self.cs = digitalio.DigitalInOut(cs)
self.cs.direction = digitalio.Direction.OUTPUT
self.spi = busio.SPI(clock=sclk, MOSI=mosi, MISO=miso)
self.invert_x = invert_x
self.invert_y = invert_y
def adns_start(self):
self.cs.value = False
def adns_stop(self):
self.cs.value = True
def adns_write(self, reg, data):
while not self.spi.try_lock():
pass
try:
self.spi.configure(baudrate=self.baud, polarity=self.cpol, phase=self.cpha)
self.adns_start()
self.spi.write(bytes([reg | self.DIR_WRITE, data]))
finally:
self.spi.unlock()
self.adns_stop()
def adns_read(self, reg):
result = bytearray(1)
while not self.spi.try_lock():
pass
try:
self.spi.configure(baudrate=self.baud, polarity=self.cpol, phase=self.cpha)
self.adns_start()
self.spi.write(bytes([reg & self.DIR_READ]))
microcontroller.delay_us(self.tsrad)
self.spi.readinto(result)
finally:
self.spi.unlock()
self.adns_stop()
return result[0]
def adns_upload_srom(self):
while not self.spi.try_lock():
pass
try:
self.spi.configure(baudrate=self.baud, polarity=self.cpol, phase=self.cpha)
self.adns_start()
self.spi.write(bytes([REG.SROM_Load_Burst | self.DIR_WRITE]))
for b in firmware:
self.spi.write(bytes([b]))
finally:
self.spi.unlock()
self.adns_stop()
def delta_to_int(self, high, low):
comp = (high << 8) | low
if comp & 0x8000:
return (-1) * (0xFFFF + 1 - comp)
return comp
def adns_read_motion(self):
result = bytearray(14)
while not self.spi.try_lock():
pass
try:
self.spi.configure(baudrate=self.baud, polarity=self.cpol, phase=self.cpha)
self.adns_start()
self.spi.write(bytes([REG.Motion_Burst & self.DIR_READ]))
microcontroller.delay_us(self.tsrad)
self.spi.readinto(result)
finally:
self.spi.unlock()
self.adns_stop()
microcontroller.delay_us(self.tbexit)
self.adns_write(REG.MOTION, 0x0)
return result
def during_bootup(self, keyboard):
self.adns_write(REG.Power_Up_Reset, 0x5A)
time.sleep(0.1)
self.adns_read(REG.MOTION)
microcontroller.delay_us(self.tsrr)
self.adns_read(REG.DELTA_X_L)
microcontroller.delay_us(self.tsrr)
self.adns_read(REG.DELTA_X_H)
microcontroller.delay_us(self.tsrr)
self.adns_read(REG.DELTA_Y_L)
microcontroller.delay_us(self.tsrr)
self.adns_read(REG.DELTA_Y_H)
microcontroller.delay_us(self.tsrw)
self.adns_write(REG.Configuration_IV, 0x2)
microcontroller.delay_us(self.tsww)
self.adns_write(REG.SROM_Enable, 0x1D)
microcontroller.delay_us(1000)
self.adns_write(REG.SROM_Enable, 0x18)
microcontroller.delay_us(self.tsww)
self.adns_upload_srom()
microcontroller.delay_us(2000)
laser_ctrl0 = self.adns_read(REG.LASER_CTRL0)
microcontroller.delay_us(self.tsrw)
self.adns_write(REG.LASER_CTRL0, laser_ctrl0 & 0xF0)
microcontroller.delay_us(self.tsww)
self.adns_write(REG.Configuration_I, 0x10)
microcontroller.delay_us(self.tsww)
if debug.enabled:
debug('ADNS: Product ID ', hex(self.adns_read(REG.Product_ID)))
microcontroller.delay_us(self.tsrr)
debug('ADNS: Revision ID ', hex(self.adns_read(REG.Revision_ID)))
microcontroller.delay_us(self.tsrr)
debug('ADNS: SROM ID ', hex(self.adns_read(REG.SROM_ID)))
microcontroller.delay_us(self.tsrr)
if self.adns_read(REG.Observation) & 0x20:
debug('ADNS: Sensor is running SROM')
else:
debug('ADNS: Error! Sensor is not running SROM!')
return
def before_matrix_scan(self, keyboard):
motion = self.adns_read_motion()
if motion[0] & 0x80:
delta_x = self.delta_to_int(motion[3], motion[2])
delta_y = self.delta_to_int(motion[5], motion[4])
if self.invert_x:
delta_x *= -1
if self.invert_y:
delta_y *= -1
if delta_x:
AX.X.move(keyboard, delta_x)
if delta_y:
AX.Y.move(keyboard, delta_y)
if debug.enabled:
debug('Delta: ', delta_x, ' ', delta_y)
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

View file

@ -1,131 +0,0 @@
from kmk.keys import KC
from kmk.modules import Module
from kmk.utils import Debug
debug = Debug(__name__)
def noop(*args):
pass
class AnalogEvent:
def __init__(self, on_change=noop, on_stop=noop):
self._on_change = on_change
self._on_stop = on_stop
def on_change(self, event, keyboard):
self._on_change(self, event, keyboard)
def on_stop(self, event, keyboard):
self._on_stop(self, event, keyboard)
class AnalogKey(AnalogEvent):
def __init__(self, key, threshold=127):
self.key = key
self.threshold = threshold
self.pressed = False
def on_change(self, event, keyboard):
if event.value >= self.threshold and not self.pressed:
self.pressed = True
keyboard.pre_process_key(self.key, True)
elif event.value < self.threshold and self.pressed:
self.pressed = False
keyboard.pre_process_key(self.key, False)
def on_stop(self, event, keyboard):
pass
class AnalogInput:
def __init__(self, input, filter=lambda input: input.value >> 8):
self.input = input
self.value = 0
self.delta = 0
self.filter = filter
def update(self):
'''
Read a new value from an analogio compatible input, apply
transformation, then return either the new value if it changed or `None`
otherwise.
'''
value = self.filter(self.input)
self.delta = value - self.value
if self.delta != 0:
self.value = value
return value
class AnalogInputs(Module):
def __init__(self, inputs, evtmap):
self._active = {}
self.inputs = inputs
self.evtmap = evtmap
def on_runtime_enable(self, keyboard):
return
def on_runtime_disable(self, keyboard):
return
def during_bootup(self, keyboard):
return
def before_matrix_scan(self, keyboard):
for idx, input in enumerate(self.inputs):
value = input.update()
# No change in value: stop or pass
if value is None:
if input in self._active:
if debug.enabled:
debug('on_stop', input, self._active[idx])
self._active[idx].on_stop(input, keyboard)
del self._active[idx]
continue
# Resolve event handler
if input in self._active:
key = self._active[idx]
else:
key = None
for layer in keyboard.active_layers:
try:
key = self.evtmap[layer][idx]
except IndexError:
if debug.enabled:
debug('evtmap IndexError: idx=', idx, ' layer=', layer)
if key and key != KC.TRNS:
break
if key == KC.NO:
continue
# Forward change to event handler
try:
self._active[idx] = key
if debug.enabled:
debug('on_change', input, key, value)
key.on_change(input, keyboard)
except Exception as e:
if debug.enabled:
debug(type(e), ': ', e, ' in ', key.on_change)
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

View file

@ -1,82 +0,0 @@
from kmk.keys import KC, KeyboardKey
from kmk.modules import Module
from kmk.scheduler import cancel_task, create_task
from kmk.utils import Debug
debug = Debug(__name__)
class Autoshift(Module):
def __init__(self, tap_time=300):
self.tap_time = tap_time
self._active = False
self._task = None
self._key = None
def during_bootup(self, keyboard):
self._task = create_task(lambda: self._shift(keyboard), after_ms=-1)
def before_matrix_scan(self, keyboard):
pass
def after_matrix_scan(self, keyboard):
pass
def process_key(self, keyboard, key, is_pressed, int_coord):
# Unshift on any key event
if self._active:
self._unshift(keyboard)
return key
# Only shift from an unshifted state
if KC.LSFT in keyboard.keys_pressed:
return key
# Ignore rolls from tapped to hold
if not is_pressed and key is not self._key:
return key
# Only shift alpha keys, iff there's no pending potential shift
if (
is_pressed
and not self._key
and isinstance(key, KeyboardKey)
and KC.A.code <= key.code <= KC.Z.code
):
create_task(self._task, after_ms=self.tap_time)
self._key = key
else:
cancel_task(self._task)
keyboard.resume_process_key(self, self._key, True)
if key is self._key:
keyboard.resume_process_key(self, self._key, False)
else:
keyboard.resume_process_key(self, key, True)
self._key = None
def before_hid_send(self, keyboard):
pass
def after_hid_send(self, keyboard):
pass
def on_powersave_enable(self, keyboard):
pass
def on_powersave_disable(self, keyboard):
pass
def _shift(self, keyboard):
if debug.enabled:
debug('activate')
self._active = True
keyboard.keys_pressed.add(KC.LSFT)
keyboard.resume_process_key(self, self._key, True)
def _unshift(self, keyboard):
if debug.enabled:
debug('deactivate')
self._active = False
self._key = None
keyboard.keys_pressed.remove(KC.LSFT)

View file

@ -1,102 +0,0 @@
from kmk.keys import KC, KeyboardKey, ModifierKey, make_key
from kmk.modules import Module
class CapsWord(Module):
# default timeout is 8000
# alphabets, numbers and few more keys will not disable capsword
def __init__(self, timeout=8000):
self._alphabets = range(KC.A.code, KC.Z.code + 1)
self._numbers = range(KC.N1.code, KC.N0.code + 1)
self.keys_ignored = [
KC.MINS,
KC.BSPC,
KC.UNDS,
]
self._timeout_key = False
self._cw_active = False
self.timeout = timeout
make_key(
names=(
'CAPSWORD',
'CW',
),
on_press=self.cw_pressed,
)
def during_bootup(self, keyboard):
return
def before_matrix_scan(self, keyboard):
return
def process_key(self, keyboard, key, is_pressed, int_coord):
if not self._cw_active or key == KC.CW:
return key
continue_cw = False
# capitalize alphabets
if isinstance(key, KeyboardKey) and key.code in self._alphabets:
keyboard.process_key(KC.LSFT, is_pressed)
continue_cw = True
elif (
not isinstance(key, KeyboardKey)
or isinstance(key, ModifierKey)
or key.code in self._numbers
or key in self.keys_ignored
):
continue_cw = True
# requests and cancels existing timeouts
if is_pressed:
if continue_cw:
self.discard_timeout(keyboard)
self.request_timeout(keyboard)
else:
self.process_timeout()
return key
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 after_matrix_scan(self, keyboard):
return
def process_timeout(self):
self._cw_active = False
self._timeout_key = False
def request_timeout(self, keyboard):
if self._cw_active:
if self.timeout:
self._timeout_key = keyboard.set_timeout(
self.timeout, lambda: self.process_timeout()
)
def discard_timeout(self, keyboard):
if self._timeout_key:
if self.timeout:
keyboard.cancel_timeout(self._timeout_key)
self._timeout_key = False
def cw_pressed(self, key, keyboard, *args, **kwargs):
# enables/disables capsword
if key == KC.CW:
if not self._cw_active:
self._cw_active = True
self.discard_timeout(keyboard)
self.request_timeout(keyboard)
else:
self.discard_timeout(keyboard)
self.process_timeout()

View file

@ -1,70 +0,0 @@
from kmk.keys import KC, ModifierKey, make_key
from kmk.modules import Module
class CgSwap(Module):
# default cg swap is disabled, can be eanbled too if needed
def __init__(self, cg_swap_enable=False):
self.cg_swap_enable = cg_swap_enable
self._cg_mapping = {
KC.LCTL: KC.LGUI,
KC.RCTL: KC.RGUI,
KC.LGUI: KC.LCTL,
KC.RGUI: KC.RCTL,
}
make_key(
names=('CG_SWAP',),
)
make_key(
names=('CG_NORM',),
)
make_key(
names=('CG_TOGG',),
)
def during_bootup(self, keyboard):
return
def matrix_detected_press(self, keyboard):
return keyboard.matrix_update is None
def before_matrix_scan(self, keyboard):
return
def process_key(self, keyboard, key, is_pressed, int_coord):
if is_pressed:
# enables or disables or toggles cg swap
if key == KC.CG_SWAP:
self.cg_swap_enable = True
elif key == KC.CG_NORM:
self.cg_swap_enable = False
elif key == KC.CG_TOGG:
if not self.cg_swap_enable:
self.cg_swap_enable = True
else:
self.cg_swap_enable = False
# performs cg swap
if (
self.cg_swap_enable
and key not in (KC.CG_SWAP, KC.CG_NORM, KC.CG_TOGG)
and isinstance(key, ModifierKey)
and key in self._cg_mapping
):
key = self._cg_mapping.get(key)
return key
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 after_matrix_scan(self, keyboard):
return

View file

@ -1,330 +0,0 @@
try:
from typing import Optional, Tuple, Union
except ImportError:
pass
from micropython import const
from kmk.keys import Key, make_key
from kmk.kmk_keyboard import KMKKeyboard
from kmk.modules import Module
from kmk.utils import Debug
debug = Debug(__name__)
class _ComboState:
RESET = const(0)
MATCHING = const(1)
ACTIVE = const(2)
IDLE = const(3)
class Combo:
fast_reset = False
per_key_timeout = False
timeout = 50
_remaining = []
_timeout = None
_state = _ComboState.IDLE
_match_coord = False
def __init__(
self,
match: Tuple[Union[Key, int], ...],
result: Key,
fast_reset=None,
per_key_timeout=None,
timeout=None,
match_coord=None,
):
'''
match: tuple of keys (KC.A, KC.B)
result: key KC.C
'''
self.match = match
self.result = result
if fast_reset is not None:
self.fast_reset = fast_reset
if per_key_timeout is not None:
self.per_key_timeout = per_key_timeout
if timeout is not None:
self.timeout = timeout
if match_coord is not None:
self._match_coord = match_coord
def __repr__(self):
return f'{self.__class__.__name__}({list(self.match)})'
def matches(self, key: Key, int_coord: int):
raise NotImplementedError
def has_match(self, key: Key, int_coord: int):
return self._match_coord and int_coord in self.match or key in self.match
def insert(self, key: Key, int_coord: int):
if self._match_coord:
self._remaining.insert(0, int_coord)
else:
self._remaining.insert(0, key)
def reset(self):
self._remaining = list(self.match)
class Chord(Combo):
def matches(self, key: Key, int_coord: int):
if not self._match_coord and key in self._remaining:
self._remaining.remove(key)
return True
elif self._match_coord and int_coord in self._remaining:
self._remaining.remove(int_coord)
return True
else:
return False
class Sequence(Combo):
fast_reset = True
per_key_timeout = True
timeout = 1000
def matches(self, key: Key, int_coord: int):
if (
not self._match_coord and self._remaining and self._remaining[0] == key
) or (
self._match_coord and self._remaining and self._remaining[0] == int_coord
):
self._remaining.pop(0)
return True
else:
return False
class Combos(Module):
def __init__(self, combos=[]):
self.combos = combos
self._key_buffer = []
make_key(names=('LEADER', 'LDR'))
def during_bootup(self, keyboard):
self.reset(keyboard)
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: Key, is_pressed, int_coord):
if is_pressed:
return self.on_press(keyboard, key, int_coord)
else:
return self.on_release(keyboard, key, int_coord)
def on_press(self, keyboard: KMKKeyboard, key: Key, int_coord: Optional[int]):
# refill potential matches from timed-out matches
if self.count_matching() == 0:
for combo in self.combos:
if combo._state == _ComboState.RESET:
combo._state = _ComboState.MATCHING
# filter potential matches
for combo in self.combos:
if combo._state != _ComboState.MATCHING:
continue
if combo.matches(key, int_coord):
continue
combo._state = _ComboState.IDLE
if combo._timeout:
keyboard.cancel_timeout(combo._timeout)
combo._timeout = keyboard.set_timeout(
combo.timeout, lambda c=combo: self.reset_combo(keyboard, c)
)
match_count = self.count_matching()
if match_count:
# At least one combo matches current key: append key to buffer.
self._key_buffer.append((int_coord, key, True))
key = None
for first_match in self.combos:
if first_match._state == _ComboState.MATCHING:
break
# Single match left: don't wait on timeout to activate
if match_count == 1 and not any(first_match._remaining):
combo = first_match
self.activate(keyboard, combo)
if combo._timeout:
keyboard.cancel_timeout(combo._timeout)
combo._timeout = None
self._key_buffer = []
self.reset(keyboard)
# Start or reset individual combo timeouts.
for combo in self.combos:
if combo._state != _ComboState.MATCHING:
continue
if combo._timeout:
if combo.per_key_timeout:
keyboard.cancel_timeout(combo._timeout)
else:
continue
combo._timeout = keyboard.set_timeout(
combo.timeout, lambda c=combo: self.on_timeout(keyboard, c)
)
else:
# There's no matching combo: send and reset key buffer
if self._key_buffer:
self._key_buffer.append((int_coord, key, True))
self.send_key_buffer(keyboard)
self._key_buffer = []
key = None
return key
def on_release(self, keyboard: KMKKeyboard, key: Key, int_coord: Optional[int]):
for combo in self.combos:
if combo._state != _ComboState.ACTIVE:
continue
if combo.has_match(key, int_coord):
# Deactivate combo if it matches current key.
self.deactivate(keyboard, combo)
if combo.fast_reset:
self.reset_combo(keyboard, combo)
self._key_buffer = []
else:
combo.insert(key, int_coord)
combo._state = _ComboState.MATCHING
key = None
break
else:
# Non-active but matching combos can either activate on key release
# if they're the only match, or "un-match" the released key but stay
# matching if they're a repeatable combo.
for combo in self.combos:
if combo._state != _ComboState.MATCHING:
continue
if not combo.has_match(key, int_coord):
continue
# Combo matches, but first key released before timeout.
elif not any(combo._remaining) and self.count_matching() == 1:
keyboard.cancel_timeout(combo._timeout)
self.activate(keyboard, combo)
self._key_buffer = []
keyboard._send_hid()
self.deactivate(keyboard, combo)
if combo.fast_reset:
self.reset_combo(keyboard, combo)
else:
combo.insert(key, int_coord)
combo._state = _ComboState.MATCHING
self.reset(keyboard)
elif not any(combo._remaining):
continue
# Skip combos that allow tapping.
elif combo.fast_reset:
continue
# This was the last key released of a repeatable combo.
elif len(combo._remaining) == len(combo.match) - 1:
self.reset_combo(keyboard, combo)
if not self.count_matching():
self._key_buffer.append((int_coord, key, False))
self.send_key_buffer(keyboard)
self._key_buffer = []
key = None
# Anything between first and last key released.
else:
combo.insert(key, int_coord)
# Don't propagate key-release events for keys that have been
# buffered. Append release events only if corresponding press is in
# buffer.
pressed = self._key_buffer.count((int_coord, key, True))
released = self._key_buffer.count((int_coord, key, False))
if (pressed - released) > 0:
self._key_buffer.append((int_coord, key, False))
key = None
# Reset on non-combo key up
if not self.count_matching():
self.reset(keyboard)
return key
def on_timeout(self, keyboard, combo):
# If combo reaches timeout and has no remaining keys, activate it;
# else, drop it from the match list.
combo._timeout = None
if not any(combo._remaining):
self.activate(keyboard, combo)
# check if the last buffered key event was a 'release'
if not self._key_buffer[-1][2]:
keyboard._send_hid()
self.deactivate(keyboard, combo)
self._key_buffer = []
self.reset(keyboard)
else:
if self.count_matching() == 1:
# This was the last pending combo: flush key buffer.
self.send_key_buffer(keyboard)
self._key_buffer = []
self.reset_combo(keyboard, combo)
def send_key_buffer(self, keyboard):
for int_coord, key, is_pressed in self._key_buffer:
keyboard.resume_process_key(self, key, is_pressed, int_coord)
def activate(self, keyboard, combo):
if debug.enabled:
debug('activate', combo)
combo.result.on_press(keyboard)
combo._state = _ComboState.ACTIVE
def deactivate(self, keyboard, combo):
if debug.enabled:
debug('deactivate', combo)
combo.result.on_release(keyboard)
combo._state = _ComboState.IDLE
def reset_combo(self, keyboard, combo):
combo.reset()
if combo._timeout is not None:
keyboard.cancel_timeout(combo._timeout)
combo._timeout = None
combo._state = _ComboState.RESET
def reset(self, keyboard):
for combo in self.combos:
if combo._state != _ComboState.ACTIVE:
self.reset_combo(keyboard, combo)
def count_matching(self):
match_count = 0
for combo in self.combos:
if combo._state == _ComboState.MATCHING:
match_count += 1
return match_count

View file

@ -1,261 +0,0 @@
from micropython import const
from supervisor import ticks_ms
from collections import namedtuple
from kmk.keys import KC, Key, make_argumented_key
from kmk.kmktime import check_deadline, ticks_diff
from kmk.modules import Module
class DynamicSequenceKey(Key):
def __init__(self, sequence_select=None, **kwargs):
super().__init__(**kwargs)
self.sequence_select = sequence_select
class SequenceStatus:
STOPPED = const(0)
RECORDING = const(1)
PLAYING = const(2)
SET_REPEPITIONS = const(3)
SET_INTERVAL = const(4)
# Keycodes for number keys
_numbers = range(KC.N1.code, KC.N0.code + 1)
SequenceFrame = namedtuple('SequenceFrame', ['keys_pressed', 'timestamp'])
class Sequence:
def __init__(self):
self.repetitions = 1
self.interval = 0
self.sequence_data = [SequenceFrame(set(), 0) for i in range(3)]
class DynamicSequences(Module):
def __init__(
self, slots=1, timeout=60000, key_interval=0, use_recorded_speed=False
):
self.sequences = [Sequence() for i in range(slots)]
self.current_slot = self.sequences[0]
self.status = SequenceStatus.STOPPED
self.index = 0
self.start_time = 0
self.current_repetition = 0
self.last_config_frame = set()
self.timeout = timeout
self.key_interval = key_interval
self.use_recorded_speed = use_recorded_speed
# Create keycodes
make_argumented_key(
names=('RECORD_SEQUENCE',),
constructor=DynamicSequenceKey,
on_press=self._record_sequence,
)
make_argumented_key(
names=('PLAY_SEQUENCE',),
constructor=DynamicSequenceKey,
on_press=self._play_sequence,
)
make_argumented_key(
names=('SET_SEQUENCE', 'STOP_SEQUENCE'),
constructor=DynamicSequenceKey,
on_press=self._stop_sequence,
)
make_argumented_key(
names=('SET_SEQUENCE_REPETITIONS',),
constructor=DynamicSequenceKey,
on_press=self._set_sequence_repetitions,
)
make_argumented_key(
names=('SET_SEQUENCE_INTERVAL',),
constructor=DynamicSequenceKey,
on_press=self._set_sequence_interval,
)
def _record_sequence(self, key, keyboard, *args, **kwargs):
self._stop_sequence(key, keyboard)
self.status = SequenceStatus.RECORDING
self.start_time = ticks_ms()
self.current_slot.sequence_data = [SequenceFrame(set(), 0)]
self.index = 0
def _play_sequence(self, key, keyboard, *args, **kwargs):
self._stop_sequence(key, keyboard)
self.status = SequenceStatus.PLAYING
self.start_time = ticks_ms()
self.index = 0
self.current_repetition = 0
def _stop_sequence(self, key, keyboard, *args, **kwargs):
if self.status == SequenceStatus.RECORDING:
self.stop_recording()
elif self.status == SequenceStatus.SET_INTERVAL:
self.stop_config()
self.status = SequenceStatus.STOPPED
# Change sequences here because stop is always called
if key.sequence_select is not None:
self.current_slot = self.sequences[key.sequence_select]
# Configure repeat settings
def _set_sequence_repetitions(self, key, keyboard, *args, **kwargs):
self._stop_sequence(key, keyboard)
self.status = SequenceStatus.SET_REPEPITIONS
self.last_config_frame = set()
self.current_slot.repetitions = 0
self.start_time = ticks_ms()
def _set_sequence_interval(self, key, keyboard, *args, **kwargs):
self._stop_sequence(key, keyboard)
self.status = SequenceStatus.SET_INTERVAL
self.last_config_frame = set()
self.current_slot.interval = 0
self.start_time = ticks_ms()
# Add the current keypress state to the sequence
def record_frame(self, keys_pressed):
if self.current_slot.sequence_data[self.index].keys_pressed != keys_pressed:
self.index += 1
# Recorded speed
if self.use_recorded_speed:
self.current_slot.sequence_data.append(
SequenceFrame(
keys_pressed.copy(), ticks_diff(ticks_ms(), self.start_time)
)
)
# Constant speed
else:
self.current_slot.sequence_data.append(
SequenceFrame(keys_pressed.copy(), self.index * self.key_interval)
)
if not check_deadline(ticks_ms(), self.start_time, self.timeout):
self.stop_recording()
# Add the ending frames to the sequence
def stop_recording(self):
# Clear the remaining keys
self.current_slot.sequence_data.append(
SequenceFrame(set(), self.current_slot.sequence_data[-1].timestamp + 20)
)
# Wait for the specified interval
prev_timestamp = self.current_slot.sequence_data[-1].timestamp
self.current_slot.sequence_data.append(
SequenceFrame(
set(),
prev_timestamp + self.current_slot.interval * 1000,
)
)
self.status = SequenceStatus.STOPPED
def play_frame(self, keyboard):
# Send the keypresses at this point in the sequence
if not check_deadline(
ticks_ms(),
self.start_time,
self.current_slot.sequence_data[self.index].timestamp,
):
if self.index:
prev = self.current_slot.sequence_data[self.index - 1].keys_pressed
cur = self.current_slot.sequence_data[self.index].keys_pressed
for key in prev.difference(cur):
keyboard.remove_key(key)
for key in cur.difference(prev):
keyboard.add_key(key)
self.index += 1
if self.index >= len(self.current_slot.sequence_data): # Reached the end
self.current_repetition += 1
if self.current_repetition == self.current_slot.repetitions:
self.status = SequenceStatus.STOPPED
else:
self.index = 0
self.start_time = ticks_ms()
# Configuration for repeating sequences
def config_mode(self, keyboard):
for key in keyboard.keys_pressed.difference(self.last_config_frame):
if key.code in _numbers:
digit = (key.code - KC.N1.code + 1) % 10
if self.status == SequenceStatus.SET_REPEPITIONS:
self.current_slot.repetitions = (
self.current_slot.repetitions * 10 + digit
)
elif self.status == SequenceStatus.SET_INTERVAL:
self.current_slot.interval = self.current_slot.interval * 10 + digit
elif key.code == KC.ENTER.code:
self.stop_config()
self.last_config_frame = keyboard.keys_pressed.copy()
keyboard.hid_pending = False # Disable typing
if not check_deadline(ticks_ms(), self.start_time, self.timeout):
self.stop_config()
# Finish configuring repetitions
def stop_config(self):
self.current_slot.sequence_data[-1] = SequenceFrame(
self.current_slot.sequence_data[-1].keys_pressed,
self.current_slot.sequence_data[-2].timestamp
+ self.current_slot.interval * 1000,
)
self.current_slot.repetitions = max(self.current_slot.repetitions, 1)
self.status = SequenceStatus.STOPPED
def on_runtime_enable(self, keyboard):
return
def on_runtime_disable(self, keyboard):
return
def during_bootup(self, keyboard):
return
def before_matrix_scan(self, keyboard):
return
def after_matrix_scan(self, keyboard):
return
def before_hid_send(self, keyboard):
if not self.status:
return
elif self.status == SequenceStatus.RECORDING:
self.record_frame(keyboard.keys_pressed)
elif self.status == SequenceStatus.PLAYING:
self.play_frame(keyboard)
elif (
self.status == SequenceStatus.SET_REPEPITIONS
or self.status == SequenceStatus.SET_INTERVAL
):
self.config_mode(keyboard)
def after_hid_send(self, keyboard):
return
def on_powersave_enable(self, keyboard):
return
def on_powersave_disable(self, keyboard):
return

View file

@ -1,130 +0,0 @@
'''
Extension handles usage of AS5013 by AMS
'''
from supervisor import ticks_ms
from kmk.keys import AX
from kmk.modules import Module
I2C_ADDRESS = 0x40
I2X_ALT_ADDRESS = 0x41
X = 0x10
Y_RES_INT = 0x11
XP = 0x12
XN = 0x13
YP = 0x14
YN = 0x15
M_CTRL = 0x2B
T_CTRL = 0x2D
Y_OFFSET = 17
X_OFFSET = 7
DEAD_X = 5
DEAD_Y = 5
class Easypoint(Module):
'''Module handles usage of AS5013 by AMS'''
def __init__(
self,
i2c,
address=I2C_ADDRESS,
y_offset=Y_OFFSET,
x_offset=X_OFFSET,
dead_x=DEAD_X,
dead_y=DEAD_Y,
):
self._i2c_address = address
self._i2c_bus = i2c
# HID parameters
self.polling_interval = 20
self.last_tick = ticks_ms()
# Offsets for poor soldering
self.y_offset = y_offset
self.x_offset = x_offset
# Deadzone
self.dead_x = DEAD_X
self.dead_y = DEAD_Y
def during_bootup(self, keyboard):
return
def before_matrix_scan(self, keyboard):
'''
Return value will be injected as an extra matrix update
'''
now = ticks_ms()
if now - self.last_tick < self.polling_interval:
return
self.last_tick = now
x, y = self._read_raw_state()
# I'm a shit coder, so offset is handled in software side
s_x = self.getSignedNumber(x, 8) - self.x_offset
s_y = self.getSignedNumber(y, 8) - self.y_offset
# Evaluate Deadzone
if s_x in range(-self.dead_x, self.dead_x) and s_y in range(
-self.dead_y, self.dead_y
):
# Within bounds, just die
return
else:
# Set the X/Y from easypoint
AX.X.move(keyboard, x)
AX.Y.move(keyboard, y)
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 _read_raw_state(self):
'''Read data from AS5013'''
x, y = self._i2c_rdwr([X], length=2)
return x, y
def getSignedNumber(self, number, bitLength=8):
mask = (2**bitLength) - 1
if number & (1 << (bitLength - 1)):
return number | ~mask
else:
return number & mask
def _i2c_rdwr(self, data, length=1):
'''Write and optionally read I2C data.'''
while not self._i2c_bus.try_lock():
pass
try:
if length > 0:
result = bytearray(length)
self._i2c_bus.writeto_then_readfrom(
self._i2c_address, bytes(data), result
)
return result
else:
self._i2c_bus.writeto(self._i2c_address, bytes(data))
return []
finally:
self._i2c_bus.unlock()

View file

@ -1,334 +0,0 @@
# See docs/encoder.md for how to use
import busio
import digitalio
from supervisor import ticks_ms
from kmk.modules import Module
from kmk.utils import Debug
debug = Debug(__name__)
# NB : not using rotaryio as it requires the pins to be consecutive
class BaseEncoder:
VELOCITY_MODE = True
def __init__(self, is_inverted=False, divisor=4):
self.is_inverted = is_inverted
self.divisor = divisor
self._state = None
self._start_state = None
self._direction = None
self._pos = 0
self._button_state = True
self._button_held = None
self._velocity = 0
self._movement = 0
self._timestamp = ticks_ms()
# callback functions on events. Need to be defined externally
self.on_move_do = None
self.on_button_do = None
def get_state(self):
return {
'direction': self.is_inverted and -self._direction or self._direction,
'position': self.is_inverted and -self._pos or self._pos,
'is_pressed': not self._button_state,
'velocity': self._velocity,
}
# Called in a loop to refresh encoder state
def update_state(self):
# Rotation events
new_state = (self.pin_a.get_value(), self.pin_b.get_value())
if new_state != self._state:
# encoder moved
self._movement += 1
# false / false and true / true are common half steps
# looking on the step just before helps determining
# the direction
if new_state[0] == new_state[1] and self._state[0] != self._state[1]:
if new_state[1] == self._state[0]:
self._direction = 1
else:
self._direction = -1
# when the encoder settles on a position (every 2 steps)
if new_state[0] == new_state[1]:
# an encoder returned to the previous
# position halfway, cancel rotation
if (
self._start_state[0] == new_state[0]
and self._start_state[1] == new_state[1]
and self._movement <= 2
):
self._movement = 0
self._direction = 0
# when the encoder made a full loop according to its divisor
elif self._movement >= self.divisor - 1:
# 1 full step is 4 movements (2 for high-resolution encoder),
# however, when rotated quickly, some steps may be missed.
# This makes it behave more naturally
real_movement = self._movement // self.divisor
self._pos += self._direction * real_movement
if self.on_move_do is not None:
for i in range(real_movement):
self.on_move_do(self.get_state())
# Rotation finished, reset to identify new movement
self._movement = 0
self._direction = 0
self._start_state = new_state
self._state = new_state
# Velocity
self.velocity_event()
# Button event
self.button_event()
def velocity_event(self):
if self.VELOCITY_MODE:
new_timestamp = ticks_ms()
self._velocity = new_timestamp - self._timestamp
self._timestamp = new_timestamp
def button_event(self):
raise NotImplementedError('subclasses must override button_event()!')
# return knob velocity as milliseconds between position changes (detents)
# for backwards compatibility
def vel_report(self):
return self._velocity
class GPIOEncoder(BaseEncoder):
def __init__(
self,
pin_a,
pin_b,
pin_button=None,
is_inverted=False,
divisor=None,
button_pull=digitalio.Pull.UP,
):
super().__init__(is_inverted)
# Divisor can be 4 or 2 depending on whether the detent
# on the encoder is defined by 2 or 4 pulses
self.divisor = divisor
self.pin_a = EncoderPin(pin_a)
self.pin_b = EncoderPin(pin_b)
if pin_button:
self.pin_button = EncoderPin(pin_button, button_type=True, pull=button_pull)
else:
self.pin_button = None
self._state = (self.pin_a.get_value(), self.pin_b.get_value())
self._start_state = self._state
def button_event(self):
if self.pin_button:
new_button_state = self.pin_button.get_value()
if new_button_state != self._button_state:
self._button_state = new_button_state
if self.on_button_do is not None:
self.on_button_do(self.get_state())
class EncoderPin:
def __init__(self, pin, button_type=False, pull=digitalio.Pull.UP):
self.pin = pin
self.button_type = button_type
self.pull = pull
self.prepare_pin()
def prepare_pin(self):
if self.pin is not None:
if isinstance(self.pin, digitalio.DigitalInOut):
self.io = self.pin
else:
self.io = digitalio.DigitalInOut(self.pin)
self.io.direction = digitalio.Direction.INPUT
self.io.pull = self.pull
else:
self.io = None
def get_value(self):
io = self.io
result = io.value
if digitalio.Pull.UP != io.pull:
result = not result
return result
class I2CEncoder(BaseEncoder):
def __init__(self, i2c, address, is_inverted=False):
try:
from adafruit_seesaw import digitalio, neopixel, rotaryio, seesaw
except ImportError:
if debug.enabled:
debug('seesaw missing')
return
super().__init__(is_inverted)
self.seesaw = seesaw.Seesaw(i2c, address)
# Check for correct product
seesaw_product = (self.seesaw.get_version() >> 16) & 0xFFFF
if seesaw_product != 4991:
if debug.enabled:
debug('Wrong firmware loaded? Expected 4991')
self.encoder = rotaryio.IncrementalEncoder(self.seesaw)
self.seesaw.pin_mode(24, self.seesaw.INPUT_PULLUP)
self.switch = digitalio.DigitalIO(self.seesaw, 24)
self.pixel = neopixel.NeoPixel(self.seesaw, 6, 1)
self._state = self.encoder.position
def update_state(self):
# Rotation events
new_state = self.encoder.position
if new_state != self._state:
# it moves !
self._movement += 1
# false / false and true / true are common half steps
# looking on the step just before helps determining
# the direction
if self.encoder.position > self._state:
self._direction = 1
else:
self._direction = -1
self._state = new_state
self.on_move_do(self.get_state())
# Velocity
self.velocity_event()
# Button events
self.button_event()
def button_event(self):
if not self.switch.value and not self._button_held:
# Pressed
self._button_held = True
if self.on_button_do is not None:
self.on_button_do(self.get_state())
if self.switch.value and self._button_held:
# Released
self._button_held = False
def get_state(self):
return {
'direction': self.is_inverted and -self._direction or self._direction,
'position': self._state,
'is_pressed': not self.switch.value,
'is_held': self._button_held,
'velocity': self._velocity,
}
class EncoderHandler(Module):
def __init__(self):
self.encoders = []
self.pins = None
self.map = None
self.divisor = 4
def on_runtime_enable(self, keyboard):
return
def on_runtime_disable(self, keyboard):
return
def during_bootup(self, keyboard):
if self.pins and self.map:
for idx, pins in enumerate(self.pins):
try:
# Check for busio.I2C
if isinstance(pins[0], busio.I2C):
new_encoder = I2CEncoder(*pins)
# Else fall back to GPIO
else:
new_encoder = GPIOEncoder(*pins)
# Set default divisor if unset
if new_encoder.divisor is None:
new_encoder.divisor = self.divisor
# In our case, we need to define keybord and encoder_id for callbacks
new_encoder.on_move_do = lambda x, bound_idx=idx: self.on_move_do(
keyboard, bound_idx, x
)
new_encoder.on_button_do = (
lambda x, bound_idx=idx: self.on_button_do(
keyboard, bound_idx, x
)
)
self.encoders.append(new_encoder)
except Exception as e:
if debug.enabled:
debug(e)
return
def on_move_do(self, keyboard, encoder_id, state):
if self.map:
layer_id = keyboard.active_layers[0]
# if Left, key index 0 else key index 1
if state['direction'] == -1:
key_index = 0
else:
key_index = 1
key = self.map[layer_id][encoder_id][key_index]
keyboard.tap_key(key)
def on_button_do(self, keyboard, encoder_id, state):
if state['is_pressed'] is True:
layer_id = keyboard.active_layers[0]
key = self.map[layer_id][encoder_id][2]
keyboard.tap_key(key)
def before_matrix_scan(self, keyboard):
'''
Return value will be injected as an extra matrix update
'''
for encoder in self.encoders:
encoder.update_state()
return keyboard
def after_matrix_scan(self, keyboard):
'''
Return value will be replace matrix update if supplied
'''
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

View file

@ -1,272 +0,0 @@
from micropython import const
from kmk.keys import Key, make_argumented_key
from kmk.modules import Module
from kmk.utils import Debug
debug = Debug(__name__)
class ActivationType:
PRESSED = const(0)
RELEASED = const(1)
HOLD_TIMEOUT = const(2)
INTERRUPTED = const(3)
REPEAT = const(4)
class HoldTapRepeat:
NONE = const(0)
TAP = const(1)
HOLD = const(2)
ALL = const(3)
class HoldTapKeyState:
def __init__(self, timeout_key, *args, **kwargs):
self.timeout_key = timeout_key
self.args = args
self.kwargs = kwargs
self.activated = ActivationType.PRESSED
class HoldTapKey(Key):
def __init__(
self,
tap,
hold,
prefer_hold=True,
tap_interrupted=False,
tap_time=None,
repeat=HoldTapRepeat.NONE,
**kwargs,
):
super().__init__(**kwargs)
self.tap = tap
self.hold = hold
self.prefer_hold = prefer_hold
self.tap_interrupted = tap_interrupted
self.tap_time = tap_time
self.repeat = repeat
class HoldTap(Module):
tap_time = 300
def __init__(self, _make_key=True):
self.key_buffer = []
self.key_states = {}
if _make_key:
make_argumented_key(
names=('HT',),
constructor=HoldTapKey,
on_press=self.ht_pressed,
on_release=self.ht_released,
)
def during_bootup(self, keyboard):
return
def before_matrix_scan(self, keyboard):
return
def after_matrix_scan(self, keyboard):
return
def process_key(self, keyboard, key, is_pressed, int_coord):
'''Handle holdtap being interrupted by another key press/release.'''
current_key = key
send_buffer = False
append_buffer = False
for key, state in self.key_states.items():
if key == current_key:
continue
if state.activated != ActivationType.PRESSED:
continue
# holdtap isn't interruptable, resolves on ht_release or timeout.
if not key.tap_interrupted and not key.prefer_hold:
append_buffer = is_pressed or self.key_buffer
continue
# holdtap is interrupted by another key event.
if (is_pressed and not key.tap_interrupted) or (
not is_pressed and key.tap_interrupted and self.key_buffer
):
keyboard.cancel_timeout(state.timeout_key)
self.key_states[key].activated = ActivationType.INTERRUPTED
self.ht_activate_on_interrupt(
key,
keyboard,
*state.args,
**state.kwargs,
)
append_buffer = True
send_buffer = True
# if interrupt on release: store interrupting keys until one of them
# is released.
if key.tap_interrupted and is_pressed:
append_buffer = True
# apply changes with 'side-effects' on key_states or the loop behaviour
# outside the loop.
if append_buffer:
self.key_buffer.append((int_coord, current_key, is_pressed))
current_key = None
if send_buffer:
self.send_key_buffer(keyboard)
return current_key
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 ht_pressed(self, key, keyboard, *args, **kwargs):
'''Unless in repeat mode, do nothing yet, action resolves when key is released, timer expires or other key is pressed.'''
if key in self.key_states:
state = self.key_states[key]
keyboard.cancel_timeout(self.key_states[key].timeout_key)
if state.activated == ActivationType.RELEASED:
state.activated = ActivationType.REPEAT
self.ht_activate_tap(key, keyboard, *args, **kwargs)
elif state.activated == ActivationType.HOLD_TIMEOUT:
self.ht_activate_hold(key, keyboard, *args, **kwargs)
elif state.activated == ActivationType.INTERRUPTED:
self.ht_activate_on_interrupt(key, keyboard, *args, **kwargs)
return
if key.tap_time is None:
tap_time = self.tap_time
else:
tap_time = key.tap_time
timeout_key = keyboard.set_timeout(
tap_time,
lambda: self.on_tap_time_expired(key, keyboard, *args, **kwargs),
)
self.key_states[key] = HoldTapKeyState(timeout_key, *args, **kwargs)
return keyboard
def ht_released(self, key, keyboard, *args, **kwargs):
'''On keyup, release mod or tap key.'''
if key not in self.key_states:
return keyboard
state = self.key_states[key]
keyboard.cancel_timeout(state.timeout_key)
repeat = key.repeat & HoldTapRepeat.TAP
if state.activated == ActivationType.HOLD_TIMEOUT:
# release hold
self.ht_deactivate_hold(key, keyboard, *args, **kwargs)
repeat = key.repeat & HoldTapRepeat.HOLD
elif state.activated == ActivationType.INTERRUPTED:
# release tap
self.ht_deactivate_on_interrupt(key, keyboard, *args, **kwargs)
if key.prefer_hold:
repeat = key.repeat & HoldTapRepeat.HOLD
elif state.activated == ActivationType.PRESSED:
# press and release tap because key released within tap time
self.ht_activate_tap(key, keyboard, *args, **kwargs)
self.send_key_buffer(keyboard)
self.ht_deactivate_tap(key, keyboard, *args, **kwargs)
state.activated = ActivationType.RELEASED
self.send_key_buffer(keyboard)
elif state.activated == ActivationType.REPEAT:
state.activated = ActivationType.RELEASED
self.ht_deactivate_tap(key, keyboard, *args, **kwargs)
# don't delete the key state right now in this case
if repeat:
if key.tap_time is None:
tap_time = self.tap_time
else:
tap_time = key.tap_time
state.timeout_key = keyboard.set_timeout(
tap_time, lambda: self.key_states.pop(key)
)
else:
del self.key_states[key]
return keyboard
def on_tap_time_expired(self, key, keyboard, *args, **kwargs):
'''When tap time expires activate hold if key is still being pressed.
Remove key if ActivationType is RELEASED.'''
try:
state = self.key_states[key]
except KeyError:
if debug.enabled:
debug(f'on_tap_time_expired: no such key {key}')
return
if self.key_states[key].activated == ActivationType.PRESSED:
# press hold because timer expired after tap time
self.key_states[key].activated = ActivationType.HOLD_TIMEOUT
self.ht_activate_hold(key, keyboard, *args, **kwargs)
self.send_key_buffer(keyboard)
elif state.activated == ActivationType.RELEASED:
self.ht_deactivate_tap(key, keyboard, *args, **kwargs)
del self.key_states[key]
def send_key_buffer(self, keyboard):
if not self.key_buffer:
return
reprocess = False
for int_coord, key, is_pressed in self.key_buffer:
keyboard.resume_process_key(self, key, is_pressed, int_coord, reprocess)
if isinstance(key, HoldTapKey):
reprocess = True
self.key_buffer.clear()
def ht_activate_hold(self, key, keyboard, *args, **kwargs):
if debug.enabled:
debug('ht_activate_hold')
keyboard.resume_process_key(self, key.hold, True)
def ht_deactivate_hold(self, key, keyboard, *args, **kwargs):
if debug.enabled:
debug('ht_deactivate_hold')
keyboard.resume_process_key(self, key.hold, False)
def ht_activate_tap(self, key, keyboard, *args, **kwargs):
if debug.enabled:
debug('ht_activate_tap')
keyboard.resume_process_key(self, key.tap, True)
def ht_deactivate_tap(self, key, keyboard, *args, **kwargs):
if debug.enabled:
debug('ht_deactivate_tap')
keyboard.resume_process_key(self, key.tap, False)
def ht_activate_on_interrupt(self, key, keyboard, *args, **kwargs):
if debug.enabled:
debug('ht_activate_on_interrupt')
if key.prefer_hold:
self.ht_activate_hold(key, keyboard, *args, **kwargs)
else:
self.ht_activate_tap(key, keyboard, *args, **kwargs)
def ht_deactivate_on_interrupt(self, key, keyboard, *args, **kwargs):
if debug.enabled:
debug('ht_deactivate_on_interrupt')
if key.prefer_hold:
self.ht_deactivate_hold(key, keyboard, *args, **kwargs)
else:
self.ht_deactivate_tap(key, keyboard, *args, **kwargs)

View file

@ -1,193 +0,0 @@
'''One layer isn't enough. Adds keys to get to more of them'''
from kmk.keys import KC, Key, make_argumented_key
from kmk.modules.holdtap import HoldTap, HoldTapKey
from kmk.utils import Debug
debug = Debug(__name__)
def lt_key(layer, key, prefer_hold=False, **kwargs):
return HoldTapKey(tap=key, hold=KC.MO(layer), prefer_hold=prefer_hold, **kwargs)
def tt_key(layer, prefer_hold=True, **kwargs):
return HoldTapKey(
tap=KC.TG(layer),
hold=KC.MO(layer),
prefer_hold=prefer_hold,
**kwargs,
)
class LayerKey(Key):
def __init__(self, layer, key=None, **kwargs):
super().__init__(**kwargs)
self.layer = layer
self.key = key
class Layers(HoldTap):
'''Gives access to the keys used to enable the layer system'''
_active_combo = None
def __init__(self, combo_layers=None):
# Layers
super().__init__(_make_key=False)
self.combo_layers = combo_layers
make_argumented_key(
names=('MO',),
constructor=LayerKey,
on_press=self._mo_pressed,
on_release=self._mo_released,
)
make_argumented_key(
names=('FD',),
constructor=LayerKey,
on_press=self._fd_pressed,
)
make_argumented_key(
names=('DF',),
constructor=LayerKey,
on_press=self._df_pressed,
)
make_argumented_key(
names=('LM',),
constructor=LayerKey,
on_press=self._lm_pressed,
on_release=self._lm_released,
)
make_argumented_key(
names=('TG',),
constructor=LayerKey,
on_press=self._tg_pressed,
)
make_argumented_key(
names=('TO',),
constructor=LayerKey,
on_press=self._to_pressed,
)
make_argumented_key(
names=('LT',),
constructor=lt_key,
on_press=self.ht_pressed,
on_release=self.ht_released,
)
make_argumented_key(
names=('TT',),
constructor=tt_key,
on_press=self.ht_pressed,
on_release=self.ht_released,
)
def _fd_pressed(self, key, keyboard, *args, **kwargs):
'''
Switches the top layer
'''
self.activate_layer(keyboard, key.layer, idx=0)
def _df_pressed(self, key, keyboard, *args, **kwargs):
'''
Switches the default layer
'''
self.activate_layer(keyboard, key.layer, idx=-1)
def _mo_pressed(self, key, keyboard, *args, **kwargs):
'''
Momentarily activates layer, switches off when you let go
'''
self.activate_layer(keyboard, key.layer)
def _mo_released(self, key, keyboard, *args, **kwargs):
self.deactivate_layer(keyboard, key.layer)
def _lm_pressed(self, key, keyboard, *args, **kwargs):
'''
As MO(layer) but with mod active
'''
keyboard.hid_pending = True
keyboard.keys_pressed.add(key.key)
self.activate_layer(keyboard, key.layer)
def _lm_released(self, key, keyboard, *args, **kwargs):
'''
As MO(layer) but with mod active
'''
keyboard.hid_pending = True
keyboard.keys_pressed.discard(key.key)
self.deactivate_layer(keyboard, key.layer)
def _tg_pressed(self, key, keyboard, *args, **kwargs):
'''
Toggles the layer (enables it if not active, and vise versa)
'''
# See mo_released for implementation details around this
if key.layer in keyboard.active_layers:
self.deactivate_layer(keyboard, key.layer)
else:
self.activate_layer(keyboard, key.layer)
def _to_pressed(self, key, keyboard, *args, **kwargs):
'''
Activates layer and deactivates all other layers
'''
self._active_combo = None
keyboard.active_layers.clear()
self.activate_layer(keyboard, key.layer)
def _print_debug(self, keyboard):
if debug.enabled:
debug(f'active_layers={keyboard.active_layers}')
def activate_layer(self, keyboard, layer, idx=None):
if idx is None:
keyboard.active_layers.insert(0, layer)
else:
keyboard.active_layers[idx] = layer
if self.combo_layers:
self._activate_combo_layer(keyboard)
self._print_debug(keyboard)
def deactivate_layer(self, keyboard, layer):
# Remove the first instance of the target layer from the active list
# under almost all normal use cases, this will disable the layer (but
# preserve it if it was triggered as a default layer, etc.).
# This also resolves an issue where using DF() on a layer
# triggered by MO() and then defaulting to the MO()'s layer
# would result in no layers active.
if len(keyboard.active_layers) > 1:
try:
idx = keyboard.active_layers.index(layer)
del keyboard.active_layers[idx]
except ValueError:
if debug.enabled:
debug(f'_mo_released: layer {layer} not active')
if self.combo_layers:
self._deactivate_combo_layer(keyboard, layer)
self._print_debug(keyboard)
def _activate_combo_layer(self, keyboard):
if self._active_combo:
return
for combo, result in self.combo_layers.items():
matching = True
for layer in combo:
if layer not in keyboard.active_layers:
matching = False
break
if matching:
self._active_combo = combo
keyboard.active_layers.insert(0, result)
break
def _deactivate_combo_layer(self, keyboard, layer):
if self._active_combo and layer in self._active_combo:
keyboard.active_layers.remove(self.combo_layers[self._active_combo])
self._active_combo = None

View file

@ -1,315 +0,0 @@
from micropython import const
from kmk.keys import KC, Key, make_argumented_key, make_key
from kmk.modules import Module
from kmk.scheduler import create_task
from kmk.utils import Debug
debug = Debug(__name__)
_IDLE = const(0)
_ON_PRESS = const(1)
_ON_HOLD = const(2)
_RELEASE = const(3)
_ON_RELEASE = const(4)
class MacroKey(Key):
def __init__(
self,
*args,
on_press=None,
on_hold=None,
on_release=None,
blocking=True,
_on_press=None,
_on_release=None,
):
super().__init__(on_press=_on_press, on_release=_on_release)
if on_press is not None:
self.on_press_macro = on_press
else:
self.on_press_macro = args
self.on_hold_macro = on_hold
self.on_release_macro = on_release
self.blocking = blocking
self.state = _IDLE
self._task = None
class UnicodeModeKey(Key):
def __init__(self, mode, **kwargs):
super().__init__(**kwargs)
self.mode = mode
def Delay(delay):
return lambda keyboard: delay
def Press(key):
return lambda keyboard: key.on_press(keyboard)
def Release(key):
return lambda keyboard: key.on_release(keyboard)
def Tap(key):
def _(keyboard):
key.on_press(keyboard)
yield
key.on_release(keyboard)
return _
class UnicodeModeIBus:
@staticmethod
def pre(keyboard):
macro = (KC.LCTL, KC.LSFT, KC.U)
for k in macro:
k.on_press(keyboard)
yield
for k in macro:
k.on_release(keyboard)
@staticmethod
def post(keyboard):
KC.ENTER.on_press(keyboard)
yield
KC.ENTER.on_release(keyboard)
class UnicodeModeMacOS:
@staticmethod
def pre(keyboard):
KC.LALT.on_press(keyboard)
yield
@staticmethod
def post(keyboard):
KC.LALT.on_release(keyboard)
yield
class UnicodeModeWinC:
@staticmethod
def pre(keyboard):
macro = (KC.RALT, KC.U)
for k in macro:
k.on_press(keyboard)
yield
for k in macro:
k.on_release(keyboard)
@staticmethod
def post(keyboard):
KC.ENTER.on_press(keyboard)
yield
KC.ENTER.on_release(keyboard)
def MacroIter(keyboard, macro, unicode_mode):
for item in macro:
if callable(item):
item = item(keyboard)
if item is None:
yield
elif isinstance(item, int):
yield item
elif isinstance(item, str):
for char in item:
if ord(char) <= 127:
# ANSII key codes
key = KC[char]
if char.isupper():
KC.LSHIFT.on_press(keyboard)
key.on_press(keyboard)
yield
if char.isupper():
KC.LSHIFT.on_release(keyboard)
key.on_release(keyboard)
yield
else:
# unicode code points
yield from unicode_mode.pre(keyboard)
yield
for digit in hex(ord(char))[2:]:
key = KC[digit]
key.on_press(keyboard)
yield
key.on_release(keyboard)
yield
yield from unicode_mode.post(keyboard)
yield
elif item.__class__.__name__ == 'generator':
yield from MacroIter(keyboard, item, unicode_mode)
yield
elif debug.enabled:
debug('unsupported macro type ', item.__class__.__name__)
class Macros(Module):
def __init__(self, unicode_mode=UnicodeModeIBus, delay=10):
self._active = []
self.key_buffer = []
self.unicode_mode = unicode_mode
self.delay = delay
make_argumented_key(
names=('MACRO',),
constructor=MacroKey,
_on_press=self.on_press_macro,
_on_release=self.on_release_macro,
)
make_key(
names=('UC_MODE_IBUS',),
constructor=UnicodeModeKey,
mode=UnicodeModeIBus,
on_press=self.on_press_unicode_mode,
)
make_key(
names=('UC_MODE_MACOS',),
constructor=UnicodeModeKey,
mode=UnicodeModeMacOS,
on_press=self.on_press_unicode_mode,
)
make_key(
names=('UC_MODE_WINC',),
constructor=UnicodeModeKey,
mode=UnicodeModeWinC,
on_press=self.on_press_unicode_mode,
)
def during_bootup(self, keyboard):
return
def before_matrix_scan(self, keyboard):
return
def after_matrix_scan(self, keyboard):
return
def process_key(self, keyboard, key, is_pressed, int_coord):
# Passthrough if there are no active macros, or the key belongs to an
# active macro, or all active macros or non-blocking.
if not self._active or key in self._active or not self._active[-1].blocking:
return key
self.key_buffer.append((int_coord, key, is_pressed))
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 on_press_unicode_mode(self, key, keyboard, *args, **kwargs):
self.unicode_mode = key.mode
def on_press_macro(self, key, keyboard, *args, **kwargs):
if key.state == _IDLE:
key.state = _ON_PRESS
self.process_macro_async(keyboard, key)
else:
self.key_buffer.append((args[1], key, True))
def on_release_macro(self, key, keyboard, *args, **kwargs):
if key.state == _ON_PRESS or key.state == _ON_HOLD:
key.state = _RELEASE
if key._task is None:
self.process_macro_async(keyboard, key)
else:
self.key_buffer.append((args[1], key, False))
def process_macro_async(self, keyboard, key, _iter=None):
# There's no active macro iterator: select the next one.
if _iter is None:
key._task = None
if key.state == _ON_PRESS:
self._active.append(key)
if (macro := key.on_press_macro) is None:
key.state = _ON_HOLD
elif debug.enabled:
debug('on_press')
if key.state == _ON_HOLD:
if (macro := key.on_hold_macro) is None:
return
elif debug.enabled:
debug('on_hold')
if key.state == _RELEASE:
key.state = _ON_RELEASE
if key.state == _ON_RELEASE:
if (macro := key.on_release_macro) is None:
macro = ()
elif debug.enabled:
debug('on_release')
_iter = MacroIter(keyboard, macro, self.unicode_mode)
# Run one step in the macro sequence.
delay = self.delay
try:
# any not None value the iterator yields is a delay value in ms.
ret = next(_iter)
if ret is not None:
delay = ret
keyboard._send_hid()
# The sequence has reached its end: advance the macro state.
except StopIteration:
_iter = None
delay = 0
key._task = None
if key.state == _ON_PRESS:
key.state = _ON_HOLD
elif key.state == _ON_RELEASE:
if debug.enabled:
debug('deactivate')
key.state = _IDLE
self._active.remove(key)
self.send_key_buffer(keyboard)
return
# Schedule the next step.
# Reuse existing task objects and save a couple of bytes and cycles for the gc.
if key._task:
task = key._task
else:
def task():
self.process_macro_async(keyboard, key, _iter)
key._task = create_task(task, after_ms=delay)
def send_key_buffer(self, keyboard):
if not self.key_buffer or self._active:
return
for int_coord, key, is_pressed in self.key_buffer:
keyboard.resume_process_key(self, key, is_pressed, int_coord, False)
self.key_buffer.clear()

View file

@ -1,114 +0,0 @@
import adafruit_midi
import usb_midi
from adafruit_midi.control_change import ControlChange
from adafruit_midi.note_off import NoteOff
from adafruit_midi.note_on import NoteOn
from adafruit_midi.pitch_bend import PitchBend
from adafruit_midi.program_change import ProgramChange
from adafruit_midi.start import Start
from adafruit_midi.stop import Stop
from kmk.keys import Key, make_argumented_key
from kmk.modules import Module
from kmk.utils import Debug
debug = Debug(__name__)
class MidiKey(Key):
def __init__(self, *args, command, channel=None, **kwargs):
super().__init__(**kwargs)
self.on_press_msg = command(*args, channel=channel)
self.on_release_msg = None
def midi_note_key(note=69, velocity=127, channel=None, **kwargs):
key = MidiKey(note, velocity, command=NoteOn, channel=channel, **kwargs)
key.on_release_msg = NoteOff(note, velocity, channel=channel)
return key
class MidiKeys(Module):
def __init__(self):
make_argumented_key(
names=('MIDI_CC',),
constructor=MidiKey,
command=ControlChange,
on_press=self.on_press,
)
make_argumented_key(
names=('MIDI_NOTE',),
constructor=midi_note_key,
on_press=self.on_press,
on_release=self.on_release,
)
make_argumented_key(
names=('MIDI_PB',),
constructor=MidiKey,
command=PitchBend,
on_press=self.on_press,
)
make_argumented_key(
names=('MIDI_PC',),
constructor=MidiKey,
command=ProgramChange,
on_press=self.on_press,
)
make_argumented_key(
names=('MIDI_START',),
constructor=MidiKey,
command=Start,
on_press=self.on_press,
)
make_argumented_key(
names=('MIDI_STOP',),
constructor=MidiKey,
command=Stop,
on_press=self.on_press,
)
try:
self.midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)
except IndexError:
self.midi = None
if debug.enabled:
debug('No midi device found.')
def during_bootup(self, keyboard):
return None
def before_matrix_scan(self, keyboard):
return None
def after_matrix_scan(self, keyboard):
return None
def process_key(self, keyboard, key, is_pressed, int_coord):
return key
def before_hid_send(self, keyboard):
return None
def after_hid_send(self, keyboard):
return None
def on_powersave_enable(self, keyboard):
return None
def on_powersave_disable(self, keyboard):
return None
def send(self, message):
if self.midi:
self.midi.send(message)
def on_press(self, key, keyboard, *args, **kwargs):
self.send(key.on_press_msg)
def on_release(self, key, keyboard, *args, **kwargs):
self.send(key.on_release_msg)

View file

@ -1,152 +0,0 @@
from micropython import const
from kmk.keys import AX, MouseKey, make_key
from kmk.modules import Module
from kmk.scheduler import cancel_task, create_task
_MU = const(0x01)
_MD = const(0x02)
_ML = const(0x04)
_MR = const(0x08)
_WU = const(0x10)
_WD = const(0x20)
_WL = const(0x40)
_WR = const(0x80)
class MouseKeys(Module):
def __init__(self, max_speed=10, acc_interval=20, move_step=1):
self._movement = 0
self.max_speed = max_speed
self.acc_interval = acc_interval
self.move_step = move_step
codes = (
(0x01, ('MB_LMB',)),
(0x02, ('MB_RMB',)),
(0x04, ('MB_MMB',)),
(0x08, ('MB_BTN4',)),
(0x10, ('MB_BTN5',)),
)
for code, names in codes:
make_key(names=names, constructor=MouseKey, code=code)
keys = (
(('MW_UP',), self._mw_up_press, self._mw_up_release),
(('MW_DOWN', 'MW_DN'), self._mw_down_press, self._mw_down_release),
(('MW_LEFT', 'MW_LT'), self._mw_left_press, self._mw_left_release),
(('MW_RIGHT', 'MW_RT'), self._mw_right_press, self._mw_right_release),
(('MS_UP',), self._ms_up_press, self._ms_up_release),
(('MS_DOWN', 'MS_DN'), self._ms_down_press, self._ms_down_release),
(('MS_LEFT', 'MS_LT'), self._ms_left_press, self._ms_left_release),
(('MS_RIGHT', 'MS_RT'), self._ms_right_press, self._ms_right_release),
)
for names, on_press, on_release in keys:
make_key(names=names, on_press=on_press, on_release=on_release)
def during_bootup(self, keyboard):
self._task = create_task(
lambda: self._move(keyboard),
period_ms=self.acc_interval,
)
cancel_task(self._task)
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 _move(self, keyboard):
if self._movement & (_MR + _ML + _MD + _MU):
if self.move_step < self.max_speed:
self.move_step = self.move_step + 1
if self._movement & _MU:
AX.Y.move(keyboard, -self.move_step)
if self._movement & _MD:
AX.Y.move(keyboard, self.move_step)
if self._movement & _ML:
AX.X.move(keyboard, -self.move_step)
if self._movement & _MR:
AX.X.move(keyboard, self.move_step)
if self._movement & _WU:
AX.W.move(keyboard, 1)
if self._movement & _WD:
AX.W.move(keyboard, -1)
if self._movement & _WL:
AX.P.move(keyboard, -1)
if self._movement & _WR:
AX.P.move(keyboard, 1)
def _maybe_start_move(self, mask):
self._movement |= mask
if self._movement == mask:
self._task.restart()
def _maybe_stop_move(self, mask):
self._movement &= ~mask
if not self._movement & (_MR + _ML + _MD + _MU):
self.move_step = 1
if not self._movement:
cancel_task(self._task)
def _mw_up_press(self, key, keyboard, *args, **kwargs):
self._maybe_start_move(_WU)
def _mw_up_release(self, key, keyboard, *args, **kwargs):
self._maybe_stop_move(_WU)
def _mw_down_press(self, key, keyboard, *args, **kwargs):
self._maybe_start_move(_WD)
def _mw_down_release(self, key, keyboard, *args, **kwargs):
self._maybe_stop_move(_WD)
def _mw_left_press(self, key, keyboard, *args, **kwargs):
self._maybe_start_move(_WL)
def _mw_left_release(self, key, keyboard, *args, **kwargs):
self._maybe_stop_move(_WL)
def _mw_right_press(self, key, keyboard, *args, **kwargs):
self._maybe_start_move(_WR)
def _mw_right_release(self, key, keyboard, *args, **kwargs):
self._maybe_stop_move(_WR)
def _ms_up_press(self, key, keyboard, *args, **kwargs):
self._maybe_start_move(_MU)
def _ms_up_release(self, key, keyboard, *args, **kwargs):
self._maybe_stop_move(_MU)
def _ms_down_press(self, key, keyboard, *args, **kwargs):
self._maybe_start_move(_MD)
def _ms_down_release(self, key, keyboard, *args, **kwargs):
self._maybe_stop_move(_MD)
def _ms_left_press(self, key, keyboard, *args, **kwargs):
self._maybe_start_move(_ML)
def _ms_left_release(self, key, keyboard, *args, **kwargs):
self._maybe_stop_move(_ML)
def _ms_right_press(self, key, keyboard, *args, **kwargs):
self._maybe_start_move(_MR)
def _ms_right_release(self, key, keyboard, *args, **kwargs):
self._maybe_stop_move(_MR)

View file

@ -1,318 +0,0 @@
'''
Extension handles usage of Trackball Breakout by Pimoroni
'''
from micropython import const
import math
import struct
from adafruit_pixelbuf import PixelBuf
from kmk.keys import AX, KC, Key, make_argumented_key, make_key
from kmk.kmktime import PeriodicTimer
from kmk.modules import Module
from kmk.utils import Debug
_I2C_ADDRESS = const(0x0A)
_I2C_ADDRESS_ALTERNATIVE = const(0x0B)
_CHIP_ID = const(0xBA11)
_VERSION = const(1)
_REG_LED_RED = const(0x00)
_REG_LED_GRN = const(0x01)
_REG_LED_BLU = const(0x02)
_REG_LED_WHT = const(0x03)
_REG_LEFT = const(0x04)
_REG_RIGHT = const(0x05)
_REG_UP = const(0x06)
_REG_DOWN = const(0x07)
_REG_SWITCH = const(0x08)
_MSK_SWITCH_STATE = const(0b10000000)
_REG_USER_FLASH = const(0xD0)
_REG_FLASH_PAGE = const(0xF0)
_REG_INT = const(0xF9)
_MSK_INT_TRIGGERED = const(0b00000001)
_MSK_INT_OUT_EN = const(0b00000010)
_REG_CHIP_ID_L = const(0xFA)
_REG_CHIP_ID_H = const(0xFB)
_REG_VERSION = const(0xFC)
_REG_I2C_ADDR = const(0xFD)
_REG_CTRL = const(0xFE)
_MSK_CTRL_SLEEP = const(0b00000001)
_MSK_CTRL_RESET = const(0b00000010)
_MSK_CTRL_FREAD = const(0b00000100)
_MSK_CTRL_FWRITE = const(0b00001000)
debug = Debug(__name__)
class TrackballMode:
'''Behaviour mode of trackball: mouse movement or vertical scroll'''
MOUSE_MODE = const(0)
SCROLL_MODE = const(1)
class ScrollDirection:
'''Behaviour mode of scrolling: natural or reverse scrolling'''
NATURAL = const(0)
REVERSE = const(1)
class TrackballHandlerKey(Key):
def __init__(self, handler=TrackballMode.MOUSE_MODE, **kwargs):
super().__init__(**kwargs)
self.handler = handler
class TrackballHandler:
def handle(self, keyboard, trackball, x, y, switch, state):
raise NotImplementedError
class PointingHandler(TrackballHandler):
def __init__(self, on_press=KC.MB_LMB):
self.on_press = on_press
def handle(self, keyboard, trackball, x, y, switch, state):
if x:
AX.X.move(keyboard, x)
if y:
AX.Y.move(keyboard, y)
if switch == 1: # Button changed state
keyboard.pre_process_key(self.on_press, is_pressed=state)
class ScrollHandler(TrackballHandler):
def __init__(self, scroll_direction=ScrollDirection.NATURAL, on_press=KC.MB_LMB):
self.scroll_direction = scroll_direction
self.on_press = on_press
def handle(self, keyboard, trackball, x, y, switch, state):
if self.scroll_direction == ScrollDirection.REVERSE:
y = -y
if y != 0:
AX.W.move(keyboard, y)
if switch == 1: # Button changed state
keyboard.pre_process_key(self.on_press, is_pressed=state)
class KeyHandler(TrackballHandler):
x = 0
y = 0
def __init__(self, up, right, down, left, press, axis_snap=0.25, steps=8):
self.up = up
self.right = right
self.down = down
self.left = left
self.press = press
self.axis_snap = axis_snap
self.steps = steps
def handle(self, keyboard, trackball, x, y, switch, state):
if y and abs(x / y) < self.axis_snap:
x = 0
if x and abs(y / x) < self.axis_snap:
y = 0
self.x += x
self.y += y
x_taps = self.x // self.steps
y_taps = self.y // self.steps
self.x %= self.steps
self.y %= self.steps
for i in range(x_taps, 0, 1):
keyboard.tap_key(self.left)
for i in range(x_taps, 0, -1):
keyboard.tap_key(self.right)
for i in range(y_taps, 0, 1):
keyboard.tap_key(self.up)
for i in range(y_taps, 0, -1):
keyboard.tap_key(self.down)
if switch and state:
keyboard.tap_key(self.press)
class Trackball(Module):
'''Module handles usage of Trackball Breakout by Pimoroni'''
def __init__(
self,
i2c,
mode=TrackballMode.MOUSE_MODE,
address=_I2C_ADDRESS,
angle_offset=0,
handlers=None,
):
self.angle_offset = angle_offset
if not handlers:
handlers = [PointingHandler(), ScrollHandler()]
if mode == TrackballMode.SCROLL_MODE:
handlers.reverse()
self._i2c_address = address
self._i2c_bus = i2c
self.mode = mode
self.handlers = handlers
self.current_handler = self.handlers[0]
self.polling_interval = 20
make_key(
names=('TB_MODE', 'TB_NEXT_HANDLER', 'TB_N'),
on_press=self._tb_handler_next_press,
)
make_argumented_key(
names=('TB_HANDLER', 'TB_H'),
constructor=TrackballHandlerKey,
on_press=self._tb_handler_press,
)
def during_bootup(self, keyboard):
chip_id = struct.unpack('<H', bytearray(self._i2c_rdwr([_REG_CHIP_ID_L], 2)))[0]
if chip_id != _CHIP_ID:
raise RuntimeError(
f'Invalid chip ID: 0x{chip_id:04X}, expected 0x{_CHIP_ID:04X}'
)
self._timer = PeriodicTimer(self.polling_interval)
a = math.pi * self.angle_offset / 180
self.rot = [[math.cos(a), math.sin(a)], [-math.sin(a), math.cos(a)]]
def before_matrix_scan(self, keyboard):
'''
Return value will be injected as an extra matrix update
'''
if not self._timer.tick():
return
if not (self._i2c_rdwr([_REG_INT], 1)[0] & _MSK_INT_TRIGGERED):
return
up, down, left, right, switch, state = self._read_raw_state()
x, y = self._calculate_movement(right - left, down - up)
self.current_handler.handle(keyboard, self, x, y, switch, state)
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 set_rgbw(self, r, g, b, w):
'''Set all LED brightness as RGBW.'''
self._i2c_rdwr([_REG_LED_RED, r, g, b, w])
def set_red(self, value):
'''Set brightness of trackball red LED.'''
self._i2c_rdwr([_REG_LED_RED, value & 0xFF])
def set_green(self, value):
'''Set brightness of trackball green LED.'''
self._i2c_rdwr([_REG_LED_GRN, value & 0xFF])
def set_blue(self, value):
'''Set brightness of trackball blue LED.'''
self._i2c_rdwr([_REG_LED_BLU, value & 0xFF])
def set_white(self, value):
'''Set brightness of trackball white LED.'''
self._i2c_rdwr([_REG_LED_WHT, value & 0xFF])
def activate_handler(self, handler):
if isinstance(handler, TrackballHandler):
self.current_handler = handler
else:
try:
self.current_handler = self.handlers[handler]
except KeyError:
if debug.enabled:
debug(f'no handler found with id {handler}')
def next_handler(self):
next_index = self.handlers.index(self.current_handler) + 1
if next_index >= len(self.handlers):
next_index = 0
self.activate_handler(next_index)
def _read_raw_state(self):
'''Read up, down, left, right and switch data from trackball.'''
left, right, up, down, switch = self._i2c_rdwr([_REG_LEFT], 5)
switch_changed, switch_state = (
switch & ~_MSK_SWITCH_STATE,
(switch & _MSK_SWITCH_STATE) > 0,
)
return up, down, left, right, switch_changed, switch_state
def _i2c_rdwr(self, data, length=0):
'''Write and optionally read I2C data.'''
if not self._i2c_bus.try_lock():
return
try:
if length > 0:
result = bytearray(length)
self._i2c_bus.writeto_then_readfrom(
self._i2c_address, bytes(data), result
)
return list(result)
else:
self._i2c_bus.writeto(self._i2c_address, bytes(data))
return []
finally:
self._i2c_bus.unlock()
def _tb_handler_press(self, key, keyboard, *args, **kwargs):
self.activate_handler(key.handler)
def _tb_handler_next_press(self, key, keyboard, *args, **kwargs):
self.next_handler()
def _calculate_movement(self, raw_x, raw_y):
'''Calculate accelerated movement vector from raw data'''
if raw_x == 0 and raw_y == 0:
return 0, 0
scale = math.sqrt(raw_x**2 + raw_y**2)
x = (self.rot[0][0] * raw_x + self.rot[0][1] * raw_y) * scale
y = (self.rot[1][0] * raw_x + self.rot[1][1] * raw_y) * scale
return int(x), int(y)
class TrackballPixel(PixelBuf):
'''PixelBuf interface for the Trackball RGBW LED'''
def __init__(self, trackball, **kwargs):
self.trackball = trackball
kwargs['byteorder'] = 'RGBW'
super().__init__(1, **kwargs)
def deinit(self):
self.trackball.set_rgbw(0, 0, 0, 0)
def _transmit(self, b):
self.trackball.set_rgbw(b[0], b[1], b[2], b[3])

View file

@ -1,94 +0,0 @@
from analogio import AnalogIn
from supervisor import ticks_ms
from kmk.modules import Module
class PotentiometerState:
def __init__(self, direction: int, position: int):
self.direction = direction
self.position = position
class Potentiometer:
def __init__(self, pin, move_callback, is_inverted=False):
self.is_inverted = is_inverted
self.read_pin = AnalogIn(pin)
self._direction = None
self._pos = self.get_pos()
self._timestamp = ticks_ms()
self.cb = move_callback
# callback function on events.
self.on_move_do = lambda state: self.cb(state)
def get_state(self) -> PotentiometerState:
return PotentiometerState(
direction=(self.is_inverted and -self._direction or self._direction),
position=(self.is_inverted and -self._pos or self._pos),
)
def get_pos(self):
'''
Read from the analog pin assingned, truncate to 7 bits,
average over 10 readings, and return a value 0-127
'''
return int(sum([(self.read_pin.value >> 9) for i in range(10)]) / 10)
def update_state(self):
self._direction = 0
new_pos = self.get_pos()
if abs(new_pos - self._pos) > 2:
# movement detected!
if new_pos > self._pos:
self._direction = 1
else:
self._direction = -1
self._pos = new_pos
if self.on_move_do is not None:
self.on_move_do(self.get_state())
class PotentiometerHandler(Module):
def __init__(self):
self.potentiometers = []
self.pins = None
def on_runtime_enable(self, keyboard):
return
def on_runtime_disable(self, keyboard):
return
def during_bootup(self, keyboard):
if self.pins:
for args in self.pins:
self.potentiometers.append(Potentiometer(*args))
return
def before_matrix_scan(self, keyboard):
'''
Return value will be injected as an extra matrix update
'''
for potentiometer in self.potentiometers:
potentiometer.update_state()
return keyboard
def after_matrix_scan(self, keyboard):
'''
Return value will be replace matrix update if supplied
'''
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

View file

@ -1,143 +0,0 @@
import board
import digitalio
from supervisor import ticks_ms
from time import sleep
from kmk.keys import make_key
from kmk.kmktime import check_deadline
from kmk.modules import Module
class Power(Module):
def __init__(self, powersave_pin=None):
self.enable = False
self.powersave_pin = powersave_pin # Powersave pin board object
self._powersave_start = ticks_ms()
self._usb_last_scan = ticks_ms() - 5000
self._psp = None # Powersave pin object
self._i2c = 0
self._i2c_deinit_count = 0
self._loopcounter = 0
make_key(names=('PS_TOG',), on_press=self._ps_tog)
make_key(names=('PS_ON',), on_press=self._ps_enable)
make_key(names=('PS_OFF',), on_press=self._ps_disable)
def __repr__(self):
return f'Power({self._to_dict()})'
def _to_dict(self):
return {
'enable': self.enable,
'powersave_pin': self.powersave_pin,
'_powersave_start': self._powersave_start,
'_usb_last_scan': self._usb_last_scan,
'_psp': self._psp,
}
def during_bootup(self, keyboard):
self._i2c_scan()
def before_matrix_scan(self, keyboard):
return
def after_matrix_scan(self, keyboard):
if keyboard.matrix_update or keyboard.secondary_matrix_update:
self.psave_time_reset()
def before_hid_send(self, keyboard):
return
def after_hid_send(self, keyboard):
if self.enable:
self.psleep()
def on_powersave_enable(self, keyboard):
'''Gives 10 cycles to allow other extensions to clean up before powersave'''
if self._loopcounter > 10:
self.enable_powersave(keyboard)
self._loopcounter = 0
else:
self._loopcounter += 1
return
def on_powersave_disable(self, keyboard):
self.disable_powersave(keyboard)
return
def enable_powersave(self, keyboard):
'''Enables power saving features'''
if self._i2c_deinit_count >= self._i2c and self.powersave_pin:
# Allows power save to prevent RGB drain.
# Example here https://docs.nicekeyboards.com/#/nice!nano/pinout_schematic
if not self._psp:
self._psp = digitalio.DigitalInOut(self.powersave_pin)
self._psp.direction = digitalio.Direction.OUTPUT
if self._psp:
self._psp.value = True
self.enable = True
keyboard._trigger_powersave_enable = False
return
def disable_powersave(self, keyboard):
'''Disables power saving features'''
if self._psp:
self._psp.value = False
# Allows power save to prevent RGB drain.
# Example here https://docs.nicekeyboards.com/#/nice!nano/pinout_schematic
keyboard._trigger_powersave_disable = False
self.enable = False
return
def psleep(self):
'''
Sleeps longer and longer to save power the more time in between updates.
'''
if check_deadline(ticks_ms(), self._powersave_start, 60000):
sleep(8 / 1000)
elif check_deadline(ticks_ms(), self._powersave_start, 240000) is False:
sleep(180 / 1000)
return
def psave_time_reset(self):
self._powersave_start = ticks_ms()
def _i2c_scan(self):
i2c = board.I2C()
while not i2c.try_lock():
pass
try:
self._i2c = len(i2c.scan())
finally:
i2c.unlock()
return
def usb_rescan_timer(self):
return bool(check_deadline(ticks_ms(), self._usb_last_scan, 5000) is False)
def usb_time_reset(self):
self._usb_last_scan = ticks_ms()
return
def usb_scan(self):
# TODO Add USB detection here. Currently lies that it's connected
# https://github.com/adafruit/circuitpython/pull/3513
return True
def _ps_tog(self, key, keyboard, *args, **kwargs):
if self.enable:
keyboard._trigger_powersave_disable = True
else:
keyboard._trigger_powersave_enable = True
def _ps_enable(self, key, keyboard, *args, **kwargs):
if not self.enable:
keyboard._trigger_powersave_enable = True
def _ps_disable(self, key, keyboard, *args, **kwargs):
if self.enable:
keyboard._trigger_powersave_disable = True

View file

@ -1,118 +0,0 @@
from micropython import const
from random import randint
from kmk.keys import Key, make_argumented_key
from kmk.modules import Module
from kmk.utils import Debug
debug = Debug(__name__)
_INACTIVE = const(0)
_HOLD = const(1)
_ACTIVE = const(2)
class RapidFireKey(Key):
def __init__(
self,
key,
interval=100,
timeout=200,
enable_interval_randomization=False,
randomization_magnitude=15,
toggle=False,
**kwargs,
):
super().__init__(**kwargs)
self.key = key
self.interval = interval
self.timeout = timeout
self.enable_interval_randomization = enable_interval_randomization
self.randomization_magnitude = randomization_magnitude
self.toggle = toggle
self._state = _INACTIVE
self._timeout = None
class RapidFire(Module):
def __init__(self):
make_argumented_key(
names=('RF',),
constructor=RapidFireKey,
on_press=self._rf_pressed,
on_release=self._rf_released,
)
def _on_timer_timeout(self, key, keyboard):
if key._state == _HOLD:
key._state = _ACTIVE
keyboard.remove_key(key.key)
key._timeout = keyboard.set_timeout(
1, lambda: self._on_timer_timeout(key, keyboard)
)
return
keyboard.add_key(key.key)
keyboard.set_timeout(1, lambda: keyboard.remove_key(key.key))
interval = key.interval
if key.enable_interval_randomization:
interval += randint(
-key.randomization_magnitude, key.randomization_magnitude
)
key._timeout = keyboard.set_timeout(
interval, lambda: self._on_timer_timeout(key, keyboard)
)
if debug.enabled:
debug(key.key, ' @', interval, 'ms')
def _rf_pressed(self, key, keyboard, *args, **kwargs):
if key._state == _ACTIVE:
self._deactivate_key(key, keyboard)
return
keyboard.add_key(key.key)
key._state = _HOLD
key._timeout = keyboard.set_timeout(
key.timeout, lambda: self._on_timer_timeout(key, keyboard)
)
def _rf_released(self, key, keyboard, *args, **kwargs):
if key._state == _ACTIVE:
if key.toggle:
return
key._state = _INACTIVE
elif key._state == _INACTIVE:
return
else:
keyboard.remove_key(key.key)
self._deactivate_key(key, keyboard)
def _deactivate_key(self, key, keyboard):
keyboard.cancel_timeout(key._timeout)
key._state = _INACTIVE
key._timeout = None
def during_bootup(self, keyboard):
return
def before_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 after_matrix_scan(self, keyboard):
return

View file

@ -1,63 +0,0 @@
from usb_cdc import data
from kmk.modules import Module
from kmk.utils import Debug
debug = Debug(__name__)
class SerialACE(Module):
buffer = bytearray()
def during_bootup(self, keyboard):
try:
data.timeout = 0
except AttributeError:
pass
def before_matrix_scan(self, keyboard):
pass
def after_matrix_scan(self, keyboard):
pass
def process_key(self, keyboard, key, is_pressed, int_coord):
return key
def before_hid_send(self, keyboard):
# Serial.data isn't initialized.
if not data:
return
# Nothing to parse.
if data.in_waiting == 0:
return
self.buffer.extend(data.read())
idx = self.buffer.find(b'\n')
# No full command yet.
if idx == -1:
return
# Split off command and evaluate.
line = self.buffer[:idx]
self.buffer = self.buffer[idx + 1 :] # noqa: E203
try:
if debug.enabled:
debug(f'eval({line})')
ret = eval(line, {'keyboard': keyboard})
data.write(bytearray(str(ret) + '\n'))
except Exception as err:
if debug.enabled:
debug(f'error: {err}')
def after_hid_send(self, keyboard):
pass
def on_powersave_enable(self, keyboard):
pass
def on_powersave_disable(self, keyboard):
pass

View file

@ -1,391 +0,0 @@
'''Enables splitting keyboards wirelessly or wired'''
import busio
from micropython import const
from supervisor import runtime, ticks_ms
from keypad import Event as KeyEvent
from storage import getmount
from kmk.hid import HIDModes
from kmk.kmktime import check_deadline
from kmk.modules import Module
from kmk.utils import Debug
debug = Debug(__name__)
class SplitSide:
LEFT = const(1)
RIGHT = const(2)
class SplitType:
UART = const(1)
I2C = const(2) # unused
ONEWIRE = const(3) # unused
BLE = const(4)
class Split(Module):
'''Enables splitting keyboards wirelessly, or wired'''
def __init__(
self,
split_flip=True,
split_side=None,
split_type=SplitType.UART,
split_target_left=True,
uart_interval=20,
data_pin=None,
data_pin2=None,
uart_flip=True,
use_pio=False,
):
self._is_target = True
self._uart_buffer = []
self.split_flip = split_flip
self.split_side = split_side
self.split_type = split_type
self.split_target_left = split_target_left
self.split_offset = None
self.data_pin = data_pin
self.data_pin2 = data_pin2
self.uart_flip = uart_flip
self._use_pio = use_pio
self._uart = None
self._uart_interval = uart_interval
self.uart_header = bytearray([0xB2]) # Any non-zero byte should work
if self.split_type == SplitType.BLE:
try:
from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import (
ProvideServicesAdvertisement,
)
from adafruit_ble.services.nordic import UARTService
self.BLERadio = BLERadio
self.ProvideServicesAdvertisement = ProvideServicesAdvertisement
self.UARTService = UARTService
except ImportError:
if debug.enabled:
debug('BLE Import error')
return # BLE isn't supported on this platform
self._ble_last_scan = ticks_ms() - 5000
self._connection_count = 0
self._split_connected = False
self._uart_connection = None
self._advertisment = None # Seems to not be used anywhere
self._advertising = False
self._psave_enable = False
if self._use_pio:
from kmk.transports.pio_uart import PIO_UART
self.PIO_UART = PIO_UART
def during_bootup(self, keyboard):
# Set up name for target side detection and BLE advertisment
name = str(getmount('/').label)
if self.split_type == SplitType.BLE:
if keyboard.hid_type == HIDModes.BLE:
self._ble = keyboard._hid_helper.ble
else:
self._ble = self.BLERadio()
self._ble.name = name
else:
# Try to guess data pins if not supplied
if not self.data_pin:
self.data_pin = keyboard.data_pin
# if split side was given, find target from split_side.
if self.split_side == SplitSide.LEFT:
self._is_target = bool(self.split_target_left)
elif self.split_side == SplitSide.RIGHT:
self._is_target = not bool(self.split_target_left)
else:
# Detect split side from name
if (
self.split_type == SplitType.UART
or self.split_type == SplitType.ONEWIRE
):
self._is_target = runtime.usb_connected
elif self.split_type == SplitType.BLE:
self._is_target = name.endswith('L') == self.split_target_left
if name.endswith('L'):
self.split_side = SplitSide.LEFT
elif name.endswith('R'):
self.split_side = SplitSide.RIGHT
if not self._is_target:
keyboard._hid_send_enabled = False
if self.split_offset is None:
self.split_offset = keyboard.matrix[-1].coord_mapping[-1] + 1
if self.split_type == SplitType.UART and self.data_pin is not None:
if self._is_target or not self.uart_flip:
if self._use_pio:
self._uart = self.PIO_UART(tx=self.data_pin2, rx=self.data_pin)
else:
self._uart = busio.UART(
tx=self.data_pin2, rx=self.data_pin, timeout=self._uart_interval
)
else:
if self._use_pio:
self._uart = self.PIO_UART(tx=self.data_pin, rx=self.data_pin2)
else:
self._uart = busio.UART(
tx=self.data_pin, rx=self.data_pin2, timeout=self._uart_interval
)
# Attempt to sanely guess a coord_mapping if one is not provided.
if not keyboard.coord_mapping and keyboard.row_pins and keyboard.col_pins:
cm = []
rows_to_calc = len(keyboard.row_pins)
cols_to_calc = len(keyboard.col_pins)
# Flips the col order if PCB is the same but flipped on right
cols_rhs = list(range(cols_to_calc))
if self.split_flip:
cols_rhs = list(reversed(cols_rhs))
for ridx in range(rows_to_calc):
for cidx in range(cols_to_calc):
cm.append(cols_to_calc * ridx + cidx)
for cidx in cols_rhs:
cm.append(cols_to_calc * (rows_to_calc + ridx) + cidx)
keyboard.coord_mapping = tuple(cm)
if not keyboard.coord_mapping and debug.enabled:
debug('Error: please provide coord_mapping for custom scanner')
if self.split_side == SplitSide.RIGHT:
offset = self.split_offset
for matrix in keyboard.matrix:
matrix.offset = offset
offset += matrix.key_count
def before_matrix_scan(self, keyboard):
if self.split_type == SplitType.BLE:
self._check_all_connections(keyboard)
self._receive_ble(keyboard)
elif self.split_type == SplitType.UART:
if self._is_target or self.data_pin2:
self._receive_uart(keyboard)
elif self.split_type == SplitType.ONEWIRE:
pass # Protocol needs written
return
def after_matrix_scan(self, keyboard):
if keyboard.matrix_update:
if self.split_type == SplitType.UART:
if not self._is_target or self.data_pin2:
self._send_uart(keyboard.matrix_update)
else:
pass # explicit pass just for dev sanity...
elif self.split_type == SplitType.BLE:
self._send_ble(keyboard.matrix_update)
elif self.split_type == SplitType.ONEWIRE:
pass # Protocol needs written
else:
if debug.enabled:
debug('Unexpected case in after_matrix_scan')
return
def before_hid_send(self, keyboard):
if not self._is_target:
keyboard.hid_pending = False
return
def after_hid_send(self, keyboard):
return
def on_powersave_enable(self, keyboard):
if self.split_type == SplitType.BLE:
if self._uart_connection and not self._psave_enable:
self._uart_connection.connection_interval = self._uart_interval
self._psave_enable = True
def on_powersave_disable(self, keyboard):
if self.split_type == SplitType.BLE:
if self._uart_connection and self._psave_enable:
self._uart_connection.connection_interval = 11.25
self._psave_enable = False
def _check_all_connections(self, keyboard):
'''Validates the correct number of BLE connections'''
self._previous_connection_count = self._connection_count
self._connection_count = len(self._ble.connections)
if self._is_target:
if self._advertising or not self._check_if_split_connected():
self._target_advertise()
elif self._connection_count < 2 and keyboard.hid_type == HIDModes.BLE:
keyboard._hid_helper.start_advertising()
elif not self._is_target and self._connection_count < 1:
self._initiator_scan()
def _check_if_split_connected(self):
# I'm looking for a way how to recognize which connection is on and which one off
# For now, I found that service name relation to having other CP device
if self._connection_count == 0:
return False
if self._connection_count == 2:
self._split_connected = True
return True
# Polling this takes some time so I check only if connection_count changed
if self._previous_connection_count == self._connection_count:
return self._split_connected
bleio_connection = self._ble.connections[0]._bleio_connection
connection_services = bleio_connection.discover_remote_services()
for service in connection_services:
if str(service.uuid).startswith("UUID('adaf0001"):
self._split_connected = True
return True
return False
def _initiator_scan(self):
'''Scans for target device'''
self._uart = None
self._uart_connection = None
# See if any existing connections are providing UARTService.
self._connection_count = len(self._ble.connections)
if self._connection_count > 0 and not self._uart:
for connection in self._ble.connections:
if self.UARTService in connection:
self._uart_connection = connection
self._uart_connection.connection_interval = 11.25
self._uart = self._uart_connection[self.UARTService]
break
if not self._uart:
if debug.enabled:
debug('Scanning')
self._ble.stop_scan()
for adv in self._ble.start_scan(
self.ProvideServicesAdvertisement, timeout=20
):
if debug.enabled:
debug('Scanning')
if self.UARTService in adv.services and adv.rssi > -70:
self._uart_connection = self._ble.connect(adv)
self._uart_connection.connection_interval = 11.25
self._uart = self._uart_connection[self.UARTService]
self._ble.stop_scan()
if debug.enabled:
debug('Scan complete')
break
self._ble.stop_scan()
def _target_advertise(self):
'''Advertises the target for the initiator to find'''
# Give previous advertising some time to complete
if self._advertising:
if self._check_if_split_connected():
if debug.enabled:
debug('Advertising complete')
self._ble.stop_advertising()
self._advertising = False
return
if not self.ble_rescan_timer():
return
if debug.enabled:
debug('Advertising not answered')
self._ble.stop_advertising()
if debug.enabled:
debug('Advertising')
# Uart must not change on this connection if reconnecting
if not self._uart:
self._uart = self.UARTService()
advertisement = self.ProvideServicesAdvertisement(self._uart)
self._ble.start_advertising(advertisement)
self._advertising = True
self.ble_time_reset()
def ble_rescan_timer(self):
'''If true, the rescan timer is up'''
return not bool(check_deadline(ticks_ms(), self._ble_last_scan, 5000))
def ble_time_reset(self):
'''Resets the rescan timer'''
self._ble_last_scan = ticks_ms()
def _serialize_update(self, update):
buffer = bytearray(2)
buffer[0] = update.key_number
buffer[1] = update.pressed
return buffer
def _deserialize_update(self, update):
kevent = KeyEvent(key_number=update[0], pressed=update[1])
return kevent
def _send_ble(self, update):
if self._uart:
try:
self._uart.write(self._serialize_update(update))
except OSError:
try:
self._uart.disconnect()
except: # noqa: E722
if debug.enabled:
debug('UART disconnect failed')
if debug.enabled:
debug('Connection error')
self._uart_connection = None
self._uart = None
def _receive_ble(self, keyboard):
if self._uart is not None and self._uart.in_waiting > 0 or self._uart_buffer:
while self._uart.in_waiting >= 2:
update = self._deserialize_update(self._uart.read(2))
self._uart_buffer.append(update)
if self._uart_buffer:
keyboard.secondary_matrix_update = self._uart_buffer.pop(0)
def _checksum(self, update):
checksum = bytes([sum(update) & 0xFF])
return checksum
def _send_uart(self, update):
# Change offsets depending on where the data is going to match the correct
# matrix location of the receiever
if self._uart is not None:
update = self._serialize_update(update)
self._uart.write(self.uart_header)
self._uart.write(update)
self._uart.write(self._checksum(update))
def _receive_uart(self, keyboard):
if self._uart is not None and self._uart.in_waiting > 0 or self._uart_buffer:
if self._uart.in_waiting >= 60:
# This is a dirty hack to prevent crashes in unrealistic cases
import microcontroller
microcontroller.reset()
while self._uart.in_waiting >= 4:
# Check the header
if self._uart.read(1) == self.uart_header:
update = self._uart.read(2)
# check the checksum
if self._checksum(update) == self._uart.read(1):
self._uart_buffer.append(self._deserialize_update(update))
if self._uart_buffer:
keyboard.secondary_matrix_update = self._uart_buffer.pop(0)

View file

@ -1,107 +0,0 @@
import usb_cdc
from kmk.keys import make_key
from kmk.modules import Module
# key order from https://github.com/openstenoproject/plover/blob/main/plover/machine/geminipr.py
# do not rearrange
STENO_KEYS = (
'STN_FN',
'STN_N1',
'STN_N2',
'STN_N3',
'STN_N4',
'STN_N5',
'STN_N6',
'STN_LS1',
'STN_LS2',
'STN_LT',
'STN_LK',
'STN_LP',
'STN_LW',
'STN_LH',
'STN_LR',
'STN_A',
'STN_O',
'STN_AS1',
'STN_AS2',
'STN_RES1',
'STN_RES2',
'STN_PWR',
'STN_AS3',
'STN_AS4',
'STN_E',
'STN_U',
'STN_RF',
'STN_RR',
'STN_RP',
'STN_RB',
'STN_RL',
'STN_RG',
'STN_RT',
'STN_RS',
'STN_RD',
'STN_N7',
'STN_N8',
'STN_N9',
'STN_NA',
'STN_NB',
'STN_NC',
'STN_RZ',
)
class Steno(Module):
def __init__(self):
self._should_write = False
self._buffer = bytearray(6)
self._initialize_buffer()
for idx, key in enumerate(STENO_KEYS):
make_key(
code=((idx // 7) << 8) | (0x40 >> (idx % 7)),
names=(key,),
on_press=self._steno_press,
on_release=self._steno_release,
)
def _initialize_buffer(self):
self._buffer[:] = b'\x80\x00\x00\x00\x00\x00'
# flip a key's bit in the buffer
def _steno_press(self, key, *_):
self._should_write = True
self._buffer[key.code >> 8] |= key.code & 0xFF
# send all keys that were pressed, and reset the buffer
def _steno_release(self, *_):
if self._should_write:
usb_cdc.data.write(self._buffer)
self._should_write = False
self._initialize_buffer()
def during_bootup(self, keyboard):
pass
def before_matrix_scan(self, keyboard):
pass
def after_matrix_scan(self, keyboard):
pass
def process_key(self, keyboard, key, is_pressed, int_coord):
return key
def before_hid_send(self, keyboard):
pass
def after_hid_send(self, keyboard):
pass
def on_powersave_enable(self, keyboard):
pass
def on_powersave_disable(self, keyboard):
pass

View file

@ -1,151 +0,0 @@
from micropython import const
from kmk.keys import Key, make_argumented_key
from kmk.modules import Module
from kmk.utils import Debug
debug = Debug(__name__)
_SK_IDLE = const(0)
_SK_PRESSED = const(1)
_SK_RELEASED = const(2)
_SK_HOLD = const(3)
_SK_STICKY = const(4)
class StickyKey(Key):
def __init__(self, key, defer_release=False, retap_cancel=True, **kwargs):
super().__init__(**kwargs)
self.key = key
self.defer_release = defer_release
self.timeout = None
self.state = _SK_IDLE
self.retap_cancel = retap_cancel
class StickyKeys(Module):
def __init__(self, release_after=1000):
self.active_keys = []
self.release_after = release_after
make_argumented_key(
names=('SK', 'STICKY'),
constructor=StickyKey,
on_press=self.on_press,
on_release=self.on_release,
)
def during_bootup(self, keyboard):
return
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, current_key, is_pressed, int_coord):
delay_current = False
for key in self.active_keys.copy():
# Ignore keys that will resolve to and emit a different key
# eventually, potentially triggering twice.
# Handle interactions among sticky keys (stacking) in `on_press`
# instead of `process_key` to avoid race conditions / causal
# reordering when resetting timeouts.
if (
isinstance(current_key, StickyKey)
or current_key.__class__.__name__ == 'TapDanceKey'
or current_key.__class__.__name__ == 'HoldTapKey'
or current_key.__class__.__name__ == 'LayerTapKeyMeta'
):
continue
if key.state == _SK_PRESSED and is_pressed:
key.state = _SK_HOLD
elif key.state == _SK_RELEASED and is_pressed:
key.state = _SK_STICKY
elif key.state == _SK_STICKY:
# Defer sticky release until last other key is released.
if key.defer_release:
if not is_pressed and len(keyboard._coordkeys_pressed) == 0:
self.deactivate(keyboard, key)
# Release sticky key; if it's a new key pressed: delay
# propagation until after the sticky release.
else:
self.deactivate(keyboard, key)
delay_current = is_pressed
if delay_current:
keyboard.resume_process_key(self, current_key, is_pressed, int_coord, False)
else:
return current_key
def set_timeout(self, keyboard, key):
key.timeout = keyboard.set_timeout(
self.release_after,
lambda: self.on_release_after(keyboard, key),
)
def on_press(self, key, keyboard, *args, **kwargs):
# Let sticky keys stack while renewing timeouts.
for sk in self.active_keys:
keyboard.cancel_timeout(sk.timeout)
# If active sticky key is tapped again, cancel.
if key.retap_cancel and (key.state == _SK_RELEASED or key.state == _SK_STICKY):
self.deactivate(keyboard, key)
# Reset on repeated taps.
elif key.state != _SK_IDLE:
key.state = _SK_PRESSED
else:
self.activate(keyboard, key)
for sk in self.active_keys:
self.set_timeout(keyboard, sk)
def on_release(self, key, keyboard, *args, **kwargs):
# No interrupt or timeout happend, mark key as RELEASED, ready to get
# STICKY.
if key.state == _SK_PRESSED:
key.state = _SK_RELEASED
# Key in HOLD state is handled like a regular release.
elif key.state == _SK_HOLD:
keyboard.cancel_timeout(key.timeout)
self.deactivate(keyboard, key)
def on_release_after(self, keyboard, key):
# Key is still pressed but nothing else happend: set to HOLD.
if key.state == _SK_PRESSED:
key.state = _SK_HOLD
keyboard.cancel_timeout(key.timeout)
# Key got released but nothing else happend: deactivate.
elif key.state == _SK_RELEASED:
self.deactivate(keyboard, key)
def activate(self, keyboard, key):
if debug.enabled:
debug('activate')
key.state = _SK_PRESSED
self.active_keys.insert(0, key)
keyboard.resume_process_key(self, key.key, True)
def deactivate(self, keyboard, key):
if debug.enabled:
debug('deactivate')
key.state = _SK_IDLE
self.active_keys.remove(key)
keyboard.resume_process_key(self, key.key, False)

View file

@ -1,64 +0,0 @@
from kmk.keys import Key, make_argumented_key
from kmk.modules import Module
class StickyModKey(Key):
def __init__(self, key, mod, **kwargs):
super().__init__(**kwargs)
self.key = key
self.mod = mod
class StickyMod(Module):
def __init__(self):
self._active = False
self._active_key = None
make_argumented_key(
names=('SM',),
constructor=StickyModKey,
on_press=self.sm_pressed,
on_release=self.sm_released,
)
def during_bootup(self, keyboard):
return
def before_matrix_scan(self, keyboard):
return
def process_key(self, keyboard, key, is_pressed, int_coord):
# release previous key if any other key is pressed
if self._active and self._active_key is not None:
self.release_key(keyboard, self._active_key)
return key
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 after_matrix_scan(self, keyboard):
return
def release_key(self, keyboard, key):
keyboard.process_key(key.mod, False)
self._active = False
self._active_key = None
def sm_pressed(self, key, keyboard, *args, **kwargs):
keyboard.process_key(key.mod, True)
keyboard.process_key(key.key, True)
self._active_key = key
def sm_released(self, key, keyboard, *args, **kwargs):
keyboard.process_key(key.key, False)
self._active_key = key
self._active = True

View file

@ -1,229 +0,0 @@
try:
from typing import Optional
except ImportError:
# we're not in a dev environment, so we don't need to worry about typing
pass
from micropython import const
from kmk.keys import KC, Key, ModifiedKey, ModifierKey
from kmk.modules import Module
class State:
LISTENING = const(0)
DELETING = const(1)
SENDING = const(2)
IGNORING = const(3)
class Character:
'''Helper class for making a left-shifted key identical to a right-shifted key'''
is_shifted: bool = False
def __init__(self, key_code: Key, is_shifted: bool) -> None:
self.is_shifted = is_shifted
self.key_code = KC.LSHIFT(key_code) if is_shifted else key_code
def __eq__(self, other: any) -> bool: # type: ignore
try:
if isinstance(self.key_code, ModifiedKey):
return (
self.key_code.key.code == other.key_code.key.code
and self.is_shifted == other.is_shifted
)
return (
self.key_code.code == other.key_code.code
and self.is_shifted == other.is_shifted
)
except AttributeError:
return False
class Phrase:
'''Manages a collection of characters and keeps an index of them so that potential matches can be tracked'''
def __init__(self, string: str) -> None:
self._characters: list[Character] = []
self._index: int = 0
for char in string:
key_code = KC[char]
if key_code == KC.NO:
raise ValueError(f'Invalid character in dictionary: {char}')
shifted = char.isupper() or (
isinstance(key_code, ModifiedKey) and key_code.modifier.code == 0x02
)
self._characters.append(Character(key_code, shifted))
def next_character(self) -> None:
'''Increment the current index for this phrase'''
if not self.index_at_end():
self._index += 1
def get_character_at_index(self, index: int) -> Character:
'''Returns the character at the given index'''
return self._characters[index]
def get_character_at_current_index(self) -> Character:
'''Returns the character at the current index for this phrase'''
return self._characters[self._index]
def reset_index(self) -> None:
'''Reset the index to the start of the phrase'''
self._index = 0
def index_at_end(self) -> bool:
'''Returns True if the index is at the end of the phrase'''
return self._index == len(self._characters)
def character_is_at_current_index(self, character) -> bool:
'''Returns True if the given character is the next character in the phrase'''
return self.get_character_at_current_index() == character
class Rule:
'''Represents the relationship between a phrase to be substituted and its substitution'''
def __init__(self, to_substitute: Phrase, substitution: Phrase) -> None:
self.to_substitute: Phrase = to_substitute
self.substitution: Phrase = substitution
def restart(self) -> None:
'''Resets this rule's to_substitute and substitution phrases'''
self.to_substitute.reset_index()
self.substitution.reset_index()
class StringSubstitution(Module):
_shifted: bool = False
_rules: list = []
_state: State = State.LISTENING
_matched_rule: Optional[Phrase] = None
_active_modifiers: list[ModifierKey] = []
def __init__(
self,
dictionary: dict,
):
for key, value in dictionary.items():
self._rules.append(Rule(Phrase(key), Phrase(value)))
def process_key(self, keyboard, key, is_pressed, int_coord):
if key is KC.LSFT or key is KC.RSFT:
if is_pressed:
self._shifted = True
else:
self._shifted = False
# control ignoring state if the key is a non-shift modifier
elif type(key) is ModifierKey:
if is_pressed and key not in self._active_modifiers:
self._active_modifiers.append(key)
self._state = State.IGNORING
elif key in self._active_modifiers:
self._active_modifiers.remove(key)
if not self._active_modifiers:
self._state = State.LISTENING
# reset rules because pressing a modifier combination
# should interrupt any current matches
for rule in self._rules:
rule.restart()
if not self._state == State.LISTENING:
return key
if is_pressed:
character = Character(key, self._shifted)
# run through the dictionary to check for a possible match on each new keypress
for rule in self._rules:
if rule.to_substitute.character_is_at_current_index(character):
rule.to_substitute.next_character()
else:
rule.restart()
# if character is not a match at the current index,
# it could still be a match at the start of the sequence
# so redo the check after resetting the sequence
if rule.to_substitute.character_is_at_current_index(character):
rule.to_substitute.next_character()
# we've matched all of the characters in a phrase to be substituted
if rule.to_substitute.index_at_end():
rule.restart()
# set the phrase indexes to where they differ
# so that only the characters that differ are replaced
for character in rule.to_substitute._characters:
if (
character
== rule.substitution.get_character_at_current_index()
):
rule.to_substitute.next_character()
rule.substitution.next_character()
else:
break
if rule.to_substitute.index_at_end():
break
self._matched_rule = rule
self._state = State.DELETING
# if we have a match there's no reason to continue the full key processing, so return out
return
return key
def during_bootup(self, keyboard):
return
def before_matrix_scan(self, keyboard):
return
def before_hid_send(self, keyboard):
if self._state == State.LISTENING:
return
if self._state == State.DELETING:
# force-release modifiers so sending the replacement text doesn't interact with them
# it should not be possible for any modifiers other than shift to be held upon rule activation
# as a modified key won't send a keycode that is matched against the user's dictionary,
# but, just in case, we'll release those too
modifiers_to_release = [
KC.LSFT,
KC.RSFT,
KC.LCTL,
KC.LGUI,
KC.LALT,
KC.RCTL,
KC.RGUI,
KC.RALT,
]
for modifier in modifiers_to_release:
keyboard.remove_key(modifier)
# send backspace taps equivalent to the length of the phrase to be substituted
to_substitute: Phrase = self._matched_rule.to_substitute # type: ignore
to_substitute.next_character()
if not to_substitute.index_at_end():
keyboard.tap_key(KC.BSPC)
else:
self._state = State.SENDING
if self._state == State.SENDING:
substitution = self._matched_rule.substitution # type: ignore
if not substitution.index_at_end():
keyboard.tap_key(substitution.get_character_at_current_index().key_code)
substitution.next_character()
else:
self._state = State.LISTENING
self._matched_rule = None
for rule in self._rules:
rule.restart()
def after_hid_send(self, keyboard):
return
def on_powersave_enable(self, keyboard):
return
def on_powersave_disable(self, keyboard):
return
def after_matrix_scan(self, keyboard):
return

View file

@ -1,121 +0,0 @@
from kmk.keys import Key, make_argumented_key
from kmk.modules.holdtap import ActivationType, HoldTap, HoldTapKey
class TapDanceKey(Key):
def __init__(self, *keys, tap_time=None, **kwargs):
'''
Any key in the tapdance sequence that is not already a holdtap
key gets converted to a holdtap key with identical tap and hold
attributes.
'''
super().__init__(**kwargs)
self.tap_time = tap_time
self.keys = []
for key in keys:
if not isinstance(key, HoldTapKey):
ht_key = HoldTapKey(
tap=key,
hold=key,
prefer_hold=True,
tap_interrupted=False,
tap_time=self.tap_time,
)
self.keys.append(ht_key)
else:
self.keys.append(key)
self.keys = tuple(self.keys)
class TapDance(HoldTap):
def __init__(self):
super().__init__(_make_key=False)
make_argumented_key(
names=('TD',),
constructor=TapDanceKey,
on_press=self.td_pressed,
on_release=self.td_released,
)
self.td_counts = {}
def process_key(self, keyboard, key, is_pressed, int_coord):
if isinstance(key, TapDanceKey):
if key in self.td_counts:
return key
for _key, state in self.key_states.copy().items():
if state.activated == ActivationType.RELEASED:
keyboard.cancel_timeout(state.timeout_key)
self.ht_activate_tap(_key, keyboard)
self.send_key_buffer(keyboard)
self.ht_deactivate_tap(_key, keyboard)
keyboard.resume_process_key(self, key, is_pressed, int_coord)
key = None
del self.key_states[_key]
del self.td_counts[state.tap_dance]
if key:
key = super().process_key(keyboard, key, is_pressed, int_coord)
return key
def td_pressed(self, key, keyboard, *args, **kwargs):
# active tap dance
if key in self.td_counts:
count = self.td_counts[key]
kc = key.keys[count]
keyboard.cancel_timeout(self.key_states[kc].timeout_key)
count += 1
# Tap dance reached the end of the list: send last tap in sequence
# and start from the beginning.
if count >= len(key.keys):
self.key_states[kc].activated = ActivationType.RELEASED
self.on_tap_time_expired(kc, keyboard)
count = 0
else:
del self.key_states[kc]
# new tap dance
else:
count = 0
current_key = key.keys[count]
self.ht_pressed(current_key, keyboard, *args, **kwargs)
self.td_counts[key] = count
# Add the active tap dance to key_states; `on_tap_time_expired` needs
# the back-reference.
self.key_states[current_key].tap_dance = key
def td_released(self, key, keyboard, *args, **kwargs):
kc = key.keys[self.td_counts[key]]
state = self.key_states[kc]
if state.activated == ActivationType.HOLD_TIMEOUT:
# release hold
self.ht_deactivate_hold(kc, keyboard, *args, **kwargs)
del self.key_states[kc]
del self.td_counts[key]
elif state.activated == ActivationType.INTERRUPTED:
# release tap
self.ht_deactivate_on_interrupt(kc, keyboard, *args, **kwargs)
del self.key_states[kc]
del self.td_counts[key]
else:
# keep counting
state.activated = ActivationType.RELEASED
def on_tap_time_expired(self, key, keyboard, *args, **kwargs):
# Note: the `key` argument is the current holdtap key in the sequence,
# not the tapdance key.
state = self.key_states[key]
if state.activated == ActivationType.RELEASED:
self.ht_activate_tap(key, keyboard, *args, **kwargs)
self.send_key_buffer(keyboard)
del self.td_counts[state.tap_dance]
super().on_tap_time_expired(key, keyboard, *args, **kwargs)

View file

@ -1,20 +0,0 @@
translate = {
'D3': 0,
'D2': 1,
'D1': 4,
'D0': 5,
'D4': 6,
'C6': 7,
'D7': 8,
'E6': 9,
'B4': 10,
'B5': 11,
'B6': 12,
'B2': 13,
'B3': 14,
'B1': 15,
'F7': 16,
'F6': 17,
'F5': 18,
'F4': 19,
}

View file

@ -1,32 +0,0 @@
import board
# Bit-C-Pro RP2040 pinout for reference, see https://nullbits.co/bit-c-pro/
# (unused)
pinout = [
board.D0, # Enc 3
board.D1, # Enc 3
None, # GND
None, # GND
board.D2, # Enc 2
board.D3, # Enc 2
board.D4, # Row 4 + breakout SDA
board.D5, # Row 3 + breakout SCL
board.D6, # Row 2
board.D7, # Row 1
board.D8, # Enc 1
board.D9, # Enc 1
# Unconnected breakout pins D11, D12, GND, D13, D14
board.D21, # WS2812 LEDs labeled D10/GP21 but only board.D21 is defined
board.D23, # MOSI - Enc 0
board.D20, # MISO - Enc 0
board.D22, # SCK - Row 0
board.D26, # A0 - Col 3
board.D27, # A1 - Col 2
board.D28, # A2 - Col 1
board.D29, # A3 - Col 0
None, # 3.3v
None, # RST
None, # GND
None, # RAW
]
# also defined: board.LED_RED, board.LED_GREEN, and board.LED_BLUE == board.LED

View file

@ -1,28 +0,0 @@
import board
pinout = [
board.TX,
board.RX,
None, # GND
None, # GND
board.SDA,
board.SCL,
board.GP04,
board.GP05,
board.GP06,
board.GP07,
board.GP08,
board.GP09,
board.GP21,
board.GP23,
board.GP20,
board.GP22,
board.GP26,
board.GP27,
board.GP28,
board.GP29,
None, # 3.3v
None, # RST
None, # GND
None, # RAW
]

View file

@ -1,28 +0,0 @@
import board
pinout = [
board.D0,
board.D1,
None, # GND
None, # GND
board.D2,
board.D3,
board.D4,
board.D5,
board.D6,
board.D7,
board.D8,
board.D9,
board.D21,
board.D23,
board.D20,
board.D22,
board.D26,
board.D27,
board.D28,
board.D29,
None, # VCC
None, # RUN
None, # GND
None, # RAW
]

View file

@ -1,33 +0,0 @@
import board
pinout = [
board.TX,
board.RX,
None, # GND
None, # GND
board.D2,
board.D3,
board.D4,
board.D5,
board.D6,
board.D7,
board.D8,
board.D9,
board.D12,
board.D13,
board.D14,
board.D15,
board.D16,
board.D21,
board.MOSI,
board.MISO,
board.SCK,
board.D26,
board.D27,
board.D28,
board.D29,
None, # 3.3v
None, # RST
None, # GND
None, # RAW
]

View file

@ -1,28 +0,0 @@
import board
pinout = [
board.TX,
board.RX,
None, # GND
None, # GND
board.GP2,
board.GP3,
board.GP4,
board.GP5,
board.GP6,
board.GP7,
board.GP8,
board.GP9,
board.CS,
board.SDO,
board.SDI,
board.SCK,
board.GP26,
board.GP27,
board.GP28,
board.GP29,
None, # 3.3v
None, # RST
None, # GND
None, # RAW
]

View file

@ -1,28 +0,0 @@
import board
pinout = [
board.D0,
board.D1,
None, # GND
None, # GND
board.D2,
board.D3,
board.D4,
board.D5,
board.D6,
board.D7,
board.D8,
board.D9,
board.D10,
board.MOSI,
board.MISO,
board.SCK,
board.A0,
board.A1,
board.A2,
board.A3,
None, # 3.3v
None, # RST
None, # GND
None, # RAW
]

View file

@ -1,43 +0,0 @@
import board
pinout = [
# Left, top->bottom
board.TX,
board.RX,
None, # GND
None, # GND
board.SDA,
board.SCL,
board.D4,
board.D5, # C6
board.D6, # D7
board.D7, # E6
board.D8, # B4
board.D9, # B5
# Right, bottom->top
board.D21, # B6
board.D23, # B2
board.D20, # B3
board.D22, # B1
board.D26, # F7
board.D27, # F6
board.D28, # F5
board.D29, # F4
None, # 3.3v
None, # RST
None, # GND
None, # RAW
# Bottom, left->right
board.D12,
board.D13,
board.D14,
board.D15,
board.D16,
# Internal
board.NEOPIXEL,
board.VBUS_SENSE,
board.POWER_LED,
board.I2C,
board.SPI,
board.UART,
]

View file

@ -1,28 +0,0 @@
import board
pinout = [
board.TX,
board.RX,
None, # GND
None, # GND
board.SDA,
board.SCL,
board.P0_22,
board.P0_24,
board.P1_00,
board.P0_11,
board.P1_04,
board.P1_06,
board.P0_09,
board.P0_10,
board.P1_11,
board.P1_13,
board.P1_15,
board.P0_02,
board.P0_29,
board.P0_31,
None, # 3.3v
None, # RST
None, # GND
None, # Battery+
]

View file

@ -1,28 +0,0 @@
import board
pinout = [
board.TX,
board.RX,
None, # GND
None, # GND
board.D2,
board.D3,
board.D4,
board.D5,
board.D6,
board.D7,
board.D8,
board.D9,
board.D21,
board.MOSI,
board.MISO,
board.SCK,
board.D26,
board.D27,
board.D28,
board.D29,
None, # 3.3v
None, # RST
None, # GND
None, # RAW
]

View file

@ -1,38 +0,0 @@
class DiodeOrientation:
'''
Orientation of diodes on handwired boards. You can think of:
COLUMNS = vertical
ROWS = horizontal
COL2ROW and ROW2COL are equivalent to their meanings in QMK.
'''
COLUMNS = 1
ROWS = 0
COL2ROW = COLUMNS
ROW2COL = ROWS
class Scanner:
'''
Base class for scanners.
'''
# for split keyboards, the offset value will be assigned in Split module
offset = 0
@property
def coord_mapping(self):
return tuple(range(self.offset, self.offset + self.key_count))
@property
def key_count(self):
raise NotImplementedError
def scan_for_changes(self):
'''
Scan for key events and return a key report if an event exists.
The key report is a byte array with contents [row, col, True if pressed else False]
'''
raise NotImplementedError

View file

@ -1,144 +0,0 @@
import digitalio
from keypad import Event as KeyEvent
from kmk.scanners import DiodeOrientation, Scanner
def ensure_DIO(x):
# __class__.__name__ is used instead of isinstance as the MCP230xx lib
# does not use the digitalio.DigitalInOut, but rather a self defined one:
# https://github.com/adafruit/Adafruit_CircuitPython_MCP230xx/blob/3f04abbd65ba5fa938fcb04b99e92ae48a8c9406/adafruit_mcp230xx/digital_inout.py#L33
if x.__class__.__name__ == 'DigitalInOut':
return x
else:
return digitalio.DigitalInOut(x)
class MatrixScanner(Scanner):
def __init__(
self,
cols,
rows,
diode_orientation=DiodeOrientation.COL2ROW,
pull=digitalio.Pull.UP,
rollover_cols_every_rows=None,
offset=0,
):
self.len_cols = len(cols)
self.len_rows = len(rows)
self.pull = pull
self.offset = offset
# A pin cannot be both a row and column, detect this by combining the
# two tuples into a set and validating that the length did not drop
#
# repr() hackery is because CircuitPython Pin objects are not hashable
unique_pins = {repr(c) for c in cols} | {repr(r) for r in rows}
assert (
len(unique_pins) == self.len_cols + self.len_rows
), 'Cannot use a pin as both a column and row'
del unique_pins
self.diode_orientation = diode_orientation
if self.diode_orientation == DiodeOrientation.COL2ROW:
self.anodes = [ensure_DIO(x) for x in cols]
self.cathodes = [ensure_DIO(x) for x in rows]
self.translate_coords = True
elif self.diode_orientation == DiodeOrientation.ROW2COL:
self.anodes = [ensure_DIO(x) for x in rows]
self.cathodes = [ensure_DIO(x) for x in cols]
self.translate_coords = False
else:
raise ValueError(f'Invalid DiodeOrientation: {self.diode_orienttaion}')
if self.pull == digitalio.Pull.DOWN:
self.outputs = self.anodes
self.inputs = self.cathodes
elif self.pull == digitalio.Pull.UP:
self.outputs = self.cathodes
self.inputs = self.anodes
self.translate_coords = not self.translate_coords
else:
raise ValueError(f'Invalid pull: {self.pull}')
for pin in self.outputs:
pin.switch_to_output()
for pin in self.inputs:
pin.switch_to_input(pull=self.pull)
self.rollover_cols_every_rows = rollover_cols_every_rows
if self.rollover_cols_every_rows is None:
self.rollover_cols_every_rows = self.len_rows
self._key_count = self.len_cols * self.len_rows
initial_state_value = b'\x01' if self.pull is digitalio.Pull.UP else b'\x00'
self.state = bytearray(initial_state_value) * self.key_count
@property
def key_count(self):
return self._key_count
def scan_for_changes(self):
'''
Poll the matrix for changes and return either None (if nothing updated)
or a bytearray (reused in later runs so copy this if you need the raw
array itself for some crazy reason) consisting of (row, col, pressed)
which are (int, int, bool)
'''
ba_idx = 0
any_changed = False
for oidx, opin in enumerate(self.outputs):
opin.value = self.pull is not digitalio.Pull.UP
for iidx, ipin in enumerate(self.inputs):
# cast to int to avoid
#
# >>> xyz = bytearray(3)
# >>> xyz[2] = True
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# OverflowError: value would overflow a 1 byte buffer
#
# I haven't dived too far into what causes this, but it's
# almost certainly because bool types in Python aren't just
# aliases to int values, but are proper pseudo-types
new_val = int(ipin.value)
old_val = self.state[ba_idx]
if old_val != new_val:
if self.translate_coords:
new_oidx = oidx + self.len_cols * (
iidx // self.rollover_cols_every_rows
)
new_iidx = iidx - self.rollover_cols_every_rows * (
iidx // self.rollover_cols_every_rows
)
row = new_iidx
col = new_oidx
else:
row = oidx
col = iidx
if self.pull is digitalio.Pull.UP:
pressed = not new_val
else:
pressed = new_val
self.state[ba_idx] = new_val
any_changed = True
break
ba_idx += 1
opin.value = self.pull is digitalio.Pull.UP
if any_changed:
break
if any_changed:
key_number = self.len_cols * row + col + self.offset
return KeyEvent(key_number, pressed)

View file

@ -1,43 +0,0 @@
import keypad
import rotaryio
from kmk.scanners import Scanner
class RotaryioEncoder(Scanner):
def __init__(self, pin_a, pin_b, divisor=4):
self.encoder = rotaryio.IncrementalEncoder(pin_a, pin_b, divisor)
self.position = 0
self._pressed = False
self._queue = []
@property
def key_count(self):
return 2
def scan_for_changes(self):
position = self.encoder.position
if position != self.position:
self._queue.append(position - self.position)
self.position = position
if not self._queue:
return
key_number = self.offset
if self._queue[0] > 0:
key_number += 1
if self._pressed:
self._queue[0] -= 1 if self._queue[0] > 0 else -1
if self._queue[0] == 0:
self._queue.pop(0)
self._pressed = False
else:
self._pressed = True
return keypad.Event(key_number, self._pressed)

View file

@ -1,59 +0,0 @@
import keypad
from kmk.scanners import Scanner
class KeypadScanner(Scanner):
'''
Translation layer around a CircuitPython 7 keypad scanner.
:param pin_map: A sequence of (row, column) tuples for each key.
:param kp: An instance of the keypad class.
'''
@property
def key_count(self):
return self.keypad.key_count
def scan_for_changes(self):
'''
Scan for key events and return a key report if an event exists.
The key report is a byte array with contents [row, col, True if pressed else False]
'''
ev = self.keypad.events.get()
if ev and self.offset:
return keypad.Event(ev.key_number + self.offset, ev.pressed)
return ev
class MatrixScanner(KeypadScanner):
'''
Row/Column matrix using the CircuitPython 7 keypad scanner.
:param row_pins: A sequence of pins used for rows.
:param col_pins: A sequence of pins used for columns.
:param direction: The diode orientation of the matrix.
'''
def __init__(self, *args, **kwargs):
self.keypad = keypad.KeyMatrix(*args, **kwargs)
super().__init__()
class KeysScanner(KeypadScanner):
'''
GPIO-per-key 'matrix' using the native CircuitPython 7 keypad scanner.
:param pins: An array of arrays of CircuitPython Pin objects, such that pins[r][c] is the pin for row r, column c.
'''
def __init__(self, *args, **kwargs):
self.keypad = keypad.Keys(*args, **kwargs)
super().__init__()
class ShiftRegisterKeys(KeypadScanner):
def __init__(self, *args, **kwargs):
self.keypad = keypad.ShiftRegisterKeys(*args, **kwargs)
super().__init__()

View file

@ -1,75 +0,0 @@
'''
Here we're abusing _asyncios TaskQueue to implement a very simple priority
queue task scheduler.
Despite documentation, Circuitpython doesn't usually ship with a min-heap
module; it does however implement a pairing-heap for `TaskQueue` in native code.
'''
try:
from typing import Callable
except ImportError:
pass
from supervisor import ticks_ms
from _asyncio import Task, TaskQueue
from kmk.kmktime import ticks_add, ticks_diff
_task_queue = TaskQueue()
class PeriodicTaskMeta:
def __init__(self, func: Callable[[None], None], period: int) -> None:
self._task = Task(self.call)
self._coro = func
self.period = period
def call(self) -> None:
after_ms = ticks_add(self._task.ph_key, self.period)
_task_queue.push_sorted(self._task, after_ms)
self._coro()
def restart(self) -> None:
_task_queue.push_sorted(self._task)
def create_task(
func: [Callable[[None], None], Task, PeriodicTaskMeta],
*,
after_ms: int = 0,
period_ms: int = 0,
) -> [Task, PeriodicTaskMeta]:
if isinstance(func, Task):
t = r = func
elif isinstance(func, PeriodicTaskMeta):
r = func
t = r._task
elif period_ms:
r = PeriodicTaskMeta(func, period_ms)
t = r._task
else:
t = r = Task(func)
if after_ms > 0:
_task_queue.push_sorted(t, ticks_add(ticks_ms(), after_ms))
elif after_ms == 0:
_task_queue.push_head(t)
return r
def get_due_task() -> [Callable, None]:
now = ticks_ms()
while True:
t = _task_queue.peek()
if not t or ticks_diff(t.ph_key, now) > 0:
break
_task_queue.pop_head()
yield t.coro
def cancel_task(t: [Task, PeriodicTaskMeta]) -> None:
if isinstance(t, PeriodicTaskMeta):
t = t._task
_task_queue.remove(t)

View file

@ -1,93 +0,0 @@
'''
Circuit Python wrapper around PIO implementation of UART
Original source of these examples: https://github.com/adafruit/Adafruit_CircuitPython_PIOASM/tree/main/examples (MIT)
'''
import rp2pio
from array import array
'''
.program uart_tx
.side_set 1 opt
; An 8n1 UART transmit program.
; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin.
pull side 1 [7] ; Assert stop bit, or stall with line in idle state
set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks
bitloop: ; This loop will run 8 times (8n1 UART)
out pins, 1 ; Shift 1 bit from OSR to the first OUT pin
jmp x-- bitloop [6] ; Each loop iteration is 8 cycles.
; compiles to:
'''
tx_code = array('H', [40864, 63271, 24577, 1602])
'''
.program uart_rx_mini
; Minimum viable 8n1 UART receiver. Wait for the start bit, then sample 8 bits
; with the correct timing.
; IN pin 0 is mapped to the GPIO used as UART RX.
; Autopush must be enabled, with a threshold of 8.
wait 0 pin 0 ; Wait for start bit
set x, 7 [10] ; Preload bit counter, delay until eye of first data bit
bitloop: ; Loop 8 times
in pins, 1 ; Sample data
jmp x-- bitloop [6] ; Each iteration is 8 cycles
; compiles to:
'''
rx_code = array('H', [8224, 59943, 16385, 1602])
class PIO_UART:
def __init__(self, *, tx, rx, baudrate=9600):
if tx:
self.tx_pio = rp2pio.StateMachine(
tx_code,
first_out_pin=tx,
first_sideset_pin=tx,
frequency=8 * baudrate,
initial_sideset_pin_state=1,
initial_sideset_pin_direction=1,
initial_out_pin_state=1,
initial_out_pin_direction=1,
sideset_enable=True,
)
if rx:
self.rx_pio = rp2pio.StateMachine(
rx_code,
first_in_pin=rx,
frequency=8 * baudrate,
auto_push=True,
push_threshold=8,
)
@property
def timeout(self):
return 0
@property
def baudrate(self):
return self.tx_pio.frequency // 8
@baudrate.setter
def baudrate(self, frequency):
self.tx_pio.frequency = frequency * 8
self.rx_pio.frequency = frequency * 8
def write(self, buf):
return self.tx_pio.write(buf)
@property
def in_waiting(self):
return self.rx_pio.in_waiting
def read(self, n):
b = bytearray(n)
n = self.rx_pio.readinto(b)
return b[:n]
def readinto(self, buf):
return self.rx_pio.readinto(buf)

View file

@ -1,49 +0,0 @@
try:
from typing import Optional
except ImportError:
pass
from supervisor import ticks_ms
from usb_cdc import console
def clamp(x: int, bottom: int = 0, top: int = 100) -> int:
return min(max(bottom, x), top)
_debug_enabled = None
class Debug:
'''default usage:
debug = Debug(__name__)
'''
def __init__(self, name: str = __name__):
self.name = name
def __call__(self, *message: str, name: Optional[str] = None) -> None:
if not name:
name = self.name
print(ticks_ms(), end=' ')
print(name, end=': ')
print(*message, sep='')
@property
def enabled(self) -> bool:
global _debug_enabled
if (
_debug_enabled is None
and console
and console.connected
and console.out_waiting == 0
):
return True
return bool(_debug_enabled)
@enabled.setter
def enabled(self, enabled: bool):
global _debug_enabled
_debug_enabled = enabled
self('debug.enabled=', enabled)

View file

@ -1 +0,0 @@
**/__pycache__/

View file

@ -1 +0,0 @@
(footprint "ALPS-1.25U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "1.25U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 11.90625 9.525) (end -11.90625 9.525) (layer Dwgs.User) (width 0.15) (tstamp cb764aad-6b18-4127-92b4-024a475df591)) (fp_line (start 11.90625 -9.525) (end -11.90625 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 8cd9b128-1b6a-404d-9264-42520593e71e)) (fp_line (start 11.90625 9.525) (end 11.90625 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 3e2ec1f3-41e2-4f84-97b4-c53898152f35)) (fp_line (start -11.90625 9.525) (end -11.90625 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 7c66d0d4-26e8-4dc5-bae4-960ed252c0b9)))

View file

@ -1 +0,0 @@
(footprint "ALPS-1.5U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "1.5U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 14.287500000000001 9.525) (end -14.287500000000001 9.525) (layer Dwgs.User) (width 0.15) (tstamp ce494295-bbdb-4f0a-9b26-fb0c3090e00d)) (fp_line (start 14.287500000000001 -9.525) (end -14.287500000000001 -9.525) (layer Dwgs.User) (width 0.15) (tstamp a6dcfc47-96ac-47db-ad65-87678157b9e3)) (fp_line (start 14.287500000000001 9.525) (end 14.287500000000001 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 0d80c430-681c-4531-8781-45941607dcc2)) (fp_line (start -14.287500000000001 9.525) (end -14.287500000000001 -9.525) (layer Dwgs.User) (width 0.15) (tstamp d6b6c97f-f158-4861-a733-3cbd7555ff11)))

View file

@ -1 +0,0 @@
(footprint "ALPS-1.75U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "1.75U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 16.66875 9.525) (end -16.66875 9.525) (layer Dwgs.User) (width 0.15) (tstamp 526105bb-a34e-45f8-a77a-c51d0596c06f)) (fp_line (start 16.66875 -9.525) (end -16.66875 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 0ff40098-df5f-4c10-b12f-b9d8a5104990)) (fp_line (start 16.66875 9.525) (end 16.66875 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 56403670-f538-4f67-b9bd-72183c3ceba9)) (fp_line (start -16.66875 9.525) (end -16.66875 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 2dc04ac7-70b2-42cf-a57c-63aefe16ba18)))

View file

@ -1 +0,0 @@
(footprint "ALPS-1U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "1U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 9.525 9.525) (end -9.525 9.525) (layer Dwgs.User) (width 0.15) (tstamp 72178b26-698a-493f-beb0-d32202d45ab6)) (fp_line (start 9.525 -9.525) (end -9.525 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 83230241-4f4a-4b2c-8e8a-2adfa6831dbb)) (fp_line (start 9.525 9.525) (end 9.525 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 497c7e83-6927-4192-a3b3-84ef76586dc3)) (fp_line (start -9.525 9.525) (end -9.525 -9.525) (layer Dwgs.User) (width 0.15) (tstamp d7eba7df-94bc-4237-902f-7ce46921cda5)))

View file

@ -1 +0,0 @@
(footprint "ALPS-2.25U-ReversedStabilizers" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "2.25U-ReversedStabilizers" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 21.431250000000002 9.525) (end -21.431250000000002 9.525) (layer Dwgs.User) (width 0.15) (tstamp b32e9a9f-e447-4c25-94d4-0ac754c74ccd)) (fp_line (start 21.431250000000002 -9.525) (end -21.431250000000002 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 5410aff4-297c-4286-9ed4-4be163983999)) (fp_line (start 21.431250000000002 9.525) (end 21.431250000000002 -9.525) (layer Dwgs.User) (width 0.15) (tstamp b1c18aeb-2bb5-43ee-ba4b-620dce82278e)) (fp_line (start -21.431250000000002 9.525) (end -21.431250000000002 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 13a49dbc-cdff-4d95-85f1-4fe5d98c627c)) (pad "" np_thru_hole circle (at -11.938 6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 2902f037-e787-43f4-aa73-cd9d8caa3578)) (pad "" np_thru_hole circle (at 11.938 6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp a7a434ba-c30a-41bc-9a6e-4df642563ec2)) (pad "" np_thru_hole circle (at -11.938 -8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 48c60381-f194-4b1a-8efc-8e3167bd9aeb)) (pad "" np_thru_hole circle (at 11.938 -8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp a75139ec-d70f-4ea4-be3d-fc21e69ab57a)))

View file

@ -1 +0,0 @@
(footprint "ALPS-2.25U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "2.25U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 21.431250000000002 9.525) (end -21.431250000000002 9.525) (layer Dwgs.User) (width 0.15) (tstamp b32e9a9f-e447-4c25-94d4-0ac754c74ccd)) (fp_line (start 21.431250000000002 -9.525) (end -21.431250000000002 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 5410aff4-297c-4286-9ed4-4be163983999)) (fp_line (start 21.431250000000002 9.525) (end 21.431250000000002 -9.525) (layer Dwgs.User) (width 0.15) (tstamp b1c18aeb-2bb5-43ee-ba4b-620dce82278e)) (fp_line (start -21.431250000000002 9.525) (end -21.431250000000002 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 13a49dbc-cdff-4d95-85f1-4fe5d98c627c)) (pad "" np_thru_hole circle (at -11.938 -6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 88f7e90b-13ea-468e-8945-135a40ef02bb)) (pad "" np_thru_hole circle (at 11.938 -6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 9faee0b1-bc5d-4b8b-92aa-744ff3d2537f)) (pad "" np_thru_hole circle (at -11.938 8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 2e7b984f-5b56-49ac-9d61-f40c3d45057f)) (pad "" np_thru_hole circle (at 11.938 8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 49e4e689-f86e-478e-a207-80b3533735f6)))

View file

@ -1 +0,0 @@
(footprint "ALPS-2.75U-ReversedStabilizers" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "2.75U-ReversedStabilizers" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 26.19375 9.525) (end -26.19375 9.525) (layer Dwgs.User) (width 0.15) (tstamp f1ed5606-9bef-4ab8-8cdf-dd0473f84f36)) (fp_line (start 26.19375 -9.525) (end -26.19375 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 7ec72a91-595a-4662-acfe-7dcabdefb318)) (fp_line (start 26.19375 9.525) (end 26.19375 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 7ec9d07b-5b65-4120-9f08-b4c42e7725b4)) (fp_line (start -26.19375 9.525) (end -26.19375 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 65965f3d-5378-4a30-b209-dc802f4b5bf8)) (pad "" np_thru_hole circle (at -11.938 6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 66607b3e-54bf-4408-8f39-2cddfd847d5a)) (pad "" np_thru_hole circle (at 11.938 6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 28d6fdac-2162-4c7e-a519-98f18610e843)) (pad "" np_thru_hole circle (at -11.938 -8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp c1fce67b-088d-450d-8003-364dbff2bb54)) (pad "" np_thru_hole circle (at 11.938 -8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 9a360af1-3137-47b6-8001-a549956a87d7)))

View file

@ -1 +0,0 @@
(footprint "ALPS-2.75U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "2.75U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 26.19375 9.525) (end -26.19375 9.525) (layer Dwgs.User) (width 0.15) (tstamp f1ed5606-9bef-4ab8-8cdf-dd0473f84f36)) (fp_line (start 26.19375 -9.525) (end -26.19375 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 7ec72a91-595a-4662-acfe-7dcabdefb318)) (fp_line (start 26.19375 9.525) (end 26.19375 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 7ec9d07b-5b65-4120-9f08-b4c42e7725b4)) (fp_line (start -26.19375 9.525) (end -26.19375 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 65965f3d-5378-4a30-b209-dc802f4b5bf8)) (pad "" np_thru_hole circle (at -11.938 -6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 943ae35d-fdb0-4257-bd22-ce4e9fdb936d)) (pad "" np_thru_hole circle (at 11.938 -6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 63eb99e8-856b-4f39-840f-b38fedf7db46)) (pad "" np_thru_hole circle (at -11.938 8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 96f9062f-6f73-4b24-8652-fd13b0ee27f8)) (pad "" np_thru_hole circle (at 11.938 8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 39f7f531-29e9-4d5b-a588-545e2feac9a4)))

View file

@ -1 +0,0 @@
(footprint "ALPS-2U-ReversedStabilizers" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "2U-ReversedStabilizers" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 19.05 9.525) (end -19.05 9.525) (layer Dwgs.User) (width 0.15) (tstamp 77a29dbb-6066-437d-806e-56f9bf5a4cc8)) (fp_line (start 19.05 -9.525) (end -19.05 -9.525) (layer Dwgs.User) (width 0.15) (tstamp f32cb924-26d9-466f-8172-d111cba8267f)) (fp_line (start 19.05 9.525) (end 19.05 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 21b532eb-75c0-41af-999b-38234fb777ee)) (fp_line (start -19.05 9.525) (end -19.05 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 636adec8-0bce-4cb5-a0fb-7d2f748c0186)) (pad "" np_thru_hole circle (at -11.938 6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 762010f1-f70e-4fa7-8dd5-e025495ea44a)) (pad "" np_thru_hole circle (at 11.938 6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp ca8e3f68-2134-42a4-8d24-164b0e125836)) (pad "" np_thru_hole circle (at -11.938 -8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp efb14d76-0c37-4802-bec9-227c3ca9a529)) (pad "" np_thru_hole circle (at 11.938 -8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 2f3e86a9-9ef1-4c3e-b37f-dfdb67b40040)))

View file

@ -1 +0,0 @@
(footprint "ALPS-2U-Vertical-ReversedStabilizers" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "2U-Vertical-ReversedStabilizers" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 9.525 19.05) (end -9.525 19.05) (layer Dwgs.User) (width 0.15) (tstamp 5127ad3f-d505-44b4-b893-f9248734716a)) (fp_line (start 9.525 -19.05) (end -9.525 -19.05) (layer Dwgs.User) (width 0.15) (tstamp e9237845-9ce7-4b87-b063-a147668bbf77)) (fp_line (start 9.525 19.05) (end 9.525 -19.05) (layer Dwgs.User) (width 0.15) (tstamp 8ee96a3f-2837-4851-ab6d-d2f52e95c258)) (fp_line (start -9.525 19.05) (end -9.525 -19.05) (layer Dwgs.User) (width 0.15) (tstamp 57ca5d7f-e237-479b-8e86-73070c718654)) (pad "" np_thru_hole circle (at -6.985 11.938) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp cd656327-a068-4cb3-83bd-72324680fded)) (pad "" np_thru_hole circle (at -6.985 -11.938) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 161035fa-5b10-45ed-a05b-2432da79cee7)) (pad "" np_thru_hole circle (at 8.255 11.938) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 2619ebfa-e2bd-45ef-9d41-7f2c00b084fb)) (pad "" np_thru_hole circle (at 8.255 -11.938) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp eed003ab-b4ae-494b-b72c-aa05f1591af0)))

View file

@ -1 +0,0 @@
(footprint "ALPS-2U-Vertical" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "2U-Vertical" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 9.525 19.05) (end -9.525 19.05) (layer Dwgs.User) (width 0.15) (tstamp 5127ad3f-d505-44b4-b893-f9248734716a)) (fp_line (start 9.525 -19.05) (end -9.525 -19.05) (layer Dwgs.User) (width 0.15) (tstamp e9237845-9ce7-4b87-b063-a147668bbf77)) (fp_line (start 9.525 19.05) (end 9.525 -19.05) (layer Dwgs.User) (width 0.15) (tstamp 8ee96a3f-2837-4851-ab6d-d2f52e95c258)) (fp_line (start -9.525 19.05) (end -9.525 -19.05) (layer Dwgs.User) (width 0.15) (tstamp 57ca5d7f-e237-479b-8e86-73070c718654)) (pad "" np_thru_hole circle (at 6.985 11.938) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 9e4fd76b-0404-4ce9-a0c2-eada7d5cf224)) (pad "" np_thru_hole circle (at 6.985 -11.938) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 4c736fb6-2527-44eb-b9eb-2f93ccbc108d)) (pad "" np_thru_hole circle (at -8.255 11.938) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 7c69285b-b251-42b5-ada9-b8abbc7e95e1)) (pad "" np_thru_hole circle (at -8.255 -11.938) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 51839e76-010e-489f-ad3d-7196f8d9c950)))

View file

@ -1 +0,0 @@
(footprint "ALPS-2U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "2U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 19.05 9.525) (end -19.05 9.525) (layer Dwgs.User) (width 0.15) (tstamp 77a29dbb-6066-437d-806e-56f9bf5a4cc8)) (fp_line (start 19.05 -9.525) (end -19.05 -9.525) (layer Dwgs.User) (width 0.15) (tstamp f32cb924-26d9-466f-8172-d111cba8267f)) (fp_line (start 19.05 9.525) (end 19.05 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 21b532eb-75c0-41af-999b-38234fb777ee)) (fp_line (start -19.05 9.525) (end -19.05 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 636adec8-0bce-4cb5-a0fb-7d2f748c0186)) (pad "" np_thru_hole circle (at -11.938 -6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 51504c2c-7b6a-401c-bace-dcb69f1909fa)) (pad "" np_thru_hole circle (at 11.938 -6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 1837d36a-3df9-4ce8-af46-5bc9e72e3643)) (pad "" np_thru_hole circle (at -11.938 8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp df0a8f5f-0d47-4d6d-830d-91fb56bfd334)) (pad "" np_thru_hole circle (at 11.938 8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 4162f0b7-a1f8-4966-8c46-44550dcdead9)))

View file

@ -1 +0,0 @@
(footprint "ALPS-6.25U-ReversedStabilizers" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "6.25U-ReversedStabilizers" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 59.53125 9.525) (end -59.53125 9.525) (layer Dwgs.User) (width 0.15) (tstamp 105d7273-a56d-4aba-9d1a-70719fd119bb)) (fp_line (start 59.53125 -9.525) (end -59.53125 -9.525) (layer Dwgs.User) (width 0.15) (tstamp c1a856a0-18e1-46db-8fdf-a78f82e264c0)) (fp_line (start 59.53125 9.525) (end 59.53125 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 55f4b4ef-b5ce-44a6-a7e3-05e756914515)) (fp_line (start -59.53125 9.525) (end -59.53125 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 3c8b0194-6950-4354-9fdb-a973bf3b4cbe)) (pad "" np_thru_hole circle (at -50 6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp ce310054-9eb2-4f17-9c74-4a9c358f2dc4)) (pad "" np_thru_hole circle (at 50 6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 4964653c-17b7-4e7e-ad7f-0e6b50fa6f0a)) (pad "" np_thru_hole circle (at -50 -8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp a4e68f92-4c0b-46ee-810c-b0732604ab86)) (pad "" np_thru_hole circle (at 50 -8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 61e5ee27-e002-4196-ad10-60bc1e0cc41e)))

View file

@ -1 +0,0 @@
(footprint "ALPS-6.25U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "6.25U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 59.53125 9.525) (end -59.53125 9.525) (layer Dwgs.User) (width 0.15) (tstamp 105d7273-a56d-4aba-9d1a-70719fd119bb)) (fp_line (start 59.53125 -9.525) (end -59.53125 -9.525) (layer Dwgs.User) (width 0.15) (tstamp c1a856a0-18e1-46db-8fdf-a78f82e264c0)) (fp_line (start 59.53125 9.525) (end 59.53125 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 55f4b4ef-b5ce-44a6-a7e3-05e756914515)) (fp_line (start -59.53125 9.525) (end -59.53125 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 3c8b0194-6950-4354-9fdb-a973bf3b4cbe)) (pad "" np_thru_hole circle (at -50 -6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp f95dac69-0d8c-4dc2-996e-0b4a1a2aa220)) (pad "" np_thru_hole circle (at 50 -6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp eb315c99-45ea-48a0-ae2c-df5909fc8716)) (pad "" np_thru_hole circle (at -50 8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 41220edf-f15d-429d-9802-1d7219061c18)) (pad "" np_thru_hole circle (at 50 8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 59ff547d-5127-4fa6-8484-a33e17922f1b)))

View file

@ -1 +0,0 @@
(footprint "ALPS-6.5U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "6.5U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 61.9125 9.525) (end -61.9125 9.525) (layer Dwgs.User) (width 0.15) (tstamp 6977c01b-893b-431d-8a48-19c0ab175aea)) (fp_line (start 61.9125 -9.525) (end -61.9125 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 9c82d20d-76bf-405e-8ae3-054685aa312f)) (fp_line (start 61.9125 9.525) (end 61.9125 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 0b8cfafd-3d7f-4384-85ac-5e0b94d3bfec)) (fp_line (start -61.9125 9.525) (end -61.9125 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 04a15c7e-5625-4f26-aa4a-69b2a2d5dc7f)))

View file

@ -1 +0,0 @@
(footprint "ALPS-7U-ReversedStabilizers" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "7U-ReversedStabilizers" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 66.675 9.525) (end -66.675 9.525) (layer Dwgs.User) (width 0.15) (tstamp e710f7c4-7eca-49ab-880a-cace6f21f1e8)) (fp_line (start 66.675 -9.525) (end -66.675 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 1386b3a6-279a-42bf-96cc-d89cb43c7327)) (fp_line (start 66.675 9.525) (end 66.675 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 9552850f-72fc-404e-897f-a67c7c4ff5bd)) (fp_line (start -66.675 9.525) (end -66.675 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 5fe8a9ce-232c-473b-9256-1f273541469e)) (pad "" np_thru_hole circle (at -57.15 6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp b712a017-ae2f-488d-b685-78d90f24b4f3)) (pad "" np_thru_hole circle (at 57.15 6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp f40fa014-a7ba-4fbf-86f6-ac46b86d737a)) (pad "" np_thru_hole circle (at -57.15 -8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp bd26e0b4-c667-4f6d-90e2-9f9e012699f6)) (pad "" np_thru_hole circle (at 57.15 -8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 666b4c17-173b-466e-a554-050c2161e2a9)))

View file

@ -1 +0,0 @@
(footprint "ALPS-7U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "7U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 66.675 9.525) (end -66.675 9.525) (layer Dwgs.User) (width 0.15) (tstamp e710f7c4-7eca-49ab-880a-cace6f21f1e8)) (fp_line (start 66.675 -9.525) (end -66.675 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 1386b3a6-279a-42bf-96cc-d89cb43c7327)) (fp_line (start 66.675 9.525) (end 66.675 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 9552850f-72fc-404e-897f-a67c7c4ff5bd)) (fp_line (start -66.675 9.525) (end -66.675 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 5fe8a9ce-232c-473b-9256-1f273541469e)) (pad "" np_thru_hole circle (at -57.15 -6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp f8103d43-7528-4dc6-8dd5-089b65e99796)) (pad "" np_thru_hole circle (at 57.15 -6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp a0ff9903-697d-419f-b5d3-a49fcb9898dd)) (pad "" np_thru_hole circle (at -57.15 8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp f90909b1-c6b9-4ebf-8fd1-8874b1ff294a)) (pad "" np_thru_hole circle (at 57.15 8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp a20d6053-cffc-4305-b785-be96082cef9c)))

View file

@ -1 +0,0 @@
(footprint "ALPS-ISO-ReversedStabilizers" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "ISO-ReversedStabilizers" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start -11.90625 19.05) (end -11.90625 0) (layer Dwgs.User) (width 0.15) (tstamp 33f7c90c-026c-4e58-bb81-26d12d165763)) (fp_line (start -11.90625 0) (end -16.66875 0) (layer Dwgs.User) (width 0.15) (tstamp 789017a0-2e5c-4f3c-a48f-0db85474a20c)) (fp_line (start -16.66875 -19.05) (end 11.90625 -19.05) (layer Dwgs.User) (width 0.15) (tstamp a518b9db-d4d4-4d5e-bc43-082b8930d671)) (fp_line (start 11.90625 -19.05) (end 11.90625 19.05) (layer Dwgs.User) (width 0.15) (tstamp 208a67dd-ee13-406d-85ec-707b1ce27f2e)) (fp_line (start -11.90625 19.05) (end 11.90625 19.05) (layer Dwgs.User) (width 0.15) (tstamp 664040e8-e7db-43ff-8a3d-e721822eb6c0)) (fp_line (start -16.66875 -19.05) (end -16.66875 0) (layer Dwgs.User) (width 0.15) (tstamp 2c392934-76d2-45a5-ba46-618618911f61)) (pad "" np_thru_hole circle (at -6.985 11.938) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp ed670121-b285-426c-b58c-4ee34a3d432f)) (pad "" np_thru_hole circle (at -6.985 -11.938) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 4f6c5565-0b0d-46e8-87dc-542408b54659)) (pad "" np_thru_hole circle (at 8.255 11.938) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp a3e2c7dd-5ac0-4624-a4da-d8bdb7943442)) (pad "" np_thru_hole circle (at 8.255 -11.938) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 7626bc04-7a60-408b-a6ce-6d0d12842636)))

View file

@ -1 +0,0 @@
(footprint "ALPS-ISO-Rotated-ReversedStabilizers" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "ISO-Rotated-ReversedStabilizers" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 19.05 11.90625) (end 19.05 -11.90625) (layer "Dwgs.User") (width 0.15) (tstamp fad52b1c-da10-45f4-8c5c-9ff2912ad24f)) (fp_line (start 0 11.90625) (end 0 16.66875) (layer "Dwgs.User") (width 0.15) (tstamp f36f4b08-0600-4bf6-9253-8e749a488b43)) (fp_line (start -19.05 -11.90625) (end 19.05 -11.90625) (layer "Dwgs.User") (width 0.15) (tstamp e332633b-7324-43bc-932e-7a620e45d322)) (fp_line (start -19.05 16.66875) (end -19.05 -11.90625) (layer "Dwgs.User") (width 0.15) (tstamp 0a96ce62-ad8a-4111-bbbf-3c423ef53072)) (fp_line (start 19.05 11.90625) (end 0 11.90625) (layer "Dwgs.User") (width 0.15) (tstamp df76f45b-270a-4fbf-b1b7-5e8b8bc0c393)) (fp_line (start -19.05 16.66875) (end 0 16.66875) (layer "Dwgs.User") (width 0.15) (tstamp 5e7e0465-093e-404a-b991-db39ab812354)) (pad "" np_thru_hole circle (at -11.938 6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 0aa3f5f1-3d8b-4834-a9aa-14ecf122a993)) (pad "" np_thru_hole circle (at 11.938 6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp d01a772a-ac83-47fc-b4af-5a9ec5bf0ff5)) (pad "" np_thru_hole circle (at -11.938 -8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp ce66d392-962f-485a-8487-2bd3a992277b)) (pad "" np_thru_hole circle (at 11.938 -8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp 366effde-6ab3-43d6-ad9e-ed4f210c1c97)))

View file

@ -1 +0,0 @@
(footprint "ALPS-ISO-Rotated" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "ISO-Rotated" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 19.05 11.90625) (end 19.05 -11.90625) (layer "Dwgs.User") (width 0.15) (tstamp fad52b1c-da10-45f4-8c5c-9ff2912ad24f)) (fp_line (start 0 11.90625) (end 0 16.66875) (layer "Dwgs.User") (width 0.15) (tstamp f36f4b08-0600-4bf6-9253-8e749a488b43)) (fp_line (start -19.05 -11.90625) (end 19.05 -11.90625) (layer "Dwgs.User") (width 0.15) (tstamp e332633b-7324-43bc-932e-7a620e45d322)) (fp_line (start -19.05 16.66875) (end -19.05 -11.90625) (layer "Dwgs.User") (width 0.15) (tstamp 0a96ce62-ad8a-4111-bbbf-3c423ef53072)) (fp_line (start 19.05 11.90625) (end 0 11.90625) (layer "Dwgs.User") (width 0.15) (tstamp df76f45b-270a-4fbf-b1b7-5e8b8bc0c393)) (fp_line (start -19.05 16.66875) (end 0 16.66875) (layer "Dwgs.User") (width 0.15) (tstamp 5e7e0465-093e-404a-b991-db39ab812354)) (pad "" np_thru_hole circle (at -11.938 -6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 21b7d1dc-21ec-40d5-90c0-dd9bff127a52)) (pad "" np_thru_hole circle (at 11.938 -6.985) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 9c58042d-467a-49f6-89cb-d9821616d7c8)) (pad "" np_thru_hole circle (at -11.938 8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp b4e62bb7-c9cc-4abb-9a0d-f35018638f94)) (pad "" np_thru_hole circle (at 11.938 8.255) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp b332b842-da45-45aa-a589-e4a47ccd4bb1)))

View file

@ -1 +0,0 @@
(footprint "ALPS-ISO" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "ISO" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start -11.90625 19.05) (end -11.90625 0) (layer Dwgs.User) (width 0.15) (tstamp 33f7c90c-026c-4e58-bb81-26d12d165763)) (fp_line (start -11.90625 0) (end -16.66875 0) (layer Dwgs.User) (width 0.15) (tstamp 789017a0-2e5c-4f3c-a48f-0db85474a20c)) (fp_line (start -16.66875 -19.05) (end 11.90625 -19.05) (layer Dwgs.User) (width 0.15) (tstamp a518b9db-d4d4-4d5e-bc43-082b8930d671)) (fp_line (start 11.90625 -19.05) (end 11.90625 19.05) (layer Dwgs.User) (width 0.15) (tstamp 208a67dd-ee13-406d-85ec-707b1ce27f2e)) (fp_line (start -11.90625 19.05) (end 11.90625 19.05) (layer Dwgs.User) (width 0.15) (tstamp 664040e8-e7db-43ff-8a3d-e721822eb6c0)) (fp_line (start -16.66875 -19.05) (end -16.66875 0) (layer Dwgs.User) (width 0.15) (tstamp 2c392934-76d2-45a5-ba46-618618911f61)) (pad "" np_thru_hole circle (at 6.985 11.938) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 2e8d3872-4afa-47ee-a7ca-1c6618c619e8)) (pad "" np_thru_hole circle (at 6.985 -11.938) (size 3.048 3.048) (drill 3.048) (layers *.Cu *.Mask) (tstamp 72756b18-cb28-4e4b-b92c-9ac9e6db5de9)) (pad "" np_thru_hole circle (at -8.255 11.938) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp cad62aba-75c3-45d8-9b61-1ab8f42edaa9)) (pad "" np_thru_hole circle (at -8.255 -11.938) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask) (tstamp efc00158-c2e3-4b49-a1d2-542786afb5c0)))

View file

@ -1 +0,0 @@
(footprint "ALPS-1.25U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "1.25U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 11.90625 9.525) (end -11.90625 9.525) (layer Dwgs.User) (width 0.15) (tstamp 0d18e5b3-ea19-44ee-8b3a-e3f365eb4221)) (fp_line (start 11.90625 -9.525) (end -11.90625 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 4ed4aab0-b151-4b46-b595-53c17812d747)) (fp_line (start 11.90625 9.525) (end 11.90625 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 4e77cf10-da50-464d-ac87-9c2a1906d8e7)) (fp_line (start -11.90625 9.525) (end -11.90625 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 3178d7f1-c8a4-49b6-a118-7807ac7381e5)))

View file

@ -1 +0,0 @@
(footprint "ALPS-1.5U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "1.5U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 14.287500000000001 9.525) (end -14.287500000000001 9.525) (layer Dwgs.User) (width 0.15) (tstamp de44335a-6670-4733-a53c-def1b0911e2a)) (fp_line (start 14.287500000000001 -9.525) (end -14.287500000000001 -9.525) (layer Dwgs.User) (width 0.15) (tstamp c98a0e68-f8f7-4466-9769-b97694177134)) (fp_line (start 14.287500000000001 9.525) (end 14.287500000000001 -9.525) (layer Dwgs.User) (width 0.15) (tstamp d58c0d7b-25cb-4af4-bbec-d5500f620274)) (fp_line (start -14.287500000000001 9.525) (end -14.287500000000001 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 31160b20-0590-40af-b681-0ed9ca0c3056)))

View file

@ -1 +0,0 @@
(footprint "ALPS-1.75U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "1.75U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 16.66875 9.525) (end -16.66875 9.525) (layer Dwgs.User) (width 0.15) (tstamp 536cad77-af9d-473b-94c8-9eeb8711d195)) (fp_line (start 16.66875 -9.525) (end -16.66875 -9.525) (layer Dwgs.User) (width 0.15) (tstamp b8070e3a-0ac7-44a5-b044-4935d6325dc6)) (fp_line (start 16.66875 9.525) (end 16.66875 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 31bb1600-828c-43ba-8db1-73f4132341fd)) (fp_line (start -16.66875 9.525) (end -16.66875 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 149ee1b6-0242-45ad-bc88-8ee462c45ea4)))

View file

@ -1 +0,0 @@
(footprint "ALPS-1U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "1U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 9.525 9.525) (end -9.525 9.525) (layer Dwgs.User) (width 0.15) (tstamp b3e936f1-4db5-475a-a3d1-300758e02194)) (fp_line (start 9.525 -9.525) (end -9.525 -9.525) (layer Dwgs.User) (width 0.15) (tstamp a8b2fb1d-247c-4de8-8694-95b1d74d67bb)) (fp_line (start 9.525 9.525) (end 9.525 -9.525) (layer Dwgs.User) (width 0.15) (tstamp d95f340b-2601-419c-9845-9473dfae2f5d)) (fp_line (start -9.525 9.525) (end -9.525 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 451e206a-9779-4e6b-9aac-4412d61bf96f)))

View file

@ -1 +0,0 @@
(footprint "ALPS-2.25U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "2.25U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 21.431250000000002 9.525) (end -21.431250000000002 9.525) (layer Dwgs.User) (width 0.15) (tstamp 3cfb1caa-5d9f-4e80-bdb2-d0fcd3346fa5)) (fp_line (start 21.431250000000002 -9.525) (end -21.431250000000002 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 79e36474-2405-430c-8812-96616f0a7cc2)) (fp_line (start 21.431250000000002 9.525) (end 21.431250000000002 -9.525) (layer Dwgs.User) (width 0.15) (tstamp def8ab42-e9c3-4e5b-b0d9-c2366edb948f)) (fp_line (start -21.431250000000002 9.525) (end -21.431250000000002 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 0b30e47c-c42e-4d05-aff6-af133494dd38)))

View file

@ -1 +0,0 @@
(footprint "ALPS-2.75U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "2.75U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 26.19375 9.525) (end -26.19375 9.525) (layer Dwgs.User) (width 0.15) (tstamp 57ca230f-02d3-4178-ac09-aead61e6f497)) (fp_line (start 26.19375 -9.525) (end -26.19375 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 1e37b8e5-3595-4c14-a4ed-7f53d67e9084)) (fp_line (start 26.19375 9.525) (end 26.19375 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 5b2e29df-2c44-4645-bd1c-e53e8cf1d184)) (fp_line (start -26.19375 9.525) (end -26.19375 -9.525) (layer Dwgs.User) (width 0.15) (tstamp dbd53230-6ec1-44e9-89b3-4a164f8c1201)))

View file

@ -1 +0,0 @@
(footprint "ALPS-2U" (version 20211014) (generator pcbnew) (layer "F.Cu") (tedit 5CF31DEF) (attr through_hole) (fp_text reference "REF**" (at 0 3.175) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 457ccf5a-5a2a-4500-9abc-eea24f520126)) (fp_text value "2U" (at 0 -7.9375) (layer "Dwgs.User") (effects (font (size 0.8 0.8) (thickness 0.15))) (tstamp 3dd8e20f-b384-4f25-a6e3-ea00fb0e7bf1)) (fp_line (start 7 -7) (end 7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 5987ac71-f023-4d7d-8582-5020dd45255c)) (fp_line (start 5 -7) (end 7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 5a9e6516-daff-46b3-89eb-35e226de0642)) (fp_line (start -7 7) (end -5 7) (layer "Dwgs.User") (width 0.15) (tstamp 64fc0026-efb3-4467-937d-07e4906e257d)) (fp_line (start -7 5) (end -7 7) (layer "Dwgs.User") (width 0.15) (tstamp 6c467cbc-3b19-40b5-b739-a032138ec1e0)) (fp_line (start -5 -7) (end -7 -7) (layer "Dwgs.User") (width 0.15) (tstamp 751ae621-3a9f-4edc-8d0b-12274e293f6b)) (fp_line (start 7 7) (end 7 5) (layer "Dwgs.User") (width 0.15) (tstamp 7a895e2a-362d-44c2-8fdf-5297370d6807)) (fp_line (start -7 -7) (end -7 -5) (layer "Dwgs.User") (width 0.15) (tstamp 9e24d424-25ae-493e-aaac-37e970d6516a)) (fp_line (start 5 7) (end 7 7) (layer "Dwgs.User") (width 0.15) (tstamp b2aa4844-59d8-47ab-99e2-704eb7844a7e)) (pad "1" thru_hole circle (at -2.5 -4) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp d0700dbc-d5c6-436f-8d37-86fdedb7d26a)) (pad "2" thru_hole circle (at 2.5 -4.5) (size 2.3 2.3) (drill 1.524) (layers *.Cu "B.Mask") (tstamp 4c5b25bf-23be-43ff-a646-2329f951b5ed)) (fp_line (start 19.05 9.525) (end -19.05 9.525) (layer Dwgs.User) (width 0.15) (tstamp c208f7d6-0203-464e-b563-30b9da8314e0)) (fp_line (start 19.05 -9.525) (end -19.05 -9.525) (layer Dwgs.User) (width 0.15) (tstamp 4ab71437-86cd-488e-9d01-b030bf4043ed)) (fp_line (start 19.05 9.525) (end 19.05 -9.525) (layer Dwgs.User) (width 0.15) (tstamp d3bea83a-026c-4777-be81-4aa5fe93bf2c)) (fp_line (start -19.05 9.525) (end -19.05 -9.525) (layer Dwgs.User) (width 0.15) (tstamp decc7183-21b7-4ab9-889d-c0e788cb60ac)))

Some files were not shown because too many files have changed in this diff Show more