Add support for yeelight ceiling ambilight (#22346)
parent
6540114ec5
commit
4de2efd07f
|
@ -91,6 +91,7 @@ YEELIGHT_SERVICE_SCHEMA = vol.Schema({
|
||||||
|
|
||||||
UPDATE_REQUEST_PROPERTIES = [
|
UPDATE_REQUEST_PROPERTIES = [
|
||||||
"power",
|
"power",
|
||||||
|
"main_power",
|
||||||
"bright",
|
"bright",
|
||||||
"ct",
|
"ct",
|
||||||
"rgb",
|
"rgb",
|
||||||
|
@ -98,6 +99,13 @@ UPDATE_REQUEST_PROPERTIES = [
|
||||||
"sat",
|
"sat",
|
||||||
"color_mode",
|
"color_mode",
|
||||||
"bg_power",
|
"bg_power",
|
||||||
|
"bg_lmode",
|
||||||
|
"bg_flowing",
|
||||||
|
"bg_ct",
|
||||||
|
"bg_bright",
|
||||||
|
"bg_hue",
|
||||||
|
"bg_sat",
|
||||||
|
"bg_rgb",
|
||||||
"nl_br",
|
"nl_br",
|
||||||
"active_mode",
|
"active_mode",
|
||||||
]
|
]
|
||||||
|
@ -249,22 +257,34 @@ class YeelightDevice:
|
||||||
"""Return true / false if nightlight is supported."""
|
"""Return true / false if nightlight is supported."""
|
||||||
return self.bulb.get_model_specs().get('night_light', False)
|
return self.bulb.get_model_specs().get('night_light', False)
|
||||||
|
|
||||||
def turn_on(self, duration=DEFAULT_TRANSITION):
|
@property
|
||||||
|
def is_ambilight_supported(self) -> bool:
|
||||||
|
"""Return true / false if ambilight is supported."""
|
||||||
|
return self.bulb.get_model_specs().get('background_light', False)
|
||||||
|
|
||||||
|
def turn_on(self, duration=DEFAULT_TRANSITION, light_type=None):
|
||||||
"""Turn on device."""
|
"""Turn on device."""
|
||||||
import yeelight
|
import yeelight
|
||||||
|
|
||||||
|
if not light_type:
|
||||||
|
light_type = yeelight.enums.LightType.Main
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._bulb_device.turn_on(duration=duration)
|
self._bulb_device.turn_on(duration=duration, light_type=light_type)
|
||||||
except yeelight.BulbException as ex:
|
except yeelight.BulbException as ex:
|
||||||
_LOGGER.error("Unable to turn the bulb on: %s", ex)
|
_LOGGER.error("Unable to turn the bulb on: %s", ex)
|
||||||
return
|
return
|
||||||
|
|
||||||
def turn_off(self, duration=DEFAULT_TRANSITION):
|
def turn_off(self, duration=DEFAULT_TRANSITION, light_type=None):
|
||||||
"""Turn off device."""
|
"""Turn off device."""
|
||||||
import yeelight
|
import yeelight
|
||||||
|
|
||||||
|
if not light_type:
|
||||||
|
light_type = yeelight.enums.LightType.Main
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._bulb_device.turn_off(duration=duration)
|
self._bulb_device.turn_off(duration=duration,
|
||||||
|
light_type=light_type)
|
||||||
except yeelight.BulbException as ex:
|
except yeelight.BulbException as ex:
|
||||||
_LOGGER.error("Unable to turn the bulb on: %s", ex)
|
_LOGGER.error("Unable to turn the bulb on: %s", ex)
|
||||||
return
|
return
|
||||||
|
|
|
@ -110,10 +110,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
_LOGGER.debug("Adding %s", device.name)
|
_LOGGER.debug("Adding %s", device.name)
|
||||||
|
|
||||||
custom_effects = discovery_info[CONF_CUSTOM_EFFECTS]
|
custom_effects = discovery_info[CONF_CUSTOM_EFFECTS]
|
||||||
light = YeelightLight(device, custom_effects=custom_effects)
|
|
||||||
|
|
||||||
hass.data[data_key].append(light)
|
lights = [YeelightLight(device, custom_effects=custom_effects)]
|
||||||
add_entities([light], True)
|
|
||||||
|
if device.is_ambilight_supported:
|
||||||
|
lights.append(
|
||||||
|
YeelightAmbientLight(device, custom_effects=custom_effects))
|
||||||
|
|
||||||
|
hass.data[data_key] += lights
|
||||||
|
add_entities(lights, True)
|
||||||
|
|
||||||
def service_handler(service):
|
def service_handler(service):
|
||||||
"""Dispatch service calls to target entities."""
|
"""Dispatch service calls to target entities."""
|
||||||
|
@ -243,9 +248,16 @@ class YeelightLight(Light):
|
||||||
"""Return list with custom effects names."""
|
"""Return list with custom effects names."""
|
||||||
return list(self.custom_effects.keys())
|
return list(self.custom_effects.keys())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def light_type(self):
|
||||||
|
"""Return light type."""
|
||||||
|
import yeelight
|
||||||
|
return yeelight.enums.LightType.Main
|
||||||
|
|
||||||
def _get_hs_from_properties(self):
|
def _get_hs_from_properties(self):
|
||||||
rgb = self._properties.get('rgb', None)
|
rgb = self._get_property('rgb')
|
||||||
color_mode = self._properties.get('color_mode', None)
|
color_mode = self._get_property('color_mode')
|
||||||
|
|
||||||
if not rgb or not color_mode:
|
if not rgb or not color_mode:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -254,8 +266,9 @@ class YeelightLight(Light):
|
||||||
temp_in_k = mired_to_kelvin(self._color_temp)
|
temp_in_k = mired_to_kelvin(self._color_temp)
|
||||||
return color_util.color_temperature_to_hs(temp_in_k)
|
return color_util.color_temperature_to_hs(temp_in_k)
|
||||||
if color_mode == 3: # hsv
|
if color_mode == 3: # hsv
|
||||||
hue = int(self._properties.get('hue'))
|
hue = int(self._get_property('hue'))
|
||||||
sat = int(self._properties.get('sat'))
|
sat = int(self._get_property('sat'))
|
||||||
|
|
||||||
return (hue / 360 * 65536, sat / 100 * 255)
|
return (hue / 360 * 65536, sat / 100 * 255)
|
||||||
|
|
||||||
rgb = int(rgb)
|
rgb = int(rgb)
|
||||||
|
@ -276,11 +289,18 @@ class YeelightLight(Light):
|
||||||
return {}
|
return {}
|
||||||
return self._bulb.last_properties
|
return self._bulb.last_properties
|
||||||
|
|
||||||
|
def _get_property(self, prop, default=None):
|
||||||
|
return self._properties.get(prop, default)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device(self):
|
def device(self):
|
||||||
"""Return yeelight device."""
|
"""Return yeelight device."""
|
||||||
return self._device
|
return self._device
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _is_nightlight_enabled(self):
|
||||||
|
return self.device.is_nightlight_enabled
|
||||||
|
|
||||||
# F821: https://github.com/PyCQA/pyflakes/issues/373
|
# F821: https://github.com/PyCQA/pyflakes/issues/373
|
||||||
@property
|
@property
|
||||||
def _bulb(self) -> 'yeelight.Bulb': # noqa: F821
|
def _bulb(self) -> 'yeelight.Bulb': # noqa: F821
|
||||||
|
@ -304,32 +324,41 @@ class YeelightLight(Light):
|
||||||
"""Update properties from the bulb."""
|
"""Update properties from the bulb."""
|
||||||
import yeelight
|
import yeelight
|
||||||
try:
|
try:
|
||||||
if self._bulb.bulb_type == yeelight.BulbType.Color:
|
bulb_type = self._bulb.bulb_type
|
||||||
|
|
||||||
|
if bulb_type == yeelight.BulbType.Color:
|
||||||
self._supported_features = SUPPORT_YEELIGHT_RGB
|
self._supported_features = SUPPORT_YEELIGHT_RGB
|
||||||
elif self._bulb.bulb_type == yeelight.BulbType.WhiteTemp:
|
elif self.light_type == yeelight.enums.LightType.Ambient:
|
||||||
if self._device.is_nightlight_enabled:
|
self._supported_features = SUPPORT_YEELIGHT_RGB
|
||||||
|
elif bulb_type in (yeelight.BulbType.WhiteTemp,
|
||||||
|
yeelight.BulbType.WhiteTempMood):
|
||||||
|
if self._is_nightlight_enabled:
|
||||||
self._supported_features = SUPPORT_YEELIGHT
|
self._supported_features = SUPPORT_YEELIGHT
|
||||||
else:
|
else:
|
||||||
self._supported_features = SUPPORT_YEELIGHT_WHITE_TEMP
|
self._supported_features = SUPPORT_YEELIGHT_WHITE_TEMP
|
||||||
|
|
||||||
if self._min_mireds is None:
|
if self.min_mireds is None:
|
||||||
model_specs = self._bulb.get_model_specs()
|
model_specs = self._bulb.get_model_specs()
|
||||||
self._min_mireds = \
|
self._min_mireds = \
|
||||||
kelvin_to_mired(model_specs['color_temp']['max'])
|
kelvin_to_mired(model_specs['color_temp']['max'])
|
||||||
self._max_mireds = \
|
self._max_mireds = \
|
||||||
kelvin_to_mired(model_specs['color_temp']['min'])
|
kelvin_to_mired(model_specs['color_temp']['min'])
|
||||||
|
|
||||||
self._is_on = self._properties.get('power') == 'on'
|
if bulb_type == yeelight.BulbType.WhiteTempMood:
|
||||||
|
self._is_on = self._get_property('main_power') == 'on'
|
||||||
if self._device.is_nightlight_enabled:
|
|
||||||
bright = self._properties.get('nl_br', None)
|
|
||||||
else:
|
else:
|
||||||
bright = self._properties.get('bright', None)
|
self._is_on = self._get_property('power') == 'on'
|
||||||
|
|
||||||
|
if self._is_nightlight_enabled:
|
||||||
|
bright = self._get_property('nl_br', None)
|
||||||
|
else:
|
||||||
|
bright = self._get_property('bright', None)
|
||||||
|
|
||||||
if bright:
|
if bright:
|
||||||
self._brightness = round(255 * (int(bright) / 100))
|
self._brightness = round(255 * (int(bright) / 100))
|
||||||
|
|
||||||
temp_in_k = self._properties.get('ct', None)
|
temp_in_k = self._get_property('ct')
|
||||||
|
|
||||||
if temp_in_k:
|
if temp_in_k:
|
||||||
self._color_temp = kelvin_to_mired(int(temp_in_k))
|
self._color_temp = kelvin_to_mired(int(temp_in_k))
|
||||||
|
|
||||||
|
@ -347,14 +376,16 @@ class YeelightLight(Light):
|
||||||
if brightness:
|
if brightness:
|
||||||
_LOGGER.debug("Setting brightness: %s", brightness)
|
_LOGGER.debug("Setting brightness: %s", brightness)
|
||||||
self._bulb.set_brightness(brightness / 255 * 100,
|
self._bulb.set_brightness(brightness / 255 * 100,
|
||||||
duration=duration)
|
duration=duration,
|
||||||
|
light_type=self.light_type)
|
||||||
|
|
||||||
@_cmd
|
@_cmd
|
||||||
def set_rgb(self, rgb, duration) -> None:
|
def set_rgb(self, rgb, duration) -> None:
|
||||||
"""Set bulb's color."""
|
"""Set bulb's color."""
|
||||||
if rgb and self.supported_features & SUPPORT_COLOR:
|
if rgb and self.supported_features & SUPPORT_COLOR:
|
||||||
_LOGGER.debug("Setting RGB: %s", rgb)
|
_LOGGER.debug("Setting RGB: %s", rgb)
|
||||||
self._bulb.set_rgb(rgb[0], rgb[1], rgb[2], duration=duration)
|
self._bulb.set_rgb(rgb[0], rgb[1], rgb[2], duration=duration,
|
||||||
|
light_type=self.light_type)
|
||||||
|
|
||||||
@_cmd
|
@_cmd
|
||||||
def set_colortemp(self, colortemp, duration) -> None:
|
def set_colortemp(self, colortemp, duration) -> None:
|
||||||
|
@ -363,7 +394,8 @@ class YeelightLight(Light):
|
||||||
temp_in_k = mired_to_kelvin(colortemp)
|
temp_in_k = mired_to_kelvin(colortemp)
|
||||||
_LOGGER.debug("Setting color temp: %s K", temp_in_k)
|
_LOGGER.debug("Setting color temp: %s K", temp_in_k)
|
||||||
|
|
||||||
self._bulb.set_color_temp(temp_in_k, duration=duration)
|
self._bulb.set_color_temp(temp_in_k, duration=duration,
|
||||||
|
light_type=self.light_type)
|
||||||
|
|
||||||
@_cmd
|
@_cmd
|
||||||
def set_default(self) -> None:
|
def set_default(self) -> None:
|
||||||
|
@ -401,7 +433,7 @@ class YeelightLight(Light):
|
||||||
|
|
||||||
flow = Flow(count=count, transitions=transitions)
|
flow = Flow(count=count, transitions=transitions)
|
||||||
try:
|
try:
|
||||||
self._bulb.start_flow(flow)
|
self._bulb.start_flow(flow, light_type=self.light_type)
|
||||||
except BulbException as ex:
|
except BulbException as ex:
|
||||||
_LOGGER.error("Unable to set flash: %s", ex)
|
_LOGGER.error("Unable to set flash: %s", ex)
|
||||||
|
|
||||||
|
@ -415,7 +447,7 @@ class YeelightLight(Light):
|
||||||
police2, christmas, rgb,
|
police2, christmas, rgb,
|
||||||
randomloop, lsd, slowdown)
|
randomloop, lsd, slowdown)
|
||||||
if effect == EFFECT_STOP:
|
if effect == EFFECT_STOP:
|
||||||
self._bulb.stop_flow()
|
self._bulb.stop_flow(light_type=self.light_type)
|
||||||
return
|
return
|
||||||
|
|
||||||
effects_map = {
|
effects_map = {
|
||||||
|
@ -447,7 +479,7 @@ class YeelightLight(Light):
|
||||||
flow = Flow(count=2, transitions=pulse(0, 172, 237))
|
flow = Flow(count=2, transitions=pulse(0, 172, 237))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._bulb.start_flow(flow)
|
self._bulb.start_flow(flow, light_type=self.light_type)
|
||||||
except BulbException as ex:
|
except BulbException as ex:
|
||||||
_LOGGER.error("Unable to set effect: %s", ex)
|
_LOGGER.error("Unable to set effect: %s", ex)
|
||||||
|
|
||||||
|
@ -465,7 +497,7 @@ class YeelightLight(Light):
|
||||||
if ATTR_TRANSITION in kwargs: # passed kwarg overrides config
|
if ATTR_TRANSITION in kwargs: # passed kwarg overrides config
|
||||||
duration = int(kwargs.get(ATTR_TRANSITION) * 1000) # kwarg in s
|
duration = int(kwargs.get(ATTR_TRANSITION) * 1000) # kwarg in s
|
||||||
|
|
||||||
self.device.turn_on(duration=duration)
|
self.device.turn_on(duration=duration, light_type=self.light_type)
|
||||||
|
|
||||||
if self.config[CONF_MODE_MUSIC] and not self._bulb.music_mode:
|
if self.config[CONF_MODE_MUSIC] and not self._bulb.music_mode:
|
||||||
try:
|
try:
|
||||||
|
@ -502,7 +534,7 @@ class YeelightLight(Light):
|
||||||
if ATTR_TRANSITION in kwargs: # passed kwarg overrides config
|
if ATTR_TRANSITION in kwargs: # passed kwarg overrides config
|
||||||
duration = int(kwargs.get(ATTR_TRANSITION) * 1000) # kwarg in s
|
duration = int(kwargs.get(ATTR_TRANSITION) * 1000) # kwarg in s
|
||||||
|
|
||||||
self.device.turn_off(duration=duration)
|
self.device.turn_off(duration=duration, light_type=self.light_type)
|
||||||
self.device.update()
|
self.device.update()
|
||||||
|
|
||||||
def set_mode(self, mode: str):
|
def set_mode(self, mode: str):
|
||||||
|
@ -525,7 +557,45 @@ class YeelightLight(Light):
|
||||||
action=yeelight.Flow.actions[action],
|
action=yeelight.Flow.actions[action],
|
||||||
transitions=transitions)
|
transitions=transitions)
|
||||||
|
|
||||||
self._bulb.start_flow(flow)
|
self._bulb.start_flow(flow, light_type=self.light_type)
|
||||||
self.device.update()
|
self.device.update()
|
||||||
except yeelight.BulbException as ex:
|
except yeelight.BulbException as ex:
|
||||||
_LOGGER.error("Unable to set effect: %s", ex)
|
_LOGGER.error("Unable to set effect: %s", ex)
|
||||||
|
|
||||||
|
|
||||||
|
class YeelightAmbientLight(YeelightLight):
|
||||||
|
"""Representation of a Yeelight ambient light."""
|
||||||
|
|
||||||
|
PROPERTIES_MAPPING = {
|
||||||
|
"color_mode": "bg_lmode",
|
||||||
|
"main_power": "bg_power",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Initialize the Yeelight Ambient light."""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self._min_mireds = kelvin_to_mired(6500)
|
||||||
|
self._max_mireds = kelvin_to_mired(1700)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Return the name of the device if any."""
|
||||||
|
return "{} ambilight".format(self.device.name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def light_type(self):
|
||||||
|
"""Return light type."""
|
||||||
|
import yeelight
|
||||||
|
return yeelight.enums.LightType.Ambient
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _is_nightlight_enabled(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_property(self, prop, default=None):
|
||||||
|
bg_prop = self.PROPERTIES_MAPPING.get(prop)
|
||||||
|
|
||||||
|
if not bg_prop:
|
||||||
|
bg_prop = "bg_" + prop
|
||||||
|
|
||||||
|
return self._properties.get(bg_prop, default)
|
||||||
|
|
Loading…
Reference in New Issue