mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Add Hyperion device support (#47881)
* Add Hyperion device support. * Update to the new typing annotations. * Add device cleanup logic. * Fixes based on the excellent feedback from emontnemery
This commit is contained in:
parent
5bf3469ffc
commit
63d42867e8
@ -15,14 +15,11 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN
|
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.dispatcher import (
|
from homeassistant.helpers.dispatcher import (
|
||||||
async_dispatcher_connect,
|
async_dispatcher_connect,
|
||||||
async_dispatcher_send,
|
async_dispatcher_send,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.entity_registry import (
|
|
||||||
async_entries_for_config_entry,
|
|
||||||
async_get_registry,
|
|
||||||
)
|
|
||||||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -72,6 +69,11 @@ def get_hyperion_unique_id(server_id: str, instance: int, name: str) -> str:
|
|||||||
return f"{server_id}_{instance}_{name}"
|
return f"{server_id}_{instance}_{name}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_hyperion_device_id(server_id: str, instance: int) -> str:
|
||||||
|
"""Get an id for a Hyperion device/instance."""
|
||||||
|
return f"{server_id}_{instance}"
|
||||||
|
|
||||||
|
|
||||||
def split_hyperion_unique_id(unique_id: str) -> tuple[str, int, str] | None:
|
def split_hyperion_unique_id(unique_id: str) -> tuple[str, int, str] | None:
|
||||||
"""Split a unique_id into a (server_id, instance, type) tuple."""
|
"""Split a unique_id into a (server_id, instance, type) tuple."""
|
||||||
data = tuple(unique_id.split("_", 2))
|
data = tuple(unique_id.split("_", 2))
|
||||||
@ -202,7 +204,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||||||
|
|
||||||
async def async_instances_to_clients_raw(instances: list[dict[str, Any]]) -> None:
|
async def async_instances_to_clients_raw(instances: list[dict[str, Any]]) -> None:
|
||||||
"""Convert instances to Hyperion clients."""
|
"""Convert instances to Hyperion clients."""
|
||||||
registry = await async_get_registry(hass)
|
device_registry = dr.async_get(hass)
|
||||||
running_instances: set[int] = set()
|
running_instances: set[int] = set()
|
||||||
stopped_instances: set[int] = set()
|
stopped_instances: set[int] = set()
|
||||||
existing_instances = hass.data[DOMAIN][config_entry.entry_id][
|
existing_instances = hass.data[DOMAIN][config_entry.entry_id][
|
||||||
@ -249,15 +251,20 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||||||
hass, SIGNAL_INSTANCE_REMOVE.format(config_entry.entry_id), instance_num
|
hass, SIGNAL_INSTANCE_REMOVE.format(config_entry.entry_id), instance_num
|
||||||
)
|
)
|
||||||
|
|
||||||
# Deregister entities that belong to removed instances.
|
# Ensure every device associated with this config entry is still in the list of
|
||||||
for entry in async_entries_for_config_entry(registry, config_entry.entry_id):
|
# motionEye cameras, otherwise remove the device (and thus entities).
|
||||||
data = split_hyperion_unique_id(entry.unique_id)
|
known_devices = {
|
||||||
if not data:
|
get_hyperion_device_id(server_id, instance_num)
|
||||||
continue
|
for instance_num in running_instances | stopped_instances
|
||||||
if data[0] == server_id and (
|
}
|
||||||
data[1] not in running_instances and data[1] not in stopped_instances
|
for device_entry in dr.async_entries_for_config_entry(
|
||||||
|
device_registry, config_entry.entry_id
|
||||||
):
|
):
|
||||||
registry.async_remove(entry.entity_id)
|
for (kind, key) in device_entry.identifiers:
|
||||||
|
if kind == DOMAIN and key in known_devices:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
device_registry.async_remove_device(device_entry.id)
|
||||||
|
|
||||||
hyperion_client.set_callbacks(
|
hyperion_client.set_callbacks(
|
||||||
{
|
{
|
||||||
|
@ -40,6 +40,8 @@ DEFAULT_PRIORITY = 128
|
|||||||
|
|
||||||
DOMAIN = "hyperion"
|
DOMAIN = "hyperion"
|
||||||
|
|
||||||
|
HYPERION_MANUFACTURER_NAME = "Hyperion"
|
||||||
|
HYPERION_MODEL_NAME = f"{HYPERION_MANUFACTURER_NAME}-NG"
|
||||||
HYPERION_RELEASES_URL = "https://github.com/hyperion-project/hyperion.ng/releases"
|
HYPERION_RELEASES_URL = "https://github.com/hyperion-project/hyperion.ng/releases"
|
||||||
HYPERION_VERSION_WARN_CUTOFF = "2.0.0-alpha.9"
|
HYPERION_VERSION_WARN_CUTOFF = "2.0.0-alpha.9"
|
||||||
|
|
||||||
|
@ -26,7 +26,11 @@ from homeassistant.helpers.dispatcher import (
|
|||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
import homeassistant.util.color as color_util
|
import homeassistant.util.color as color_util
|
||||||
|
|
||||||
from . import get_hyperion_unique_id, listen_for_instance_updates
|
from . import (
|
||||||
|
get_hyperion_device_id,
|
||||||
|
get_hyperion_unique_id,
|
||||||
|
listen_for_instance_updates,
|
||||||
|
)
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_EFFECT_HIDE_LIST,
|
CONF_EFFECT_HIDE_LIST,
|
||||||
CONF_INSTANCE_CLIENTS,
|
CONF_INSTANCE_CLIENTS,
|
||||||
@ -34,6 +38,8 @@ from .const import (
|
|||||||
DEFAULT_ORIGIN,
|
DEFAULT_ORIGIN,
|
||||||
DEFAULT_PRIORITY,
|
DEFAULT_PRIORITY,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
HYPERION_MANUFACTURER_NAME,
|
||||||
|
HYPERION_MODEL_NAME,
|
||||||
NAME_SUFFIX_HYPERION_LIGHT,
|
NAME_SUFFIX_HYPERION_LIGHT,
|
||||||
NAME_SUFFIX_HYPERION_PRIORITY_LIGHT,
|
NAME_SUFFIX_HYPERION_PRIORITY_LIGHT,
|
||||||
SIGNAL_ENTITY_REMOVE,
|
SIGNAL_ENTITY_REMOVE,
|
||||||
@ -85,24 +91,17 @@ async def async_setup_entry(
|
|||||||
def instance_add(instance_num: int, instance_name: str) -> None:
|
def instance_add(instance_num: int, instance_name: str) -> None:
|
||||||
"""Add entities for a new Hyperion instance."""
|
"""Add entities for a new Hyperion instance."""
|
||||||
assert server_id
|
assert server_id
|
||||||
|
args = (
|
||||||
|
server_id,
|
||||||
|
instance_num,
|
||||||
|
instance_name,
|
||||||
|
config_entry.options,
|
||||||
|
entry_data[CONF_INSTANCE_CLIENTS][instance_num],
|
||||||
|
)
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
[
|
[
|
||||||
HyperionLight(
|
HyperionLight(*args),
|
||||||
get_hyperion_unique_id(
|
HyperionPriorityLight(*args),
|
||||||
server_id, instance_num, TYPE_HYPERION_LIGHT
|
|
||||||
),
|
|
||||||
f"{instance_name} {NAME_SUFFIX_HYPERION_LIGHT}",
|
|
||||||
config_entry.options,
|
|
||||||
entry_data[CONF_INSTANCE_CLIENTS][instance_num],
|
|
||||||
),
|
|
||||||
HyperionPriorityLight(
|
|
||||||
get_hyperion_unique_id(
|
|
||||||
server_id, instance_num, TYPE_HYPERION_PRIORITY_LIGHT
|
|
||||||
),
|
|
||||||
f"{instance_name} {NAME_SUFFIX_HYPERION_PRIORITY_LIGHT}",
|
|
||||||
config_entry.options,
|
|
||||||
entry_data[CONF_INSTANCE_CLIENTS][instance_num],
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -127,14 +126,17 @@ class HyperionBaseLight(LightEntity):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
unique_id: str,
|
server_id: str,
|
||||||
name: str,
|
instance_num: int,
|
||||||
|
instance_name: str,
|
||||||
options: MappingProxyType[str, Any],
|
options: MappingProxyType[str, Any],
|
||||||
hyperion_client: client.HyperionClient,
|
hyperion_client: client.HyperionClient,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
self._unique_id = unique_id
|
self._unique_id = self._compute_unique_id(server_id, instance_num)
|
||||||
self._name = name
|
self._name = self._compute_name(instance_name)
|
||||||
|
self._device_id = get_hyperion_device_id(server_id, instance_num)
|
||||||
|
self._instance_name = instance_name
|
||||||
self._options = options
|
self._options = options
|
||||||
self._client = hyperion_client
|
self._client = hyperion_client
|
||||||
|
|
||||||
@ -156,6 +158,14 @@ class HyperionBaseLight(LightEntity):
|
|||||||
f"{const.KEY_CLIENT}-{const.KEY_UPDATE}": self._update_client,
|
f"{const.KEY_CLIENT}-{const.KEY_UPDATE}": self._update_client,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _compute_unique_id(self, server_id: str, instance_num: int) -> str:
|
||||||
|
"""Compute a unique id for this instance."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _compute_name(self, instance_name: str) -> str:
|
||||||
|
"""Compute the name of the light."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entity_registry_enabled_default(self) -> bool:
|
def entity_registry_enabled_default(self) -> bool:
|
||||||
"""Whether or not the entity is enabled by default."""
|
"""Whether or not the entity is enabled by default."""
|
||||||
@ -216,6 +226,16 @@ class HyperionBaseLight(LightEntity):
|
|||||||
"""Return a unique id for this instance."""
|
"""Return a unique id for this instance."""
|
||||||
return self._unique_id
|
return self._unique_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self) -> dict[str, Any] | None:
|
||||||
|
"""Return device information."""
|
||||||
|
return {
|
||||||
|
"identifiers": {(DOMAIN, self._device_id)},
|
||||||
|
"name": self._instance_name,
|
||||||
|
"manufacturer": HYPERION_MANUFACTURER_NAME,
|
||||||
|
"model": HYPERION_MODEL_NAME,
|
||||||
|
}
|
||||||
|
|
||||||
def _get_option(self, key: str) -> Any:
|
def _get_option(self, key: str) -> Any:
|
||||||
"""Get a value from the provided options."""
|
"""Get a value from the provided options."""
|
||||||
defaults = {
|
defaults = {
|
||||||
@ -412,7 +432,7 @@ class HyperionBaseLight(LightEntity):
|
|||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
async_dispatcher_connect(
|
async_dispatcher_connect(
|
||||||
self.hass,
|
self.hass,
|
||||||
SIGNAL_ENTITY_REMOVE.format(self._unique_id),
|
SIGNAL_ENTITY_REMOVE.format(self.unique_id),
|
||||||
functools.partial(self.async_remove, force_remove=True),
|
functools.partial(self.async_remove, force_remove=True),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -455,6 +475,14 @@ class HyperionLight(HyperionBaseLight):
|
|||||||
shown state rather than exclusively the HA priority.
|
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
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return true if light is on."""
|
"""Return true if light is on."""
|
||||||
@ -504,6 +532,16 @@ class HyperionLight(HyperionBaseLight):
|
|||||||
class HyperionPriorityLight(HyperionBaseLight):
|
class HyperionPriorityLight(HyperionBaseLight):
|
||||||
"""A Hyperion light that only acts on a single Hyperion priority."""
|
"""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
|
@property
|
||||||
def entity_registry_enabled_default(self) -> bool:
|
def entity_registry_enabled_default(self) -> bool:
|
||||||
"""Whether or not the entity is enabled by default."""
|
"""Whether or not the entity is enabled by default."""
|
||||||
|
@ -33,11 +33,17 @@ from homeassistant.helpers.dispatcher import (
|
|||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
from . import get_hyperion_unique_id, listen_for_instance_updates
|
from . import (
|
||||||
|
get_hyperion_device_id,
|
||||||
|
get_hyperion_unique_id,
|
||||||
|
listen_for_instance_updates,
|
||||||
|
)
|
||||||
from .const import (
|
from .const import (
|
||||||
COMPONENT_TO_NAME,
|
COMPONENT_TO_NAME,
|
||||||
CONF_INSTANCE_CLIENTS,
|
CONF_INSTANCE_CLIENTS,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
HYPERION_MANUFACTURER_NAME,
|
||||||
|
HYPERION_MODEL_NAME,
|
||||||
NAME_SUFFIX_HYPERION_COMPONENT_SWITCH,
|
NAME_SUFFIX_HYPERION_COMPONENT_SWITCH,
|
||||||
SIGNAL_ENTITY_REMOVE,
|
SIGNAL_ENTITY_REMOVE,
|
||||||
TYPE_HYPERION_COMPONENT_SWITCH_BASE,
|
TYPE_HYPERION_COMPONENT_SWITCH_BASE,
|
||||||
@ -55,27 +61,18 @@ COMPONENT_SWITCHES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
def _component_to_unique_id(server_id: str, component: str, instance_num: int) -> str:
|
||||||
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable
|
|
||||||
) -> bool:
|
|
||||||
"""Set up a Hyperion platform from config entry."""
|
|
||||||
entry_data = hass.data[DOMAIN][config_entry.entry_id]
|
|
||||||
server_id = config_entry.unique_id
|
|
||||||
|
|
||||||
def component_to_switch_type(component: str) -> str:
|
|
||||||
"""Convert a component to a switch type string."""
|
|
||||||
return slugify(
|
|
||||||
f"{TYPE_HYPERION_COMPONENT_SWITCH_BASE} {COMPONENT_TO_NAME[component]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def component_to_unique_id(component: str, instance_num: int) -> str:
|
|
||||||
"""Convert a component to a unique_id."""
|
"""Convert a component to a unique_id."""
|
||||||
assert server_id
|
|
||||||
return get_hyperion_unique_id(
|
return get_hyperion_unique_id(
|
||||||
server_id, instance_num, component_to_switch_type(component)
|
server_id,
|
||||||
|
instance_num,
|
||||||
|
slugify(
|
||||||
|
f"{TYPE_HYPERION_COMPONENT_SWITCH_BASE} {COMPONENT_TO_NAME[component]}"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def component_to_switch_name(component: str, instance_name: str) -> str:
|
|
||||||
|
def _component_to_switch_name(component: str, instance_name: str) -> str:
|
||||||
"""Convert a component to a switch name."""
|
"""Convert a component to a switch name."""
|
||||||
return (
|
return (
|
||||||
f"{instance_name} "
|
f"{instance_name} "
|
||||||
@ -83,6 +80,14 @@ async def async_setup_entry(
|
|||||||
f"{COMPONENT_TO_NAME.get(component, component.capitalize())}"
|
f"{COMPONENT_TO_NAME.get(component, component.capitalize())}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable
|
||||||
|
) -> bool:
|
||||||
|
"""Set up a Hyperion platform from config entry."""
|
||||||
|
entry_data = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
server_id = config_entry.unique_id
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def instance_add(instance_num: int, instance_name: str) -> None:
|
def instance_add(instance_num: int, instance_name: str) -> None:
|
||||||
"""Add entities for a new Hyperion instance."""
|
"""Add entities for a new Hyperion instance."""
|
||||||
@ -91,8 +96,9 @@ async def async_setup_entry(
|
|||||||
for component in COMPONENT_SWITCHES:
|
for component in COMPONENT_SWITCHES:
|
||||||
switches.append(
|
switches.append(
|
||||||
HyperionComponentSwitch(
|
HyperionComponentSwitch(
|
||||||
component_to_unique_id(component, instance_num),
|
server_id,
|
||||||
component_to_switch_name(component, instance_name),
|
instance_num,
|
||||||
|
instance_name,
|
||||||
component,
|
component,
|
||||||
entry_data[CONF_INSTANCE_CLIENTS][instance_num],
|
entry_data[CONF_INSTANCE_CLIENTS][instance_num],
|
||||||
),
|
),
|
||||||
@ -107,7 +113,7 @@ async def async_setup_entry(
|
|||||||
async_dispatcher_send(
|
async_dispatcher_send(
|
||||||
hass,
|
hass,
|
||||||
SIGNAL_ENTITY_REMOVE.format(
|
SIGNAL_ENTITY_REMOVE.format(
|
||||||
component_to_unique_id(component, instance_num),
|
_component_to_unique_id(server_id, component, instance_num),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -120,14 +126,19 @@ class HyperionComponentSwitch(SwitchEntity):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
unique_id: str,
|
server_id: str,
|
||||||
name: str,
|
instance_num: int,
|
||||||
|
instance_name: str,
|
||||||
component_name: str,
|
component_name: str,
|
||||||
hyperion_client: client.HyperionClient,
|
hyperion_client: client.HyperionClient,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the switch."""
|
"""Initialize the switch."""
|
||||||
self._unique_id = unique_id
|
self._unique_id = _component_to_unique_id(
|
||||||
self._name = name
|
server_id, component_name, instance_num
|
||||||
|
)
|
||||||
|
self._device_id = get_hyperion_device_id(server_id, instance_num)
|
||||||
|
self._name = _component_to_switch_name(component_name, instance_name)
|
||||||
|
self._instance_name = instance_name
|
||||||
self._component_name = component_name
|
self._component_name = component_name
|
||||||
self._client = hyperion_client
|
self._client = hyperion_client
|
||||||
self._client_callbacks = {
|
self._client_callbacks = {
|
||||||
@ -168,6 +179,16 @@ class HyperionComponentSwitch(SwitchEntity):
|
|||||||
"""Return server availability."""
|
"""Return server availability."""
|
||||||
return bool(self._client.has_loaded_state)
|
return bool(self._client.has_loaded_state)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self) -> dict[str, Any] | None:
|
||||||
|
"""Return device information."""
|
||||||
|
return {
|
||||||
|
"identifiers": {(DOMAIN, self._device_id)},
|
||||||
|
"name": self._instance_name,
|
||||||
|
"manufacturer": HYPERION_MANUFACTURER_NAME,
|
||||||
|
"model": HYPERION_MODEL_NAME,
|
||||||
|
}
|
||||||
|
|
||||||
async def _async_send_set_component(self, value: bool) -> None:
|
async def _async_send_set_component(self, value: bool) -> None:
|
||||||
"""Send a component control request."""
|
"""Send a component control request."""
|
||||||
await self._client.async_send_set_component(
|
await self._client.async_send_set_component(
|
||||||
|
@ -7,9 +7,11 @@ from unittest.mock import AsyncMock, Mock, patch
|
|||||||
|
|
||||||
from hyperion import const
|
from hyperion import const
|
||||||
|
|
||||||
|
from homeassistant.components.hyperion import get_hyperion_unique_id
|
||||||
from homeassistant.components.hyperion.const import CONF_PRIORITY, DOMAIN
|
from homeassistant.components.hyperion.const import CONF_PRIORITY, DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
@ -20,7 +22,7 @@ TEST_PORT_UI = const.DEFAULT_PORT_UI + 1
|
|||||||
TEST_INSTANCE = 1
|
TEST_INSTANCE = 1
|
||||||
TEST_ID = "default"
|
TEST_ID = "default"
|
||||||
TEST_SYSINFO_ID = "f9aab089-f85a-55cf-b7c1-222a72faebe9"
|
TEST_SYSINFO_ID = "f9aab089-f85a-55cf-b7c1-222a72faebe9"
|
||||||
TEST_SYSINFO_VERSION = "2.0.0-alpha.8"
|
TEST_SYSINFO_VERSION = "2.0.0-alpha.9"
|
||||||
TEST_PRIORITY = 180
|
TEST_PRIORITY = 180
|
||||||
TEST_ENTITY_ID_1 = "light.test_instance_1"
|
TEST_ENTITY_ID_1 = "light.test_instance_1"
|
||||||
TEST_ENTITY_ID_2 = "light.test_instance_2"
|
TEST_ENTITY_ID_2 = "light.test_instance_2"
|
||||||
@ -168,3 +170,20 @@ def call_registered_callback(
|
|||||||
for call in client.add_callbacks.call_args_list:
|
for call in client.add_callbacks.call_args_list:
|
||||||
if key in call[0][0]:
|
if key in call[0][0]:
|
||||||
call[0][0][key](*args, **kwargs)
|
call[0][0][key](*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def register_test_entity(
|
||||||
|
hass: HomeAssistantType, domain: str, type_name: str, entity_id: str
|
||||||
|
) -> None:
|
||||||
|
"""Register a test entity."""
|
||||||
|
unique_id = get_hyperion_unique_id(TEST_SYSINFO_ID, TEST_INSTANCE, type_name)
|
||||||
|
entity_id = entity_id.split(".")[1]
|
||||||
|
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
entity_registry.async_get_or_create(
|
||||||
|
domain,
|
||||||
|
DOMAIN,
|
||||||
|
unique_id,
|
||||||
|
suggested_object_id=entity_id,
|
||||||
|
disabled_by=None,
|
||||||
|
)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any
|
from typing import Any, Awaitable
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
from hyperion import const
|
from hyperion import const
|
||||||
@ -419,13 +419,13 @@ async def test_auth_create_token_approval_declined_task_canceled(
|
|||||||
class CanceledAwaitableMock(AsyncMock):
|
class CanceledAwaitableMock(AsyncMock):
|
||||||
"""A canceled awaitable mock."""
|
"""A canceled awaitable mock."""
|
||||||
|
|
||||||
def __await__(self):
|
def __await__(self) -> None:
|
||||||
raise asyncio.CancelledError
|
raise asyncio.CancelledError
|
||||||
|
|
||||||
mock_task = CanceledAwaitableMock()
|
mock_task = CanceledAwaitableMock()
|
||||||
task_coro = None
|
task_coro: Awaitable | None = None
|
||||||
|
|
||||||
def create_task(arg):
|
def create_task(arg: Any) -> CanceledAwaitableMock:
|
||||||
nonlocal task_coro
|
nonlocal task_coro
|
||||||
task_coro = arg
|
task_coro = arg
|
||||||
return mock_task
|
return mock_task
|
||||||
@ -453,6 +453,7 @@ async def test_auth_create_token_approval_declined_task_canceled(
|
|||||||
result = await _configure_flow(hass, result)
|
result = await _configure_flow(hass, result)
|
||||||
|
|
||||||
# This await will advance to the next step.
|
# This await will advance to the next step.
|
||||||
|
assert task_coro
|
||||||
await task_coro
|
await task_coro
|
||||||
|
|
||||||
# Assert that cancel is called on the task.
|
# Assert that cancel is called on the task.
|
||||||
|
@ -1,15 +1,22 @@
|
|||||||
"""Tests for the Hyperion integration."""
|
"""Tests for the Hyperion integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
from unittest.mock import AsyncMock, Mock, call, patch
|
from unittest.mock import AsyncMock, Mock, call, patch
|
||||||
|
|
||||||
from hyperion import const
|
from hyperion import const
|
||||||
|
|
||||||
from homeassistant.components.hyperion import light as hyperion_light
|
from homeassistant.components.hyperion import (
|
||||||
|
get_hyperion_device_id,
|
||||||
|
light as hyperion_light,
|
||||||
|
)
|
||||||
from homeassistant.components.hyperion.const import (
|
from homeassistant.components.hyperion.const import (
|
||||||
CONF_EFFECT_HIDE_LIST,
|
CONF_EFFECT_HIDE_LIST,
|
||||||
DEFAULT_ORIGIN,
|
DEFAULT_ORIGIN,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
HYPERION_MANUFACTURER_NAME,
|
||||||
|
HYPERION_MODEL_NAME,
|
||||||
|
TYPE_HYPERION_PRIORITY_LIGHT,
|
||||||
)
|
)
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
@ -19,6 +26,7 @@ from homeassistant.components.light import (
|
|||||||
)
|
)
|
||||||
from homeassistant.config_entries import (
|
from homeassistant.config_entries import (
|
||||||
ENTRY_STATE_SETUP_ERROR,
|
ENTRY_STATE_SETUP_ERROR,
|
||||||
|
RELOAD_AFTER_UPDATE_DELAY,
|
||||||
SOURCE_REAUTH,
|
SOURCE_REAUTH,
|
||||||
ConfigEntry,
|
ConfigEntry,
|
||||||
)
|
)
|
||||||
@ -31,18 +39,21 @@ from homeassistant.const import (
|
|||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
from homeassistant.util import dt
|
||||||
import homeassistant.util.color as color_util
|
import homeassistant.util.color as color_util
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
TEST_AUTH_NOT_REQUIRED_RESP,
|
TEST_AUTH_NOT_REQUIRED_RESP,
|
||||||
TEST_AUTH_REQUIRED_RESP,
|
TEST_AUTH_REQUIRED_RESP,
|
||||||
|
TEST_CONFIG_ENTRY_ID,
|
||||||
TEST_ENTITY_ID_1,
|
TEST_ENTITY_ID_1,
|
||||||
TEST_ENTITY_ID_2,
|
TEST_ENTITY_ID_2,
|
||||||
TEST_ENTITY_ID_3,
|
TEST_ENTITY_ID_3,
|
||||||
TEST_HOST,
|
TEST_HOST,
|
||||||
TEST_ID,
|
TEST_ID,
|
||||||
|
TEST_INSTANCE,
|
||||||
TEST_INSTANCE_1,
|
TEST_INSTANCE_1,
|
||||||
TEST_INSTANCE_2,
|
TEST_INSTANCE_2,
|
||||||
TEST_INSTANCE_3,
|
TEST_INSTANCE_3,
|
||||||
@ -53,9 +64,12 @@ from . import (
|
|||||||
add_test_config_entry,
|
add_test_config_entry,
|
||||||
call_registered_callback,
|
call_registered_callback,
|
||||||
create_mock_client,
|
create_mock_client,
|
||||||
|
register_test_entity,
|
||||||
setup_test_config_entry,
|
setup_test_config_entry,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
COLOR_BLACK = color_util.COLORS["black"]
|
COLOR_BLACK = color_util.COLORS["black"]
|
||||||
|
|
||||||
|
|
||||||
@ -814,10 +828,12 @@ async def test_priority_light_async_updates(
|
|||||||
client = create_mock_client()
|
client = create_mock_client()
|
||||||
client.priorities = [{**priority_template}]
|
client.priorities = [{**priority_template}]
|
||||||
|
|
||||||
with patch(
|
register_test_entity(
|
||||||
"homeassistant.components.hyperion.light.HyperionPriorityLight.entity_registry_enabled_default"
|
hass,
|
||||||
) as enabled_by_default_mock:
|
LIGHT_DOMAIN,
|
||||||
enabled_by_default_mock.return_value = True
|
TYPE_HYPERION_PRIORITY_LIGHT,
|
||||||
|
TEST_PRIORITY_LIGHT_ENTITY_ID_1,
|
||||||
|
)
|
||||||
await setup_test_config_entry(hass, hyperion_client=client)
|
await setup_test_config_entry(hass, hyperion_client=client)
|
||||||
|
|
||||||
# == Scenario: Color at HA priority will show light as on.
|
# == Scenario: Color at HA priority will show light as on.
|
||||||
@ -974,10 +990,12 @@ async def test_priority_light_async_updates_off_sets_black(
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch(
|
register_test_entity(
|
||||||
"homeassistant.components.hyperion.light.HyperionPriorityLight.entity_registry_enabled_default"
|
hass,
|
||||||
) as enabled_by_default_mock:
|
LIGHT_DOMAIN,
|
||||||
enabled_by_default_mock.return_value = True
|
TYPE_HYPERION_PRIORITY_LIGHT,
|
||||||
|
TEST_PRIORITY_LIGHT_ENTITY_ID_1,
|
||||||
|
)
|
||||||
await setup_test_config_entry(hass, hyperion_client=client)
|
await setup_test_config_entry(hass, hyperion_client=client)
|
||||||
|
|
||||||
client.async_send_clear = AsyncMock(return_value=True)
|
client.async_send_clear = AsyncMock(return_value=True)
|
||||||
@ -1026,10 +1044,12 @@ async def test_priority_light_prior_color_preserved_after_black(
|
|||||||
client.priorities = []
|
client.priorities = []
|
||||||
client.visible_priority = None
|
client.visible_priority = None
|
||||||
|
|
||||||
with patch(
|
register_test_entity(
|
||||||
"homeassistant.components.hyperion.light.HyperionPriorityLight.entity_registry_enabled_default"
|
hass,
|
||||||
) as enabled_by_default_mock:
|
LIGHT_DOMAIN,
|
||||||
enabled_by_default_mock.return_value = True
|
TYPE_HYPERION_PRIORITY_LIGHT,
|
||||||
|
TEST_PRIORITY_LIGHT_ENTITY_ID_1,
|
||||||
|
)
|
||||||
await setup_test_config_entry(hass, hyperion_client=client)
|
await setup_test_config_entry(hass, hyperion_client=client)
|
||||||
|
|
||||||
# Turn the light on full green...
|
# Turn the light on full green...
|
||||||
@ -1132,10 +1152,12 @@ async def test_priority_light_has_no_external_sources(hass: HomeAssistantType) -
|
|||||||
client = create_mock_client()
|
client = create_mock_client()
|
||||||
client.priorities = []
|
client.priorities = []
|
||||||
|
|
||||||
with patch(
|
register_test_entity(
|
||||||
"homeassistant.components.hyperion.light.HyperionPriorityLight.entity_registry_enabled_default"
|
hass,
|
||||||
) as enabled_by_default_mock:
|
LIGHT_DOMAIN,
|
||||||
enabled_by_default_mock.return_value = True
|
TYPE_HYPERION_PRIORITY_LIGHT,
|
||||||
|
TEST_PRIORITY_LIGHT_ENTITY_ID_1,
|
||||||
|
)
|
||||||
await setup_test_config_entry(hass, hyperion_client=client)
|
await setup_test_config_entry(hass, hyperion_client=client)
|
||||||
|
|
||||||
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
|
entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1)
|
||||||
@ -1153,9 +1175,75 @@ async def test_light_option_effect_hide_list(hass: HomeAssistantType) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
entity_state = hass.states.get(TEST_ENTITY_ID_1)
|
entity_state = hass.states.get(TEST_ENTITY_ID_1)
|
||||||
|
assert entity_state
|
||||||
assert entity_state.attributes["effect_list"] == [
|
assert entity_state.attributes["effect_list"] == [
|
||||||
"Solid",
|
"Solid",
|
||||||
"BOBLIGHTSERVER",
|
"BOBLIGHTSERVER",
|
||||||
"GRABBER",
|
"GRABBER",
|
||||||
"One",
|
"One",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_device_info(hass: HomeAssistantType) -> 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)
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
|
||||||
|
device = device_registry.async_get_device({(DOMAIN, device_id)})
|
||||||
|
assert device
|
||||||
|
assert device.config_entries == {TEST_CONFIG_ENTRY_ID}
|
||||||
|
assert device.identifiers == {(DOMAIN, device_id)}
|
||||||
|
assert device.manufacturer == HYPERION_MANUFACTURER_NAME
|
||||||
|
assert device.model == HYPERION_MODEL_NAME
|
||||||
|
assert device.name == TEST_INSTANCE_1["friendly_name"]
|
||||||
|
|
||||||
|
entity_registry = await er.async_get_registry(hass)
|
||||||
|
entities_from_device = [
|
||||||
|
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: HomeAssistantType) -> 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 == "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( # type: ignore[no-untyped-call]
|
||||||
|
hass,
|
||||||
|
dt.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
|
||||||
|
@ -1,27 +1,41 @@
|
|||||||
"""Tests for the Hyperion integration."""
|
"""Tests for the Hyperion integration."""
|
||||||
|
from datetime import timedelta
|
||||||
from unittest.mock import AsyncMock, call, patch
|
from unittest.mock import AsyncMock, call, patch
|
||||||
|
|
||||||
from hyperion.const import (
|
from hyperion.const import (
|
||||||
KEY_COMPONENT,
|
KEY_COMPONENT,
|
||||||
KEY_COMPONENTID_ALL,
|
KEY_COMPONENTID_ALL,
|
||||||
KEY_COMPONENTID_BLACKBORDER,
|
|
||||||
KEY_COMPONENTID_BOBLIGHTSERVER,
|
|
||||||
KEY_COMPONENTID_FORWARDER,
|
|
||||||
KEY_COMPONENTID_GRABBER,
|
|
||||||
KEY_COMPONENTID_LEDDEVICE,
|
|
||||||
KEY_COMPONENTID_SMOOTHING,
|
|
||||||
KEY_COMPONENTID_V4L,
|
|
||||||
KEY_COMPONENTSTATE,
|
KEY_COMPONENTSTATE,
|
||||||
KEY_STATE,
|
KEY_STATE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.components.hyperion.const import COMPONENT_TO_NAME
|
from homeassistant.components.hyperion import get_hyperion_device_id
|
||||||
|
from homeassistant.components.hyperion.const import (
|
||||||
|
COMPONENT_TO_NAME,
|
||||||
|
DOMAIN,
|
||||||
|
HYPERION_MANUFACTURER_NAME,
|
||||||
|
HYPERION_MODEL_NAME,
|
||||||
|
TYPE_HYPERION_COMPONENT_SWITCH_BASE,
|
||||||
|
)
|
||||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||||
|
from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
|
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
|
||||||
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import dt, slugify
|
||||||
|
|
||||||
from . import call_registered_callback, create_mock_client, setup_test_config_entry
|
from . import (
|
||||||
|
TEST_CONFIG_ENTRY_ID,
|
||||||
|
TEST_INSTANCE,
|
||||||
|
TEST_INSTANCE_1,
|
||||||
|
TEST_SYSINFO_ID,
|
||||||
|
call_registered_callback,
|
||||||
|
create_mock_client,
|
||||||
|
register_test_entity,
|
||||||
|
setup_test_config_entry,
|
||||||
|
)
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
TEST_COMPONENTS = [
|
TEST_COMPONENTS = [
|
||||||
{"enabled": True, "name": "ALL"},
|
{"enabled": True, "name": "ALL"},
|
||||||
@ -45,10 +59,12 @@ async def test_switch_turn_on_off(hass: HomeAssistantType) -> None:
|
|||||||
client.components = TEST_COMPONENTS
|
client.components = TEST_COMPONENTS
|
||||||
|
|
||||||
# Setup component switch.
|
# Setup component switch.
|
||||||
with patch(
|
register_test_entity(
|
||||||
"homeassistant.components.hyperion.switch.HyperionComponentSwitch.entity_registry_enabled_default"
|
hass,
|
||||||
) as enabled_by_default_mock:
|
SWITCH_DOMAIN,
|
||||||
enabled_by_default_mock.return_value = True
|
f"{TYPE_HYPERION_COMPONENT_SWITCH_BASE}_all",
|
||||||
|
TEST_SWITCH_COMPONENT_ALL_ENTITY_ID,
|
||||||
|
)
|
||||||
await setup_test_config_entry(hass, hyperion_client=client)
|
await setup_test_config_entry(hass, hyperion_client=client)
|
||||||
|
|
||||||
# Verify switch is on (as per TEST_COMPONENTS above).
|
# Verify switch is on (as per TEST_COMPONENTS above).
|
||||||
@ -111,28 +127,96 @@ async def test_switch_has_correct_entities(hass: HomeAssistantType) -> None:
|
|||||||
client.components = TEST_COMPONENTS
|
client.components = TEST_COMPONENTS
|
||||||
|
|
||||||
# Setup component switch.
|
# Setup component switch.
|
||||||
with patch(
|
for component in TEST_COMPONENTS:
|
||||||
"homeassistant.components.hyperion.switch.HyperionComponentSwitch.entity_registry_enabled_default"
|
name = slugify(COMPONENT_TO_NAME[str(component["name"])])
|
||||||
) as enabled_by_default_mock:
|
register_test_entity(
|
||||||
enabled_by_default_mock.return_value = True
|
hass,
|
||||||
|
SWITCH_DOMAIN,
|
||||||
|
f"{TYPE_HYPERION_COMPONENT_SWITCH_BASE}_{name}",
|
||||||
|
f"{TEST_SWITCH_COMPONENT_BASE_ENTITY_ID}_{name}",
|
||||||
|
)
|
||||||
await setup_test_config_entry(hass, hyperion_client=client)
|
await setup_test_config_entry(hass, hyperion_client=client)
|
||||||
|
|
||||||
entity_state = hass.states.get(TEST_SWITCH_COMPONENT_ALL_ENTITY_ID)
|
for component in TEST_COMPONENTS:
|
||||||
|
name = slugify(COMPONENT_TO_NAME[str(component["name"])])
|
||||||
for component in (
|
entity_id = TEST_SWITCH_COMPONENT_BASE_ENTITY_ID + "_" + name
|
||||||
KEY_COMPONENTID_ALL,
|
|
||||||
KEY_COMPONENTID_SMOOTHING,
|
|
||||||
KEY_COMPONENTID_BLACKBORDER,
|
|
||||||
KEY_COMPONENTID_FORWARDER,
|
|
||||||
KEY_COMPONENTID_BOBLIGHTSERVER,
|
|
||||||
KEY_COMPONENTID_GRABBER,
|
|
||||||
KEY_COMPONENTID_LEDDEVICE,
|
|
||||||
KEY_COMPONENTID_V4L,
|
|
||||||
):
|
|
||||||
entity_id = (
|
|
||||||
TEST_SWITCH_COMPONENT_BASE_ENTITY_ID
|
|
||||||
+ "_"
|
|
||||||
+ slugify(COMPONENT_TO_NAME[component])
|
|
||||||
)
|
|
||||||
entity_state = hass.states.get(entity_id)
|
entity_state = hass.states.get(entity_id)
|
||||||
assert entity_state, f"Couldn't find entity: {entity_id}"
|
assert entity_state, f"Couldn't find entity: {entity_id}"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_device_info(hass: HomeAssistantType) -> None:
|
||||||
|
"""Verify device information includes expected details."""
|
||||||
|
client = create_mock_client()
|
||||||
|
client.components = TEST_COMPONENTS
|
||||||
|
|
||||||
|
for component in TEST_COMPONENTS:
|
||||||
|
name = slugify(COMPONENT_TO_NAME[str(component["name"])])
|
||||||
|
register_test_entity(
|
||||||
|
hass,
|
||||||
|
SWITCH_DOMAIN,
|
||||||
|
f"{TYPE_HYPERION_COMPONENT_SWITCH_BASE}_{name}",
|
||||||
|
f"{TEST_SWITCH_COMPONENT_BASE_ENTITY_ID}_{name}",
|
||||||
|
)
|
||||||
|
await setup_test_config_entry(hass, hyperion_client=client)
|
||||||
|
assert hass.states.get(TEST_SWITCH_COMPONENT_ALL_ENTITY_ID) is not None
|
||||||
|
|
||||||
|
device_identifer = get_hyperion_device_id(TEST_SYSINFO_ID, TEST_INSTANCE)
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
|
||||||
|
device = device_registry.async_get_device({(DOMAIN, device_identifer)})
|
||||||
|
assert device
|
||||||
|
assert device.config_entries == {TEST_CONFIG_ENTRY_ID}
|
||||||
|
assert device.identifiers == {(DOMAIN, device_identifer)}
|
||||||
|
assert device.manufacturer == HYPERION_MANUFACTURER_NAME
|
||||||
|
assert device.model == HYPERION_MODEL_NAME
|
||||||
|
assert device.name == TEST_INSTANCE_1["friendly_name"]
|
||||||
|
|
||||||
|
entity_registry = await er.async_get_registry(hass)
|
||||||
|
entities_from_device = [
|
||||||
|
entry.entity_id
|
||||||
|
for entry in er.async_entries_for_device(entity_registry, device.id)
|
||||||
|
]
|
||||||
|
|
||||||
|
for component in TEST_COMPONENTS:
|
||||||
|
name = slugify(COMPONENT_TO_NAME[str(component["name"])])
|
||||||
|
entity_id = TEST_SWITCH_COMPONENT_BASE_ENTITY_ID + "_" + name
|
||||||
|
assert entity_id in entities_from_device
|
||||||
|
|
||||||
|
|
||||||
|
async def test_switches_can_be_enabled(hass: HomeAssistantType) -> None:
|
||||||
|
"""Verify switches can be enabled."""
|
||||||
|
client = create_mock_client()
|
||||||
|
client.components = TEST_COMPONENTS
|
||||||
|
await setup_test_config_entry(hass, hyperion_client=client)
|
||||||
|
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
for component in TEST_COMPONENTS:
|
||||||
|
name = slugify(COMPONENT_TO_NAME[str(component["name"])])
|
||||||
|
entity_id = TEST_SWITCH_COMPONENT_BASE_ENTITY_ID + "_" + name
|
||||||
|
|
||||||
|
entry = entity_registry.async_get(entity_id)
|
||||||
|
assert entry
|
||||||
|
assert entry.disabled
|
||||||
|
assert entry.disabled_by == "integration"
|
||||||
|
entity_state = hass.states.get(entity_id)
|
||||||
|
assert not entity_state
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.hyperion.client.HyperionClient",
|
||||||
|
return_value=client,
|
||||||
|
):
|
||||||
|
updated_entry = entity_registry.async_update_entity(
|
||||||
|
entity_id, disabled_by=None
|
||||||
|
)
|
||||||
|
assert not updated_entry.disabled
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
async_fire_time_changed( # type: ignore[no-untyped-call]
|
||||||
|
hass,
|
||||||
|
dt.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1),
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_state = hass.states.get(entity_id)
|
||||||
|
assert entity_state
|
||||||
|
Loading…
x
Reference in New Issue
Block a user