1 """Component to integrate ambilight for TVs exposing the Joint Space API."""
3 from __future__
import annotations
5 from dataclasses
import dataclass
8 from haphilipsjs
import PhilipsTV
9 from haphilipsjs.typing
import AmbilightCurrentConfiguration
24 from .
import PhilipsTVConfigEntry
25 from .coordinator
import PhilipsTVDataUpdateCoordinator
26 from .entity
import PhilipsJsEntity
28 EFFECT_PARTITION =
": "
30 EFFECT_EXPERT =
"Expert"
32 EFFECT_EXPERT_STYLES = {
"FOLLOW_AUDIO",
"FOLLOW_COLOR",
"Lounge light"}
37 config_entry: PhilipsTVConfigEntry,
38 async_add_entities: AddEntitiesCallback,
40 """Set up the configuration entry."""
41 coordinator = config_entry.runtime_data
46 """Extract the color settings data from a style."""
47 if style[
"styleName"]
in (
"FOLLOW_COLOR",
"Lounge light"):
48 return style[
"colorSettings"]
49 if style[
"styleName"] ==
"FOLLOW_AUDIO":
50 return style[
"audioSettings"]
56 """Data class describing the ambilight effect."""
60 algorithm: str |
None =
None
62 def is_on(self, powerstate) -> bool:
63 """Check whether the ambilight is considered on."""
64 if self.
modemode
in (EFFECT_AUTO, EFFECT_EXPERT):
65 if self.
stylestyle
in (
"FOLLOW_VIDEO",
"FOLLOW_AUDIO"):
66 return powerstate
in (
"On",
None)
71 if self.
modemode == EFFECT_MODE:
72 if self.
stylestyle ==
"internal":
73 return powerstate
in (
"On",
None)
79 """Validate the effect configuration."""
80 if self.
modemode == EFFECT_EXPERT:
81 return self.
stylestyle
in EFFECT_EXPERT_STYLES
85 def from_str(effect_string: str) -> AmbilightEffect:
86 """Create AmbilightEffect object from string."""
87 style, _, algorithm = effect_string.partition(EFFECT_PARTITION)
88 if style == EFFECT_MODE:
89 return AmbilightEffect(mode=EFFECT_MODE, style=algorithm, algorithm=
None)
90 algorithm, _, expert = algorithm.partition(EFFECT_PARTITION)
92 return AmbilightEffect(mode=EFFECT_EXPERT, style=style, algorithm=algorithm)
93 return AmbilightEffect(mode=EFFECT_AUTO, style=style, algorithm=algorithm)
96 """Get a string representation of the effect."""
97 if self.
modemode == EFFECT_MODE:
98 return f
"{EFFECT_MODE}{EFFECT_PARTITION}{self.style}"
99 if self.
modemode == EFFECT_EXPERT:
100 return f
"{self.style}{EFFECT_PARTITION}{self.algorithm}{EFFECT_PARTITION}{EFFECT_EXPERT}"
101 return f
"{self.style}{EFFECT_PARTITION}{self.algorithm}"
105 """Return a cache keys to avoid always updating."""
109 device.ambilight_current_configuration,
110 device.ambilight_mode,
115 """Calculate an average color over all ambilight pixels."""
120 for layer
in data.values():
121 for side
in layer.values():
122 for pixel
in side.values():
124 color_r += pixel[
"r"]
125 color_g += pixel[
"g"]
126 color_b += pixel[
"b"]
132 return color_r, color_g, color_b
137 """Representation of a Philips TV exposing the JointSpace API."""
139 _attr_translation_key =
"ambilight"
143 coordinator: PhilipsTVDataUpdateCoordinator,
145 """Initialize light."""
146 self.
_tv_tv = coordinator.api
160 """Calculate an effect list based on current status."""
161 effects: list[AmbilightEffect] = []
164 for style, data
in self.
_tv_tv.ambilight_styles.items()
165 for setting
in data.get(
"menuSettings", [])
170 for style, data
in self.
_tv_tv.ambilight_styles.items()
171 for algorithm
in data.get(
"algorithms", [])
176 for style
in self.
_tv_tv.ambilight_modes
181 for effect
in effects
182 if effect.is_valid()
and effect.is_on(self.
_tv_tv.powerstate)
185 return sorted(filtered_effects)
188 """Return the current effect."""
189 current = self.
_tv_tv.ambilight_current_configuration
190 if current
and self.
_tv_tv.ambilight_mode !=
"manual":
191 if current[
"isExpert"]:
194 EFFECT_EXPERT, current[
"styleName"], settings[
"algorithm"]
199 EFFECT_AUTO, current[
"styleName"], current.get(
"menuSetting",
None)
206 """Return the current color mode."""
207 current = self.
_tv_tv.ambilight_current_configuration
208 if current
and current[
"isExpert"]:
211 if self.
_tv_tv.ambilight_mode
in [
"manual",
"expert"]:
214 return ColorMode.ONOFF
218 """Return if the light is turned on."""
220 effect = AmbilightEffect.from_str(self.
effecteffect)
221 return effect.is_on(self.
_tv_tv.powerstate)
226 current = self.
_tv_tv.ambilight_current_configuration
234 if current
and current[
"isExpert"]:
236 color = settings[
"color"]
238 effect = AmbilightEffect.from_str(self.
_attr_effect_attr_effect)
239 if effect.is_on(self.
_tv_tv.powerstate):
242 if effect.mode == EFFECT_EXPERT
and color:
244 color[
"hue"] * 360.0 / 255.0,
245 color[
"saturation"] * 100.0 / 255.0,
248 elif effect.mode == EFFECT_MODE
and self.
_tv_tv.ambilight_cached:
260 """Handle updated data from the coordinator."""
265 self, effect: AmbilightEffect, hs_color: tuple[float, float], brightness: int
267 """Set ambilight via the manual or expert mode."""
276 if not await self.
_tv_tv.setAmbilightCached(data):
279 if effect.style != self.
_tv_tv.ambilight_mode:
280 if not await self.
_tv_tv.setAmbilightMode(effect.style):
284 self, effect: AmbilightEffect, hs_color: tuple[float, float], brightness: int
286 """Set ambilight via current configuration."""
287 config: AmbilightCurrentConfiguration = {
288 "styleName": effect.style,
293 "algorithm": effect.algorithm,
295 "hue": round(hs_color[0] * 255.0 / 360.0),
296 "saturation": round(hs_color[1] * 255.0 / 100.0),
297 "brightness": round(brightness),
306 if effect.style
in (
"FOLLOW_COLOR",
"Lounge light"):
307 config[
"colorSettings"] = setting
310 elif effect.style ==
"FOLLOW_AUDIO":
311 config[
"audioSettings"] = setting
314 if not await self.
_tv_tv.setAmbilightCurrentConfiguration(config):
318 """Set ambilight via current configuration."""
319 config: AmbilightCurrentConfiguration = {
320 "styleName": effect.style,
322 "menuSetting": effect.algorithm,
325 if await self.
_tv_tv.setAmbilightCurrentConfiguration(config)
is False:
329 """Turn the bulb on."""
330 brightness = kwargs.get(ATTR_BRIGHTNESS, self.
brightnessbrightness)
331 hs_color = kwargs.get(ATTR_HS_COLOR, self.
hs_colorhs_color)
332 attr_effect = kwargs.get(ATTR_EFFECT, self.
effecteffect)
334 if not self.
_tv_tv.on:
337 effect = AmbilightEffect.from_str(attr_effect)
339 if effect.style ==
"OFF":
345 if not effect.is_on(self.
_tv_tv.powerstate):
346 effect.mode = EFFECT_MODE
347 effect.algorithm =
None
348 if self.
_tv_tv.powerstate
in (
"On",
None):
349 effect.style =
"internal"
351 effect.style =
"manual"
353 if brightness
is None:
359 if effect.mode == EFFECT_MODE:
361 elif effect.mode == EFFECT_AUTO:
363 elif effect.mode == EFFECT_EXPERT:
370 """Turn of ambilight."""
372 if not self.
_tv_tv.on:
375 if await self.
_tv_tv.setAmbilightMode(
"internal")
is False:
385 """Return true if entity is available."""
386 if not super().available:
388 if not self.
_tv_tv.on:
int|None brightness(self)
tuple[float, float]|None hs_color(self)
bool is_on(self, powerstate)
AmbilightEffect from_str(str effect_string)
None __init__(self, PhilipsTVDataUpdateCoordinator coordinator)
_attr_supported_color_modes
def _set_ambilight_config(self, AmbilightEffect effect)
def _calculate_effect_list(self)
AmbilightEffect _calculate_effect(self)
None async_turn_off(self, **Any kwargs)
ColorMode color_mode(self)
None async_turn_on(self, **Any kwargs)
def _set_ambilight_expert_config(self, AmbilightEffect effect, tuple[float, float] hs_color, int brightness)
def _update_from_coordinator(self)
None _handle_coordinator_update(self)
def _set_ambilight_cached(self, AmbilightEffect effect, tuple[float, float] hs_color, int brightness)
None async_write_ha_state(self)
def _average_pixels(data)
None async_setup_entry(HomeAssistant hass, PhilipsTVConfigEntry config_entry, AddEntitiesCallback async_add_entities)
def _get_settings(AmbilightCurrentConfiguration style)
def _get_cache_keys(PhilipsTV device)
tuple[int, int, int] color_hsv_to_RGB(float iH, float iS, float iV)
tuple[float, float, float] color_RGB_to_hsv(float iR, float iG, float iB)