* Hyperion: Add brightness, HDMI and effect support - added brightness support to dim the hyperion light - changed the "OFF" command to set the color to [0,0,0] after clearing all priorities. This is neccesary to keep the light turned off when an HDMI grabber is used for ambilight with hyperion. - added HDMI ambilight mode recognition and control. by setting the "hdmi_priority" in your "configuration.yaml" file (defaults to 880), home assistant will now be able to recognize when the hyperion light is in HDMI ambilight mode and will change its icon to an HDMI symbol and set the status to ON. Switching the hyperion light to HDMI ambilight mode can be done through the effect option (clears all priorities such that the HDMI grabber remains). - added effect support for the default effects of hyperion, a custom list can be defined in the "configuration.yaml" file by using the "effect_list" option. * Hyperion: Add brightness, HDMI and effect support - added brightness support to dim the hyperion light - changed the "OFF" command to set the color to [0,0,0] after clearing all priorities. This is neccesary to keep the light turned off when an HDMI grabber is used for ambilight with hyperion. - added HDMI ambilight mode recognition and control. by setting the "hdmi_priority" in your "configuration.yaml" file (defaults to 880), home assistant will now be able to recognize when the hyperion light is in HDMI ambilight mode and will change its icon to an HDMI symbol and set the status to ON. Switching the hyperion light to HDMI ambilight mode can be done through the effect option (clears all priorities such that the HDMI grabber remains). - added effect support for the default effects of hyperion, a custom list can be defined in the "configuration.yaml" file by using the "effect_list" option. - fixed some style issues with too long lines * Hyperion: Add brightness, HDMI and effect support - fixed some more indentation style issues * Hyperion: Add brightness, HDMI and effect support - yet more fixed visuel indent issues * Hyperion: Add brightness, HDMI and effect support - more visuel indents * Hyperion: Add brightness, HDMI and effect support - fixed invalid variable "A" * Hyperion: Add brightness, HDMI and effect support - remove unnececary brackets - specify specific exceptions * correct changing state holding attributes during a service method Proccesed the comments of @MartinHjelmare: https://github.com/home-assistant/home-assistant/pull/11543#pullrequestreview-88328659 * indent correction corrected tab instead of 4 spaces * Hyperion: Add brightness, HDMI and effect support - changed 'none' to None - renamed "self._skip_check" to "self._skip_update" * Add brightness, HDMI and effect support changed checking if a list is empty from "list == []" to "not list"
287 lines
9.9 KiB
Python
287 lines
9.9 KiB
Python
"""
|
|
Support for Hyperion remotes.
|
|
|
|
For more details about this platform, please refer to the documentation at
|
|
https://home-assistant.io/components/light.hyperion/
|
|
"""
|
|
import json
|
|
import logging
|
|
import socket
|
|
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.light import (
|
|
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_BRIGHTNESS | SUPPORT_EFFECT)
|
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|
vol.Required(CONF_HOST): cv.string,
|
|
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
|
vol.Optional(CONF_DEFAULT_COLOR, default=DEFAULT_COLOR):
|
|
vol.All(list, vol.Length(min=3, max=3),
|
|
[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]),
|
|
})
|
|
|
|
|
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
|
"""Set up a Hyperion server remote."""
|
|
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, hdmi_priority, effect_list)
|
|
|
|
if device.setup():
|
|
add_devices([device])
|
|
return True
|
|
return False
|
|
|
|
|
|
class Hyperion(Light):
|
|
"""Representation of a Hyperion remote."""
|
|
|
|
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
|
|
self._skip_update = False
|
|
|
|
@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."""
|
|
return self._rgb_color
|
|
|
|
@property
|
|
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."""
|
|
return SUPPORT_HYPERION
|
|
|
|
def turn_on(self, **kwargs):
|
|
"""Turn the lights on."""
|
|
if ATTR_RGB_COLOR in kwargs:
|
|
rgb_color = kwargs[ATTR_RGB_COLOR]
|
|
elif self._rgb_mem == [0, 0, 0]:
|
|
rgb_color = self._default_color
|
|
else:
|
|
rgb_color = self._rgb_mem
|
|
|
|
if ATTR_BRIGHTNESS in kwargs:
|
|
brightness = kwargs[ATTR_BRIGHTNESS]
|
|
|
|
if ATTR_EFFECT in kwargs:
|
|
self._skip_update = True
|
|
self._effect = kwargs[ATTR_EFFECT]
|
|
if self._effect == 'HDMI':
|
|
self.json_request({'command': 'clearall'})
|
|
self._icon = 'mdi:video-input-hdmi'
|
|
self._brightness = 255
|
|
self._rgb_color = [125, 125, 125]
|
|
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': cal_color
|
|
})
|
|
|
|
def turn_off(self, **kwargs):
|
|
"""Disconnect all remotes."""
|
|
self.json_request({'command': 'clearall'})
|
|
self.json_request({
|
|
'command': 'color',
|
|
'priority': self._priority,
|
|
'color': [0, 0, 0]
|
|
})
|
|
|
|
def update(self):
|
|
"""Get the lights status."""
|
|
# postpone the immediate state check for changes that take time
|
|
if self._skip_update:
|
|
self._skip_update = 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 not response['info']['activeLedColor']:
|
|
# Get the active effect
|
|
if response['info']['activeEffects']:
|
|
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
|
|
# Bulb off state
|
|
else:
|
|
self._rgb_color = [0, 0, 0]
|
|
self._icon = 'mdi:lightbulb'
|
|
self._effect = None
|
|
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
|
|
|
|
def setup(self):
|
|
"""Get the hostname of the remote."""
|
|
response = self.json_request({'command': 'serverinfo'})
|
|
if response:
|
|
if self._name == self._host:
|
|
self._name = response['info']['hostname']
|
|
return True
|
|
return False
|
|
|
|
def json_request(self, request, wait_for_response=False):
|
|
"""Communicate with the JSON server."""
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
sock.settimeout(5)
|
|
|
|
try:
|
|
sock.connect((self._host, self._port))
|
|
except OSError:
|
|
sock.close()
|
|
return False
|
|
|
|
sock.send(bytearray(json.dumps(request) + '\n', 'utf-8'))
|
|
try:
|
|
buf = sock.recv(4096)
|
|
except socket.timeout:
|
|
# Something is wrong, assume it's offline
|
|
sock.close()
|
|
return False
|
|
|
|
# Read until a newline or timeout
|
|
buffering = True
|
|
while buffering:
|
|
if '\n' in str(buf, 'utf-8'):
|
|
response = str(buf, 'utf-8').split('\n')[0]
|
|
buffering = False
|
|
else:
|
|
try:
|
|
more = sock.recv(4096)
|
|
except socket.timeout:
|
|
more = None
|
|
if not more:
|
|
buffering = False
|
|
response = str(buf, 'utf-8')
|
|
else:
|
|
buf += more
|
|
|
|
sock.close()
|
|
return json.loads(response)
|