Fix/Refactor Hyperion Integration (#39738)
This commit is contained in:
parent
e06f2a89ea
commit
0a656f13eb
7 changed files with 711 additions and 209 deletions
|
@ -193,6 +193,7 @@ homeassistant/components/humidifier/* @home-assistant/core @Shulyaka
|
||||||
homeassistant/components/hunterdouglas_powerview/* @bdraco
|
homeassistant/components/hunterdouglas_powerview/* @bdraco
|
||||||
homeassistant/components/hvv_departures/* @vigonotion
|
homeassistant/components/hvv_departures/* @vigonotion
|
||||||
homeassistant/components/hydrawise/* @ptcryan
|
homeassistant/components/hydrawise/* @ptcryan
|
||||||
|
homeassistant/components/hyperion/* @dermotduffy
|
||||||
homeassistant/components/iammeter/* @lewei50
|
homeassistant/components/iammeter/* @lewei50
|
||||||
homeassistant/components/iaqualink/* @flz
|
homeassistant/components/iaqualink/* @flz
|
||||||
homeassistant/components/icloud/* @Quentame
|
homeassistant/components/icloud/* @Quentame
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
"""Support for Hyperion remotes."""
|
"""Support for Hyperion-NG remotes."""
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import socket
|
|
||||||
|
|
||||||
|
from hyperion import client, const
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
|
@ -16,6 +15,7 @@ from homeassistant.components.light import (
|
||||||
LightEntity,
|
LightEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
|
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
|
||||||
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
import homeassistant.util.color as color_util
|
import homeassistant.util.color as color_util
|
||||||
|
|
||||||
|
@ -26,103 +26,91 @@ CONF_PRIORITY = "priority"
|
||||||
CONF_HDMI_PRIORITY = "hdmi_priority"
|
CONF_HDMI_PRIORITY = "hdmi_priority"
|
||||||
CONF_EFFECT_LIST = "effect_list"
|
CONF_EFFECT_LIST = "effect_list"
|
||||||
|
|
||||||
|
# As we want to preserve brightness control for effects (e.g. to reduce the
|
||||||
|
# brightness for V4L), we need to persist the effect that is in flight, so
|
||||||
|
# subsequent calls to turn_on will know the keep the effect enabled.
|
||||||
|
# Unfortunately the Home Assistant UI does not easily expose a way to remove a
|
||||||
|
# selected effect (there is no 'No Effect' option by default). Instead, we
|
||||||
|
# create a new fake effect ("Solid") that is always selected by default for
|
||||||
|
# showing a solid color. This is the same method used by WLED.
|
||||||
|
KEY_EFFECT_SOLID = "Solid"
|
||||||
|
|
||||||
DEFAULT_COLOR = [255, 255, 255]
|
DEFAULT_COLOR = [255, 255, 255]
|
||||||
|
DEFAULT_BRIGHTNESS = 255
|
||||||
|
DEFAULT_EFFECT = KEY_EFFECT_SOLID
|
||||||
DEFAULT_NAME = "Hyperion"
|
DEFAULT_NAME = "Hyperion"
|
||||||
|
DEFAULT_ORIGIN = "Home Assistant"
|
||||||
DEFAULT_PORT = 19444
|
DEFAULT_PORT = 19444
|
||||||
DEFAULT_PRIORITY = 128
|
DEFAULT_PRIORITY = 128
|
||||||
DEFAULT_HDMI_PRIORITY = 880
|
DEFAULT_HDMI_PRIORITY = 880
|
||||||
DEFAULT_EFFECT_LIST = [
|
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_COLOR | SUPPORT_BRIGHTNESS | SUPPORT_EFFECT
|
SUPPORT_HYPERION = SUPPORT_COLOR | SUPPORT_BRIGHTNESS | SUPPORT_EFFECT
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = vol.All(
|
||||||
{
|
cv.deprecated(CONF_HDMI_PRIORITY, invalidation_version="0.118"),
|
||||||
vol.Required(CONF_HOST): cv.string,
|
cv.deprecated(CONF_DEFAULT_COLOR, invalidation_version="0.118"),
|
||||||
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
cv.deprecated(CONF_EFFECT_LIST, invalidation_version="0.118"),
|
||||||
vol.Optional(CONF_DEFAULT_COLOR, default=DEFAULT_COLOR): vol.All(
|
PLATFORM_SCHEMA.extend(
|
||||||
list,
|
{
|
||||||
vol.Length(min=3, max=3),
|
vol.Required(CONF_HOST): cv.string,
|
||||||
[vol.All(vol.Coerce(int), vol.Range(min=0, max=255))],
|
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||||
),
|
vol.Optional(CONF_DEFAULT_COLOR, default=DEFAULT_COLOR): vol.All(
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
list,
|
||||||
vol.Optional(CONF_PRIORITY, default=DEFAULT_PRIORITY): cv.positive_int,
|
vol.Length(min=3, max=3),
|
||||||
vol.Optional(
|
[vol.All(vol.Coerce(int), vol.Range(min=0, max=255))],
|
||||||
CONF_HDMI_PRIORITY, default=DEFAULT_HDMI_PRIORITY
|
),
|
||||||
): cv.positive_int,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
vol.Optional(CONF_EFFECT_LIST, default=DEFAULT_EFFECT_LIST): vol.All(
|
vol.Optional(CONF_PRIORITY, default=DEFAULT_PRIORITY): cv.positive_int,
|
||||||
cv.ensure_list, [cv.string]
|
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]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ICON_LIGHTBULB = "mdi:lightbulb"
|
||||||
|
ICON_EFFECT = "mdi:lava-lamp"
|
||||||
|
ICON_EXTERNAL_SOURCE = "mdi:video-input-hdmi"
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
|
||||||
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
"""Set up a Hyperion server remote."""
|
"""Set up a Hyperion server remote."""
|
||||||
name = config[CONF_NAME]
|
name = config[CONF_NAME]
|
||||||
host = config[CONF_HOST]
|
host = config[CONF_HOST]
|
||||||
port = config[CONF_PORT]
|
port = config[CONF_PORT]
|
||||||
priority = config[CONF_PRIORITY]
|
priority = config[CONF_PRIORITY]
|
||||||
hdmi_priority = config[CONF_HDMI_PRIORITY]
|
|
||||||
default_color = config[CONF_DEFAULT_COLOR]
|
|
||||||
effect_list = config[CONF_EFFECT_LIST]
|
|
||||||
|
|
||||||
device = Hyperion(
|
hyperion_client = client.HyperionClient(host, port)
|
||||||
name, host, port, priority, default_color, hdmi_priority, effect_list
|
|
||||||
)
|
|
||||||
|
|
||||||
if device.setup():
|
if not await hyperion_client.async_client_connect():
|
||||||
add_entities([device])
|
raise PlatformNotReady
|
||||||
|
|
||||||
|
async_add_entities([Hyperion(name, priority, hyperion_client)])
|
||||||
|
|
||||||
|
|
||||||
class Hyperion(LightEntity):
|
class Hyperion(LightEntity):
|
||||||
"""Representation of a Hyperion remote."""
|
"""Representation of a Hyperion remote."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, name, priority, hyperion_client):
|
||||||
self, name, host, port, priority, default_color, hdmi_priority, effect_list
|
|
||||||
):
|
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
self._host = host
|
|
||||||
self._port = port
|
|
||||||
self._name = name
|
self._name = name
|
||||||
self._priority = priority
|
self._priority = priority
|
||||||
self._hdmi_priority = hdmi_priority
|
self._client = hyperion_client
|
||||||
self._default_color = default_color
|
|
||||||
self._rgb_color = [0, 0, 0]
|
# Active state representing the Hyperion instance.
|
||||||
self._rgb_mem = [0, 0, 0]
|
self._set_internal_state(
|
||||||
self._brightness = 255
|
brightness=255, rgb_color=DEFAULT_COLOR, effect=KEY_EFFECT_SOLID
|
||||||
self._icon = "mdi:lightbulb"
|
)
|
||||||
self._effect_list = effect_list
|
self._effect_list = []
|
||||||
self._effect = None
|
|
||||||
self._skip_update = False
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Return whether or not this entity should be polled."""
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
|
@ -142,7 +130,7 @@ class Hyperion(LightEntity):
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if not black."""
|
"""Return true if not black."""
|
||||||
return self._rgb_color != [0, 0, 0]
|
return self._client.is_on()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self):
|
||||||
|
@ -157,158 +145,233 @@ class Hyperion(LightEntity):
|
||||||
@property
|
@property
|
||||||
def effect_list(self):
|
def effect_list(self):
|
||||||
"""Return the list of supported effects."""
|
"""Return the list of supported effects."""
|
||||||
return self._effect_list
|
return (
|
||||||
|
self._effect_list
|
||||||
|
+ const.KEY_COMPONENTID_EXTERNAL_SOURCES
|
||||||
|
+ [KEY_EFFECT_SOLID]
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
return SUPPORT_HYPERION
|
return SUPPORT_HYPERION
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return server availability."""
|
||||||
|
return self._client.has_loaded_state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return a unique id for this instance."""
|
||||||
|
return self._client.id
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs):
|
||||||
"""Turn the lights on."""
|
"""Turn the lights on."""
|
||||||
|
# == Turn device on ==
|
||||||
|
# Turn on both ALL (Hyperion itself) and LEDDEVICE. It would be
|
||||||
|
# preferable to enable LEDDEVICE after the settings (e.g. brightness,
|
||||||
|
# color, effect), but this is not possible due to:
|
||||||
|
# https://github.com/hyperion-project/hyperion.ng/issues/967
|
||||||
|
if not self.is_on:
|
||||||
|
if not await self._client.async_send_set_component(
|
||||||
|
**{
|
||||||
|
const.KEY_COMPONENTSTATE: {
|
||||||
|
const.KEY_COMPONENT: const.KEY_COMPONENTID_ALL,
|
||||||
|
const.KEY_STATE: True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not await self._client.async_send_set_component(
|
||||||
|
**{
|
||||||
|
const.KEY_COMPONENTSTATE: {
|
||||||
|
const.KEY_COMPONENT: const.KEY_COMPONENTID_LEDDEVICE,
|
||||||
|
const.KEY_STATE: True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
# == Get key parameters ==
|
||||||
|
brightness = kwargs.get(ATTR_BRIGHTNESS, self._brightness)
|
||||||
|
effect = kwargs.get(ATTR_EFFECT, self._effect)
|
||||||
if ATTR_HS_COLOR in kwargs:
|
if ATTR_HS_COLOR in kwargs:
|
||||||
rgb_color = color_util.color_hs_to_RGB(*kwargs[ATTR_HS_COLOR])
|
rgb_color = color_util.color_hs_to_RGB(*kwargs[ATTR_HS_COLOR])
|
||||||
elif self._rgb_mem == [0, 0, 0]:
|
|
||||||
rgb_color = self._default_color
|
|
||||||
else:
|
else:
|
||||||
rgb_color = self._rgb_mem
|
rgb_color = self._rgb_color
|
||||||
|
|
||||||
brightness = kwargs.get(ATTR_BRIGHTNESS, self._brightness)
|
# == Set brightness ==
|
||||||
|
if self._brightness != brightness:
|
||||||
if ATTR_EFFECT in kwargs:
|
if not await self._client.async_send_set_adjustment(
|
||||||
self._skip_update = True
|
**{
|
||||||
self._effect = kwargs[ATTR_EFFECT]
|
const.KEY_ADJUSTMENT: {
|
||||||
if self._effect == "HDMI":
|
const.KEY_BRIGHTNESS: int(
|
||||||
self.json_request({"command": "clearall"})
|
round((float(brightness) * 100) / 255)
|
||||||
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
|
return
|
||||||
# Check if Hyperion is in ambilight mode trough an HDMI grabber
|
|
||||||
try:
|
# == Set an external source
|
||||||
active_priority = response["info"]["priorities"][0]["priority"]
|
if effect and effect in const.KEY_COMPONENTID_EXTERNAL_SOURCES:
|
||||||
if active_priority == self._hdmi_priority:
|
|
||||||
self._brightness = 255
|
# Clear any color/effect.
|
||||||
self._rgb_color = [125, 125, 125]
|
if not await self._client.async_send_clear(
|
||||||
self._icon = "mdi:video-input-hdmi"
|
**{const.KEY_PRIORITY: self._priority}
|
||||||
self._effect = "HDMI"
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Turn off all external sources, except the intended.
|
||||||
|
for key in const.KEY_COMPONENTID_EXTERNAL_SOURCES:
|
||||||
|
if not await self._client.async_send_set_component(
|
||||||
|
**{
|
||||||
|
const.KEY_COMPONENTSTATE: {
|
||||||
|
const.KEY_COMPONENT: key,
|
||||||
|
const.KEY_STATE: effect == key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
):
|
||||||
return
|
return
|
||||||
except (KeyError, IndexError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
led_color = response["info"]["activeLedColor"]
|
# == Set an effect
|
||||||
if not led_color or led_color[0]["RGB Value"] == [0, 0, 0]:
|
elif effect and effect != KEY_EFFECT_SOLID:
|
||||||
# Get the active effect
|
# This call should not be necessary, but without it there is no priorities-update issued:
|
||||||
if response["info"].get("activeEffects"):
|
# https://github.com/hyperion-project/hyperion.ng/issues/992
|
||||||
self._rgb_color = [175, 0, 255]
|
if not await self._client.async_send_clear(
|
||||||
self._icon = "mdi:lava-lamp"
|
**{const.KEY_PRIORITY: self._priority}
|
||||||
try:
|
):
|
||||||
s_name = response["info"]["activeEffects"][0]["script"]
|
return
|
||||||
s_name = s_name.split("/")[-1][:-3].split("-")[0]
|
|
||||||
self._effect = [
|
if not await self._client.async_send_set_effect(
|
||||||
x for x in self._effect_list if s_name.lower() in x.lower()
|
**{
|
||||||
][0]
|
const.KEY_PRIORITY: self._priority,
|
||||||
except (KeyError, IndexError):
|
const.KEY_EFFECT: {const.KEY_NAME: effect},
|
||||||
self._effect = None
|
const.KEY_ORIGIN: DEFAULT_ORIGIN,
|
||||||
# Bulb off state
|
}
|
||||||
else:
|
):
|
||||||
self._rgb_color = [0, 0, 0]
|
return
|
||||||
self._icon = "mdi:lightbulb"
|
# == Set a color
|
||||||
self._effect = None
|
else:
|
||||||
|
if not await self._client.async_send_set_color(
|
||||||
|
**{
|
||||||
|
const.KEY_PRIORITY: self._priority,
|
||||||
|
const.KEY_COLOR: rgb_color,
|
||||||
|
const.KEY_ORIGIN: DEFAULT_ORIGIN,
|
||||||
|
}
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs):
|
||||||
|
"""Disable the LED output component."""
|
||||||
|
if not await self._client.async_send_set_component(
|
||||||
|
**{
|
||||||
|
const.KEY_COMPONENTSTATE: {
|
||||||
|
const.KEY_COMPONENT: const.KEY_COMPONENTID_LEDDEVICE,
|
||||||
|
const.KEY_STATE: False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
def _set_internal_state(self, brightness=None, rgb_color=None, effect=None):
|
||||||
|
"""Set the internal state."""
|
||||||
|
if brightness is not None:
|
||||||
|
self._brightness = brightness
|
||||||
|
if rgb_color is not None:
|
||||||
|
self._rgb_color = rgb_color
|
||||||
|
if effect is not None:
|
||||||
|
self._effect = effect
|
||||||
|
if effect == KEY_EFFECT_SOLID:
|
||||||
|
self._icon = ICON_LIGHTBULB
|
||||||
|
elif effect in const.KEY_COMPONENTID_EXTERNAL_SOURCES:
|
||||||
|
self._icon = ICON_EXTERNAL_SOURCE
|
||||||
else:
|
else:
|
||||||
# Get the RGB color
|
self._icon = ICON_EFFECT
|
||||||
self._rgb_color = led_color[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):
|
def _update_components(self, _=None):
|
||||||
"""Get the hostname of the remote."""
|
"""Update Hyperion components."""
|
||||||
response = self.json_request({"command": "serverinfo"})
|
self.async_write_ha_state()
|
||||||
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):
|
def _update_adjustment(self, _=None):
|
||||||
"""Communicate with the JSON server."""
|
"""Update Hyperion adjustments."""
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
if self._client.adjustment:
|
||||||
sock.settimeout(5)
|
brightness_pct = self._client.adjustment[0].get(
|
||||||
|
const.KEY_BRIGHTNESS, DEFAULT_BRIGHTNESS
|
||||||
|
)
|
||||||
|
if brightness_pct < 0 or brightness_pct > 100:
|
||||||
|
return
|
||||||
|
self._set_internal_state(
|
||||||
|
brightness=int(round((brightness_pct * 255) / float(100)))
|
||||||
|
)
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
try:
|
def _update_priorities(self, _=None):
|
||||||
sock.connect((self._host, self._port))
|
"""Update Hyperion priorities."""
|
||||||
except OSError:
|
visible_priority = self._client.visible_priority
|
||||||
sock.close()
|
if visible_priority:
|
||||||
return False
|
componentid = visible_priority.get(const.KEY_COMPONENTID)
|
||||||
|
if componentid in const.KEY_COMPONENTID_EXTERNAL_SOURCES:
|
||||||
|
self._set_internal_state(rgb_color=DEFAULT_COLOR, effect=componentid)
|
||||||
|
elif componentid == const.KEY_COMPONENTID_EFFECT:
|
||||||
|
# Owner is the effect name.
|
||||||
|
# See: https://docs.hyperion-project.org/en/json/ServerInfo.html#priorities
|
||||||
|
self._set_internal_state(
|
||||||
|
rgb_color=DEFAULT_COLOR, effect=visible_priority[const.KEY_OWNER]
|
||||||
|
)
|
||||||
|
elif componentid == const.KEY_COMPONENTID_COLOR:
|
||||||
|
self._set_internal_state(
|
||||||
|
rgb_color=visible_priority[const.KEY_VALUE][const.KEY_RGB],
|
||||||
|
effect=KEY_EFFECT_SOLID,
|
||||||
|
)
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
sock.send(bytearray(f"{json.dumps(request)}\n", "utf-8"))
|
def _update_effect_list(self, _=None):
|
||||||
try:
|
"""Update Hyperion effects."""
|
||||||
buf = sock.recv(4096)
|
if not self._client.effects:
|
||||||
except socket.timeout:
|
return
|
||||||
# Something is wrong, assume it's offline
|
effect_list = []
|
||||||
sock.close()
|
for effect in self._client.effects or []:
|
||||||
return False
|
if const.KEY_NAME in effect:
|
||||||
|
effect_list.append(effect[const.KEY_NAME])
|
||||||
|
if effect_list:
|
||||||
|
self._effect_list = effect_list
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
# Read until a newline or timeout
|
def _update_full_state(self):
|
||||||
buffering = True
|
"""Update full Hyperion state."""
|
||||||
while buffering:
|
self._update_adjustment()
|
||||||
if "\n" in str(buf, "utf-8"):
|
self._update_priorities()
|
||||||
response = str(buf, "utf-8").split("\n")[0]
|
self._update_effect_list()
|
||||||
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()
|
_LOGGER.debug(
|
||||||
return json.loads(response)
|
"Hyperion full state update: On=%s,Brightness=%i,Effect=%s "
|
||||||
|
"(%i effects total),Color=%s",
|
||||||
|
self.is_on,
|
||||||
|
self._brightness,
|
||||||
|
self._effect,
|
||||||
|
len(self._effect_list),
|
||||||
|
self._rgb_color,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _update_client(self, json):
|
||||||
|
"""Update client connection state."""
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Register callbacks when entity added to hass."""
|
||||||
|
self._client.set_callbacks(
|
||||||
|
{
|
||||||
|
f"{const.KEY_ADJUSTMENT}-{const.KEY_UPDATE}": self._update_adjustment,
|
||||||
|
f"{const.KEY_COMPONENTS}-{const.KEY_UPDATE}": self._update_components,
|
||||||
|
f"{const.KEY_EFFECTS}-{const.KEY_UPDATE}": self._update_effect_list,
|
||||||
|
f"{const.KEY_PRIORITIES}-{const.KEY_UPDATE}": self._update_priorities,
|
||||||
|
f"{const.KEY_CLIENT}-{const.KEY_UPDATE}": self._update_client,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load initial state.
|
||||||
|
self._update_full_state()
|
||||||
|
return True
|
||||||
|
|
|
@ -2,5 +2,6 @@
|
||||||
"domain": "hyperion",
|
"domain": "hyperion",
|
||||||
"name": "Hyperion",
|
"name": "Hyperion",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/hyperion",
|
"documentation": "https://www.home-assistant.io/integrations/hyperion",
|
||||||
"codeowners": []
|
"requirements": ["hyperion-py==0.3.0"],
|
||||||
|
"codeowners": ["@dermotduffy"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -774,6 +774,9 @@ huawei-lte-api==1.4.12
|
||||||
# homeassistant.components.hydrawise
|
# homeassistant.components.hydrawise
|
||||||
hydrawiser==0.2
|
hydrawiser==0.2
|
||||||
|
|
||||||
|
# homeassistant.components.hyperion
|
||||||
|
hyperion-py==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.bh1750
|
# homeassistant.components.bh1750
|
||||||
# homeassistant.components.bme280
|
# homeassistant.components.bme280
|
||||||
# homeassistant.components.htu21d
|
# homeassistant.components.htu21d
|
||||||
|
|
|
@ -391,6 +391,9 @@ httplib2==0.10.3
|
||||||
# homeassistant.components.huawei_lte
|
# homeassistant.components.huawei_lte
|
||||||
huawei-lte-api==1.4.12
|
huawei-lte-api==1.4.12
|
||||||
|
|
||||||
|
# homeassistant.components.hyperion
|
||||||
|
hyperion-py==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.iaqualink
|
# homeassistant.components.iaqualink
|
||||||
iaqualink==0.3.4
|
iaqualink==0.3.4
|
||||||
|
|
||||||
|
|
1
tests/components/hyperion/__init__.py
Normal file
1
tests/components/hyperion/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Tests for the Hyperion component."""
|
430
tests/components/hyperion/test_light.py
Normal file
430
tests/components/hyperion/test_light.py
Normal file
|
@ -0,0 +1,430 @@
|
||||||
|
"""Tests for the Hyperion integration."""
|
||||||
|
# from tests.async_mock import AsyncMock, MagicMock, patch
|
||||||
|
from asynctest import CoroutineMock, Mock, call, patch
|
||||||
|
from hyperion import const
|
||||||
|
|
||||||
|
from homeassistant.components.hyperion import light as hyperion_light
|
||||||
|
from homeassistant.components.light import (
|
||||||
|
ATTR_BRIGHTNESS,
|
||||||
|
ATTR_EFFECT,
|
||||||
|
ATTR_HS_COLOR,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
TEST_HOST = "test-hyperion-host"
|
||||||
|
TEST_PORT = const.DEFAULT_PORT
|
||||||
|
TEST_NAME = "test_hyperion_name"
|
||||||
|
TEST_PRIORITY = 128
|
||||||
|
TEST_ENTITY_ID = f"{DOMAIN}.{TEST_NAME}"
|
||||||
|
|
||||||
|
|
||||||
|
def create_mock_client():
|
||||||
|
"""Create a mock Hyperion client."""
|
||||||
|
mock_client = Mock()
|
||||||
|
mock_client.async_client_connect = CoroutineMock(return_value=True)
|
||||||
|
mock_client.adjustment = None
|
||||||
|
mock_client.effects = None
|
||||||
|
mock_client.id = "%s:%i" % (TEST_HOST, TEST_PORT)
|
||||||
|
return mock_client
|
||||||
|
|
||||||
|
|
||||||
|
def call_registered_callback(client, key, *args, **kwargs):
|
||||||
|
"""Call a Hyperion entity callback that was registered with the client."""
|
||||||
|
return client.set_callbacks.call_args[0][0][key](*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup_entity(hass, client=None):
|
||||||
|
"""Add a test Hyperion entity to hass."""
|
||||||
|
client = client or create_mock_client()
|
||||||
|
with patch("hyperion.client.HyperionClient", return_value=client):
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
{
|
||||||
|
DOMAIN: {
|
||||||
|
"platform": "hyperion",
|
||||||
|
"name": TEST_NAME,
|
||||||
|
"host": TEST_HOST,
|
||||||
|
"port": const.DEFAULT_PORT,
|
||||||
|
"priority": TEST_PRIORITY,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_platform(hass):
|
||||||
|
"""Test setting up the platform."""
|
||||||
|
client = create_mock_client()
|
||||||
|
await setup_entity(hass, client=client)
|
||||||
|
assert hass.states.get(TEST_ENTITY_ID) is not None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_platform_not_ready(hass):
|
||||||
|
"""Test the platform not being ready."""
|
||||||
|
client = create_mock_client()
|
||||||
|
client.async_client_connect = CoroutineMock(return_value=False)
|
||||||
|
|
||||||
|
await setup_entity(hass, client=client)
|
||||||
|
assert hass.states.get(TEST_ENTITY_ID) is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_light_basic_properies(hass):
|
||||||
|
"""Test the basic properties."""
|
||||||
|
client = create_mock_client()
|
||||||
|
await setup_entity(hass, client=client)
|
||||||
|
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.state == "on"
|
||||||
|
assert entity_state.attributes["brightness"] == 255
|
||||||
|
assert entity_state.attributes["hs_color"] == (0.0, 0.0)
|
||||||
|
assert entity_state.attributes["icon"] == hyperion_light.ICON_LIGHTBULB
|
||||||
|
assert entity_state.attributes["effect"] == hyperion_light.KEY_EFFECT_SOLID
|
||||||
|
|
||||||
|
# By default the effect list is the 3 external sources + 'Solid'.
|
||||||
|
assert len(entity_state.attributes["effect_list"]) == 4
|
||||||
|
|
||||||
|
assert (
|
||||||
|
entity_state.attributes["supported_features"] == hyperion_light.SUPPORT_HYPERION
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_light_async_turn_on(hass):
|
||||||
|
"""Test turning the light on."""
|
||||||
|
client = create_mock_client()
|
||||||
|
await setup_entity(hass, client=client)
|
||||||
|
|
||||||
|
# On (=), 100% (=), solid (=), [255,255,255] (=)
|
||||||
|
client.async_send_set_color = CoroutineMock(return_value=True)
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: TEST_ENTITY_ID}, blocking=True
|
||||||
|
)
|
||||||
|
|
||||||
|
assert client.async_send_set_color.call_args == call(
|
||||||
|
**{
|
||||||
|
const.KEY_PRIORITY: TEST_PRIORITY,
|
||||||
|
const.KEY_COLOR: [255, 255, 255],
|
||||||
|
const.KEY_ORIGIN: hyperion_light.DEFAULT_ORIGIN,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# On (=), 50% (!), solid (=), [255,255,255] (=)
|
||||||
|
# ===
|
||||||
|
brightness = 128
|
||||||
|
client.async_send_set_color = CoroutineMock(return_value=True)
|
||||||
|
client.async_send_set_adjustment = CoroutineMock(return_value=True)
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID, ATTR_BRIGHTNESS: brightness},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert client.async_send_set_adjustment.call_args == call(
|
||||||
|
**{const.KEY_ADJUSTMENT: {const.KEY_BRIGHTNESS: 50}}
|
||||||
|
)
|
||||||
|
assert client.async_send_set_color.call_args == call(
|
||||||
|
**{
|
||||||
|
const.KEY_PRIORITY: TEST_PRIORITY,
|
||||||
|
const.KEY_COLOR: [255, 255, 255],
|
||||||
|
const.KEY_ORIGIN: hyperion_light.DEFAULT_ORIGIN,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Simulate a state callback from Hyperion.
|
||||||
|
client.adjustment = [{const.KEY_BRIGHTNESS: 50}]
|
||||||
|
call_registered_callback(client, "adjustment-update")
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.state == "on"
|
||||||
|
assert entity_state.attributes["brightness"] == brightness
|
||||||
|
|
||||||
|
# On (=), 50% (=), solid (=), [0,255,255] (!)
|
||||||
|
hs_color = (180.0, 100.0)
|
||||||
|
client.async_send_set_color = CoroutineMock(return_value=True)
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID, ATTR_HS_COLOR: hs_color},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert client.async_send_set_color.call_args == call(
|
||||||
|
**{
|
||||||
|
const.KEY_PRIORITY: TEST_PRIORITY,
|
||||||
|
const.KEY_COLOR: (0, 255, 255),
|
||||||
|
const.KEY_ORIGIN: hyperion_light.DEFAULT_ORIGIN,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Simulate a state callback from Hyperion.
|
||||||
|
client.visible_priority = {
|
||||||
|
const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR,
|
||||||
|
const.KEY_VALUE: {const.KEY_RGB: (0, 255, 255)},
|
||||||
|
}
|
||||||
|
|
||||||
|
call_registered_callback(client, "priorities-update")
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.attributes["hs_color"] == hs_color
|
||||||
|
assert entity_state.attributes["icon"] == hyperion_light.ICON_LIGHTBULB
|
||||||
|
|
||||||
|
# On (=), 100% (!), solid, [0,255,255] (=)
|
||||||
|
brightness = 255
|
||||||
|
client.async_send_set_color = CoroutineMock(return_value=True)
|
||||||
|
client.async_send_set_adjustment = CoroutineMock(return_value=True)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID, ATTR_BRIGHTNESS: brightness},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert client.async_send_set_adjustment.call_args == call(
|
||||||
|
**{const.KEY_ADJUSTMENT: {const.KEY_BRIGHTNESS: 100}}
|
||||||
|
)
|
||||||
|
assert client.async_send_set_color.call_args == call(
|
||||||
|
**{
|
||||||
|
const.KEY_PRIORITY: TEST_PRIORITY,
|
||||||
|
const.KEY_COLOR: (0, 255, 255),
|
||||||
|
const.KEY_ORIGIN: hyperion_light.DEFAULT_ORIGIN,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
client.adjustment = [{const.KEY_BRIGHTNESS: 100}]
|
||||||
|
call_registered_callback(client, "adjustment-update")
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.attributes["brightness"] == brightness
|
||||||
|
|
||||||
|
# On (=), 100% (=), V4L (!), [0,255,255] (=)
|
||||||
|
effect = const.KEY_COMPONENTID_EXTERNAL_SOURCES[2] # V4L
|
||||||
|
client.async_send_clear = CoroutineMock(return_value=True)
|
||||||
|
client.async_send_set_component = CoroutineMock(return_value=True)
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID, ATTR_EFFECT: effect},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert client.async_send_clear.call_args == call(
|
||||||
|
**{const.KEY_PRIORITY: TEST_PRIORITY}
|
||||||
|
)
|
||||||
|
assert client.async_send_set_component.call_args_list == [
|
||||||
|
call(
|
||||||
|
**{
|
||||||
|
const.KEY_COMPONENTSTATE: {
|
||||||
|
const.KEY_COMPONENT: const.KEY_COMPONENTID_EXTERNAL_SOURCES[0],
|
||||||
|
const.KEY_STATE: False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
call(
|
||||||
|
**{
|
||||||
|
const.KEY_COMPONENTSTATE: {
|
||||||
|
const.KEY_COMPONENT: const.KEY_COMPONENTID_EXTERNAL_SOURCES[1],
|
||||||
|
const.KEY_STATE: False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
call(
|
||||||
|
**{
|
||||||
|
const.KEY_COMPONENTSTATE: {
|
||||||
|
const.KEY_COMPONENT: const.KEY_COMPONENTID_EXTERNAL_SOURCES[2],
|
||||||
|
const.KEY_STATE: True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]
|
||||||
|
client.visible_priority = {const.KEY_COMPONENTID: effect}
|
||||||
|
call_registered_callback(client, "priorities-update")
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.attributes["icon"] == hyperion_light.ICON_EXTERNAL_SOURCE
|
||||||
|
assert entity_state.attributes["effect"] == effect
|
||||||
|
|
||||||
|
# On (=), 100% (=), "Warm Blobs" (!), [0,255,255] (=)
|
||||||
|
effect = "Warm Blobs"
|
||||||
|
client.async_send_clear = CoroutineMock(return_value=True)
|
||||||
|
client.async_send_set_effect = CoroutineMock(return_value=True)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID, ATTR_EFFECT: effect},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert client.async_send_clear.call_args == call(
|
||||||
|
**{const.KEY_PRIORITY: TEST_PRIORITY}
|
||||||
|
)
|
||||||
|
assert client.async_send_set_effect.call_args == call(
|
||||||
|
**{
|
||||||
|
const.KEY_PRIORITY: TEST_PRIORITY,
|
||||||
|
const.KEY_EFFECT: {const.KEY_NAME: effect},
|
||||||
|
const.KEY_ORIGIN: hyperion_light.DEFAULT_ORIGIN,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
client.visible_priority = {
|
||||||
|
const.KEY_COMPONENTID: const.KEY_COMPONENTID_EFFECT,
|
||||||
|
const.KEY_OWNER: effect,
|
||||||
|
}
|
||||||
|
call_registered_callback(client, "priorities-update")
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.attributes["icon"] == hyperion_light.ICON_EFFECT
|
||||||
|
assert entity_state.attributes["effect"] == effect
|
||||||
|
|
||||||
|
# No calls if disconnected.
|
||||||
|
client.has_loaded_state = False
|
||||||
|
call_registered_callback(client, "client-update", {"loaded-state": False})
|
||||||
|
client.async_send_clear = CoroutineMock(return_value=True)
|
||||||
|
client.async_send_set_effect = CoroutineMock(return_value=True)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: TEST_ENTITY_ID}, blocking=True
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not client.async_send_clear.called
|
||||||
|
assert not client.async_send_set_effect.called
|
||||||
|
|
||||||
|
|
||||||
|
async def test_light_async_turn_off(hass):
|
||||||
|
"""Test turning the light off."""
|
||||||
|
client = create_mock_client()
|
||||||
|
await setup_entity(hass, client=client)
|
||||||
|
|
||||||
|
client.async_send_set_component = CoroutineMock(return_value=True)
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: TEST_ENTITY_ID}, blocking=True
|
||||||
|
)
|
||||||
|
|
||||||
|
assert client.async_send_set_component.call_args == call(
|
||||||
|
**{
|
||||||
|
const.KEY_COMPONENTSTATE: {
|
||||||
|
const.KEY_COMPONENT: const.KEY_COMPONENTID_LEDDEVICE,
|
||||||
|
const.KEY_STATE: False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# No calls if no state loaded.
|
||||||
|
client.has_loaded_state = False
|
||||||
|
client.async_send_set_component = CoroutineMock(return_value=True)
|
||||||
|
call_registered_callback(client, "client-update", {"loaded-state": False})
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: TEST_ENTITY_ID}, blocking=True
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not client.async_send_set_component.called
|
||||||
|
|
||||||
|
|
||||||
|
async def test_light_async_updates_from_hyperion_client(hass):
|
||||||
|
"""Test receiving a variety of Hyperion client callbacks."""
|
||||||
|
client = create_mock_client()
|
||||||
|
await setup_entity(hass, client=client)
|
||||||
|
|
||||||
|
# Bright change gets accepted.
|
||||||
|
brightness = 10
|
||||||
|
client.adjustment = [{const.KEY_BRIGHTNESS: brightness}]
|
||||||
|
call_registered_callback(client, "adjustment-update")
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.attributes["brightness"] == round(255 * (brightness / 100.0))
|
||||||
|
|
||||||
|
# Broken brightness value is ignored.
|
||||||
|
bad_brightness = -200
|
||||||
|
client.adjustment = [{const.KEY_BRIGHTNESS: bad_brightness}]
|
||||||
|
call_registered_callback(client, "adjustment-update")
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.attributes["brightness"] == round(255 * (brightness / 100.0))
|
||||||
|
|
||||||
|
# Update components.
|
||||||
|
client.is_on.return_value = True
|
||||||
|
call_registered_callback(client, "components-update")
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.state == "on"
|
||||||
|
|
||||||
|
client.is_on.return_value = False
|
||||||
|
call_registered_callback(client, "components-update")
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.state == "off"
|
||||||
|
|
||||||
|
# Update priorities (V4L)
|
||||||
|
client.is_on.return_value = True
|
||||||
|
client.visible_priority = {const.KEY_COMPONENTID: const.KEY_COMPONENTID_V4L}
|
||||||
|
call_registered_callback(client, "priorities-update")
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.attributes["icon"] == hyperion_light.ICON_EXTERNAL_SOURCE
|
||||||
|
assert entity_state.attributes["hs_color"] == (0.0, 0.0)
|
||||||
|
assert entity_state.attributes["effect"] == const.KEY_COMPONENTID_V4L
|
||||||
|
|
||||||
|
# Update priorities (Effect)
|
||||||
|
effect = "foo"
|
||||||
|
client.visible_priority = {
|
||||||
|
const.KEY_COMPONENTID: const.KEY_COMPONENTID_EFFECT,
|
||||||
|
const.KEY_OWNER: effect,
|
||||||
|
}
|
||||||
|
|
||||||
|
call_registered_callback(client, "priorities-update")
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.attributes["effect"] == effect
|
||||||
|
assert entity_state.attributes["icon"] == hyperion_light.ICON_EFFECT
|
||||||
|
assert entity_state.attributes["hs_color"] == (0.0, 0.0)
|
||||||
|
|
||||||
|
# Update priorities (Color)
|
||||||
|
rgb = (0, 100, 100)
|
||||||
|
client.visible_priority = {
|
||||||
|
const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR,
|
||||||
|
const.KEY_VALUE: {const.KEY_RGB: rgb},
|
||||||
|
}
|
||||||
|
|
||||||
|
call_registered_callback(client, "priorities-update")
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.attributes["effect"] == hyperion_light.KEY_EFFECT_SOLID
|
||||||
|
assert entity_state.attributes["icon"] == hyperion_light.ICON_LIGHTBULB
|
||||||
|
assert entity_state.attributes["hs_color"] == (180.0, 100.0)
|
||||||
|
|
||||||
|
# Update effect list
|
||||||
|
effects = [{const.KEY_NAME: "One"}, {const.KEY_NAME: "Two"}]
|
||||||
|
client.effects = effects
|
||||||
|
call_registered_callback(client, "effects-update")
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.attributes["effect_list"] == [
|
||||||
|
effect[const.KEY_NAME] for effect in effects
|
||||||
|
] + const.KEY_COMPONENTID_EXTERNAL_SOURCES + [hyperion_light.KEY_EFFECT_SOLID]
|
||||||
|
|
||||||
|
# Update connection status (e.g. disconnection).
|
||||||
|
|
||||||
|
# Turn on late, check state, disconnect, ensure it cannot be turned off.
|
||||||
|
client.has_loaded_state = False
|
||||||
|
call_registered_callback(client, "client-update", {"loaded-state": False})
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.state == "unavailable"
|
||||||
|
|
||||||
|
# Update connection status (e.g. re-connection)
|
||||||
|
client.has_loaded_state = True
|
||||||
|
call_registered_callback(client, "client-update", {"loaded-state": True})
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert entity_state.state == "on"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_full_state_loaded_on_start(hass):
|
||||||
|
"""Test receiving a variety of Hyperion client callbacks."""
|
||||||
|
client = create_mock_client()
|
||||||
|
|
||||||
|
# Update full state (should call all update methods).
|
||||||
|
brightness = 25
|
||||||
|
client.adjustment = [{const.KEY_BRIGHTNESS: brightness}]
|
||||||
|
client.visible_priority = {
|
||||||
|
const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR,
|
||||||
|
const.KEY_VALUE: {const.KEY_RGB: (0, 100, 100)},
|
||||||
|
}
|
||||||
|
client.effects = [{const.KEY_NAME: "One"}, {const.KEY_NAME: "Two"}]
|
||||||
|
|
||||||
|
await setup_entity(hass, client=client)
|
||||||
|
|
||||||
|
entity_state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
|
||||||
|
assert entity_state.attributes["brightness"] == round(255 * (brightness / 100.0))
|
||||||
|
assert entity_state.attributes["effect"] == hyperion_light.KEY_EFFECT_SOLID
|
||||||
|
assert entity_state.attributes["icon"] == hyperion_light.ICON_LIGHTBULB
|
||||||
|
assert entity_state.attributes["hs_color"] == (180.0, 100.0)
|
Loading…
Add table
Add a link
Reference in a new issue