diff --git a/kmk/ble.py b/kmk/ble.py deleted file mode 100644 index cf1be8d..0000000 --- a/kmk/ble.py +++ /dev/null @@ -1,101 +0,0 @@ -from adafruit_ble import BLERadio -from adafruit_ble.advertising.standard import ProvideServicesAdvertisement -from adafruit_ble.services.standard.hid import HIDService -from kmk.hid import AbstractHID - -BLE_APPEARANCE_HID_KEYBOARD = 961 -# Hardcoded in CPy -MAX_CONNECTIONS = 2 - - -class BLEHID(AbstractHID): - def post_init(self, ble_name='KMK Keyboard', **kwargs): - self.conn_id = -1 - - self.ble = BLERadio() - self.ble.name = ble_name - self.hid = HIDService() - self.hid.protocol_mode = 0 # Boot protocol - - # 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 - if not self.ble.connected or not self.hid.devices: - self.start_advertising() - - self.conn_id = 0 - - @property - def devices(self): - '''Search through the provided list of devices to find the ones with the - send_report attribute.''' - if not self.ble.connected: - return [] - - result = [] - # Security issue: - # This introduces a race condition. Let's say you have 2 active - # connections: Alice and Bob - Alice is connection 1 and Bob 2. - # Now Chuck who has already paired with the device in the past - # (this assumption is needed only in the case of LESC) - # wants to gather the keystrokes you send to Alice. You have - # selected right now to talk to Alice (1) and you're typing a secret. - # If Chuck kicks Alice off and is quick enough to connect to you, - # which means quicker than the running interval of this function, - # he'll be earlier in the `self.hid.devices` so will take over the - # selected 1 position in the resulted array. - # If no LESC is in place, Chuck can sniff the keystrokes anyway - for device in self.hid.devices: - if hasattr(device, 'send_report'): - result.append(device) - - return result - - def _check_connection(self): - devices = self.devices - if not devices: - return False - - if self.conn_id >= len(devices): - self.conn_id = len(devices) - 1 - - if self.conn_id < 0: - return False - - if not devices[self.conn_id]: - return False - - return True - - def hid_send(self, evt): - if not self._check_connection(): - return - - device = self.devices[self.conn_id] - - while len(evt) < len(device._characteristic.value) + 1: - evt.append(0) - - return device.send_report(evt[1:]) - - def clear_bonds(self): - import _bleio - - _bleio.adapter.erase_bonding() - - def next_connection(self): - self.conn_id = (self.conn_id + 1) % len(self.devices) - - def previous_connection(self): - self.conn_id = (self.conn_id - 1) % len(self.devices) - - def start_advertising(self): - advertisement = ProvideServicesAdvertisement(self.hid) - advertisement.appearance = BLE_APPEARANCE_HID_KEYBOARD - - self.ble.start_advertising(advertisement) - - def stop_advertising(self): - self.ble.stop_advertising() diff --git a/kmk/consts.py b/kmk/consts.py index 48b3a81..b222ede 100644 --- a/kmk/consts.py +++ b/kmk/consts.py @@ -1,3 +1,5 @@ +from micropython import const + try: from kmk.release_info import KMK_RELEASE except Exception: @@ -5,7 +7,7 @@ except Exception: class UnicodeMode: - NOOP = 0 - LINUX = IBUS = 1 - MACOS = OSX = RALT = 2 - WINC = 3 + NOOP = const(0) + LINUX = IBUS = const(1) + MACOS = OSX = RALT = const(2) + WINC = const(3) diff --git a/kmk/led.py b/kmk/extensions/led.py similarity index 99% rename from kmk/led.py rename to kmk/extensions/led.py index 40b5b84..b586369 100644 --- a/kmk/led.py +++ b/kmk/extensions/led.py @@ -23,6 +23,7 @@ class LED(Extension): animation_mode=AnimationModes.STATIC, animation_speed=1, user_animation=None, + val=100, ): try: self._led = pulseio.PWMOut(led_pin) @@ -41,6 +42,7 @@ class LED(Extension): 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 diff --git a/kmk/rgb.py b/kmk/extensions/rgb.py similarity index 78% rename from kmk/rgb.py rename to kmk/extensions/rgb.py index 291ceb1..43e51ab 100644 --- a/kmk/rgb.py +++ b/kmk/extensions/rgb.py @@ -3,6 +3,7 @@ import time from math import e, exp, pi, sin from kmk.extensions import Extension +from kmk.keys import make_key rgb_config = {} @@ -40,6 +41,7 @@ class RGB(Extension): reverse_animation=False, user_animation=None, disable_auto_write=False, + loopcounter=0, ): self.neopixel = neopixel.NeoPixel( pixel_pin, @@ -63,9 +65,30 @@ class RGB(Extension): 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.disable_auto_write = disable_auto_write + self.loopcounter = loopcounter + + 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) def during_bootup(self, keyboard): pass @@ -357,7 +380,6 @@ class RGB(Extension): if self.effect_init: self._init_effect() - if self.enabled: if self.animation_mode == 'breathing': return self.effect_breathing() elif self.animation_mode == 'rainbow': @@ -386,8 +408,7 @@ class RGB(Extension): return max(self.intervals) if interval in self.intervals: return interval - else: - return False + return None def _init_effect(self): if ( @@ -404,8 +425,7 @@ class RGB(Extension): return self def _check_update(self): - if self.animation_mode == 'static_standby': - return True + return bool(self.animation_mode == 'static_standby') def _do_update(self): if self.animation_mode == 'static_standby': @@ -478,3 +498,71 @@ class RGB(Extension): self.show() return self + + def _rgb_tog(self, key, state, *args, **kwargs): + if state.pixels.animation_mode == 'static_standby': + state.pixels.animation_mode = 'static' + state.pixels.enabled = not state.pixels.enabled + return state + + def _rgb_hui(self, key, state, *args, **kwargs): + state.pixels.increase_hue() + return state + + def _rgb_hud(self, key, state, *args, **kwargs): + state.pixels.decrease_hue() + return state + + def _rgb_sai(self, key, state, *args, **kwargs): + state.pixels.increase_sat() + return state + + def _rgb_sad(self, key, state, *args, **kwargs): + state.pixels.decrease_sat() + return state + + def _rgb_vai(self, key, state, *args, **kwargs): + state.pixels.increase_val() + return state + + def _rgb_vad(self, key, state, *args, **kwargs): + state.pixels.decrease_val() + return state + + def _rgb_ani(self, key, state, *args, **kwargs): + state.pixels.increase_ani() + return state + + def _rgb_and(self, key, state, *args, **kwargs): + state.pixels.decrease_ani() + return state + + def _rgb_mode_static(self, key, state, *args, **kwargs): + state.pixels.effect_init = True + state.pixels.animation_mode = 'static' + return state + + def _rgb_mode_breathe(self, key, state, *args, **kwargs): + state.pixels.effect_init = True + state.pixels.animation_mode = 'breathing' + return state + + def _rgb_mode_breathe_rainbow(self, key, state, *args, **kwargs): + state.pixels.effect_init = True + state.pixels.animation_mode = 'breathing_rainbow' + return state + + def _rgb_mode_rainbow(self, key, state, *args, **kwargs): + state.pixels.effect_init = True + state.pixels.animation_mode = 'rainbow' + return state + + def _rgb_mode_swirl(self, key, state, *args, **kwargs): + state.pixels.effect_init = True + state.pixels.animation_mode = 'swirl' + return state + + def _rgb_mode_knight(self, key, state, *args, **kwargs): + state.pixels.effect_init = True + state.pixels.animation_mode = 'knight' + return state diff --git a/kmk/handlers/stock.py b/kmk/handlers/stock.py index 7a8edb7..c6c6fda 100644 --- a/kmk/handlers/stock.py +++ b/kmk/handlers/stock.py @@ -128,86 +128,3 @@ def td_pressed(key, state, *args, **kwargs): def td_released(key, state, *args, **kwargs): return state._process_tap_dance(key, False) - - -def rgb_tog(key, state, *args, **kwargs): - if state.pixels.animation_mode == 'static_standby': - state.pixels.animation_mode = 'static' - state.pixels.enabled = not state.pixels.enabled - return state - - -def rgb_hui(key, state, *args, **kwargs): - state.pixels.increase_hue() - return state - - -def rgb_hud(key, state, *args, **kwargs): - state.pixels.decrease_hue() - return state - - -def rgb_sai(key, state, *args, **kwargs): - state.pixels.increase_sat() - return state - - -def rgb_sad(key, state, *args, **kwargs): - state.pixels.decrease_sat() - return state - - -def rgb_vai(key, state, *args, **kwargs): - state.pixels.increase_val() - return state - - -def rgb_vad(key, state, *args, **kwargs): - state.pixels.decrease_val() - return state - - -def rgb_ani(key, state, *args, **kwargs): - state.pixels.increase_ani() - return state - - -def rgb_and(key, state, *args, **kwargs): - state.pixels.decrease_ani() - return state - - -def rgb_mode_static(key, state, *args, **kwargs): - state.pixels.effect_init = True - state.pixels.animation_mode = 'static' - return state - - -def rgb_mode_breathe(key, state, *args, **kwargs): - state.pixels.effect_init = True - state.pixels.animation_mode = 'breathing' - return state - - -def rgb_mode_breathe_rainbow(key, state, *args, **kwargs): - state.pixels.effect_init = True - state.pixels.animation_mode = 'breathing_rainbow' - return state - - -def rgb_mode_rainbow(key, state, *args, **kwargs): - state.pixels.effect_init = True - state.pixels.animation_mode = 'rainbow' - return state - - -def rgb_mode_swirl(key, state, *args, **kwargs): - state.pixels.effect_init = True - state.pixels.animation_mode = 'swirl' - return state - - -def rgb_mode_knight(key, state, *args, **kwargs): - state.pixels.effect_init = True - state.pixels.animation_mode = 'knight' - return state diff --git a/kmk/hid.py b/kmk/hid.py index 42fb988..cdfcf2a 100644 --- a/kmk/hid.py +++ b/kmk/hid.py @@ -1,45 +1,44 @@ -import usb_hid - from kmk.keys import FIRST_KMK_INTERNAL_KEY, ConsumerKey, ModifierKey +from mycropython import const class HIDModes: - NOOP = 0 # currently unused; for testing? - USB = 1 - BLE = 2 # currently unused; for bluetooth + NOOP = const(0) # currently unused; for testing? + USB = const(1) + BLE = const(2) # currently unused; for bluetooth ALL_MODES = (NOOP, USB, BLE) class HIDReportTypes: - KEYBOARD = 1 - MOUSE = 2 - CONSUMER = 3 - SYSCONTROL = 4 + KEYBOARD = const(1) + MOUSE = const(2) + CONSUMER = const(3) + SYSCONTROL = const(4) class HIDUsage: - KEYBOARD = 0x06 - MOUSE = 0x02 - CONSUMER = 0x01 - SYSCONTROL = 0x80 + KEYBOARD = const(0x06) + MOUSE = const(0x02) + CONSUMER = const(0x01) + SYSCONTROL = const(0x80) class HIDUsagePage: - CONSUMER = 0x0C - KEYBOARD = MOUSE = SYSCONTROL = 0x01 + CONSUMER = const(0x0C) + KEYBOARD = MOUSE = SYSCONTROL = const(0x01) HID_REPORT_SIZES = { - HIDReportTypes.KEYBOARD: 8, - HIDReportTypes.MOUSE: 4, - HIDReportTypes.CONSUMER: 2, - HIDReportTypes.SYSCONTROL: 8, # TODO find the correct value for this + HIDReportTypes.KEYBOARD: const(8), + HIDReportTypes.MOUSE: const(4), + HIDReportTypes.CONSUMER: const(2), + HIDReportTypes.SYSCONTROL: const(8), # TODO find the correct value for this } class AbstractHID: - REPORT_BYTES = 8 + REPORT_BYTES = const(8) def __init__(self, **kwargs): self._evt = bytearray(self.REPORT_BYTES) @@ -189,12 +188,14 @@ class AbstractHID: class USBHID(AbstractHID): - REPORT_BYTES = 9 + import usb_hid + + REPORT_BYTES = const(9) def post_init(self, **kwargs): self.devices = {} - for device in usb_hid.devices: + for device in self.usb_hid.devices: us = device.usage up = device.usage_page @@ -224,4 +225,101 @@ class USBHID(AbstractHID): class BLEHID(AbstractHID): - pass + from adafruit_ble import BLERadio + from adafruit_ble.advertising.standard import ProvideServicesAdvertisement + from adafruit_ble.services.standard.hid import HIDService + + BLE_APPEARANCE_HID_KEYBOARD = const(961) + # Hardcoded in CPy + MAX_CONNECTIONS = const(2) + + def post_init(self, ble_name='KMK Keyboard', **kwargs): + self.conn_id = -1 + + self.ble = self.BLERadio() + self.ble.name = ble_name + self.hid = self.HIDService() + self.hid.protocol_mode = 0 # Boot protocol + + # 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 + if not self.ble.connected or not self.hid.devices: + self.start_advertising() + + self.conn_id = 0 + + @property + def devices(self): + '''Search through the provided list of devices to find the ones with the + send_report attribute.''' + if not self.ble.connected: + return [] + + result = [] + # Security issue: + # This introduces a race condition. Let's say you have 2 active + # connections: Alice and Bob - Alice is connection 1 and Bob 2. + # Now Chuck who has already paired with the device in the past + # (this assumption is needed only in the case of LESC) + # wants to gather the keystrokes you send to Alice. You have + # selected right now to talk to Alice (1) and you're typing a secret. + # If Chuck kicks Alice off and is quick enough to connect to you, + # which means quicker than the running interval of this function, + # he'll be earlier in the `self.hid.devices` so will take over the + # selected 1 position in the resulted array. + # If no LESC is in place, Chuck can sniff the keystrokes anyway + for device in self.hid.devices: + if hasattr(device, 'send_report'): + result.append(device) + + return result + + def _check_connection(self): + devices = self.devices + if not devices: + return False + + if self.conn_id >= len(devices): + self.conn_id = len(devices) - 1 + + if self.conn_id < 0: + return False + + if not devices[self.conn_id]: + return False + + return True + + def hid_send(self, evt): + if not self._check_connection(): + return + + device = self.devices[self.conn_id] + + while len(evt) < len(device._characteristic.value) + 1: + evt.append(0) + + return device.send_report(evt[1:]) + + def clear_bonds(self): + import _bleio + + _bleio.adapter.erase_bonding() + + def next_connection(self): + self.conn_id = (self.conn_id + 1) % len(self.devices) + + def previous_connection(self): + self.conn_id = (self.conn_id - 1) % len(self.devices) + + def start_advertising(self): + advertisement = self.ProvideServicesAdvertisement(self.hid) + advertisement.appearance = self.BLE_APPEARANCE_HID_KEYBOARD + + self.ble.start_advertising(advertisement) + + def stop_advertising(self): + self.ble.stop_advertising() diff --git a/kmk/keys.py b/kmk/keys.py index 79c0d02..327f607 100644 --- a/kmk/keys.py +++ b/kmk/keys.py @@ -1,3 +1,5 @@ +from micropython import const + import kmk.handlers.layers as layers import kmk.handlers.modtap as modtap import kmk.handlers.stock as handlers @@ -11,12 +13,12 @@ from kmk.key_validators import ( ) from kmk.types import AttrDict, UnicodeModeKeyMeta -FIRST_KMK_INTERNAL_KEY = 1000 +FIRST_KMK_INTERNAL_KEY = const(1000) NEXT_AVAILABLE_KEY = 1000 -KEY_SIMPLE = 0 -KEY_MODIFIER = 1 -KEY_CONSUMER = 2 +KEY_SIMPLE = const(0) +KEY_MODIFIER = const(1) +KEY_CONSUMER = const(2) # Global state, will be filled in througout this file, and # anywhere the user creates custom keys @@ -202,7 +204,7 @@ class ModifierKey(Key): # FIXME this is atrocious to read. Please, please, please, strike down upon # this with great vengeance and furious anger. - FAKE_CODE = -1 + FAKE_CODE = const(-1) def __call__(self, modified_code=None, no_press=None, no_release=None): if modified_code is None and no_press is None and no_release is None: @@ -610,24 +612,6 @@ make_key( on_press=handlers.gesc_pressed, on_release=handlers.gesc_released, ) -make_key(names=('RGB_TOG',), on_press=handlers.rgb_tog) -make_key(names=('RGB_HUI',), on_press=handlers.rgb_hui) -make_key(names=('RGB_HUD',), on_press=handlers.rgb_hud) -make_key(names=('RGB_SAI',), on_press=handlers.rgb_sai) -make_key(names=('RGB_SAD',), on_press=handlers.rgb_sad) -make_key(names=('RGB_VAI',), on_press=handlers.rgb_vai) -make_key(names=('RGB_VAD',), on_press=handlers.rgb_vad) -make_key(names=('RGB_ANI',), on_press=handlers.rgb_ani) -make_key(names=('RGB_AND',), on_press=handlers.rgb_and) -make_key(names=('RGB_MODE_PLAIN', 'RGB_M_P'), on_press=handlers.rgb_mode_static) -make_key(names=('RGB_MODE_BREATHE', 'RGB_M_B'), on_press=handlers.rgb_mode_breathe) -make_key(names=('RGB_MODE_RAINBOW', 'RGB_M_R'), on_press=handlers.rgb_mode_rainbow) -make_key( - names=('RGB_MODE_BREATHE_RAINBOW', 'RGB_M_BR'), - on_press=handlers.rgb_mode_breathe_rainbow, -) -make_key(names=('RGB_MODE_SWIRL', 'RGB_M_S'), on_press=handlers.rgb_mode_swirl) -make_key(names=('RGB_MODE_KNIGHT', 'RGB_M_K'), on_press=handlers.rgb_mode_knight) # Layers make_argumented_key( diff --git a/kmk/kmk_keyboard.py b/kmk/kmk_keyboard.py index d994bdc..fd834d9 100644 --- a/kmk/kmk_keyboard.py +++ b/kmk/kmk_keyboard.py @@ -3,7 +3,6 @@ # a line into their keymaps. import kmk.preload_imports # isort:skip # NOQA -from kmk import rgb from kmk.consts import KMK_RELEASE, UnicodeMode from kmk.hid import BLEHID, USBHID, AbstractHID, HIDModes from kmk.keys import KC @@ -29,14 +28,11 @@ class KMKKeyboard: unicode_mode = UnicodeMode.NOOP tap_time = 300 - # RGB config - rgb_pixel_pin = None - rgb_config = rgb.rgb_config - ##### # Internal State _keys_pressed = set() _coord_keys_pressed = {} + _hid_helper = None _hid_pending = False # this should almost always be PREpended to, replaces @@ -62,7 +58,7 @@ class KMKKeyboard: 'matrix_scanner={} ' 'unicode_mode={} ' 'tap_time={} ' - 'hid_helper={} ' + '_hid_helper={} ' 'keys_pressed={} ' 'coord_keys_pressed={} ' 'hid_pending={} ' @@ -83,7 +79,7 @@ class KMKKeyboard: self.matrix_scanner, self.unicode_mode, self.tap_time, - self.hid_helper.__name__, + self._hid_helper.__name__, # internal state self._keys_pressed, self._coord_keys_pressed, @@ -104,7 +100,7 @@ class KMKKeyboard: print(self) def _send_hid(self): - self._hid_helper_inst.create_report(self._keys_pressed).send() + self._hid_helper.create_report(self._keys_pressed).send() self._hid_pending = False def _handle_matrix_report(self, update=None): @@ -112,37 +108,6 @@ class KMKKeyboard: self._on_matrix_changed(update[0], update[1], update[2]) self.state_changed = True - def _receive_from_initiator(self): - 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 >= 3: - self.uart_buffer.append(self._uart.read(3)) - if self.uart_buffer: - update = bytearray(self.uart_buffer.pop(0)) - - # Built in debug mode switch - if update == b'DEB': - print(self._uart.readline()) - return None - return update - - return None - - def _send_debug(self, message): - ''' - Prepends DEB and appends a newline to allow debug messages to - be detected and handled differently than typical keypresses. - :param message: Debug message - ''' - if self._uart is not None: - self._uart.write('DEB') - self._uart.write(message, '\n') - ##### # SPLICE: INTERNAL STATE # FIXME CLEAN THIS @@ -353,12 +318,13 @@ class KMKKeyboard: def _init_hid(self): if self.hid_type == HIDModes.NOOP: - self.hid_helper = AbstractHID + self._hid_helper = AbstractHID elif self.hid_type == HIDModes.USB: - self.hid_helper = USBHID + self._hid_helper = USBHID elif self.hid_type == HIDModes.BLE: - self.hid_helper = BLEHID - self._hid_helper_inst = self.hid_helper() + self._hid_helper = BLEHID + else: + self._hid_helper = AbstractHID def _init_matrix(self): self.matrix = MatrixScanner( @@ -374,7 +340,7 @@ class KMKKeyboard: self._extensions = [] + getattr(self, 'extensions', []) try: - del self.extensions + del self._extensions except Exception: pass finally: