-
-
Notifications
You must be signed in to change notification settings - Fork 37.9k
Hyperion: Add brightness, HDMI and effect support #11543
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
d869e44
aab5b8b
3a975b2
b9c44fd
192f6b9
b46bb39
4061922
d1e5b2a
6da5939
e0e39c7
687fd67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
|
|
@@ -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]), | ||
| }) | ||
|
|
||
|
|
||
|
|
@@ -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]) | ||
|
|
@@ -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] | ||
| self._brightness = 255 | ||
| self._icon = 'mdi:lightbulb' | ||
| self._effect_list = effect_list | ||
| self._effect = 'none' | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in new commit |
||
| self._skip_check = False | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename this to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.""" | ||
|
|
@@ -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.""" | ||
|
|
@@ -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] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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': | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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' | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I disagree, see above: effect special case. |
||
| self._brightness = 255 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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'] != []: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 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' | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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' | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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' | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.""" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why you need this?
There was a problem hiding this comment.
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.