Decouple Hyperion entitites and clear source when light is off (#80478)

* Remove Hyperion Priority Light

* Remove coupling between light entity and led device

* Merge HyperionLight and HyperionBaseLight as we will only have one light entity

* Set state based on whether priority channel is open or not

* Remove leftover variable from Priority Light

* Remove external sources from light entity; use switch entities instead

* Remove external effects from effects to show dropdown

* Remove workaround for hyperion.ng issue 992

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Sab44 2023-06-28 13:51:42 +02:00 committed by GitHub
parent ee4459f41e
commit c1953b0ae4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 966 deletions

View file

@ -447,7 +447,7 @@ class HyperionOptionsFlow(OptionsFlow):
) -> FlowResult:
"""Manage the options."""
effects = {source: source for source in const.KEY_COMPONENTID_EXTERNAL_SOURCES}
effects = {}
async with self._create_client() as hyperion_client:
if not hyperion_client:
return self.async_abort(reason="cannot_connect")

View file

@ -21,8 +21,6 @@ HYPERION_MODEL_NAME = f"{HYPERION_MANUFACTURER_NAME}-NG"
HYPERION_RELEASES_URL = "https://github.com/hyperion-project/hyperion.ng/releases"
HYPERION_VERSION_WARN_CUTOFF = "2.0.0-alpha.9"
NAME_SUFFIX_HYPERION_LIGHT = ""
NAME_SUFFIX_HYPERION_PRIORITY_LIGHT = "Priority"
NAME_SUFFIX_HYPERION_COMPONENT_SWITCH = "Component"
NAME_SUFFIX_HYPERION_CAMERA = ""
@ -32,5 +30,4 @@ SIGNAL_ENTITY_REMOVE = f"{DOMAIN}_entity_remove_signal.{{}}"
TYPE_HYPERION_CAMERA = "hyperion_camera"
TYPE_HYPERION_LIGHT = "hyperion_light"
TYPE_HYPERION_PRIORITY_LIGHT = "hyperion_priority_light"
TYPE_HYPERION_COMPONENT_SWITCH_BASE = "hyperion_component_switch"

View file

