Skip to content
Merged
139 changes: 129 additions & 10 deletions homeassistant/components/light/hyperion.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,36 @@
import voluptuous as vol

from homeassistant.components.light import (
ATTR_RGB_COLOR, SUPPORT_RGB_COLOR, Light, PLATFORM_SCHEMA)
ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_EFFECT, SUPPORT_BRIGHTNESS,
SUPPORT_RGB_COLOR, SUPPORT_EFFECT, Light, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_HOST, CONF_PORT, CONF_NAME)
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(__name__)

CONF_DEFAULT_COLOR = 'default_color'
CONF_PRIORITY = 'priority'
CONF_HDMI_PRIORITY = 'hdmi_priority'
CONF_EFFECT_LIST = 'effect_list'

DEFAULT_COLOR = [255, 255, 255]
DEFAULT_NAME = 'Hyperion'
DEFAULT_PORT = 19444
DEFAULT_PRIORITY = 128
DEFAULT_HDMI_PRIORITY = 880
DEFAULT_EFFECT_LIST = ['HDMI', 'Cinema brighten lights', 'Cinema dim lights',
'Knight rider', 'Blue mood blobs', 'Cold mood blobs',
'Full color mood blobs', 'Green mood blobs',
'Red mood blobs', 'Warm mood blobs',
'Police Lights Single', 'Police Lights Solid',
'Rainbow mood', 'Rainbow swirl fast',
'Rainbow swirl', 'Random', 'Running dots',
'System Shutdown', 'Snake', 'Sparks Color', 'Sparks',
'Strobe blue', 'Strobe Raspbmc', 'Strobe white',
'Color traces', 'UDP multicast listener',
'UDP listener', 'X-Mas']

SUPPORT_HYPERION = SUPPORT_RGB_COLOR
SUPPORT_HYPERION = (SUPPORT_RGB_COLOR | SUPPORT_BRIGHTNESS | SUPPORT_EFFECT)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
Expand All @@ -35,6 +50,11 @@
[vol.All(vol.Coerce(int), vol.Range(min=0, max=255))]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PRIORITY, default=DEFAULT_PRIORITY): cv.positive_int,
vol.Optional(CONF_HDMI_PRIORITY,
default=DEFAULT_HDMI_PRIORITY): cv.positive_int,
vol.Optional(CONF_EFFECT_LIST,
default=DEFAULT_EFFECT_LIST): vol.All(cv.ensure_list,
[cv.string]),
})


Expand All @@ -43,10 +63,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
priority = config.get(CONF_PRIORITY)
hdmi_priority = config.get(CONF_HDMI_PRIORITY)
default_color = config.get(CONF_DEFAULT_COLOR)
effect_list = config.get(CONF_EFFECT_LIST)

device = Hyperion(config.get(CONF_NAME), host, port, priority,
default_color)
default_color, hdmi_priority, effect_list)

if device.setup():
add_devices([device])
Expand All @@ -57,20 +79,33 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class Hyperion(Light):
"""Representation of a Hyperion remote."""

def __init__(self, name, host, port, priority, default_color):
def __init__(self, name, host, port, priority, default_color,
hdmi_priority, effect_list):
"""Initialize the light."""
self._host = host
self._port = port
self._name = name
self._priority = priority
self._hdmi_priority = hdmi_priority
self._default_color = default_color
self._rgb_color = [0, 0, 0]
self._rgb_mem = [0, 0, 0]

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.

why you need this?

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.

The "self._default_color" is used to set the color for the first time when hass is just booted. (after switching off again and switching on the color will be remembered and set to the previous value).
I changed this behaviour, in the previous (current) version the color is not remembered and after switching off and back on the color was reset to this default color.
I agree that this "self._default_color" is not perticularly nessesary because it is only used once after a reboot of hass, but I kept it because it was in the previous release and why not keep it.

