260 lines
7.6 KiB
Python
260 lines
7.6 KiB
Python
|
import pwmio
|
||
|
from math import e, exp, pi, sin
|
||
|
|
||
|
from kmk.extensions import Extension, InvalidExtensionEnvironment
|
||
|
from kmk.keys import make_argumented_key, make_key
|
||
|
from kmk.utils import clamp
|
||
|
|
||
|
|
||
|
class LEDKeyMeta:
|
||
|
def __init__(self, *leds):
|
||
|
self.leds = leds
|
||
|
self.brightness = None
|
||
|
|
||
|
|
||
|
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:
|
||
|
print(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',),
|
||
|
validator=self._led_key_validator,
|
||
|
on_press=self._key_led_tog,
|
||
|
)
|
||
|
make_argumented_key(
|
||
|
names=('LED_INC',),
|
||
|
validator=self._led_key_validator,
|
||
|
on_press=self._key_led_inc,
|
||
|
)
|
||
|
make_argumented_key(
|
||
|
names=('LED_DEC',),
|
||
|
validator=self._led_key_validator,
|
||
|
on_press=self._key_led_dec,
|
||
|
)
|
||
|
make_argumented_key(
|
||
|
names=('LED_SET',),
|
||
|
validator=self._led_set_key_validator,
|
||
|
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 _led_key_validator(self, *leds):
|
||
|
if leds:
|
||
|
for led in leds:
|
||
|
assert self._leds[led]
|
||
|
return LEDKeyMeta(*leds)
|
||
|
|
||
|
def _led_set_key_validator(self, brightness, *leds):
|
||
|
meta = self._led_key_validator(*leds)
|
||
|
meta.brightness = brightness
|
||
|
return meta
|
||
|
|
||
|
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.meta.leds)
|
||
|
|
||
|
def _key_led_dec(self, key, *args, **kwargs):
|
||
|
self.decrease_brightness(leds=key.meta.leds)
|
||
|
|
||
|
def _key_led_set(self, key, *args, **kwargs):
|
||
|
self.set_brightness(percent=key.meta.brightness, leds=key.meta.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
|