2024-03-06 17:15:07 +01:00
from dataclasses import dataclass , field
2021-11-27 19:04:08 +01:00
import struct
2021-12-19 13:46:45 +01:00
import colorsys
2021-11-27 19:04:08 +01:00
@dataclass
class ColorRGBW :
r : float
g : float
b : float
w : float
2021-12-19 13:46:45 +01:00
def __repr__ ( self ) :
return f " #( { self . r } , { self . g } , { self . b } , { self . w } ) "
2021-11-27 21:18:54 +01:00
2021-11-27 19:04:08 +01:00
def as_bytes ( self ) - > bytes :
2021-11-27 21:18:54 +01:00
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 ) )
2021-11-27 19:04:08 +01:00
def is_valid ( self ) :
vals = ( self . r , self . g , self . b , self . w )
return all ( 0 < = v < = 1 for v in vals )
2022-01-12 22:35:49 +01:00
def __mul__ ( self , other : float ) :
assert 0 < = other < = 1
return ColorRGBW ( self . r * other , self . g * other , self . b * other , self . w * other )
2021-11-27 19:04:08 +01:00
2022-01-12 22:35:49 +01:00
def __eq__ ( self , other : ' ColorRGBW ' ) :
return self . r == other . r and self . g == other . g and self . b == other . b and self . w == other . w
def __neq__ ( self , other : ' ColorRGBW ' ) :
return not self == other
2022-02-04 18:13:14 +01:00
def without_white_channel ( self , scale = 1 ) :
args = ( min ( 1 , e + self . w ) for e in ( self . r , self . g , self . b ) )
return ColorRGBW ( * args , 0 )
2021-11-27 19:04:08 +01:00
@dataclass
class ColorHSV :
h : float
s : float
v : float
2021-12-19 13:46:45 +01:00
@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 } ) "
2021-11-27 19:04:08 +01:00
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
2021-12-15 22:47:28 +01:00
begin : int = 0
end : int = 0
2021-11-27 19:04:08 +01:00
2021-12-19 13:46:45 +01:00
def __repr__ ( self ) :
return f " EffectStaticConfig { str ( self . color ) } , beg: { self . begin } , end { self . end } "
2021-11-27 19:04:08 +01:00
def as_bytes ( self ) - > bytes :
2021-12-15 22:47:28 +01:00
return self . color . as_bytes ( ) + struct . pack ( " <HH " , self . begin , self . end )
2021-11-27 19:04:08 +01:00
2024-01-01 18:24:55 +01:00
@dataclass
class EffectStaticDetailedConfig :
color : ColorRGBW
increment : int = 1
begin : float = 0.0
end : float = 1.0
transition_time_in_ms : float = 500
def __repr__ ( self ) :
return f " EffectStaticDetailedConfig { str ( self . color ) } , beg: { self . begin } , end { self . end } , incr { self . increment } , transition in ms { self . transition_time_in_ms } "
def as_bytes ( self ) - > bytes :
return self . color . as_bytes ( ) + struct . pack ( " <Hfff " , self . increment , self . begin , self . end , self . transition_time_in_ms )
2021-11-27 19:04:08 +01:00
@dataclass
class EffectAlexaSwipeConfig :
2021-11-27 21:18:54 +01:00
primary_color_width : float = 20 # in degrees
transition_width : float = 30 # in degrees
2021-12-20 20:41:50 +01:00
swipe_speed : float = 2 * 360 # in degrees per second
2021-11-27 21:18:54 +01:00
bell_curve_width_in_leds : float = 3
start_position : float = 180 # in degrees
forward : bool = True
2024-03-06 17:15:07 +01:00
primary_color : ColorRGBW = field ( default_factory = lambda : ColorRGBW ( 0 , 0 , 1 , 0 ) )
secondary_color : ColorRGBW = field ( default_factory = lambda : ColorRGBW ( 0 , 200 / 255 , 1 , 0 ) )
2021-11-27 19:04:08 +01:00
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 ( )
2021-12-19 13:46:45 +01:00
def __repr__ ( self ) :
return f " EffectAlexaSwipe primary { str ( self . primary_color ) } , { str ( self . secondary_color ) } "
2021-11-27 19:04:08 +01:00
@dataclass
class EffectRandomTwoColorInterpolationConfig :
2021-11-27 21:18:54 +01:00
cycle_durations_ms : int = 6000
start_with_existing : bool = True
num_segments : int = 3
hue1_random : bool = False
hue2_random : bool = False
2024-03-06 17:15:07 +01:00
color1 : ColorHSV = field ( default_factory = lambda : ColorHSV ( 240 , 1 , 1 ) )
color2 : ColorHSV = field ( default_factory = lambda : ColorHSV ( 192 , 1 , 1 ) )
2021-11-27 19:04:08 +01:00
def as_bytes ( self ) - > bytes :
2021-12-19 13:46:45 +01:00
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
2021-11-27 19:04:08 +01:00
return struct . pack ( " <i?i?? " , self . cycle_durations_ms , self . start_with_existing ,
self . num_segments , self . hue1_random ,
2021-12-19 13:46:45 +01:00
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 } "
2021-11-27 19:04:08 +01:00
@dataclass
class EffectCircularConfig :
2021-11-27 21:18:54 +01:00
speed : float = 360 # in degrees per second
width : float = 180 # in degrees
2024-03-06 17:15:07 +01:00
color : ColorRGBW = field ( default_factory = lambda : ColorRGBW ( 0 , 0 , 1 , 0 ) )
2021-11-27 19:04:08 +01:00
def as_bytes ( self ) - > bytes :
return struct . pack ( " <ff " , self . speed , self . width ) + self . color . as_bytes ( )
2021-11-27 21:18:54 +01:00
@dataclass
class EffectSwipeAndChange :
2024-03-06 17:15:07 +01:00
swipe : EffectAlexaSwipeConfig = field ( default_factory = lambda : EffectAlexaSwipeConfig ( ) )
change : EffectRandomTwoColorInterpolationConfig = field ( default_factory = lambda : EffectRandomTwoColorInterpolationConfig ( ) )
2021-11-27 21:18:54 +01:00
def as_bytes ( self ) - > bytes :
2021-12-19 13:46:45 +01:00
return self . swipe . as_bytes ( ) + self . change . as_bytes ( )
def __repr__ ( self ) - > str :
2021-12-20 20:41:50 +01:00
return f " Swipe and Change: \n { str ( self . swipe ) } \n { str ( self . change ) } "
@dataclass
class EffectReverseSwipe :
swipeSpeed : float = 2 * 360
bellCurveWidthInLeds : float = 3
startPosition : float = 180
def as_bytes ( self ) - > bytes :
return struct . pack ( " <fff " , self . swipeSpeed , self . bellCurveWidthInLeds , self . startPosition )
def __repr__ ( self ) - > str :
2024-03-06 17:15:07 +01:00
return f " Reverse swipe, speed { self . swipeSpeed } , width in leds { self . bellCurveWidthInLeds } , start position { self . startPosition } "