The "self._rgb_color" and "self._rgb_mem" originate from the fact that Home Assistant and Hyperion use to diffrent definitions of RGB color. In Home Assistant an "RGB" color contains information about the Hue value (angle in the round color picker) and the saturation value (radius in the round color picker) but does not contain information about the brightness. Therefore in the HASS definition of "RGB" at least one of the values R, G or B is always 255 (exept when the light is off). The brightness is defined in a seperate value in home assistant.

However Hyperion uses the "true" RGB color definition: it contains the information about hue, saturation and brightness. Hyperion does not have any seprate brightness control. Therefore to set for instance a lower brighness pure blue color you simply sent [0,0,150] as RGB color.

It turns out Home Assistant misbehaved if i started to set self._rgb_color to values such as [0,0,150]. It wants a value of [0,0,255] for blue and then a brightness set to 150 to dim it. That is why I needed both values saved in memory. The "self._rgb_color" represents the HASS definition and "self._rgb_mem" is the Hyperion definition of RGB.

Furthermore when the light is turned off, the "self._rgb_color" is set to [0,0,0], therefore losing the information about the previous color. I wanted to store the previous color in memory "self._rgb_mem", so I can sent that color when the light is turend back on.

self._brightness = 255
self._icon = 'mdi:lightbulb'
self._effect_list = effect_list
self._effect = 'none'

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.

This should be None.

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 in new commit

self._skip_check = False

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.

Rename this to self._skip_update.

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 in new commit


@property
def name(self):
"""Return the name of the light."""
return self._name

@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
return self._brightness

@property
def rgb_color(self):
"""Return last RGB color value set."""
Expand All @@ -81,6 +116,21 @@ def is_on(self):
"""Return true if not black."""
return self._rgb_color != [0, 0, 0]

@property
def icon(self):
"""Return state specific icon."""
return self._icon

@property
def effect(self):
"""Return the current effect."""
return self._effect

@property
def effect_list(self):
"""Return the list of supported effects."""
return self._effect_list

@property
def supported_features(self):
"""Flag supported features."""
Expand All @@ -89,35 +139,104 @@ def supported_features(self):
def turn_on(self, **kwargs):
"""Turn the lights on."""
if ATTR_RGB_COLOR in kwargs:
self._rgb_color = kwargs[ATTR_RGB_COLOR]
rgb_color = kwargs[ATTR_RGB_COLOR]
elif self._rgb_mem == [0, 0, 0]:
rgb_color = self._default_color
else:
self._rgb_color = self._default_color
rgb_color = self._rgb_mem

if ATTR_BRIGHTNESS in kwargs:
brightness = kwargs[ATTR_BRIGHTNESS]

if ATTR_EFFECT in kwargs:
self._skip_check = True
self._effect = kwargs[ATTR_EFFECT]

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.

Same as above.

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.

The Effect case is special, Hyperion takes to long to activate an effect and update its internal status. Therefore I experianced that when you activate an effect, Hyperion will start activating that effect, but imediatly HASS will ask for an update of the status, and because Hyperion is not yet done with activating the effect it will return a json staing that Hyperion is OFF. in the second time an update is requested by HASS the correct json response will be sent by Hyperion and the state will display correctly.

Therfore HASS behaved as followed: you turn on an effect, HASS will imediatly show the light is turned off, and a few seconds later HASS will show the light is turned on again and the correct effect is displayed.

This is super anying, therfore I have used "self._skip_check = True", this will cause the first update HASS requests imidiatly after you select an effect to be aborted. The second and following update requests will be done as usual. If some strange thing happed, HASS will always correct the state in the second update request, but I have not seen that this was necassary.

To display the correct Effect in HASS imediatly after you select an effect I do need to update the self._ properties in the special case of effects.

if self._effect == 'HDMI':

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

indentation contains mixed spaces and tabs

self.json_request({'command': 'clearall'})
self._icon = 'mdi:video-input-hdmi'

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.

