Rewrite Osram Lightify component (#22184)

* Rewrite Osram Lightify component

* Update python-lightify version to 1.0.7.2

* Remove unneeded code

* 1. Remove changes in light/__init__.py, 2. Set properties to None by default

* Fix typo
This commit is contained in:
OleksandrBerchenko 2019-03-31 22:02:45 +03:00 committed by Anders Melchiorsen
parent 1b0b5b4b8c
commit 842a36dc9e
2 changed files with 246 additions and 170 deletions

View File

@ -4,41 +4,35 @@ Support for Osram Lightify.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.osramlightify/ https://home-assistant.io/components/light.osramlightify/
""" """
from datetime import timedelta
import logging import logging
import random import random
import socket import socket
import voluptuous as vol import voluptuous as vol
from homeassistant import util
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_HS_COLOR, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION,
ATTR_TRANSITION, EFFECT_RANDOM, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, ATTR_EFFECT, EFFECT_RANDOM, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS,
SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_TRANSITION, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_TRANSITION,
Light) Light)
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.util.color import (
color_temperature_kelvin_to_mired, color_temperature_mired_to_kelvin)
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
REQUIREMENTS = ['lightify==1.0.6.1'] REQUIREMENTS = ['lightify==1.0.7.2']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_ALLOW_LIGHTIFY_NODES = 'allow_lightify_nodes' CONF_ALLOW_LIGHTIFY_NODES = 'allow_lightify_nodes'
CONF_ALLOW_LIGHTIFY_GROUPS = 'allow_lightify_groups' CONF_ALLOW_LIGHTIFY_GROUPS = 'allow_lightify_groups'
CONF_INTERVAL_LIGHTIFY_STATUS = 'interval_lightify_status'
CONF_INTERVAL_LIGHTIFY_CONF = 'interval_lightify_conf'
DEFAULT_ALLOW_LIGHTIFY_NODES = True DEFAULT_ALLOW_LIGHTIFY_NODES = True
DEFAULT_ALLOW_LIGHTIFY_GROUPS = True DEFAULT_ALLOW_LIGHTIFY_GROUPS = True
DEFAULT_INTERVAL_LIGHTIFY_STATUS = 5
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) DEFAULT_INTERVAL_LIGHTIFY_CONF = 3600
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
SUPPORT_OSRAMLIGHTIFY = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP |
SUPPORT_EFFECT | SUPPORT_COLOR |
SUPPORT_TRANSITION)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
@ -46,228 +40,310 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
default=DEFAULT_ALLOW_LIGHTIFY_NODES): cv.boolean, default=DEFAULT_ALLOW_LIGHTIFY_NODES): cv.boolean,
vol.Optional(CONF_ALLOW_LIGHTIFY_GROUPS, vol.Optional(CONF_ALLOW_LIGHTIFY_GROUPS,
default=DEFAULT_ALLOW_LIGHTIFY_GROUPS): cv.boolean, default=DEFAULT_ALLOW_LIGHTIFY_GROUPS): cv.boolean,
vol.Optional(CONF_INTERVAL_LIGHTIFY_STATUS,
default=DEFAULT_INTERVAL_LIGHTIFY_STATUS): cv.positive_int,
vol.Optional(CONF_INTERVAL_LIGHTIFY_CONF,
default=DEFAULT_INTERVAL_LIGHTIFY_CONF): cv.positive_int
}) })
DEFAULT_BRIGHTNESS = 2
DEFAULT_KELVIN = 2700
def setup_platform(hass, config, add_entities, discovery_info=None):
def setup_platform(_hass, config, add_entities, _discovery_info=None):
"""Set up the Osram Lightify lights.""" """Set up the Osram Lightify lights."""
import lightify import lightify
host = config.get(CONF_HOST) host = config[CONF_HOST]
add_nodes = config.get(CONF_ALLOW_LIGHTIFY_NODES)
add_groups = config.get(CONF_ALLOW_LIGHTIFY_GROUPS)
try: try:
bridge = lightify.Lightify(host) bridge = lightify.Lightify(host, log_level=logging.NOTSET)
except socket.error as err: except socket.error as err:
msg = "Error connecting to bridge: {} due to: {}".format( msg = "Error connecting to bridge: {} due to: {}".format(
host, str(err)) host, str(err))
_LOGGER.exception(msg) _LOGGER.exception(msg)
return return
setup_bridge(bridge, add_entities, add_nodes, add_groups) setup_bridge(bridge, add_entities, config)
def setup_bridge(bridge, add_entities, add_nodes, add_groups): def setup_bridge(bridge, add_entities, config):
"""Set up the Lightify bridge.""" """Set up the Lightify bridge."""
lights = {} lights = {}
groups = {}
groups_last_updated = [0]
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_lights(): def update_lights():
"""Update the lights objects with latest info from bridge.""" """Update the lights objects with the latest info from the bridge."""
try: try:
bridge.update_all_light_status() new_lights = bridge.update_all_light_status(
bridge.update_group_list() config[CONF_INTERVAL_LIGHTIFY_STATUS])
lights_changed = bridge.lights_changed()
except TimeoutError: except TimeoutError:
_LOGGER.error("Timeout during updating of lights") _LOGGER.error("Timeout during updating of lights")
return 0
except OSError: except OSError:
_LOGGER.error("OSError during updating of lights") _LOGGER.error("OSError during updating of lights")
return 0
new_lights = [] if new_lights and config[CONF_ALLOW_LIGHTIFY_NODES]:
new_entities = []
if add_nodes: for addr, light in new_lights.items():
for (light_id, light) in bridge.lights().items(): if addr not in lights:
if light_id not in lights: osram_light = OsramLightifyLight(light, update_lights,
osram_light = OsramLightifyLight( lights_changed)
light_id, light, update_lights) lights[addr] = osram_light
lights[light_id] = osram_light new_entities.append(osram_light)
new_lights.append(osram_light)
else: else:
lights[light_id].light = light lights[addr].update_luminary(light)
if add_groups: add_entities(new_entities)
for (group_name, group) in bridge.groups().items():
if group_name not in lights: return lights_changed
osram_group = OsramLightifyGroup(
group, bridge, update_lights) def update_groups():
lights[group_name] = osram_group """Update the groups objects with the latest info from the bridge."""
new_lights.append(osram_group) lights_changed = update_lights()
try:
new_groups = bridge.update_group_list(
config[CONF_INTERVAL_LIGHTIFY_CONF])
groups_updated = bridge.groups_updated()
except TimeoutError:
_LOGGER.error("Timeout during updating of groups")
return 0
except OSError:
_LOGGER.error("OSError during updating of groups")
return 0
if new_groups:
new_groups = {group.idx(): group for group in new_groups.values()}
new_entities = []
for idx, group in new_groups.items():
if idx not in groups:
osram_group = OsramLightifyGroup(group, update_groups,
groups_updated)
groups[idx] = osram_group
new_entities.append(osram_group)
else: else:
lights[group_name].group = group groups[idx].update_luminary(group)
if new_lights: add_entities(new_entities)
add_entities(new_lights)
if groups_updated > groups_last_updated[0]:
groups_last_updated[0] = groups_updated
for idx, osram_group in groups.items():
if idx not in new_groups:
osram_group.update_static_attributes()
return max(lights_changed, groups_updated)
update_lights() update_lights()
if config[CONF_ALLOW_LIGHTIFY_GROUPS]:
update_groups()
class Luminary(Light): class Luminary(Light):
"""Representation of Luminary Lights and Groups.""" """Representation of Luminary Lights and Groups."""
def __init__(self, luminary, update_lights): def __init__(self, luminary, update_func, changed):
"""Initialize a Luminary light.""" """Initialize a Luminary Light."""
self.update_lights = update_lights self.update_func = update_func
self._luminary = luminary self._luminary = luminary
self._changed = changed
self._unique_id = None
self._supported_features = []
self._effect_list = []
self._is_on = False
self._min_mireds = None
self._max_mireds = None
self._brightness = None self._brightness = None
self._hs = None self._color_temp = None
self._name = None self._rgb_color = None
self._temperature = None
self._state = False self.update_static_attributes()
self.update() self.update_dynamic_attributes()
def _get_unique_id(self):
"""Get a unique ID (not implemented)."""
raise NotImplementedError
def _get_supported_features(self):
"""Get list of supported features."""
features = 0
if 'lum' in self._luminary.supported_features():
features = features | SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION
if 'temp' in self._luminary.supported_features():
features = features | SUPPORT_COLOR_TEMP | SUPPORT_TRANSITION
if 'rgb' in self._luminary.supported_features():
features = (features | SUPPORT_COLOR | SUPPORT_TRANSITION |
SUPPORT_EFFECT)
return features
def _get_effect_list(self):
"""Get list of supported effects."""
effects = []
if 'rgb' in self._luminary.supported_features():
effects.append(EFFECT_RANDOM)
return effects
@property @property
def name(self): def name(self):
"""Return the name of the device if any.""" """Return the name of the luminary."""
return self._name return self._luminary.name()
@property @property
def hs_color(self): def hs_color(self):
"""Last hs color value set.""" """Return last hs color value set."""
return self._hs return color_util.color_RGB_to_hs(*self._rgb_color)
@property @property
def color_temp(self): def color_temp(self):
"""Return the color temperature.""" """Return the color temperature."""
return self._temperature return self._color_temp
@property @property
def brightness(self): def brightness(self):
"""Brightness of this light between 0..255.""" """Return brightness of the luminary (0..255)."""
return self._brightness return self._brightness
@property @property
def is_on(self): def is_on(self):
"""Update status to True if device is on.""" """Return True if the device is on."""
return self._state return self._is_on
@property @property
def supported_features(self): def supported_features(self):
"""Flag supported features.""" """List of supported features."""
return SUPPORT_OSRAMLIGHTIFY return self._supported_features
@property @property
def effect_list(self): def effect_list(self):
"""List of supported effects.""" """List of supported effects."""
return [EFFECT_RANDOM] return self._effect_list
def turn_on(self, **kwargs):
"""Turn the device on."""
if ATTR_TRANSITION in kwargs:
transition = int(kwargs[ATTR_TRANSITION] * 10)
else:
transition = 0
if ATTR_BRIGHTNESS in kwargs:
self._brightness = kwargs[ATTR_BRIGHTNESS]
self._luminary.set_luminance(
int(self._brightness / 2.55), transition)
else:
self._luminary.set_onoff(1)
if ATTR_HS_COLOR in kwargs:
red, green, blue = \
color_util.color_hs_to_RGB(*kwargs[ATTR_HS_COLOR])
self._luminary.set_rgb(red, green, blue, transition)
if ATTR_COLOR_TEMP in kwargs:
color_t = kwargs[ATTR_COLOR_TEMP]
kelvin = int(color_temperature_mired_to_kelvin(color_t))
self._luminary.set_temperature(kelvin, transition)
if ATTR_EFFECT in kwargs:
effect = kwargs.get(ATTR_EFFECT)
if effect == EFFECT_RANDOM:
self._luminary.set_rgb(
random.randrange(0, 255), random.randrange(0, 255),
random.randrange(0, 255), transition)
self.schedule_update_ha_state()
def turn_off(self, **kwargs):
"""Turn the device off."""
if ATTR_TRANSITION in kwargs:
transition = int(kwargs[ATTR_TRANSITION] * 10)
self._luminary.set_luminance(0, transition)
else:
transition = 0
self._luminary.set_onoff(0)
self.schedule_update_ha_state()
def update(self):
"""Synchronize state with bridge."""
self.update_lights(no_throttle=True)
self._name = self._luminary.name()
class OsramLightifyLight(Luminary):
"""Representation of an Osram Lightify Light."""
def __init__(self, light_id, light, update_lights):
"""Initialize the Lightify light."""
self._light_id = light_id
super().__init__(light, update_lights)
def update(self):
"""Update status of a light."""
super().update()
self._state = self._luminary.on()
rgb = self._luminary.rgb()
self._hs = color_util.color_RGB_to_hs(*rgb)
o_temp = self._luminary.temp()
if o_temp == 0:
self._temperature = None
else:
self._temperature = color_temperature_kelvin_to_mired(
self._luminary.temp())
self._brightness = int(self._luminary.lum() * 2.55)
@property @property
def unique_id(self): def min_mireds(self):
"""Return a unique ID.""" """Return the coldest color_temp that this light supports."""
return self._light_id return self._min_mireds
@property
class OsramLightifyGroup(Luminary): def max_mireds(self):
"""Representation of an Osram Lightify Group.""" """Return the warmest color_temp that this light supports."""
return self._max_mireds
def __init__(self, group, bridge, update_lights):
"""Initialize the Lightify light group."""
self._bridge = bridge
self._light_ids = []
super().__init__(group, update_lights)
self._unique_id = '{}'.format(self._light_ids)
def _get_state(self):
"""Get state of group."""
lights = self._bridge.lights()
return any(lights[light_id].on() for light_id in self._light_ids)
def update(self):
"""Update group status."""
super().update()
self._light_ids = self._luminary.lights()
light = self._bridge.lights()[self._light_ids[0]]
self._brightness = int(light.lum() * 2.55)
rgb = light.rgb()
self._hs = color_util.color_RGB_to_hs(*rgb)
o_temp = light.temp()
if o_temp == 0:
self._temperature = None
else:
self._temperature = color_temperature_kelvin_to_mired(o_temp)
self._state = light.on()
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique ID.""" """Return a unique ID."""
return self._unique_id return self._unique_id
def play_effect(self, effect, transition):
"""Play selected effect."""
if effect == EFFECT_RANDOM:
self._rgb_color = (random.randrange(0, 256),
random.randrange(0, 256),
random.randrange(0, 256))
self._luminary.set_rgb(*self._rgb_color, transition)
self._luminary.set_onoff(True)
return True
return False
def turn_on(self, **kwargs):
"""Turn the device on."""
transition = int(kwargs.get(ATTR_TRANSITION, 0) * 10)
if ATTR_EFFECT in kwargs:
self.play_effect(kwargs[ATTR_EFFECT], transition)
return
if ATTR_HS_COLOR in kwargs:
self._rgb_color = color_util.color_hs_to_RGB(
*kwargs[ATTR_HS_COLOR])
self._luminary.set_rgb(*self._rgb_color, transition)
if ATTR_COLOR_TEMP in kwargs:
self._color_temp = kwargs[ATTR_COLOR_TEMP]
self._luminary.set_temperature(
int(color_util.color_temperature_mired_to_kelvin(
self._color_temp)), transition)
self._is_on = True
if ATTR_BRIGHTNESS in kwargs:
self._brightness = kwargs[ATTR_BRIGHTNESS]
self._luminary.set_luminance(int(self._brightness / 2.55),
transition)
else:
self._luminary.set_onoff(True)
def turn_off(self, **kwargs):
"""Turn the device off."""
self._is_on = False
if ATTR_TRANSITION in kwargs:
transition = int(kwargs[ATTR_TRANSITION] * 10)
self._brightness = DEFAULT_BRIGHTNESS
self._luminary.set_luminance(0, transition)
else:
self._luminary.set_onoff(False)
def update_luminary(self, luminary):
"""Update internal luminary object."""
self._luminary = luminary
self.update_static_attributes()
def update_static_attributes(self):
"""Update static attributes of the luminary."""
self._unique_id = self._get_unique_id()
self._supported_features = self._get_supported_features()
self._effect_list = self._get_effect_list()
if self._supported_features & SUPPORT_COLOR_TEMP:
self._min_mireds = color_util.color_temperature_kelvin_to_mired(
self._luminary.max_temp() or DEFAULT_KELVIN)
self._max_mireds = color_util.color_temperature_kelvin_to_mired(
self._luminary.min_temp() or DEFAULT_KELVIN)
def update_dynamic_attributes(self):
"""Update dynamic attributes of the luminary."""
self._is_on = self._luminary.on()
if self._supported_features & SUPPORT_BRIGHTNESS:
self._brightness = int(self._luminary.lum() * 2.55)
if self._supported_features & SUPPORT_COLOR_TEMP:
self._color_temp = color_util.color_temperature_kelvin_to_mired(
self._luminary.temp() or DEFAULT_KELVIN)
if self._supported_features & SUPPORT_COLOR:
self._rgb_color = self._luminary.rgb()
def update(self):
"""Synchronize state with bridge."""
changed = self.update_func()
if changed > self._changed:
self._changed = changed
self.update_dynamic_attributes()
class OsramLightifyLight(Luminary):
"""Representation of an Osram Lightify Light."""
def _get_unique_id(self):
"""Get a unique ID."""
return self._luminary.addr()
class OsramLightifyGroup(Luminary):
"""Representation of an Osram Lightify Group."""
def _get_unique_id(self):
"""Get a unique ID for the group."""
# Actually, it's a wrong choice for a unique ID, because a combination of
# lights is NOT unique (Osram Lightify allows to create different groups
# with the same lights). Also a combination of lights may easily change,
# but the group remains the same from the user's perspective.
# It should be something like "<gateway host>-<group.idx()>"
# For now keeping it as is for backward compatibility with existing
# users.
return '{}'.format(self._luminary.lights())

View File

@ -643,7 +643,7 @@ libsoundtouch==0.7.2
liffylights==0.9.4 liffylights==0.9.4
# homeassistant.components.osramlightify.light # homeassistant.components.osramlightify.light
lightify==1.0.6.1 lightify==1.0.7.2
# homeassistant.components.lightwave # homeassistant.components.lightwave
lightwave==0.15 lightwave==0.15