diff --git a/kmk/firmware.py b/kmk/firmware.py index a5c37a6..db34eb0 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -92,8 +92,8 @@ class Firmware: uart_pin = None pixel_pin = None num_pixels = None + rgb_order = (1, 0, 2) # GRB WS2812 pixels = None - pixel_state = rgb.pixelinit() def __init__(self): # Attempt to sanely guess a coord_mapping if one is not provided @@ -188,13 +188,6 @@ class Firmware: else: return busio.UART(tx=pin, rx=None, timeout=timeout) - def init_pixels(self, pixel_pin, num_pixels=0): - try: - import neopixel - return neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.3, auto_write=False) - except ImportError as e: - print(e) - return None def go(self): assert self.keymap, 'must define a keymap with at least one row' @@ -217,7 +210,8 @@ class Firmware: self.uart = self.init_uart(self.uart_pin) if self.pixel_pin is not None: - self.pixels = self.init_pixels(self.pixel_pin, self.num_pixels) + self.pixels = rgb.RGB(self.pixel_pin, self.rgb_order, self.num_pixels) + self.matrix = MatrixScanner( cols=self.col_pins, @@ -274,8 +268,8 @@ class Firmware: if self.debug_enabled and state_changed: print('New State: {}'.format(self._state._to_dict())) - if self.pixel_state['animation_mode'] is not None: - self.pixel_state = rgb.animate(self.pixel_state, self.pixels) + if self.pixels.animation_mode is not None: + self.pixels = self.pixels.animate() gc.collect() diff --git a/kmk/handlers/stock.py b/kmk/handlers/stock.py index e19aba5..2e135c4 100644 --- a/kmk/handlers/stock.py +++ b/kmk/handlers/stock.py @@ -121,11 +121,61 @@ def td_released(key, state, *args, **kwargs): def rgb_tog(key, state, *args, **kwargs): - state.config.pixel_state['enable'] = not state.config.pixel_state['enable'] + state.config.pixels.enabled = not state.config.pixels.enabled + return state + + +def rgb_forward(key, state, *args, **kwargs): + # TODO + return state + + +def rgb_reverse(key, state, *args, **kwargs): + # TODO + return state + + +def rgb_hui(key, state, *args, **kwargs): + state.config.pixels.increase_hue(state.config.pixels.hue_step) + return state + + +def rgb_hud(key, state, *args, **kwargs): + state.config.pixels.decrease_hue(state.config.pixels.hue_step) + return state + + +def rgb_sai(key, state, *args, **kwargs): + state.config.pixels.increase_sat(state.config.pixels.sat_step) + return state + + +def rgb_sad(key, state, *args, **kwargs): + state.config.pixels.decrease_sat(state.config.pixels.sat_step) + return state + + +def rgb_vai(key, state, *args, **kwargs): + state.config.pixels.increase_val(state.config.pixels.val_step) + return state + + +def rgb_vad(key, state, *args, **kwargs): + state.config.pixels.decrease_val(state.config.pixels.val_step) + return state + + +def rgb_mode_static(key, state, *args, **kwargs): + state.config.pixels.animation_mode = 'static' return state def rgb_mode_breathe(key, state, *args, **kwargs): - state.config.pixel_state['animation_mode'] = 'breathing' + state.config.pixels.animation_mode = 'breathing' + return state + + +def rgb_mode_rainbow(key, state, *args, **kwargs): + state.config.pixels.animation_mode = 'rainbow' return state diff --git a/kmk/keys.py b/kmk/keys.py index 065d173..5ab8849 100644 --- a/kmk/keys.py +++ b/kmk/keys.py @@ -627,7 +627,17 @@ make_key(names=('GESC',), on_press=handlers.gesc_pressed, on_release=handlers.ge make_key(names=('BKDL',), on_press=handlers.bkdl_pressed, on_release=handlers.bkdl_released) make_key(names=('GESC', 'GRAVE_ESC'), on_press=handlers.gesc_pressed, on_release=handlers.gesc_released) make_key(names=('RGB_TOG',), on_press=handlers.rgb_tog) +make_key(names=('RGB_MODE_FORWARD', 'RGB_MOD'), on_press=handlers.rgb_forward) +make_key(names=('RGB_MODE_REVERSE', 'RGB_RMOD'), on_press=handlers.rgb_reverse) +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_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=('LEADER', 'LEAD'), on_press=handlers.leader_pressed, diff --git a/kmk/rgb.py b/kmk/rgb.py index 0bc313b..bc0a12a 100644 --- a/kmk/rgb.py +++ b/kmk/rgb.py @@ -2,172 +2,307 @@ from math import sin, exp, pi, floor from math import e as M_E import time -COLORS = { - 'OFF': (0, 0, 0), - 'RED': (255, 0, 0), - 'GREEN': (0, 255, 0), - 'BLUE': (0, 0, 255, 0), - 'YELLOW': (255, 150, 0), - 'CYAN': (0, 255, 255), - 'PURPLE': (180, 0, 255), - 'WHITE': (255, 255, 255), -} +class RGB: + hue = 240 + sat = 100 + val = 80 + animation_mode = 'breathing' + pos = 0 + time = floor(time.monotonic() * 10) + intervals = (30, 20, 10, 5) + speed = 120 # Bigger is slower + enabled = True + neopixel = None + rgbw = False + num_pixels = 0 + disable_auto_write = False + hue_step = 5 + sat_step = 5 + val_step = 5 + limit_val = 255 -def pixelinit(): - return { - 'h': 180, - 's': 100, - 'v': 80, - 'animation_mode': 'Breathing', - 'pos': 0, - 'time': time_ms(), - 'intervals': (30, 20, 10, 5), - 'speed': 120, # Bigger is slower - 'enable': True - } + def __init__(self, pixel_pin, rgb_order, num_pixels=0): + try: + import neopixel + self.neopixel = neopixel.NeoPixel(pixel_pin, + num_pixels, + pixel_order=rgb_order, + auto_write=False) + if len(rgb_order) == 4: + self.rgbw = True + self.num_pixels = num_pixels + except ImportError as e: + print(e) -def time_ms(): - return floor(time.monotonic() * 10) + def __repr__(self): + return 'RGB({})'.format(self._to_dict()) + def _to_dict(self): + ret = { + 'hue': self.hue, + 'sat': self.sat, + 'val': self.val, + 'animation_mode': self.animation_mode, + 'time': self.time, + 'intervals': self.intervals, + 'speed': self.speed, + 'enabled': self.enabled, + 'neopixel': self.neopixel, + 'disable_auto_write': self.disable_auto_write, + } -def hsv_to_rgb(hue, sat, val): - r = 0 - g = 0 - b = 0 - RGBLIGHT_LIMIT_VAL = 255 + return ret - if val > 255: - val = 255 + def time_ms(self): + return floor(time.monotonic() * 10) - if sat == 0: - r = val - g = val - b = val + def hsv_to_rgb(self, hue, sat, val): + ''' + Converts HSV values, and returns a tuple of RGB values + :param hue: + :param sat: + :param val: + :return: (r, g, b) + ''' + r = 0 + g = 0 + b = 0 + self.limit_val = 255 - else: - base = ((255 - sat) * val) >> 8 - color = (val - base) * (hue % 60) / 60 + if val > 255: + val = 255 - x = floor(hue / 60) - if x == 0: + if sat == 0: r = val - g = base + color - b = base - elif x == 1: - r = val - color g = val - b = base - elif x == 2: - r = base - g = val - b = base + color - elif x == 3: - r = base - g = val - color b = val - elif x == 4: - r = base + color - g = base - b = val - elif x == 5: - r = val - g = base - b = val - color - return floor(r), floor(g), floor(b) + else: + base = ((255 - sat) * val) >> 8 + color = (val - base) * (hue % 60) / 60 + x = floor(hue / 60) + if x == 0: + r = val + g = base + color + b = base + elif x == 1: + r = val - color + g = val + b = base + elif x == 2: + r = base + g = val + b = base + color + elif x == 3: + r = base + g = val - color + b = val + elif x == 4: + r = base + color + g = base + b = val + elif x == 5: + r = val + g = base + b = val - color -def set_hsv(hue, sat, val, pixels, index): - set_rgb(hsv_to_rgb(hue, sat, val), pixels, index) + return floor(r), floor(g), floor(b) + def hsv_to_rgbw(self, 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 = self.hsv_to_rgb(hue, sat, val) + return rgb[0], rgb[1], rgb[2], min(rgb) -def set_hsv_fill(hue, sat, val, pixels): - pixels.fill(hsv_to_rgb(hue, sat, val)) - pixels.show() + 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 + ''' + if self.neopixel: + if self.rgbw: + self.set_rgb(self.hsv_to_rgbw(hue, sat, val), index) + else: + self.set_rgb(self.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: + :param index: Index of LED/Pixel + ''' + if self.neopixel: + if self.rgbw: + self.neopixel.fill(self.hsv_to_rgbw(hue, sat, val)) + else: + self.neopixel.fill(self.hsv_to_rgb(hue, sat, val)) + self.neopixel.show() -def set_rgb(rgb, pixels, index): - pixels[index] = (rgb[0], rgb[1], rgb[2]) - pixels.show() + 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 self.neopixel: + self.neopixel[index] = rgb + if not self.disable_auto_write: + self.neopixel.show() + def set_rgb_fill(self, rgb): + ''' + Takes an RGB or RGBW and displays it on all LEDs/Neopixels + :param rgb: RGB or RGBW + ''' + if self.neopixel: + self.neopixel.fill(rgb) + self.neopixel.show() -def set_rgb_fill(rgb, pixels): - pixels.fill(rgb[0], rgb[1], rgb[2]) - pixels.show() + def increase_hue(self, step): + ''' + Increases hue by step amount rolling at 360 and returning to 0 + :param step: + ''' + self.hue = (self.hue + step) % 360 + def decrease_hue(self, step): + ''' + Decreases hue by step amount rolling at 0 and returning to 360 + :param step: + ''' + if self.hue - step < 0: + self.hue = (self.hue + 360 - step) % 360 + else: + self.hue = (self.hue - step) % 360 -def increase_hue(hue, step): - return (hue + step) % 360 + def increase_sat(self, step): + ''' + Increases saturation by step amount stopping at 100 + :param step: + ''' + if self.sat + step >= 100: + self.sat = 100 + else: + self.sat += step + def decrease_sat(self, step): + ''' + Decreases saturation by step amount stopping at 0 + :param step: + ''' + if self.sat + step <= 0: + self.sat = 0 + else: + self.sat -= step -def decrease_hue(hue, step): - if hue - step < 0: - return (hue + 360 - step) % 360 - else: - return (hue - step) % 360 + def increase_val(self, step): + ''' + Increases value by step amount stopping at 100 + :param step: + ''' + if self.val + step >= 100: + self.val = 100 + else: + self.val += step + def decrease_val(self, step): + ''' + Decreases value by step amount stopping at 0 + :param step: + ''' + if self.val + step <= 0: + self.val = 0 + else: + self.val -= step -def off(pixels): - set_hsv_fill(0, 0, 0, pixels) + def off(self): + ''' + Turns off all LEDs/Neopixels without changing stored values + ''' + if self.neopixel: + if not self.disable_auto_write: + self.set_hsv_fill(0, 0, 0) + def show(self): + ''' + Turns on all LEDs/Neopixels without changing stored values + ''' + if self.neopixel: + self.neopixel.show() -def animate(state, pixels): - if state['enable']: - if state['animation_mode'] == 'breathing': - return effect_breathing(state, pixels) - elif state['animation_mode'] == 'rainbow': - return effect_rainbow(state, pixels) - else: - off(pixels) + def animate(self): + ''' + Activates a "step" in the animation based on the active mode + :return: Returns the new state in animation + ''' + if self.enabled: + if self.animation_mode == 'breathing': + return self.effect_breathing() + elif self.animation_mode == 'rainbow': + return self.effect_rainbow() + if self.animation_mode == 'static': + return self.effect_static() + else: + self.off() - return state + return self + def animation_step(self): + interval = self.time_ms() - self.time + if interval >= max(self.intervals): + self.time = self.time_ms() + return max(self.intervals) + if interval in self.intervals: + return interval + else: + return False -def animation_step(state): - interval = time_ms() - state['time'] - if interval >= max(state['intervals']): - state['time'] = time_ms() - return max(state['intervals']) - if interval in state['intervals']: - return interval - else: - return False + def effect_static(self): + self.set_hsv_fill(self.hue, self.sat, self.val) + return self + def effect_breathing(self): + RGBLIGHT_EFFECT_BREATHE_CENTER = 1.5 # 1.0-2.7 + RGBLIGHT_EFFECT_BREATHE_MAX = 100 # 0-255 + # http://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/ + # https://github.com/qmk/qmk_firmware/blob/9f1d781fcb7129a07e671a46461e501e3f1ae59d/quantum/rgblight.c#L787 + self.val = floor((exp(sin((self.pos/255.0)*pi)) - RGBLIGHT_EFFECT_BREATHE_CENTER/M_E)* + (RGBLIGHT_EFFECT_BREATHE_MAX/(M_E-1/M_E))) + self.pos = (self.pos + 1) % 256; + self.set_hsv_fill(self.hue, self.sat, self.val) -def effect_breathing(state, pixels): - RGBLIGHT_EFFECT_BREATHE_CENTER = 1.5 # 1.0-2.7 - RGBLIGHT_EFFECT_BREATHE_MAX = 150 # 0-255 - interval = time_ms() - state['time'] - # http://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/ - # https://github.com/qmk/qmk_firmware/blob/9f1d781fcb7129a07e671a46461e501e3f1ae59d/quantum/rgblight.c#L787 - state['v'] = floor((exp(sin((state['pos']/255.0)*pi)) - RGBLIGHT_EFFECT_BREATHE_CENTER/M_E)*(RGBLIGHT_EFFECT_BREATHE_MAX/(M_E-1/M_E))) - state['pos'] = (state['pos'] + 1) % 256; - set_hsv_fill(state['h'], state['s'], state['v'], pixels) + return self - return state + def effect_rainbow(self): + if self.animation_step(self): + self.increase_hue(self.hue, 1) + self.set_hsv_fill(self.hue, self.sat, self.val) + return self -def effect_rainbow(state, pixels): - if animation_step(state): - state['h'] = increase_hue(state['h'], 1) - set_hsv_fill(state['h'], state['s'], state['v'], pixels) + def effect_rainbow_swirl(self): + interval = self.animation_step(self) + if interval: + for i in range(0, self.num_pixels): + self.hue = (360 / self.num_pixels * i + self.hue) % 360 + self.set_hsv_fill(self.hue, self.sat, self.val) - return state + if interval % 2: + self.increase_hue(self.hue, 1) + else: + self.decrease_hue(self.hue, 1) - -def effect_rainbow_swirl(state, pixels): - interval = animation_step(state) - if interval: - MAX_RGB_NUM = 12 # TODO Actually pass this - for i in range(0, MAX_RGB_NUM): - state['h'] = (360 / MAX_RGB_NUM * i + state['h']) % 360 - set_hsv_fill(state['h'], state['s'], state['v'], pixels) - - if interval % 2: - state['h'] = increase_hue(state['h'], 1) - else: - state['h'] = decrease_hue(state['h'], 1) - - return state + return self diff --git a/user_keymaps/kdb424/nyquist_converter.py b/user_keymaps/kdb424/nyquist_converter.py index a59a5b0..8979510 100644 --- a/user_keymaps/kdb424/nyquist_converter.py +++ b/user_keymaps/kdb424/nyquist_converter.py @@ -69,48 +69,56 @@ r3 = 4 def base(*args, **kwargs): + keyboard.pixel_state['animation_mode'] = 'breathing' keyboard.pixels.fill(OFF) keyboard.pixels.show() return df_pressed(*args, **kwargs) def layer1p(*args, **kwargs): + keyboard.pixel_state['animation_mode'] = 'User' keyboard.pixels.fill(WHITE) keyboard.pixels.show() return mo_pressed(*args, **kwargs) def layer1r(*args, **kwargs): + keyboard.pixel_state['animation_mode'] = 'breathing' keyboard.pixels.fill(OFF) keyboard.pixels.show() return mo_released(*args, **kwargs) def layer2p(*args, **kwargs): + keyboard.pixel_state['animation_mode'] = 'User' keyboard.pixels.fill(BLUE) keyboard.pixels.show() return lt_pressed(*args, **kwargs) def layer2r(*args, **kwargs): + keyboard.pixel_state['animation_mode'] = 'breathing' keyboard.pixels.fill(OFF) keyboard.pixels.show() return lt_released(*args, **kwargs) def layer3p(*args, **kwargs): + keyboard.pixel_state['animation_mode'] = 'User' keyboard.pixels.fill(PURPLE) keyboard.pixels.show() return mo_pressed(*args, **kwargs) def layer3r(*args, **kwargs): + keyboard.pixel_state['animation_mode'] = 'breathing' keyboard.pixels.fill(OFF) keyboard.pixels.show() return mo_released(*args, **kwargs) def gaming(*args, **kwargs): + keyboard.pixel_state['animation_mode'] = 'User' keyboard.pixels.fill(CYAN) keyboard.pixels.show() return df_pressed(*args, **kwargs)