musicmouse/espmusicmouse/host_driver/led_cmds.py

127 lines
3.9 KiB
Python

from dataclasses import dataclass
import struct
import colorsys
@dataclass
class ColorRGBW:
r: float
g: float
b: float
w: float
def __repr__(self):
return f"#({self.r}, {self.g}, {self.b}, {self.w})"
def as_bytes(self) -> bytes:
assert self.is_valid(), "Trying to send invalid " + repr(self)
return struct.pack("<BBBB", int(self.r * 255), int(self.g * 255), int(self.b * 255),
int(self.w * 255))
def is_valid(self):
vals = (self.r, self.g, self.b, self.w)
return all(0 <= v <= 1 for v in vals)
@dataclass
class ColorHSV:
h: float
s: float
v: float
@staticmethod
def fromRGB(rgb):
conv = colorsys.rgb_to_hsv(rgb.r, rgb.g, rgb.b)
return ColorHSV(conv[0] * 360, conv[1], conv[2])
def __repr__(self):
return f"ColorHSV({self.h}, {self.s}, {self.v})"
def as_bytes(self) -> bytes:
return struct.pack("<fff", self.h, self.s, self.v)
def is_valid(self):
if not 0 <= self.h <= 360:
return False
if not 0 <= self.s <= 1:
return False
if not 0 <= self.v <= 2:
return False
return True
@dataclass
class EffectStaticConfig:
color: ColorRGBW
begin: int = 0
end: int = 0
def __repr__(self):
return f"EffectStaticConfig {str(self.color)}, beg: {self.begin}, end {self.end}"
def as_bytes(self) -> bytes:
return self.color.as_bytes() + struct.pack("<HH", self.begin, self.end)
@dataclass
class EffectAlexaSwipeConfig:
primary_color_width: float = 20 # in degrees
transition_width: float = 30 # in degrees
swipe_speed: float = 3 * 360 # in degrees per second
bell_curve_width_in_leds: float = 3
start_position: float = 180 # in degrees
forward: bool = True
primary_color: ColorRGBW = ColorRGBW(0, 0, 1, 0)
secondary_color: ColorRGBW = ColorRGBW(0, 200 / 255, 1, 0)
def as_bytes(self) -> bytes:
return struct.pack(
"<fffff?", self.primary_color_width, self.transition_width, self.swipe_speed,
self.bell_curve_width_in_leds, self.start_position,
self.forward) + self.primary_color.as_bytes() + self.secondary_color.as_bytes()
def __repr__(self):
return f"EffectAlexaSwipe primary {str(self.primary_color)}, {str(self.secondary_color)}"
@dataclass
class EffectRandomTwoColorInterpolationConfig:
cycle_durations_ms: int = 6000
start_with_existing: bool = True
num_segments: int = 3
hue1_random: bool = False
hue2_random: bool = False
color1: ColorHSV = ColorHSV(240, 1, 1)
color2: ColorHSV = ColorHSV(192, 1, 1)
def as_bytes(self) -> bytes:
c1 = ColorHSV.fromRGB(self.color1) if isinstance(self.color1, ColorRGBW) else self.color1
c2 = ColorHSV.fromRGB(self.color2) if isinstance(self.color2, ColorRGBW) else self.color2
return struct.pack("<i?i??", self.cycle_durations_ms, self.start_with_existing,
self.num_segments, self.hue1_random,
self.hue2_random) + c1.as_bytes() + c2.as_bytes()
def __repr__(self):
return f"RandTwoColor {str(self.color1)}, {str(self.color2)}, segments {self.num_segments}"
@dataclass
class EffectCircularConfig:
speed: float = 360 # in degrees per second
width: float = 180 # in degrees
color: ColorRGBW = ColorRGBW(0, 0, 1, 0)
def as_bytes(self) -> bytes:
return struct.pack("<ff", self.speed, self.width) + self.color.as_bytes()
@dataclass
class EffectSwipeAndChange:
swipe: EffectAlexaSwipeConfig = EffectAlexaSwipeConfig()
change: EffectRandomTwoColorInterpolationConfig = EffectRandomTwoColorInterpolationConfig()
def as_bytes(self) -> bytes:
return self.swipe.as_bytes() + self.change.as_bytes()
def __repr__(self) -> str:
return f"Swipe and Change: \n {str(self.swipe)}\n {str(self.change)}"