Add support for yeelight ceiling ambilight (#22346)

pull/22463/head
zewelor 2019-03-27 13:39:55 +01:00 committed by Sebastian Muszynski
parent 6540114ec5
commit 4de2efd07f
2 changed files with 120 additions and 30 deletions

View File

@ -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

View File

@ -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)