Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 118 additions & 42 deletions homeassistant/components/light/osramlightify.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.osramlightify/
"""
import sys

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'sys' imported but unused

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

import logging
import socket
import random
Expand All @@ -15,33 +16,40 @@
from homeassistant.const import CONF_HOST
from homeassistant.components.light import (
Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_RGB_COLOR,
ATTR_TRANSITION, EFFECT_RANDOM, SUPPORT_BRIGHTNESS, SUPPORT_EFFECT,
SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, PLATFORM_SCHEMA)
ATTR_XY_COLOR, ATTR_TRANSITION, EFFECT_RANDOM, SUPPORT_BRIGHTNESS,
SUPPORT_EFFECT, SUPPORT_XY_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR,
SUPPORT_TRANSITION, PLATFORM_SCHEMA)
from homeassistant.util.color import (
color_temperature_mired_to_kelvin, color_temperature_kelvin_to_mired)
color_temperature_mired_to_kelvin, color_temperature_kelvin_to_mired,
color_xy_brightness_to_RGB)
import homeassistant.helpers.config_validation as cv

REQUIREMENTS = ['https://github.com/tfriedel/python-lightify/archive/'
'd6eadcf311e6e21746182d1480e97b350dda2b3e.zip#lightify==1.0.4']
'1bb1db0e7bd5b14304d7bb267e2398cd5160df46.zip#lightify==1.0.5']

_LOGGER = logging.getLogger(__name__)

MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
CONF_ALLOW_LIGHTIFY_GROUPS = "allow_lightify_groups"
DEFAULT_ALLOW_LIGHTIFY_GROUPS = True

SUPPORT_OSRAMLIGHTIFY = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP |
SUPPORT_EFFECT | SUPPORT_RGB_COLOR |
SUPPORT_TRANSITION)
SUPPORT_TRANSITION | SUPPORT_XY_COLOR)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_ALLOW_LIGHTIFY_GROUPS,
default=DEFAULT_ALLOW_LIGHTIFY_GROUPS): cv.boolean,
})


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Osram Lightify lights."""
import lightify
host = config.get(CONF_HOST)
add_groups = config.get(CONF_ALLOW_LIGHTIFY_GROUPS)
if host:
try:
bridge = lightify.Lightify(host)
Expand All @@ -50,20 +58,26 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
str(err))
_LOGGER.exception(msg)
return False
setup_bridge(bridge, add_devices)
setup_bridge(bridge, add_devices, add_groups)
else:
_LOGGER.error('No host found in configuration')
return False


