From 63060b6f101249eafad451eb13433a1fdfb320f4 Mon Sep 17 00:00:00 2001 From: Kyle Brown Date: Fri, 30 Jul 2021 11:23:20 -0700 Subject: [PATCH] split rgb to library --- .gitignore | 1 - kmk/extensions/rgb.py | 524 ++++++++++++------------------------------ kmk/lib/rgb_helper.py | 254 ++++++++++++++++++++ 3 files changed, 395 insertions(+), 384 deletions(-) create mode 100644 kmk/lib/rgb_helper.py diff --git a/.gitignore b/.gitignore index f504bdf..be5aeba 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ dist/ downloads/ eggs/ .eggs/ -lib/ lib64/ parts/ sdist/ diff --git a/kmk/extensions/rgb.py b/kmk/extensions/rgb.py index 93c4312..61817ab 100644 --- a/kmk/extensions/rgb.py +++ b/kmk/extensions/rgb.py @@ -6,6 +6,8 @@ from math import e, exp, pi, sin from kmk.extensions import Extension from kmk.handlers.stock import passthrough as handler_passthrough from kmk.keys import make_key +from kmk.kmktime import ticks_ms +from kmk.lib import rgb_helper rgb_config = {} @@ -169,404 +171,29 @@ class RGB(Extension): def on_powersave_disable(self, sandbox): self._do_update() - @staticmethod - def time_ms(): - return int(time.monotonic() * 1000) - - 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 - - if val > self.val_limit: - val = self.val_limit - - if sat == 0: - r = val - g = val - b = val - - else: - base = ((100 - sat) * val) / 100 - color = (val - base) * ((hue % 60) / 60) - - x = int(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 - - return int(r), int(g), int(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(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: - ''' - if self.neopixel: - if self.rgbw: - self.set_rgb_fill(self.hsv_to_rgbw(hue, sat, val)) - else: - self.set_rgb_fill(self.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 self.neopixel and 0 <= index <= self.num_pixels - 1: - 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) - if not self.disable_auto_write: - self.neopixel.show() - - def increase_hue(self, step=None): - ''' - Increases hue by step amount rolling at 360 and returning to 0 - :param step: - ''' - if not step: - step = self.hue_step - - self.hue = (self.hue + step) % 360 - - if self._check_update(): - self._do_update() - - def decrease_hue(self, step=None): - ''' - Decreases hue by step amount rolling at 0 and returning to 360 - :param step: - ''' - if not step: - step = self.hue_step - - if (self.hue - step) <= 0: - self.hue = (self.hue + 360 - step) % 360 - else: - self.hue = (self.hue - step) % 360 - - if self._check_update(): - self._do_update() - - def increase_sat(self, step=None): - ''' - Increases saturation by step amount stopping at 100 - :param step: - ''' - if not step: - step = self.sat_step - - if self.sat + step >= 100: - self.sat = 100 - else: - self.sat += step - - if self._check_update(): - self._do_update() - - def decrease_sat(self, step=None): - ''' - Decreases saturation by step amount stopping at 0 - :param step: - ''' - if not step: - step = self.sat_step - - if (self.sat - step) <= 0: - self.sat = 0 - else: - self.sat -= step - - if self._check_update(): - self._do_update() - - def increase_val(self, step=None): - ''' - Increases value by step amount stopping at 100 - :param step: - ''' - if not step: - step = self.val_step - if (self.val + step) >= 100: - self.val = 100 - else: - self.val += step - - if self._check_update(): - self._do_update() - - def decrease_val(self, step=None): - ''' - Decreases value by step amount stopping at 0 - :param step: - ''' - if not step: - step = self.val_step - if (self.val - step) <= 0: - self.val = 0 - else: - self.val -= step - - if self._check_update(): - self._do_update() - - 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.animation_speed += 1 - if self._check_update(): - self._do_update() - - def decrease_ani(self): - ''' - Decreases animation speed by 1 amount stopping at 0 - :param step: - ''' - if (self.animation_speed - 1) <= 0: - self.animation_speed = 0 - else: - self.animation_speed -= 1 - if self._check_update(): - self._do_update() - - def off(self): - ''' - Turns off all LEDs/Neopixels without changing stored values - ''' - if self.neopixel: - 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(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 not AnimationModes.STATIC_STANDBY: - self.loopcounter += 1 - if self.loopcounter >= 7 and self.enable: - self.loopcounter = 0 - if self.animation_mode == AnimationModes.BREATHING: - self.effect_breathing() - elif self.animation_mode == AnimationModes.RAINBOW: - self.effect_rainbow() - elif self.animation_mode == AnimationModes.BREATHING_RAINBOW: - self.effect_breathing_rainbow() - elif self.animation_mode == AnimationModes.STATIC: - self.effect_static() - elif self.animation_mode == AnimationModes.KNIGHT: - self.effect_knight() - elif self.animation_mode == AnimationModes.SWIRL: - self.effect_swirl() - elif self.animation_mode == AnimationModes.USER: - self.user_animation(self) - elif self.animation_mode == AnimationModes.STATIC_STANDBY: - pass - else: - self.off() - if self.loopcounter >= 7: - self.loopcounter = 0 - - 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 - return None - - def _init_effect(self): - if ( - self.animation_mode == AnimationModes.BREATHING - or self.animation_mode == AnimationModes.BREATHING_RAINBOW - ): - self.intervals = (30, 20, 10, 5) - elif self.animation_mode == AnimationModes.SWIRL: - self.intervals = (50, 50) - - 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 = self.val_limit / (e - 1 / e) - - self.val = int(multip_1 * multip_2) - self.pos = (self.pos + self.animation_speed) % 256 - self.set_hsv_fill(self.hue, self.sat, self.val) - - def effect_breathing_rainbow(self): - self.increase_hue(self.animation_speed) - self.effect_breathing() - - def effect_rainbow(self): - self.increase_hue(self.animation_speed) - self.set_hsv_fill(self.hue, self.sat, self.val) - - def effect_swirl(self): - self.increase_hue(self.animation_speed) - self.disable_auto_write = True # Turn off instantly showing - for i in range(0, self.num_pixels): - self.set_hsv( - (self.hue - (i * self.num_pixels)) % 360, self.sat, self.val, i - ) - - # Show final results - self.disable_auto_write = False # Resume showing changes - self.show() - - def effect_knight(self): - # Determine which LEDs should be lit up - self.disable_auto_write = True # Turn off instantly showing - 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 or pos - 1 < (self.knight_effect_length * -1): - self.reverse_animation = not self.reverse_animation - - if self.reverse_animation: - self.pos -= self.animation_speed / 2 - else: - self.pos += self.animation_speed / 2 - - # Show final results - self.disable_auto_write = False # Resume showing changes - self.show() - - 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() + rgb_helper.increase_hue(self) def _rgb_hud(self, *args, **kwargs): - self.decrease_hue() + rgb_helper.decrease_hue(self) def _rgb_sai(self, *args, **kwargs): - self.increase_sat() + rgb_helper.increase_sat(self) def _rgb_sad(self, *args, **kwargs): - self.decrease_sat() + rgb_helper.decrease_sat(self) def _rgb_vai(self, *args, **kwargs): - self.increase_val() + rgb_helper.increase_val(self) def _rgb_vad(self, *args, **kwargs): - self.decrease_val() + rgb_helper.decrease_val(self) def _rgb_ani(self, *args, **kwargs): - self.increase_ani() + rgb_helper.increase_ani(self) def _rgb_and(self, *args, **kwargs): - self.decrease_ani() + rgb_helper.decrease_ani(self) def _rgb_mode_static(self, *args, **kwargs): self.effect_init = True @@ -599,3 +226,134 @@ class RGB(Extension): if self.animation_mode == AnimationModes.STATIC_STANDBY: self.animation_mode = AnimationModes.STATIC self._do_update() + + 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 not AnimationModes.STATIC_STANDBY: + self.loopcounter += 1 + if self.loopcounter >= 7 and self.enable: + self.loopcounter = 0 + if self.animation_mode == AnimationModes.BREATHING: + self.effect_breathing() + elif self.animation_mode == AnimationModes.RAINBOW: + self.effect_rainbow() + elif self.animation_mode == AnimationModes.BREATHING_RAINBOW: + self.effect_breathing_rainbow() + elif self.animation_mode == AnimationModes.STATIC: + self.effect_static() + elif self.animation_mode == AnimationModes.KNIGHT: + self.effect_knight() + elif self.animation_mode == AnimationModes.SWIRL: + self.effect_swirl() + elif self.animation_mode == AnimationModes.USER: + self.user_animation(self) + elif self.animation_mode == AnimationModes.STATIC_STANDBY: + pass + else: + rgb_helper.off(self) + if self.loopcounter >= 7: + self.loopcounter = 0 + + def _animation_step(self): + interval = ticks_ms() - self.time + if interval >= max(self.intervals): + self.time = ticks_ms() + return max(self.intervals) + if interval in self.intervals: + return interval + return None + + def _init_effect(self): + if ( + self.animation_mode == AnimationModes.BREATHING + or self.animation_mode == AnimationModes.BREATHING_RAINBOW + ): + self.intervals = (30, 20, 10, 5) + elif self.animation_mode == AnimationModes.SWIRL: + self.intervals = (50, 50) + + 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): + rgb_helper.set_hsv_fill(self, 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 = self.val_limit / (e - 1 / e) + + self.val = int(multip_1 * multip_2) + self.pos = (self.pos + self.animation_speed) % 256 + rgb_helper.set_hsv_fill(self, self.hue, self.sat, self.val) + + def effect_breathing_rainbow(self): + rgb_helper.increase_hue(self, self.animation_speed) + self.effect_breathing() + + def effect_rainbow(self): + rgb_helper.increase_hue(self, self.animation_speed) + rgb_helper.set_hsv_fill(self, self.hue, self.sat, self.val) + + def effect_swirl(self): + rgb_helper.increase_hue(self, self.animation_speed) + self.disable_auto_write = True # Turn off instantly showing + for i in range(0, self.num_pixels): + rgb_helper.set_hsv( + self, (self.hue - (i * self.num_pixels)) % 360, self.sat, self.val, i + ) + + # Show final results + self.disable_auto_write = False # Resume showing changes + rgb_helper.show(self) + + def effect_knight(self): + # Determine which LEDs should be lit up + self.disable_auto_write = True # Turn off instantly showing + rgb_helper.off(self) # 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)): + rgb_helper.set_hsv(self, self.hue, self.sat, self.val, i) + + # Reverse animation when a boundary is hit + if pos >= self.num_pixels or pos - 1 < (self.knight_effect_length * -1): + self.reverse_animation = not self.reverse_animation + + if self.reverse_animation: + self.pos -= self.animation_speed / 2 + else: + self.pos += self.animation_speed / 2 + + # Show final results + self.disable_auto_write = False # Resume showing changes + rgb_helper.show(self) + + 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: + rgb_helper.off(self) + self.enable = not self.enable diff --git a/kmk/lib/rgb_helper.py b/kmk/lib/rgb_helper.py new file mode 100644 index 0000000..d4ddc74 --- /dev/null +++ b/kmk/lib/rgb_helper.py @@ -0,0 +1,254 @@ +def hsv_to_rgb(rgb_obj, 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 + + if val > rgb_obj.val_limit: + val = rgb_obj.val_limit + + if sat == 0: + r = val + g = val + b = val + + else: + base = ((100 - sat) * val) / 100 + color = (val - base) * ((hue % 60) / 60) + + x = int(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 + + return int(r), int(g), int(b) + + +def hsv_to_rgbw(rgb_obj, 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 = rgb_obj.hsv_to_rgb(hue, sat, val) + return rgb[0], rgb[1], rgb[2], min(rgb) + + +def set_hsv(rgb_obj, 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 rgb_obj.neopixel: + if rgb_obj.rgbw: + rgb_obj.set_rgb(rgb_obj.hsv_to_rgbw(hue, sat, val), index) + else: + rgb_obj.set_rgb(rgb_obj.hsv_to_rgb(hue, sat, val), index) + + +def set_hsv_fill(rgb_obj, hue, sat, val): + ''' + Takes HSV values and displays it on all LEDs/Neopixels + :param hue: + :param sat: + :param val: + ''' + if rgb_obj.neopixel: + if rgb_obj.rgbw: + rgb_obj.set_rgb_fill(rgb_obj.hsv_to_rgbw(hue, sat, val)) + else: + rgb_obj.set_rgb_fill(rgb_obj.hsv_to_rgb(hue, sat, val)) + + +def set_rgb(rgb_obj, 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 rgb_obj.neopixel and 0 <= index <= rgb_obj.num_pixels - 1: + rgb_obj.neopixel[index] = rgb + if not rgb_obj.disable_auto_write: + rgb_obj.neopixel.show() + + +def set_rgb_fill(rgb_obj, rgb): + ''' + Takes an RGB or RGBW and displays it on all LEDs/Neopixels + :param rgb: RGB or RGBW + ''' + if rgb_obj.neopixel: + rgb_obj.neopixel.fill(rgb) + if not rgb_obj.disable_auto_write: + rgb_obj.neopixel.show() + + +def increase_hue(rgb_obj, step=None): + ''' + Increases hue by step amount rolling at 360 and returning to 0 + :param step: + ''' + if not step: + step = rgb_obj.hue_step + + rgb_obj.hue = (rgb_obj.hue + step) % 360 + + if rgb_obj._check_update(): + rgb_obj._do_update() + + +def decrease_hue(rgb_obj, step=None): + ''' + Decreases hue by step amount rolling at 0 and returning to 360 + :param step: + ''' + if not step: + step = rgb_obj.hue_step + + if (rgb_obj.hue - step) <= 0: + rgb_obj.hue = (rgb_obj.hue + 360 - step) % 360 + else: + rgb_obj.hue = (rgb_obj.hue - step) % 360 + + if rgb_obj._check_update(): + rgb_obj._do_update() + + +def increase_sat(rgb_obj, step=None): + ''' + Increases saturation by step amount stopping at 100 + :param step: + ''' + if not step: + step = rgb_obj.sat_step + + if rgb_obj.sat + step >= 100: + rgb_obj.sat = 100 + else: + rgb_obj.sat += step + + if rgb_obj._check_update(): + rgb_obj._do_update() + + +def decrease_sat(rgb_obj, step=None): + ''' + Decreases saturation by step amount stopping at 0 + :param step: + ''' + if not step: + step = rgb_obj.sat_step + + if (rgb_obj.sat - step) <= 0: + rgb_obj.sat = 0 + else: + rgb_obj.sat -= step + + if rgb_obj._check_update(): + rgb_obj._do_update() + + +def increase_val(rgb_obj, step=None): + ''' + Increases value by step amount stopping at 100 + :param step: + ''' + if not step: + step = rgb_obj.val_step + if (rgb_obj.val + step) >= 100: + rgb_obj.val = 100 + else: + rgb_obj.val += step + + if rgb_obj._check_update(): + rgb_obj._do_update() + + +def decrease_val(rgb_obj, step=None): + ''' + Decreases value by step amount stopping at 0 + :param step: + ''' + if not step: + step = rgb_obj.val_step + if (rgb_obj.val - step) <= 0: + rgb_obj.val = 0 + else: + rgb_obj.val -= step + + if rgb_obj._check_update(): + rgb_obj._do_update() + + +def increase_ani(rgb_obj): + ''' + Increases animation speed by 1 amount stopping at 10 + :param step: + ''' + if (rgb_obj.animation_speed + 1) > 10: + rgb_obj.animation_speed = 10 + else: + rgb_obj.animation_speed += 1 + if rgb_obj._check_update(): + rgb_obj._do_update() + + +def decrease_ani(rgb_obj): + ''' + Decreases animation speed by 1 amount stopping at 0 + :param step: + ''' + if (rgb_obj.animation_speed - 1) <= 0: + rgb_obj.animation_speed = 0 + else: + rgb_obj.animation_speed -= 1 + if rgb_obj._check_update(): + rgb_obj._do_update() + + +def off(rgb_obj): + ''' + Turns off all LEDs/Neopixels without changing stored values + ''' + if rgb_obj.neopixel: + rgb_obj.set_hsv_fill(0, 0, 0) + + +def show(rgb_obj): + ''' + Turns on all LEDs/Neopixels without changing stored values + ''' + if rgb_obj.neopixel: + rgb_obj.neopixel.show()