Same as above.

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 disagree, see above: effect special case.

self._brightness = 255

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.

Same as above.

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 disagree, see above: effect special case.

self._rgb_color = [125, 125, 125]

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.

Same as above.

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 disagree, see above: effect special case.

else:
self.json_request({
'command': 'effect',
'priority': self._priority,
'effect': {'name': self._effect}
})
self._icon = 'mdi:lava-lamp'
self._rgb_color = [175, 0, 255]
return

cal_color = [int(round(x*float(brightness)/255))
for x in rgb_color]
self.json_request({
'command': 'color',
'priority': self._priority,
'color': self._rgb_color
'color': cal_color
})

def turn_off(self, **kwargs):
"""Disconnect all remotes."""
self.json_request({'command': 'clearall'})
self._rgb_color = [0, 0, 0]
self.json_request({
'command': 'color',
'priority': self._priority,
'color': [0, 0, 0]
})

def update(self):
"""Get the remote's active color."""
"""Get the lights status."""
# postpone the immediate state check for changes that take time
if self._skip_check:
self._skip_check = False
return
response = self.json_request({'command': 'serverinfo'})
if response:
# workaround for outdated Hyperion
if 'activeLedColor' not in response['info']:
self._rgb_color = self._default_color
self._rgb_mem = self._default_color
self._brightness = 255
self._icon = 'mdi:lightbulb'
self._effect = 'none'
return
# Check if Hyperion is in ambilight mode trough an HDMI grabber
try:
active_priority = response['info']['priorities'][0]['priority']
if active_priority == self._hdmi_priority:
self._brightness = 255
self._rgb_color = [125, 125, 125]
self._icon = 'mdi:video-input-hdmi'
self._effect = 'HDMI'
return
except (KeyError, IndexError):
pass

if response['info']['activeLedColor'] == []:
self._rgb_color = [0, 0, 0]
# Get the active effect
if response['info']['activeEffects'] != []:

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's the aim of this check? Checking if it's not a list?

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.

the Json messages that HASS receives from Hyperion when the 'serverinfo' command is sent, contains a lot of information about the status of Hyperion in a dictonary.
It always contains the keys ['info']['activeEffects']. If Hyperion is not playing any effects like flashing the lights in all kind of ways or displaying rainbows etc. the ['info']['activeEffects'] contains an empty array: []. In that case the hyperion instance is doing something else like displaying a solid color (contained in the ['info']['activeLedColor'] key) or in ambilight mode (both ['info']['activeEffects'] and ['info']['activeLedColor'] are empty arrays and the ['info']['priorities'] key contains the specific priority for the HDMI grabber (or Kodi grabber)).

Therfore if the ['info']['activeEffects'] is not an empty array, it means that some kind of effect is playing. I will then try to retrive which effect is playing using the file-name of the script that is beeing played.

That check is thererefore ment to indicate if hyperion is playing an effect or not.

self._rgb_color = [175, 0, 255]
self._icon = 'mdi:lava-lamp'
try:
s_name = response['info']['activeEffects'][0]["script"]
s_name = s_name.split('/')[-1][:-3].split("-")[0]
self._effect = [x for x in self._effect_list
if s_name.lower() in x.lower()][0]
except (KeyError, IndexError):
self._effect = 'none'

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.

None

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 in new commit

# Bulb off state
else:
self._rgb_color = [0, 0, 0]
self._icon = 'mdi:lightbulb'
self._effect = 'none'

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.

Same as above.

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 in new commit

else:
# Get the RGB color
self._rgb_color =\
response['info']['activeLedColor'][0]['RGB Value']
self._brightness = max(self._rgb_color)
self._rgb_mem = [int(round(float(x)*255/self._brightness))
for x in self._rgb_color]
self._icon = 'mdi:lightbulb'
self._effect = 'none'

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.

Same as above.

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 in new commit


def setup(self):
"""Get the hostname of the remote."""
Expand Down