def setup_bridge(bridge, add_devices_callback):
def setup_bridge(bridge, add_devices_callback, add_groups):
"""Setup the Lightify bridge."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remove it, the DEFAULT_ALLOW_LIGHTIFY_GROUPS = True config becomes useless.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

never mind

lights = {}

@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_lights():
"""Update the lights objects with latest info from bridge."""
bridge.update_all_light_status()
try:
bridge.update_all_light_status()
bridge.update_group_list()
except TimeoutError:
_LOGGER.error('Timeout during updating of lights.')
except OSError:
_LOGGER.error('OSError during updating of lights.')

new_lights = []

Expand All @@ -77,22 +91,31 @@ def update_lights():
else:
lights[light_id].light = light

if add_groups:
for (group_name, group) in bridge.groups().items():
if group_name not in lights:
osram_group = OsramLightifyGroup(group, bridge,
update_lights)
lights[group_name] = osram_group
new_lights.append(osram_group)
else:
lights[group_name].group = group

if new_lights:
add_devices_callback(new_lights)

update_lights()


class OsramLightifyLight(Light):
"""Representation of an Osram Lightify Light."""
class Luminary(Light):
"""ABS for Lightify Lights and Groups."""

def __init__(self, light_id, light, update_lights):
"""Initialize the light."""
self._light = light
self._light_id = light_id
def __init__(self, luminary, update_lights):
"""Init Luminary object."""
self.update_lights = update_lights
self._luminary = luminary
self._brightness = None
self._rgb = None
self._rgb = [None]
self._name = None
self._temperature = None
self._state = False
Expand All @@ -106,8 +129,6 @@ def name(self):
@property
def rgb_color(self):
"""Last RGB color value set."""
_LOGGER.debug("rgb_color light state for light: %s is: %s %s %s ",
self._name, self._rgb[0], self._rgb[1], self._rgb[2])
return self._rgb

@property
Expand All @@ -118,29 +139,27 @@ def color_temp(self):
@property
def brightness(self):
"""Brightness of this light between 0..255."""
_LOGGER.debug("brightness for light %s is: %s",
self._name, self._brightness)
return self._brightness

@property
def is_on(self):
"""Update Status to True if device is on."""
_LOGGER.debug("is_on light state for light: %s is: %s",
self._name, self._state)
return self._state

@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_OSRAMLIGHTIFY

@property
def effect_list(self):
"""List of supported effects."""
return [EFFECT_RANDOM]

def turn_on(self, **kwargs):
"""Turn the device on."""
_LOGGER.debug("turn_on Attempting to turn on light: %s ",
self._name)

self._light.set_onoff(1)
self._state = self._light.on()
self._luminary.set_onoff(1)
self._state = True

if ATTR_TRANSITION in kwargs:
transition = int(kwargs[ATTR_TRANSITION] * 10)
Expand All @@ -158,30 +177,41 @@ def turn_on(self, **kwargs):
_LOGGER.debug("turn_on requested ATTR_RGB_COLOR for light:"
" %s is: %s %s %s ",
self._name, red, green, blue)
self._light.set_rgb(red, green, blue, transition)
self._luminary.set_rgb(red, green, blue, transition)

if ATTR_XY_COLOR in kwargs:
x_mired, y_mired = kwargs[ATTR_XY_COLOR]
_LOGGER.debug("turn_on requested ATTR_XY_COLOR for light:"
" %s is: %s,%s", self._name, x_mired, y_mired)
red, green, blue = color_xy_brightness_to_RGB(
x_mired, y_mired, self._brightness
)
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))
_LOGGER.debug("turn_on requested set_temperature for light:"
" %s: %s ", self._name, kelvin)
self._light.set_temperature(kelvin, transition)
self._luminary.set_temperature(kelvin, transition)

if ATTR_BRIGHTNESS in kwargs:
self._brightness = kwargs[ATTR_BRIGHTNESS]
_LOGGER.debug("turn_on requested brightness for light: %s is: %s ",
self._name, self._brightness)
self._brightness = self._light.set_luminance(
self._brightness = self._luminary.set_luminance(
int(self._brightness / 2.55),
transition)

if ATTR_EFFECT in kwargs:
effect = kwargs.get(ATTR_EFFECT)
if effect == EFFECT_RANDOM:
self._light.set_rgb(random.randrange(0, 255),
random.randrange(0, 255),
random.randrange(0, 255),
transition)
self._luminary.set_rgb(
random.randrange(0, 255),
random.randrange(0, 255),
random.randrange(0, 255),
transition
)
_LOGGER.debug("turn_on requested random effect for light:"
" %s with transition %s ",
self._name, transition)
Expand All @@ -197,26 +227,72 @@ def turn_off(self, **kwargs):
_LOGGER.debug("turn_off requested transition time for light:"
" %s is: %s ",
self._name, transition)
self._light.set_luminance(0, transition)
self._luminary.set_luminance(0, transition)
else:
transition = 0
_LOGGER.debug("turn_off requested transition time for light:"
" %s is: %s ",
self._name, transition)
self._light.set_onoff(0)
self._state = self._light.on()

self._luminary.set_onoff(0)
self._state = False
self.schedule_update_ha_state()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove this line? it's not needed since should_poll is defaulting to True and so this is automatically called.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean the self._state = False ?
Well yes I can remove it, but I saw, that without it, HA gets a lot more unresponsive, because otherwise it the GUI jumps back to the on state and then after a couple of seconds it jumps back to off. If that is okay, I'm willing to remove it, I just felt it works better with.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not something that we should fix in a platform but instead fix for everyone at once. We already wait 2 seconds for a status update: https://github.com/home-assistant/home-assistant-polymer/blob/master/src/components/entity/ha-entity-toggle.html#L135-L136

Please remove this line.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, done


def update(self):
"""Synchronize state with bridge."""
self.update_lights(no_throttle=True)
self._brightness = int(self._light.lum() * 2.55)
self._name = self._light.name()
self._rgb = self._light.rgb()
o_temp = self._light.temp()
self._name = self._luminary.name()


class OsramLightifyLight(Luminary):
"""Representation of an Osram Lightify Light."""

def __init__(self, light_id, light, update_lights):
"""Initialize the 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()
self._rgb = self._luminary.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)


class OsramLightifyGroup(Luminary):
"""Representation of an Osram Lightify Group."""

def __init__(self, group, bridge, update_lights):
"""Init light group."""
self._bridge = bridge
self._light_ids = []
super().__init__(group, update_lights)

def _get_state(self):
"""Get state of group.

The group is on, if any of the lights in on.
"""
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()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is self._luminiary and is it only used to fetch the lights?

I think that things are getting very complicated because of the inheritance.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that things are getting very complicated because of the inheritance.

I first stared out without, but my code became 90% redundant and so I splittend i t up. The only difference between the two is the update and get_state method.

What is self._luminiary and is it only used to fetch the lights?

Well its the main interface to the lightify API. Essentially all communication with the lights goes through the self._luminary obj. It is called like this, because it is the name of the class within the lightify API itself. I use it, because it is the common ground between lights and groups of lights just like throughout this module as well.

light = self._bridge.lights()[self._light_ids[0]]
self._brightness = int(light.lum() * 2.55)
self._rgb = light.rgb()
o_temp = light.temp()
if o_temp == 0:
self._temperature = None
else:
self._temperature = color_temperature_kelvin_to_mired(o_temp)
self._state = self._light.on()
self._state = light.on()
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ https://github.com/sander76/powerviewApi/archive/246e782d60d5c0addcc98d7899a0186
https://github.com/soldag/pyflic/archive/0.4.zip#pyflic==0.4

# homeassistant.components.light.osramlightify
https://github.com/tfriedel/python-lightify/archive/d6eadcf311e6e21746182d1480e97b350dda2b3e.zip#lightify==1.0.4
https://github.com/tfriedel/python-lightify/archive/1bb1db0e7bd5b14304d7bb267e2398cd5160df46.zip#lightify==1.0.5

# homeassistant.components.lutron
https://github.com/thecynic/pylutron/archive/v0.1.0.zip#pylutron==0.1.0
Expand Down