2019-02-20 03:07:22 +01:00
|
|
|
import time
|
2019-02-24 00:12:50 +01:00
|
|
|
from math import e as M_E
|
|
|
|
from math import exp, floor, pi, sin
|
2019-02-20 03:07:22 +01:00
|
|
|
|
2019-02-22 03:27:35 +01:00
|
|
|
|
|
|
|
class RGB:
|
2019-02-23 04:50:56 +01:00
|
|
|
hue = 0
|
2019-02-22 03:27:35 +01:00
|
|
|
sat = 100
|
|
|
|
val = 80
|
|
|
|
pos = 0
|
|
|
|
time = floor(time.monotonic() * 10)
|
|
|
|
intervals = (30, 20, 10, 5)
|
2019-02-23 04:50:56 +01:00
|
|
|
animation_speed = 1
|
2019-02-22 03:27:35 +01:00
|
|
|
enabled = True
|
|
|
|
neopixel = None
|
|
|
|
rgbw = False
|
2019-02-23 23:03:13 +01:00
|
|
|
reverse_animation = False
|
2019-02-22 03:27:35 +01:00
|
|
|
disable_auto_write = False
|
2019-02-23 04:50:56 +01:00
|
|
|
|
|
|
|
# Set by config
|
|
|
|
num_pixels = 0
|
|
|
|
hue_step = 1
|
2019-02-22 03:27:35 +01:00
|
|
|
sat_step = 5
|
|
|
|
val_step = 5
|
2019-02-23 04:50:56 +01:00
|
|
|
breath_center = 1.5 # 1.0-2.7
|
2019-02-23 23:03:13 +01:00
|
|
|
knight_effect_length = 4
|
2019-02-23 04:50:56 +01:00
|
|
|
val_limit = 255
|
|
|
|
animation_mode = 'static'
|
2019-02-23 23:03:13 +01:00
|
|
|
effect_init = False
|
2019-02-23 04:50:56 +01:00
|
|
|
|
|
|
|
def __init__(self, pixel_pin, rgb_order, num_pixels,
|
|
|
|
hue_step, sat_step, val_step,
|
|
|
|
hue_default, sat_default, val_default,
|
2019-02-23 23:03:13 +01:00
|
|
|
breath_center, knight_effect_length,
|
|
|
|
val_limit, animation_mode, animation_speed):
|
2019-02-22 03:27:35 +01:00
|
|
|
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
|
2019-02-23 04:50:56 +01:00
|
|
|
self.hue_step = hue_step
|
|
|
|
self.sat_step = sat_step
|
|
|
|
self.val_step = val_step
|
|
|
|
self.hue = hue_default
|
|
|
|
self.sat = sat_default
|
|
|
|
self.val = val_default
|
|
|
|
self.breath_center = breath_center
|
2019-02-23 23:03:13 +01:00
|
|
|
self.knight_effect_length = knight_effect_length
|
2019-02-23 04:50:56 +01:00
|
|
|
self.val_limit = val_limit
|
|
|
|
self.animation_mode = animation_mode
|
2019-02-23 23:03:13 +01:00
|
|
|
self.animation_speed = animation_speed
|
2019-02-22 03:27:35 +01:00
|
|
|
|
|
|
|
except ImportError as e:
|
|
|
|
print(e)
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return 'RGB({})'.format(self._to_dict())
|
|
|
|
|
|
|
|
def _to_dict(self):
|
|
|
|
ret = {
|
|
|
|
'hue': self.hue,
|
|
|
|
'sat': self.sat,
|
|
|
|
'val': self.val,
|
|
|
|
'time': self.time,
|
|
|
|
'intervals': self.intervals,
|
2019-02-23 23:03:13 +01:00
|
|
|
'animation_mode': self.animation_mode,
|
2019-02-23 04:50:56 +01:00
|
|
|
'animation_speed': self.animation_speed,
|
2019-02-22 03:27:35 +01:00
|
|
|
'enabled': self.enabled,
|
|
|
|
'neopixel': self.neopixel,
|
|
|
|
'disable_auto_write': self.disable_auto_write,
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def time_ms(self):
|
|
|
|
return floor(time.monotonic() * 10)
|
|
|
|
|
|
|
|
def hsv_to_rgb(self, hue, sat, val):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Converts HSV values, and returns a tuple of RGB values
|
|
|
|
:param hue:
|
|
|
|
:param sat:
|
|
|
|
:param val:
|
|
|
|
:return: (r, g, b)
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
r = 0
|
|
|
|
g = 0
|
|
|
|
b = 0
|
|
|
|
|
2019-02-23 04:50:56 +01:00
|
|
|
if val > self.val_limit:
|
|
|
|
val = self.val_limit
|
2019-02-22 03:27:35 +01:00
|
|
|
|
|
|
|
if sat == 0:
|
2019-02-21 05:40:46 +01:00
|
|
|
r = val
|
|
|
|
g = val
|
|
|
|
b = val
|
2019-02-20 03:07:22 +01:00
|
|
|
|
2019-02-22 03:27:35 +01:00
|
|
|
else:
|
2019-02-23 04:50:56 +01:00
|
|
|
base = ((100 - sat) * val) / 100
|
|
|
|
color = floor((val - base) * ((hue % 60) / 60))
|
2019-02-22 03:27:35 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
return floor(r), floor(g), floor(b)
|
|
|
|
|
|
|
|
def hsv_to_rgbw(self, hue, sat, val):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Converts HSV values, and returns a tuple of RGBW values
|
|
|
|
:param hue:
|
|
|
|
:param sat:
|
|
|
|
:param val:
|
|
|
|
:return: (r, g, b, w)
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
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):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Takes HSV values and displays it on a single LED/Neopixel
|
|
|
|
:param hue:
|
|
|
|
:param sat:
|
|
|
|
:param val:
|
|
|
|
:param index: Index of LED/Pixel
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
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):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Takes HSV values and displays it on all LEDs/Neopixels
|
|
|
|
:param hue:
|
|
|
|
:param sat:
|
|
|
|
:param val:
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
if self.neopixel:
|
|
|
|
if self.rgbw:
|
2019-02-23 23:03:13 +01:00
|
|
|
self.set_rgb_fill(self.hsv_to_rgbw(hue, sat, val))
|
2019-02-22 03:27:35 +01:00
|
|
|
else:
|
2019-02-23 23:03:13 +01:00
|
|
|
self.set_rgb_fill(self.hsv_to_rgb(hue, sat, val))
|
2019-02-22 03:27:35 +01:00
|
|
|
|
|
|
|
def set_rgb(self, rgb, index):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Takes an RGB or RGBW and displays it on a single LED/Neopixel
|
|
|
|
:param rgb: RGB or RGBW
|
|
|
|
:param index: Index of LED/Pixel
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
|
|
|
if self.neopixel and 0 <= index <= self.num_pixels - 1:
|
|
|
|
self.neopixel[index] = rgb
|
|
|
|
if not self.disable_auto_write:
|
|
|
|
self.neopixel.show()
|
2019-02-22 03:27:35 +01:00
|
|
|
|
|
|
|
def set_rgb_fill(self, rgb):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Takes an RGB or RGBW and displays it on all LEDs/Neopixels
|
|
|
|
:param rgb: RGB or RGBW
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
if self.neopixel:
|
|
|
|
self.neopixel.fill(rgb)
|
2019-02-23 23:03:13 +01:00
|
|
|
if not self.disable_auto_write:
|
|
|
|
self.neopixel.show()
|
2019-02-22 03:27:35 +01:00
|
|
|
|
|
|
|
def increase_hue(self, step):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Increases hue by step amount rolling at 360 and returning to 0
|
|
|
|
:param step:
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
self.hue = (self.hue + step) % 360
|
|
|
|
|
|
|
|
def decrease_hue(self, step):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Decreases hue by step amount rolling at 0 and returning to 360
|
|
|
|
:param step:
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-23 04:50:56 +01:00
|
|
|
if (self.hue - step) <= 0:
|
2019-02-22 03:27:35 +01:00
|
|
|
self.hue = (self.hue + 360 - step) % 360
|
|
|
|
else:
|
|
|
|
self.hue = (self.hue - step) % 360
|
|
|
|
|
|
|
|
def increase_sat(self, step):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Increases saturation by step amount stopping at 100
|
|
|
|
:param step:
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
if self.sat + step >= 100:
|
|
|
|
self.sat = 100
|
|
|
|
else:
|
|
|
|
self.sat += step
|
|
|
|
|
|
|
|
def decrease_sat(self, step):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Decreases saturation by step amount stopping at 0
|
|
|
|
:param step:
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-23 04:50:56 +01:00
|
|
|
if (self.sat - step) <= 0:
|
2019-02-22 03:27:35 +01:00
|
|
|
self.sat = 0
|
|
|
|
else:
|
|
|
|
self.sat -= step
|
|
|
|
|
|
|
|
def increase_val(self, step):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Increases value by step amount stopping at 100
|
|
|
|
:param step:
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-23 04:50:56 +01:00
|
|
|
if (self.val + step) >= 100:
|
2019-02-22 03:27:35 +01:00
|
|
|
self.val = 100
|
|
|
|
else:
|
|
|
|
self.val += step
|
|
|
|
|
|
|
|
def decrease_val(self, step):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Decreases value by step amount stopping at 0
|
|
|
|
:param step:
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-23 04:50:56 +01:00
|
|
|
if (self.val - step) <= 0:
|
2019-02-22 03:27:35 +01:00
|
|
|
self.val = 0
|
|
|
|
else:
|
|
|
|
self.val -= step
|
|
|
|
|
|
|
|
def off(self):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Turns off all LEDs/Neopixels without changing stored values
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
if self.neopixel:
|
2019-02-23 23:03:13 +01:00
|
|
|
self.set_hsv_fill(0, 0, 0)
|
2019-02-22 03:27:35 +01:00
|
|
|
|
|
|
|
def show(self):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Turns on all LEDs/Neopixels without changing stored values
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
if self.neopixel:
|
|
|
|
self.neopixel.show()
|
|
|
|
|
|
|
|
def animate(self):
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-22 03:27:35 +01:00
|
|
|
Activates a "step" in the animation based on the active mode
|
|
|
|
:return: Returns the new state in animation
|
2019-02-24 00:12:50 +01:00
|
|
|
"""
|
2019-02-23 23:03:13 +01:00
|
|
|
if self.effect_init:
|
|
|
|
self.init_effect()
|
2019-02-22 03:27:35 +01:00
|
|
|
if self.enabled:
|
|
|
|
if self.animation_mode == 'breathing':
|
|
|
|
return self.effect_breathing()
|
|
|
|
elif self.animation_mode == 'rainbow':
|
|
|
|
return self.effect_rainbow()
|
2019-02-23 04:50:56 +01:00
|
|
|
elif self.animation_mode == 'breathing_rainbow':
|
|
|
|
return self.effect_breathing_rainbow()
|
|
|
|
elif self.animation_mode == 'static':
|
2019-02-22 03:27:35 +01:00
|
|
|
return self.effect_static()
|
2019-02-23 23:03:13 +01:00
|
|
|
elif self.animation_mode == 'knight':
|
|
|
|
return self.effect_knight()
|
2019-02-22 03:27:35 +01:00
|
|
|
else:
|
|
|
|
self.off()
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2019-02-23 23:03:13 +01:00
|
|
|
def init_effect(self):
|
|
|
|
self.pos = 0
|
|
|
|
self.reverse_animation = False
|
|
|
|
self.effect_init = False
|
|
|
|
|
2019-02-22 03:27:35 +01:00
|
|
|
def effect_static(self):
|
|
|
|
self.set_hsv_fill(self.hue, self.sat, self.val)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def effect_breathing(self):
|
|
|
|
# http://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/
|
2019-02-23 04:50:56 +01:00
|
|
|
# https://github.com/qmk/qmk_firmware/blob/9f1d781fcb7129a07e671a46461e501e3f1ae59d/quantum/rgblight.c#L806
|
|
|
|
self.val = floor((exp(sin((self.pos / 255.0) * pi)) - self.breath_center / M_E) *
|
|
|
|
(self.val_limit / (M_E - 1 / M_E)))
|
|
|
|
self.pos = (self.pos + self.animation_speed) % 256
|
2019-02-22 03:27:35 +01:00
|
|
|
self.set_hsv_fill(self.hue, self.sat, self.val)
|
|
|
|
|
|
|
|
return self
|
|
|
|
|
2019-02-23 04:50:56 +01:00
|
|
|
def effect_breathing_rainbow(self):
|
|
|
|
if self.animation_step():
|
|
|
|
self.increase_hue(self.animation_speed)
|
|
|
|
self.effect_breathing()
|
|
|
|
|
|
|
|
return self
|
|
|
|
|
2019-02-22 03:27:35 +01:00
|
|
|
def effect_rainbow(self):
|
2019-02-23 04:50:56 +01:00
|
|
|
if self.animation_step():
|
|
|
|
self.increase_hue(self.animation_speed)
|
2019-02-22 03:27:35 +01:00
|
|
|
self.set_hsv_fill(self.hue, self.sat, self.val)
|
|
|
|
|
|
|
|
return self
|
|
|
|
|
2019-02-23 23:03:13 +01:00
|
|
|
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 = floor(self.pos)
|
2019-02-22 03:27:35 +01:00
|
|
|
|
2019-02-23 23:03:13 +01:00
|
|
|
# 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 / 5
|
2019-02-22 03:27:35 +01:00
|
|
|
else:
|
2019-02-23 23:03:13 +01:00
|
|
|
self.pos += self.animation_speed / 5
|
|
|
|
|
|
|
|
# Show final results
|
|
|
|
self.disable_auto_write = False # Resume showing changes
|
|
|
|
self.show()
|
2019-02-22 03:27:35 +01:00
|
|
|
|
|
|
|
return self
|