@ -41,24 +41,19 @@ from .const import (
DOMAIN,
HYPERION_MANUFACTURER_NAME,
HYPERION_MODEL_NAME,
NAME_SUFFIX_HYPERION_LIGHT,
NAME_SUFFIX_HYPERION_PRIORITY_LIGHT,
SIGNAL_ENTITY_REMOVE,
TYPE_HYPERION_LIGHT,
TYPE_HYPERION_PRIORITY_LIGHT,
)
_LOGGER = logging.getLogger(__name__)
COLOR_BLACK = color_util.COLORS["black"]
CONF_DEFAULT_COLOR = "default_color"
CONF_HDMI_PRIORITY = "hdmi_priority"
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.
# brightness), we need to persist the effect that is in flight, so
# subsequent calls to turn_on will know to 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
@ -75,7 +70,6 @@ DEFAULT_EFFECT_LIST: list[str] = []
ICON_LIGHTBULB = "mdi:lightbulb"
ICON_EFFECT = "mdi:lava-lamp"
ICON_EXTERNAL_SOURCE = "mdi:television-ambient-light"
async def async_setup_entry(
@ -102,7 +96,6 @@ async def async_setup_entry(
async_add_entities(
[
HyperionLight(*args),
HyperionPriorityLight(*args),
]
)
@ -110,19 +103,18 @@ async def async_setup_entry(
def instance_remove(instance_num: int) -> None:
"""Remove entities for an old Hyperion instance."""
assert server_id
for light_type in LIGHT_TYPES:
async_dispatcher_send(
hass,
SIGNAL_ENTITY_REMOVE.format(
get_hyperion_unique_id(server_id, instance_num, light_type)
),
)
async_dispatcher_send(
hass,
SIGNAL_ENTITY_REMOVE.format(
get_hyperion_unique_id(server_id, instance_num, TYPE_HYPERION_LIGHT)
),
)
listen_for_instance_updates(hass, config_entry, instance_add, instance_remove)
class HyperionBaseLight(LightEntity):
"""A Hyperion light base class."""
class HyperionLight(LightEntity):
"""A Hyperion light that acts as a client for the configured priority."""
_attr_color_mode = ColorMode.HS
_attr_should_poll = False
@ -151,11 +143,6 @@ class HyperionBaseLight(LightEntity):
self._effect: str = KEY_EFFECT_SOLID
self._static_effect_list: list[str] = [KEY_EFFECT_SOLID]
if self._support_external_effects:
self._static_effect_list += [
const.KEY_COMPONENTID_TO_NAME[component]
for component in const.KEY_COMPONENTID_EXTERNAL_SOURCES
]
self._effect_list: list[str] = self._static_effect_list[:]
self._client_callbacks: Mapping[str, Callable[[dict[str, Any]], None]] = {
@ -168,11 +155,11 @@ class HyperionBaseLight(LightEntity):
def _compute_unique_id(self, server_id: str, instance_num: int) -> str:
"""Compute a unique id for this instance."""
raise NotImplementedError
return get_hyperion_unique_id(server_id, instance_num, TYPE_HYPERION_LIGHT)
def _compute_name(self, instance_name: str) -> str:
"""Compute the name of the light."""
raise NotImplementedError
return f"{instance_name}".strip()
@property
def entity_registry_enabled_default(self) -> bool:
@ -198,12 +185,6 @@ class HyperionBaseLight(LightEntity):
def icon(self) -> str:
"""Return state specific icon."""
if self.is_on:
if (
self.effect in const.KEY_COMPONENTID_FROM_NAME
and const.KEY_COMPONENTID_FROM_NAME[self.effect]
in const.KEY_COMPONENTID_EXTERNAL_SOURCES
):
return ICON_EXTERNAL_SOURCE
if self.effect != KEY_EFFECT_SOLID:
return ICON_EFFECT
return ICON_LIGHTBULB
@ -247,6 +228,11 @@ class HyperionBaseLight(LightEntity):
}
return self._options.get(key, defaults[key])
@property
def is_on(self) -> bool:
"""Return true if light is on. Light is considered on when there is a source at the configured HA priority."""
return self._get_priority_entry_that_dictates_state() is not None
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the light."""
# == Get key parameters ==
@ -279,55 +265,8 @@ class HyperionBaseLight(LightEntity):
):
return
# == Set an external source
if (
effect
and self._support_external_effects
and (
effect in const.KEY_COMPONENTID_EXTERNAL_SOURCES
or effect in const.KEY_COMPONENTID_FROM_NAME
)
):
if effect in const.KEY_COMPONENTID_FROM_NAME:
component = const.KEY_COMPONENTID_FROM_NAME[effect]
else:
_LOGGER.warning(
(
"Use of Hyperion effect '%s' is deprecated and will be removed "
"in a future release. Please use '%s' instead"
),
effect,
const.KEY_COMPONENTID_TO_NAME[effect],
)
component = effect
# Clear any color/effect.
if not await self._client.async_send_clear(
**{const.KEY_PRIORITY: self._get_option(CONF_PRIORITY)}
):
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: component == key,
}
}
):
return
# == Set an effect
elif effect and effect != KEY_EFFECT_SOLID:
# This call should not be necessary, but without it there is no priorities-update issued:
# https://github.com/hyperion-project/hyperion.ng/issues/992
if not await self._client.async_send_clear(
**{const.KEY_PRIORITY: self._get_option(CONF_PRIORITY)}
):
return
if effect and effect != KEY_EFFECT_SOLID:
if not await self._client.async_send_set_effect(
**{
const.KEY_PRIORITY: self._get_option(CONF_PRIORITY),
@ -336,6 +275,7 @@ class HyperionBaseLight(LightEntity):
}
):
return
# == Set a color
elif not await self._client.async_send_set_color(
**{
@ -346,6 +286,13 @@ class HyperionBaseLight(LightEntity):
):
return
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the light i.e. clear the configured priority."""
if not await self._client.async_send_clear(
**{const.KEY_PRIORITY: self._get_option(CONF_PRIORITY)}
):
return
def _set_internal_state(
self,
brightness: int | None = None,
@ -383,24 +330,15 @@ class HyperionBaseLight(LightEntity):
def _update_priorities(self, _: dict[str, Any] | None = None) -> None:
"""Update Hyperion priorities."""
priority = self._get_priority_entry_that_dictates_state()
if priority and self._allow_priority_update(priority):
componentid = priority.get(const.KEY_COMPONENTID)
if (
self._support_external_effects
and componentid in const.KEY_COMPONENTID_EXTERNAL_SOURCES
and componentid in const.KEY_COMPONENTID_TO_NAME
):
self._set_internal_state(
rgb_color=DEFAULT_COLOR,
effect=const.KEY_COMPONENTID_TO_NAME[componentid],
)
elif componentid == const.KEY_COMPONENTID_EFFECT:
if priority:
component_id = priority.get(const.KEY_COMPONENTID)
if component_id == 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=priority[const.KEY_OWNER]
)
elif componentid == const.KEY_COMPONENTID_COLOR:
elif component_id == const.KEY_COMPONENTID_COLOR:
self._set_internal_state(
rgb_color=priority[const.KEY_VALUE][const.KEY_RGB],
effect=KEY_EFFECT_SOLID,
@ -469,172 +407,10 @@ class HyperionBaseLight(LightEntity):
"""Cleanup prior to hass removal."""
self._client.remove_callbacks(self._client_callbacks)
@property
def _support_external_effects(self) -> bool:
"""Whether or not to support setting external effects from the light entity."""
return True
def _get_priority_entry_that_dictates_state(self) -> dict[str, Any] | None:
"""Get the relevant Hyperion priority entry to consider."""
# Return the visible priority (whether or not it is the HA priority).
# Explicit type specifier to ensure this works when the underlying (typed)
# library is installed along with the tests. Casts would trigger a
# redundant-cast warning in this case.
priority: dict[str, Any] | None = self._client.visible_priority
return priority
def _allow_priority_update(self, priority: dict[str, Any] | None = None) -> bool:
"""Determine whether to allow a priority to update internal state."""
return True
class HyperionLight(HyperionBaseLight):
"""A Hyperion light that acts in absolute (vs priority) manner.
Light state is the absolute Hyperion component state (e.g. LED device on/off) rather
than color based at a particular priority, and the 'winning' priority determines
shown state rather than exclusively the HA priority.
"""
def _compute_unique_id(self, server_id: str, instance_num: int) -> str:
"""Compute a unique id for this instance."""
return get_hyperion_unique_id(server_id, instance_num, TYPE_HYPERION_LIGHT)
def _compute_name(self, instance_name: str) -> str:
"""Compute the name of the light."""
return f"{instance_name} {NAME_SUFFIX_HYPERION_LIGHT}".strip()
@property
def is_on(self) -> bool:
"""Return true if light is on."""
return (
bool(self._client.is_on())
and self._get_priority_entry_that_dictates_state() is not None
)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the light."""
# == 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 bool(self._client.is_on()):
for component in (
const.KEY_COMPONENTID_ALL,
const.KEY_COMPONENTID_LEDDEVICE,
):
if not await self._client.async_send_set_component(
**{
const.KEY_COMPONENTSTATE: {
const.KEY_COMPONENT: component,
const.KEY_STATE: True,
}
}
):
return
# Turn on the relevant Hyperion priority as usual.
await super().async_turn_on(**kwargs)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the light."""
if not await self._client.async_send_set_component(
**{
const.KEY_COMPONENTSTATE: {
const.KEY_COMPONENT: const.KEY_COMPONENTID_LEDDEVICE,
const.KEY_STATE: False,
}
}
):
return
class HyperionPriorityLight(HyperionBaseLight):
"""A Hyperion light that only acts on a single Hyperion priority."""
def _compute_unique_id(self, server_id: str, instance_num: int) -> str:
"""Compute a unique id for this instance."""
return get_hyperion_unique_id(
server_id, instance_num, TYPE_HYPERION_PRIORITY_LIGHT
)
def _compute_name(self, instance_name: str) -> str:
"""Compute the name of the light."""
return f"{instance_name} {NAME_SUFFIX_HYPERION_PRIORITY_LIGHT}".strip()
@property
def entity_registry_enabled_default(self) -> bool:
"""Whether or not the entity is enabled by default."""
return False
@property
def is_on(self) -> bool:
"""Return true if light is on."""
priority = self._get_priority_entry_that_dictates_state()
return (
priority is not None
and not HyperionPriorityLight._is_priority_entry_black(priority)
)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the light."""
if not await self._client.async_send_clear(
**{const.KEY_PRIORITY: self._get_option(CONF_PRIORITY)}
):
return
await self._client.async_send_set_color(
**{
const.KEY_PRIORITY: self._get_option(CONF_PRIORITY),
const.KEY_COLOR: COLOR_BLACK,
const.KEY_ORIGIN: DEFAULT_ORIGIN,
}
)
@property
def _support_external_effects(self) -> bool:
"""Whether or not to support setting external effects from the light entity."""
return False
def _get_priority_entry_that_dictates_state(self) -> dict[str, Any] | None:
"""Get the relevant Hyperion priority entry to consider."""
# Return the active priority (if any) at the configured HA priority.
for candidate in self._client.priorities or []:
if const.KEY_PRIORITY not in candidate:
continue
if candidate[const.KEY_PRIORITY] == self._get_option(
CONF_PRIORITY
) and candidate.get(const.KEY_ACTIVE, False):
# Explicit type specifier to ensure this works when the underlying
# (typed) library is installed along with the tests. Casts would trigger
# a redundant-cast warning in this case.
output: dict[str, Any] = candidate
return output
# Return whether or not the HA priority is among the active priorities.
for priority in self._client.priorities or []:
if priority.get(const.KEY_PRIORITY) == self._get_option(CONF_PRIORITY):
return priority
return None
@classmethod
def _is_priority_entry_black(cls, priority: dict[str, Any] | None) -> bool:
"""Determine if a given priority entry is the color black."""
if (
priority
and priority.get(const.KEY_COMPONENTID) == const.KEY_COMPONENTID_COLOR
):
rgb_color = priority.get(const.KEY_VALUE, {}).get(const.KEY_RGB)
if rgb_color is not None and tuple(rgb_color) == COLOR_BLACK:
return True
return False
def _allow_priority_update(self, priority: dict[str, Any] | None = None) -> bool:
"""Determine whether to allow a Hyperion priority to update entity attributes."""
# Black is treated as 'off' (and Home Assistant does not support selecting black
# from the color selector). Do not set our internal attributes if the priority is
# 'off' (i.e. if black is active). Do this to ensure it seamlessly turns back on
# at the correct prior color on the next 'on' call.
return not HyperionPriorityLight._is_priority_entry_black(priority)
LIGHT_TYPES = {
TYPE_HYPERION_LIGHT: HyperionLight,
TYPE_HYPERION_PRIORITY_LIGHT: HyperionPriorityLight,
}

View file

@ -27,7 +27,6 @@ TEST_PRIORITY = 180
TEST_ENTITY_ID_1 = "light.test_instance_1"
TEST_ENTITY_ID_2 = "light.test_instance_2"
TEST_ENTITY_ID_3 = "light.test_instance_3"
TEST_PRIORITY_LIGHT_ENTITY_ID_1 = "light.test_instance_1_priority"
TEST_TITLE = f"{TEST_HOST}:{TEST_PORT}"
TEST_TOKEN = "sekr1t"

View file

@ -795,10 +795,8 @@ async def test_options_effect_show_list(hass: HomeAssistant) -> None:
await hass.async_block_till_done()
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
# effect1 and effect3 only, so effect2 & external sources are hidden.
assert result["data"][CONF_EFFECT_HIDE_LIST] == sorted(
["effect2"] + const.KEY_COMPONENTID_EXTERNAL_SOURCES
)
# effect1 and effect3 only, so effect2 is hidden.
assert result["data"][CONF_EFFECT_HIDE_LIST] == ["effect2"]
async def test_options_effect_hide_list_cannot_connect(hass: HomeAssistant) -> None:

View file

@ -1,7 +1,6 @@
"""Tests for the Hyperion integration."""
from __future__ import annotations
from datetime import timedelta
from unittest.mock import AsyncMock, Mock, call, patch
from hyperion import const
@ -17,7 +16,6 @@ from homeassistant.components.hyperion.const import (
DOMAIN,
HYPERION_MANUFACTURER_NAME,
HYPERION_MODEL_NAME,
TYPE_HYPERION_PRIORITY_LIGHT,
)
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
@ -27,12 +25,7 @@ from homeassistant.components.light import (
ColorMode,
LightEntityFeature,
)
from homeassistant.config_entries import (
RELOAD_AFTER_UPDATE_DELAY,
SOURCE_REAUTH,
ConfigEntry,
ConfigEntryState,
)
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry, ConfigEntryState
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_HOST,
@ -44,8 +37,6 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.util import dt as dt_util
import homeassistant.util.color as color_util
from . import (
TEST_AUTH_NOT_REQUIRED_RESP,
@ -62,19 +53,13 @@ from . import (
TEST_INSTANCE_3,
TEST_PORT,
TEST_PRIORITY,
TEST_PRIORITY_LIGHT_ENTITY_ID_1,
TEST_SYSINFO_ID,
add_test_config_entry,
call_registered_callback,
create_mock_client,
register_test_entity,
setup_test_config_entry,
)
from tests.common import async_fire_time_changed
COLOR_BLACK = color_util.COLORS["black"]
def _get_config_entry_from_unique_id(
hass: HomeAssistant, unique_id: str
@ -247,6 +232,7 @@ async def test_setup_config_entry_dynamic_instances(hass: HomeAssistant) -> None
async def test_light_basic_properties(hass: HomeAssistant) -> None:
"""Test the basic properties."""
client = create_mock_client()
client.priorities = [{const.KEY_PRIORITY: TEST_PRIORITY}]
await setup_test_config_entry(hass, hyperion_client=client)
entity_state = hass.states.get(TEST_ENTITY_ID_1)
@ -257,8 +243,8 @@ async def test_light_basic_properties(hass: HomeAssistant) -> None:
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
# By default the effect list contains only 'Solid'.
assert len(entity_state.attributes["effect_list"]) == 1
assert entity_state.attributes["color_mode"] == ColorMode.HS
assert entity_state.attributes["supported_color_modes"] == [ColorMode.HS]
@ -268,6 +254,7 @@ async def test_light_basic_properties(hass: HomeAssistant) -> None:
async def test_light_async_turn_on(hass: HomeAssistant) -> None:
"""Test turning the light on."""
client = create_mock_client()
client.priorities = [{const.KEY_PRIORITY: TEST_PRIORITY}]
await setup_test_config_entry(hass, hyperion_client=client)
# On (=), 100% (=), solid (=), [255,255,255] (=)
@ -345,10 +332,13 @@ async def test_light_async_turn_on(hass: HomeAssistant) -> None:
)
# 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)},
}
client.priorities = [
{
const.KEY_PRIORITY: TEST_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_1)
@ -385,54 +375,6 @@ async def test_light_async_turn_on(hass: HomeAssistant) -> None:
assert entity_state
assert entity_state.attributes["brightness"] == brightness
# On (=), 100% (=), "USB Capture (!), [0,255,255] (=)
component = "V4L"
effect = const.KEY_COMPONENTID_TO_NAME[component]
client.async_send_clear = AsyncMock(return_value=True)
client.async_send_set_component = AsyncMock(return_value=True)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: TEST_ENTITY_ID_1, 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: component}
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_ENTITY_ID_1)
assert entity_state
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 = AsyncMock(return_value=True)
@ -445,9 +387,6 @@ async def test_light_async_turn_on(hass: HomeAssistant) -> None:
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,
@ -455,10 +394,13 @@ async def test_light_async_turn_on(hass: HomeAssistant) -> None:
const.KEY_ORIGIN: DEFAULT_ORIGIN,
}
)
client.visible_priority = {
const.KEY_COMPONENTID: const.KEY_COMPONENTID_EFFECT,
const.KEY_OWNER: effect,
}
client.priorities = [
{
const.KEY_PRIORITY: TEST_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_1)
assert entity_state
@ -484,10 +426,13 @@ async def test_light_async_turn_on(hass: HomeAssistant) -> None:
}
)
# Simulate a state callback from Hyperion.
client.visible_priority = {
const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR,
const.KEY_VALUE: {const.KEY_RGB: (0, 0, 255)},
}
client.priorities = [
{
const.KEY_PRIORITY: TEST_PRIORITY,
const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR,
const.KEY_VALUE: {const.KEY_RGB: (0, 0, 255)},
}
]
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_ENTITY_ID_1)
assert entity_state
@ -509,82 +454,6 @@ async def test_light_async_turn_on(hass: HomeAssistant) -> None:
assert not client.async_send_set_effect.called
async def test_light_async_turn_on_fail_async_send_set_component(
hass: HomeAssistant,
) -> None:
"""Test set_component failure when turning the light on."""
client = create_mock_client()
client.async_send_set_component = AsyncMock(return_value=False)
client.is_on = Mock(return_value=False)
await setup_test_config_entry(hass, hyperion_client=client)
await hass.services.async_call(
LIGHT_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: TEST_ENTITY_ID_1}, blocking=True
)
assert client.method_calls[-1] == call.async_send_set_component(
componentstate={"component": "ALL", "state": True}
)
async def test_light_async_turn_on_fail_async_send_set_component_source(
hass: HomeAssistant,
) -> None:
"""Test async_send_set_component failure when selecting the source."""
client = create_mock_client()
client.async_send_clear = AsyncMock(return_value=True)
client.async_send_set_component = AsyncMock(return_value=False)
client.is_on = Mock(return_value=True)
await setup_test_config_entry(hass, hyperion_client=client)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: TEST_ENTITY_ID_1,
ATTR_EFFECT: const.KEY_COMPONENTID_TO_NAME["V4L"],
},
blocking=True,
)
assert client.method_calls[-1] == call.async_send_set_component(
componentstate={"component": "BOBLIGHTSERVER", "state": False}
)
async def test_light_async_turn_on_fail_async_send_clear_source(
hass: HomeAssistant,
) -> None:
"""Test async_send_clear failure when turning the light on."""
client = create_mock_client()
client.is_on = Mock(return_value=True)
client.async_send_clear = AsyncMock(return_value=False)
await setup_test_config_entry(hass, hyperion_client=client)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: TEST_ENTITY_ID_1,
ATTR_EFFECT: const.KEY_COMPONENTID_TO_NAME["V4L"],
},
blocking=True,
)
assert client.method_calls[-1] == call.async_send_clear(priority=180)
async def test_light_async_turn_on_fail_async_send_clear_effect(
hass: HomeAssistant,
) -> None:
"""Test async_send_clear failure when turning on an effect."""
client = create_mock_client()
client.is_on = Mock(return_value=True)
client.async_send_clear = AsyncMock(return_value=False)
await setup_test_config_entry(hass, hyperion_client=client)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: TEST_ENTITY_ID_1, ATTR_EFFECT: "Warm Mood Blobs"},
blocking=True,
)
assert client.method_calls[-1] == call.async_send_clear(priority=180)
async def test_light_async_turn_on_fail_async_send_set_effect(
hass: HomeAssistant,
) -> None:
@ -625,12 +494,12 @@ async def test_light_async_turn_on_fail_async_send_set_color(
)
async def test_light_async_turn_off_fail_async_send_set_component(
async def test_light_async_turn_off_fail_async_send_send_clear(
hass: HomeAssistant,
) -> None:
"""Test async_send_set_component failure when turning off the light."""
"""Test async_send_clear failure when turning off the light."""
client = create_mock_client()
client.async_send_set_component = AsyncMock(return_value=False)
client.async_send_clear = AsyncMock(return_value=False)
await setup_test_config_entry(hass, hyperion_client=client)
await hass.services.async_call(
@ -639,30 +508,7 @@ async def test_light_async_turn_off_fail_async_send_set_component(
{ATTR_ENTITY_ID: TEST_ENTITY_ID_1},
blocking=True,
)
assert client.method_calls[-1] == call.async_send_set_component(
componentstate={"component": "LEDDEVICE", "state": False}
)
async def test_priority_light_async_turn_off_fail_async_send_clear(
hass: HomeAssistant,
) -> None:
"""Test async_send_clear failure when turning off a priority light."""
client = create_mock_client()
client.async_send_clear = AsyncMock(return_value=False)
with patch(
"homeassistant.components.hyperion.light.HyperionPriorityLight.entity_registry_enabled_default"
) as enabled_by_default_mock:
enabled_by_default_mock.return_value = True
await setup_test_config_entry(hass, hyperion_client=client)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: TEST_PRIORITY_LIGHT_ENTITY_ID_1},
blocking=True,
)
assert client.method_calls[-1] == call.async_send_clear(priority=180)
assert client.method_calls[-1] == call.async_send_clear(priority=TEST_PRIORITY)
async def test_light_async_turn_off(hass: HomeAssistant) -> None:
@ -670,7 +516,7 @@ async def test_light_async_turn_off(hass: HomeAssistant) -> None:
client = create_mock_client()
await setup_test_config_entry(hass, hyperion_client=client)
client.async_send_set_component = AsyncMock(return_value=True)
client.async_send_clear = AsyncMock(return_value=True)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
@ -678,40 +524,18 @@ async def test_light_async_turn_off(hass: HomeAssistant) -> None:
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,
}
}
assert client.async_send_clear.called
assert client.async_send_clear.call_args == call(
**{const.KEY_PRIORITY: TEST_PRIORITY}
)
call_registered_callback(client, "components-update")
entity_state = hass.states.get(TEST_ENTITY_ID_1)
assert entity_state
assert entity_state.attributes["icon"] == hyperion_light.ICON_LIGHTBULB
# No calls if no state loaded.
client.has_loaded_state = False
client.async_send_set_component = AsyncMock(return_value=True)
call_registered_callback(client, "client-update", {"loaded-state": False})
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: TEST_ENTITY_ID_1},
blocking=True,
)
assert not client.async_send_set_component.called
async def test_light_async_updates_from_hyperion_client(
hass: HomeAssistant,
) -> None:
"""Test receiving a variety of Hyperion client callbacks."""
client = create_mock_client()
client.priorities = [{const.KEY_PRIORITY: TEST_PRIORITY}]
await setup_test_config_entry(hass, hyperion_client=client)
# Bright change gets accepted.
@ -720,6 +544,7 @@ async def test_light_async_updates_from_hyperion_client(
call_registered_callback(client, "adjustment-update")
entity_state = hass.states.get(TEST_ENTITY_ID_1)
assert entity_state
assert entity_state.state == "on"
assert entity_state.attributes["brightness"] == round(255 * (brightness / 100.0))
# Broken brightness value is ignored.
@ -728,40 +553,18 @@ async def test_light_async_updates_from_hyperion_client(
call_registered_callback(client, "adjustment-update")
entity_state = hass.states.get(TEST_ENTITY_ID_1)
assert entity_state
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_1)
assert entity_state
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_1)
assert entity_state
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_1)
assert entity_state
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_TO_NAME[const.KEY_COMPONENTID_V4L]
)
assert entity_state.attributes["brightness"] == round(255 * (brightness / 100.0))
# Update priorities (Effect)
effect = "foo"
client.visible_priority = {
const.KEY_COMPONENTID: const.KEY_COMPONENTID_EFFECT,
const.KEY_OWNER: effect,
}
client.priorities = [
{
const.KEY_PRIORITY: TEST_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_1)
@ -772,10 +575,13 @@ async def test_light_async_updates_from_hyperion_client(
# Update priorities (Color)
rgb = (0, 100, 100)
client.visible_priority = {
const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR,
const.KEY_VALUE: {const.KEY_RGB: rgb},
}
client.priorities = [
{
const.KEY_PRIORITY: TEST_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_1)
@ -785,7 +591,7 @@ async def test_light_async_updates_from_hyperion_client(
assert entity_state.attributes["hs_color"] == (180.0, 100.0)
# Update priorities (None)
client.visible_priority = None
client.priorities = []
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_ENTITY_ID_1)
@ -800,12 +606,7 @@ async def test_light_async_updates_from_hyperion_client(
assert entity_state
assert entity_state.attributes["effect_list"] == [
hyperion_light.KEY_EFFECT_SOLID
] + [
const.KEY_COMPONENTID_TO_NAME[component]
for component in const.KEY_COMPONENTID_EXTERNAL_SOURCES
] + [
effect[const.KEY_NAME] for effect in effects
]
] + [effect[const.KEY_NAME] for effect in effects]
# Update connection status (e.g. disconnection).
@ -818,10 +619,13 @@ async def test_light_async_updates_from_hyperion_client(
# Update connection status (e.g. re-connection)
client.has_loaded_state = True
client.visible_priority = {
const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR,
const.KEY_VALUE: {const.KEY_RGB: rgb},
}
client.priorities = [
{
const.KEY_PRIORITY: TEST_PRIORITY,
const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR,
const.KEY_VALUE: {const.KEY_RGB: rgb},
}
]
call_registered_callback(client, "client-update", {"loaded-state": True})
entity_state = hass.states.get(TEST_ENTITY_ID_1)
assert entity_state
@ -835,10 +639,13 @@ async def test_full_state_loaded_on_start(hass: HomeAssistant) -> None:
# 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.priorities = [
{
const.KEY_PRIORITY: TEST_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_test_config_entry(hass, hyperion_client=client)
@ -940,358 +747,6 @@ async def test_setup_entry_bad_token_reauth(hass: HomeAssistant) -> None:
assert config_entry.state is ConfigEntryState.SETUP_ERROR
async def test_priority_light_async_updates(
hass: HomeAssistant,
) -> None:
"""Test receiving a variety of Hyperion client callbacks to a HyperionPriorityLight."""
priority_template = {
const.KEY_ACTIVE: True,
const.KEY_VISIBLE: True,
const.KEY_PRIORITY: TEST_PRIORITY,
const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR,
const.KEY_VALUE: {const.KEY_RGB: (100, 100, 100)},
}
client = create_mock_client()
client.priorities = [{**priority_template}]
register_test_entity(
hass,
LIGHT_DOMAIN,
TYPE_HYPERION_PRIORITY_LIGHT,
TEST_PRIORITY_LIGHT_ENTITY_ID_1,
)
await setup_test_config_entry(hass, hyperion_client=client)
# == Scenario: Color at HA priority will show light as on.
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entity_state
assert entity_state.state == "on"
assert entity_state.attributes["hs_color"] == (0.0, 0.0)
# == Scenario: Color going to black shows the light as off.
client.priorities = [
{
**priority_template,
const.KEY_VALUE: {const.KEY_RGB: COLOR_BLACK},
}
]
client.visible_priority = client.priorities[0]
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entity_state
assert entity_state.state == "off"
# == Scenario: Lower priority than HA priority should have no impact on what HA
# shows when the HA priority is present.
client.priorities = [
{**priority_template, const.KEY_PRIORITY: TEST_PRIORITY - 1},
{
**priority_template,
const.KEY_VALUE: {const.KEY_RGB: COLOR_BLACK},
},
]
client.visible_priority = client.priorities[0]
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entity_state
assert entity_state.state == "off"
# == Scenario: Fresh color at HA priority should turn HA entity on (even though
# there's a lower priority enabled/visible in Hyperion).
client.priorities = [
{**priority_template, const.KEY_PRIORITY: TEST_PRIORITY - 1},
{
**priority_template,
const.KEY_PRIORITY: TEST_PRIORITY,
const.KEY_VALUE: {const.KEY_RGB: (100, 100, 150)},
},
]
client.visible_priority = client.priorities[0]
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entity_state
assert entity_state.state == "on"
assert entity_state.attributes["hs_color"] == (240.0, 33.333)
# == Scenario: V4L at a higher priority, with no other HA priority at all, should
# have no effect.
# Emulate HA turning the light off with black at the HA priority.
client.priorities = []
client.visible_priority = None
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entity_state
assert entity_state.state == "off"
# Emulate V4L turning on.
client.priorities = [
{
**priority_template,
const.KEY_PRIORITY: 240,
const.KEY_COMPONENTID: const.KEY_COMPONENTID_V4L,
const.KEY_VALUE: {const.KEY_RGB: (100, 100, 150)},
},
]
client.visible_priority = client.priorities[0]
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entity_state
assert entity_state.state == "off"
# == Scenario: A lower priority input (lower priority than HA) should have no effect.
client.priorities = [
{
**priority_template,
const.KEY_VISIBLE: True,
const.KEY_PRIORITY: TEST_PRIORITY - 1,
const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR,
const.KEY_VALUE: {const.KEY_RGB: (255, 0, 0)},
},
{
**priority_template,
const.KEY_PRIORITY: 240,
const.KEY_COMPONENTID: const.KEY_COMPONENTID_V4L,
const.KEY_VALUE: {const.KEY_RGB: (100, 100, 150)},
const.KEY_VISIBLE: False,
},
]
client.visible_priority = client.priorities[0]
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entity_state
assert entity_state.state == "off"
# == Scenario: A non-active priority is ignored.
client.priorities = [
{
const.KEY_ACTIVE: False,
const.KEY_VISIBLE: False,
const.KEY_PRIORITY: TEST_PRIORITY,
const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR,
const.KEY_VALUE: {const.KEY_RGB: (100, 100, 100)},
}
]
client.visible_priority = None
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entity_state
assert entity_state.state == "off"
# == Scenario: A priority with no ... priority ... is ignored.
client.priorities = [
{
const.KEY_ACTIVE: True,
const.KEY_VISIBLE: True,
const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR,
const.KEY_VALUE: {const.KEY_RGB: (100, 100, 100)},
}
]
client.visible_priority = None
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entity_state
assert entity_state.state == "off"
async def test_priority_light_async_updates_off_sets_black(
hass: HomeAssistant,
) -> None:
"""Test turning the HyperionPriorityLight off."""
client = create_mock_client()
client.priorities = [
{
const.KEY_ACTIVE: True,
const.KEY_VISIBLE: True,
const.KEY_PRIORITY: TEST_PRIORITY,
const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR,
const.KEY_VALUE: {const.KEY_RGB: (100, 100, 100)},
}
]
register_test_entity(
hass,
LIGHT_DOMAIN,
TYPE_HYPERION_PRIORITY_LIGHT,
TEST_PRIORITY_LIGHT_ENTITY_ID_1,
)
await setup_test_config_entry(hass, hyperion_client=client)
client.async_send_clear = AsyncMock(return_value=True)
client.async_send_set_color = AsyncMock(return_value=True)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: TEST_PRIORITY_LIGHT_ENTITY_ID_1},
blocking=True,
)
assert client.async_send_clear.call_args == call(
**{
const.KEY_PRIORITY: TEST_PRIORITY,
}
)
assert client.async_send_set_color.call_args == call(
**{
const.KEY_PRIORITY: TEST_PRIORITY,
const.KEY_COLOR: COLOR_BLACK,
const.KEY_ORIGIN: DEFAULT_ORIGIN,
}
)
async def test_priority_light_prior_color_preserved_after_black(
hass: HomeAssistant,
) -> None:
"""Test that color is preserved in an on->off->on cycle for a HyperionPriorityLight.
For a HyperionPriorityLight the color black is used to indicate off. This test
ensures that a cycle through 'off' will preserve the original color.
"""
priority_template = {
const.KEY_ACTIVE: True,
const.KEY_VISIBLE: True,
const.KEY_PRIORITY: TEST_PRIORITY,
const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR,
}
client = create_mock_client()
client.async_send_set_color = AsyncMock(return_value=True)
client.async_send_clear = AsyncMock(return_value=True)
client.priorities = []
client.visible_priority = None
register_test_entity(
hass,
LIGHT_DOMAIN,
TYPE_HYPERION_PRIORITY_LIGHT,
TEST_PRIORITY_LIGHT_ENTITY_ID_1,
)
await setup_test_config_entry(hass, hyperion_client=client)
# Turn the light on full green...
# On (=), 100% (=), solid (=), [0,0,255] (=)
hs_color = (240.0, 100.0)
rgb_color = (0, 0, 255)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: TEST_PRIORITY_LIGHT_ENTITY_ID_1, ATTR_HS_COLOR: hs_color},
blocking=True,
)
assert client.async_send_set_color.call_args == call(
**{
const.KEY_PRIORITY: TEST_PRIORITY,
const.KEY_COLOR: rgb_color,
const.KEY_ORIGIN: DEFAULT_ORIGIN,
}
)
client.priorities = [
{
**priority_template,
const.KEY_VALUE: {const.KEY_RGB: rgb_color},
}
]
client.visible_priority = client.priorities[0]
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entity_state
assert entity_state.state == "on"
assert entity_state.attributes["hs_color"] == hs_color
# Then turn it off.
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: TEST_PRIORITY_LIGHT_ENTITY_ID_1},
blocking=True,
)
assert client.async_send_set_color.call_args == call(
**{
const.KEY_PRIORITY: TEST_PRIORITY,
const.KEY_COLOR: COLOR_BLACK,
const.KEY_ORIGIN: DEFAULT_ORIGIN,
}
)
client.priorities = [
{
**priority_template,
const.KEY_VALUE: {const.KEY_RGB: COLOR_BLACK},
}
]
client.visible_priority = client.priorities[0]
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entity_state
assert entity_state.state == "off"
# Then turn it back on and ensure it's still green.
# On (=), 100% (=), solid (=), [0,0,255] (=)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: TEST_PRIORITY_LIGHT_ENTITY_ID_1},
blocking=True,
)
assert client.async_send_set_color.call_args == call(
**{
const.KEY_PRIORITY: TEST_PRIORITY,
const.KEY_COLOR: rgb_color,
const.KEY_ORIGIN: DEFAULT_ORIGIN,
}
)
client.priorities = [
{
**priority_template,
const.KEY_VALUE: {const.KEY_RGB: rgb_color},
}
]
client.visible_priority = client.priorities[0]
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entity_state
assert entity_state.state == "on"
assert entity_state.attributes["hs_color"] == hs_color
async def test_priority_light_has_no_external_sources(hass: HomeAssistant) -> None:
"""Ensure a HyperionPriorityLight does not list external sources."""
client = create_mock_client()
client.priorities = []
register_test_entity(
hass,
LIGHT_DOMAIN,
TYPE_HYPERION_PRIORITY_LIGHT,
TEST_PRIORITY_LIGHT_ENTITY_ID_1,
)
await setup_test_config_entry(hass, hyperion_client=client)
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entity_state
assert entity_state.attributes["effect_list"] == [hyperion_light.KEY_EFFECT_SOLID]
async def test_light_option_effect_hide_list(hass: HomeAssistant) -> None:
"""Test the effect_hide_list option."""
client = create_mock_client()
@ -1300,15 +755,13 @@ async def test_light_option_effect_hide_list(hass: HomeAssistant) -> None:
await setup_test_config_entry(
hass,
hyperion_client=client,
options={CONF_EFFECT_HIDE_LIST: ["Two", "USB Capture"]},
options={CONF_EFFECT_HIDE_LIST: ["Two", "Three"]},
)
entity_state = hass.states.get(TEST_ENTITY_ID_1)
assert entity_state
assert entity_state.attributes["effect_list"] == [
"Solid",
"Boblight Server",
"Platform Capture",
"One",
]
@ -1317,12 +770,6 @@ async def test_device_info(hass: HomeAssistant) -> None:
"""Verify device information includes expected details."""
client = create_mock_client()
register_test_entity(
hass,
LIGHT_DOMAIN,
TYPE_HYPERION_PRIORITY_LIGHT,
TEST_PRIORITY_LIGHT_ENTITY_ID_1,
)
await setup_test_config_entry(hass, hyperion_client=client)
device_id = get_hyperion_device_id(TEST_SYSINFO_ID, TEST_INSTANCE)
@ -1341,82 +788,4 @@ async def test_device_info(hass: HomeAssistant) -> None:
entry.entity_id
for entry in er.async_entries_for_device(entity_registry, device.id)
]
assert TEST_PRIORITY_LIGHT_ENTITY_ID_1 in entities_from_device
assert TEST_ENTITY_ID_1 in entities_from_device
async def test_lights_can_be_enabled(hass: HomeAssistant) -> None:
"""Verify lights can be enabled."""
client = create_mock_client()
await setup_test_config_entry(hass, hyperion_client=client)
entity_registry = er.async_get(hass)
entry = entity_registry.async_get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entry
assert entry.disabled
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert not entity_state
with patch(
"homeassistant.components.hyperion.client.HyperionClient",
return_value=client,
):
updated_entry = entity_registry.async_update_entity(
TEST_PRIORITY_LIGHT_ENTITY_ID_1, disabled_by=None
)
assert not updated_entry.disabled
await hass.async_block_till_done()
async_fire_time_changed(
hass,
dt_util.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1),
)
await hass.async_block_till_done()
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
assert entity_state
async def test_deprecated_effect_names(
caplog: pytest.LogCaptureFixture, hass: HomeAssistant
) -> None:
"""Test deprecated effects function and issue a warning."""
client = create_mock_client()
client.async_send_clear = AsyncMock(return_value=True)
client.async_send_set_component = AsyncMock(return_value=True)
await setup_test_config_entry(hass, hyperion_client=client)
for component in const.KEY_COMPONENTID_EXTERNAL_SOURCES:
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: TEST_ENTITY_ID_1, ATTR_EFFECT: component},
blocking=True,
)
assert f"Use of Hyperion effect '{component}' is deprecated" in caplog.text
# Simulate a state callback from Hyperion.
client.visible_priority = {
const.KEY_COMPONENTID: component,
}
call_registered_callback(client, "priorities-update")
entity_state = hass.states.get(TEST_ENTITY_ID_1)
assert entity_state
assert (
entity_state.attributes["effect"]
== const.KEY_COMPONENTID_TO_NAME[component]
)
async def test_deprecated_effect_names_not_in_effect_list(
hass: HomeAssistant,
) -> None:
"""Test deprecated effects are not in shown effect list."""
await setup_test_config_entry(hass)
entity_state = hass.states.get(TEST_ENTITY_ID_1)
assert entity_state
for component in const.KEY_COMPONENTID_EXTERNAL_SOURCES:
assert component not in entity_state.attributes["effect_list"]