Skip to content

Commit 777765a

Browse files
authored
Fix SLR-22 RGBW control, add support for effects (#10) (#11)
* many fixes for RGBW control in HA and representation when notification is received form the controller * ADD SUPPORT FOR SLR-22 EFFECTS IN HA LIGHT!! :)
1 parent b022087 commit 777765a

2 files changed

Lines changed: 151 additions & 32 deletions

File tree

extalife/light.py

Lines changed: 147 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,55 @@
66
from pprint import pformat
77
from .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)
1010
from . 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

1515
import 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

1959
def 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

32101
def 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)

extalife/pyextalife.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
DEVICE_ARR_SWITCH = [10, 11, 22, 23, 24]
1919
DEVICE_ARR_COVER = [12, 25]
2020
DEVICE_ARR_LIGHT = [13, 26, 45, 27, 46]
21-
DEVICE_ARR_LIGHT_RGB = [27, 38]
21+
DEVICE_ARR_LIGHT_RGB = [] #RGB only
22+
DEVICE_ARR_LIGHT_RGBW = [27, 38]
23+
DEVICE_ARR_LIGHT_EFFECT = [27, 38]
2224
DEVICE_ARR_CLIMATE = [16]
2325
DEVICE_ARR_REPEATER = [237]
2426

2527
DEVICE_ARR_ALL_SWITCH = DEVICE_ARR_SWITCH
26-
DEVICE_ARR_ALL_LIGHT = [*DEVICE_ARR_LIGHT, *DEVICE_ARR_LIGHT_RGB]
28+
DEVICE_ARR_ALL_LIGHT = [*DEVICE_ARR_LIGHT, *DEVICE_ARR_LIGHT_RGB, *DEVICE_ARR_LIGHT_RGBW]
2729
DEVICE_ARR_ALL_COVER = [*DEVICE_ARR_COVER]
2830
DEVICE_ARR_ALL_CLIMATE = [*DEVICE_ARR_CLIMATE]
2931
DEVICE_ARR_ALL_IGNORE = [*DEVICE_ARR_REPEATER]

0 commit comments

Comments
 (0)