66from pprint import pformat
77from .pyextalife import ExtaLifeAPI
88
9- from homeassistant .components .light import (Light , SUPPORT_BRIGHTNESS , SUPPORT_COLOR , SUPPORT_WHITE_VALUE , ATTR_BRIGHTNESS , ATTR_HS_COLOR , ATTR_WHITE_VALUE )
9+ from homeassistant .components .light import (Light , SUPPORT_BRIGHTNESS , SUPPORT_COLOR , SUPPORT_WHITE_VALUE , SUPPORT_EFFECT , ATTR_BRIGHTNESS , ATTR_HS_COLOR , ATTR_WHITE_VALUE , ATTR_EFFECT )
1010from . import ExtaLifeChannel
1111
1212
13- from .pyextalife import DEVICE_ARR_ALL_LIGHT , DEVICE_ARR_LIGHT_RGB
13+ from .pyextalife import DEVICE_ARR_ALL_LIGHT , DEVICE_ARR_LIGHT_RGB , DEVICE_ARR_LIGHT_RGBW , DEVICE_ARR_LIGHT_EFFECT
1414
1515import homeassistant .util .color as color_util
1616_LOGGER = logging .getLogger (__name__ )
1717
18+ EFFECT_1 = "Program 1"
19+ EFFECT_2 = "Program 2"
20+ EFFECT_3 = "Program 3"
21+ EFFECT_4 = "Program 4"
22+ EFFECT_5 = "Program 5"
23+ EFFECT_6 = "Program 6"
24+ EFFECT_7 = "Program 7"
25+ EFFECT_8 = "Program 8"
26+ EFFECT_9 = "Program 9"
27+ EFFECT_10 = "Program 10"
28+ EFFECT_FLOAT = "Floating"
29+ EFFECT_LIST = [EFFECT_1 , EFFECT_2 , EFFECT_3 , EFFECT_4 , EFFECT_5 , EFFECT_6 , EFFECT_7 , EFFECT_8 , EFFECT_9 , EFFECT_10 , EFFECT_FLOAT ]
30+ EFFECT_LIST_SLR = EFFECT_LIST
31+
32+ MAP_MODE_VAL_EFFECT = {
33+ 0 : EFFECT_FLOAT ,
34+ 1 : EFFECT_1 ,
35+ 2 : EFFECT_2 ,
36+ 3 : EFFECT_3 ,
37+ 4 : EFFECT_4 ,
38+ 5 : EFFECT_5 ,
39+ 6 : EFFECT_6 ,
40+ 7 : EFFECT_7 ,
41+ 8 : EFFECT_8 ,
42+ 9 : EFFECT_9 ,
43+ 10 : EFFECT_10 ,
44+ }
45+ MAP_EFFECT_MODE_VAL = {
46+ EFFECT_FLOAT : 0 ,
47+ EFFECT_1 : 1 ,
48+ EFFECT_2 : 2 ,
49+ EFFECT_3 : 3 ,
50+ EFFECT_4 : 4 ,
51+ EFFECT_5 : 5 ,
52+ EFFECT_6 : 6 ,
53+ EFFECT_7 : 7 ,
54+ EFFECT_8 : 8 ,
55+ EFFECT_9 : 9 ,
56+ EFFECT_10 : 10 ,
57+ }
1858
1959def scaleto255 (value ):
2060 """Scale the input value from 0-100 to 0-255."""
@@ -28,6 +68,35 @@ def scaleto100(value):
2868 return 1
2969 return int (max (0 , min (100 , ((value * 100.0 ) / 255.0 ))))
3070
71+ def modevaltohex (mode_val ):
72+ """ convert mode_val value that can be either xeh string or int to a hex string """
73+ if isinstance (mode_val , int ):
74+ return (hex (mode_val )[2 :]).upper ()
75+ if isinstance (mode_val , str ):
76+ return mode_val
77+ return None
78+
79+ def modevaltoint (mode_val ):
80+ """ convert mode_val value that can be either xeh string or int to int """
81+ if isinstance (mode_val , str ):
82+ return int (mode_val , 16 )
83+ if isinstance (mode_val , int ):
84+ return mode_val
85+ return None
86+
87+ def modeval_upd (old , new ):
88+ """ Update mode_val contextually. Convert to type of the old value and update"""
89+ if isinstance (old , int ):
90+ if isinstance (new , int ):
91+ return new
92+ return modevaltoint (new )
93+
94+ if isinstance (old , str ):
95+ if isinstance (new , str ):
96+ return new
97+ return modevaltohex (new )
98+
99+ return None
31100
32101def setup_platform (hass , config , add_entities , discovery_info = None ):
33102 """Set up ExtaLife lighs."""
@@ -46,20 +115,29 @@ def __init__(self, channel_data):
46115 super ().__init__ (channel_data )
47116
48117 self ._supported_flags = 0
118+ self ._effect_list = None
49119 self .channel_data = channel_data .get ("data" )
50120
51121 dev_type = self .channel_data .get ("type" )
52122 _LOGGER .debug ("Light type: %s" , dev_type )
53123 if dev_type in DEVICE_ARR_ALL_LIGHT :
54124 self ._supported_flags |= SUPPORT_BRIGHTNESS
55125
56- if dev_type in DEVICE_ARR_LIGHT_RGB :
126+ if dev_type in DEVICE_ARR_LIGHT_RGBW :
57127 self ._supported_flags |= SUPPORT_COLOR | SUPPORT_WHITE_VALUE
128+ elif dev_type in DEVICE_ARR_LIGHT_RGB :
129+ self ._supported_flags |= SUPPORT_COLOR
130+
131+ if dev_type in DEVICE_ARR_LIGHT_EFFECT :
132+ self ._supported_flags |= SUPPORT_EFFECT
133+ if dev_type in [27 , 38 ]:
134+ self ._effect_list = EFFECT_LIST_SLR
58135
59136 def turn_on (self , ** kwargs ):
60137 """Turn on the switch."""
61138 data = self .channel_data
62139 params = dict ()
140+ rgb = w = None
63141 if self ._supported_flags & SUPPORT_BRIGHTNESS :
64142 target_brightness = kwargs .get (ATTR_BRIGHTNESS )
65143
@@ -70,42 +148,81 @@ def turn_on(self, **kwargs):
70148 else :
71149 params .update ({"value" : data .get ("value" )})
72150
73- _LOGGER .debug ("white value: %s" , kwargs )
151+ mode_val = self .channel_data .get ("mode_val" )
152+ mode_val_int = modevaltoint (mode_val )
153+ effect = kwargs .get (ATTR_EFFECT )
154+ _LOGGER .debug ("kwargs: %s" , kwargs )
155+ _LOGGER .debug ("'mode_val' value: %s" , mode_val )
156+ _LOGGER .debug ("turn_on for entity: %s(%s). mode_val_int: %s" , self .entity_id , self .channel_id , mode_val_int )
157+
158+ # WARNING: Exta LIfe 'mode_val' from command 37 is a HEX STRING, but command 20 requires INT!!! 🤦♂️
159+ if self ._supported_flags & SUPPORT_WHITE_VALUE and effect is None :
160+ if kwargs .get (ATTR_WHITE_VALUE ) is None :
161+ w = mode_val_int & 255 # default
162+ else :
163+ w = int (kwargs .get (ATTR_WHITE_VALUE )) & 255
74164
75- if self ._supported_flags & SUPPORT_COLOR :
76- target_color = kwargs .get (ATTR_HS_COLOR )
165+ if self ._supported_flags & SUPPORT_COLOR and effect is None :
166+ if not kwargs .get (ATTR_HS_COLOR ):
167+ rgb = mode_val_int >> 8 # default
168+ else :
169+ hs = kwargs .get (ATTR_HS_COLOR ) # should return a tuple (h, s)
170+ rgb = color_util .color_hs_to_RGB (* hs ) # returns a tuple (R, G, B)
171+ rgb = (int (rgb [0 ]) << 16 ) | (int (rgb [1 ]) << 8 ) | (int (rgb [2 ]))
77172
173+ if self ._supported_flags & SUPPORT_WHITE_VALUE and self ._supported_flags & SUPPORT_COLOR and effect is None :
78174 # Exta Life colors in SLR22 are 4 bytes: RGBW
79- # HA passes either color or white value information
80- if target_color is not None :
81- # We set it to the target brightness and turn it on
82- w = int (data .get ("mode_val" )) & 255
83- hs = kwargs .get (ATTR_HS_COLOR ) # should return a tuple (h, s)
84- rgb = color_util .color_hs_to_RGB (* hs ) # returns a tuple (R, G, B)
85- rgbw = (int (rgb [0 ]) << 24 ) | (int (rgb [1 ]) << 16 ) | (int (rgb [2 ]) << 8 ) | int (w )
86- params .update ({"mode_val" : rgbw })
87- params .update ({"mode" : 0 }) # white component only = false
88- else :
89- w = int (kwargs .get (ATTR_WHITE_VALUE )) & 255
90- rgbw = int (data .get ("mode_val" )) | w # get RGB from channel data and new value from HA
91- params .update ({"mode_val" : rgbw })
92- params .update ({"mode" : 1 }) # white component only = true
175+ _LOGGER .debug ("RGB value: %s. W value: %s" , rgb , w )
176+ rgbw = (rgb << 8 ) | w # merge RGB & W
177+ params .update ({"mode_val" : rgbw })
178+ params .update ({"mode" : 1 }) # mode - still light or predefined programs; set it as still light
179+
180+ if effect is not None :
181+ params .update ({"mode" : 2 }) # mode - turn on effect
182+ params .update ({"mode_val" : MAP_EFFECT_MODE_VAL [effect ]}) # mode - one of effects
93183
94- #self.action(ExtaLifeAPI.ACTN_TURN_ON)
95184 if self .action (ExtaLifeAPI .ACTN_TURN_ON , ** params ):
185+ # update channel data with new values
96186 data ["power" ] = 1
187+ mode_val_new = params .get ("mode_val" )
188+ if mode_val_new is not None :
189+ params ["mode_val" ] = modeval_upd (mode_val , mode_val_new ) # convert new value to the format of the old value from channel_data
97190 data .update (params )
98191 self .schedule_update_ha_state ()
99192
100193 def turn_off (self , ** kwargs ):
101194 """Turn off the switch."""
102195 data = self .channel_data
103-
104- if self .action (ExtaLifeAPI .ACTN_TURN_OFF , value = data .get ("value" )):
105-
196+ params = dict ()
197+ mode = data .get ("mode" )
198+ if mode is not None :
199+ params .update ({"mode" : mode })
200+ mode_val = data .get ("mode_val" )
201+ if mode_val is not None :
202+ params .update ({"mode_val" : modevaltoint (mode_val )})
203+ value = data .get ("value" )
204+ if value is not None :
205+ params .update ({"value" : value })
206+
207+ if self .action (ExtaLifeAPI .ACTN_TURN_OFF , ** params ):
106208 data ["power" ] = 0
209+ data ["mode" ] = mode
107210 self .schedule_update_ha_state ()
108211
212+ @property
213+ def effect (self ):
214+ mode = self .channel_data .get ("mode" )
215+ if mode is None or mode != 2 :
216+ return None
217+ mode_val = self .channel_data .get ("mode_val" )
218+ if mode_val is None :
219+ return None
220+ return MAP_MODE_VAL_EFFECT [modevaltoint (mode_val )]
221+
222+ @property
223+ def effect_list (self ):
224+ return self ._effect_list
225+
109226 @property
110227 def brightness (self ):
111228 """ Device brightness """
@@ -122,8 +239,7 @@ def supported_features(self):
122239 @property
123240 def hs_color (self ):
124241 """ Device colour setting """
125- # TODO: need some research to implement this for SLR22
126- rgbw = self .channel_data .get ("mode_val" )
242+ rgbw = modevaltoint (self .channel_data .get ("mode_val" ))
127243 rgb = rgbw >> 8
128244 r = rgb >> 16
129245 g = (rgb >> 8 ) & 255
@@ -134,8 +250,8 @@ def hs_color(self):
134250
135251 @property
136252 def white_value (self ):
137- rgbw = self .channel_data .get ("mode_val" )
138- return int ( rgbw & 255 )
253+ rgbw = modevaltoint ( self .channel_data .get ("mode_val" ) )
254+ return rgbw & 255
139255
140256 @property
141257 def is_on (self ):
@@ -156,9 +272,10 @@ def on_state_notification(self, data):
156272 ch_data ["value" ] = data .get ("value" )
157273
158274 if self ._supported_flags & SUPPORT_COLOR :
159- ch_data ["mode_val" ] = data .get ("mode_val" )
275+ mode_val = ch_data .get ("mode_val" )
276+ ch_data ["mode_val" ] = modeval_upd (mode_val , data .get ("mode_val" ))
160277
161278 # update only if notification data contains new status; prevent HS event bus overloading
162279 if ch_data != self .channel_data :
163280 self .channel_data .update (ch_data )
164- self .async_schedule_update_ha_state (True )
281+ self .async_schedule_update_ha_state (True )
0 commit comments