Use runtime data for hyperion (#143461)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Marc Mueller 2025-04-23 01:24:47 +02:00 committed by GitHub
parent 0b2e5cd253
commit 2d20df37b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 67 additions and 93 deletions

View File

@ -5,6 +5,7 @@ from __future__ import annotations
import asyncio
from collections.abc import Callable
from contextlib import suppress
from dataclasses import dataclass
import logging
from typing import Any, cast
@ -22,9 +23,6 @@ from homeassistant.helpers.dispatcher import (
)
from .const import (
CONF_INSTANCE_CLIENTS,
CONF_ON_UNLOAD,
CONF_ROOT_CLIENT,
DEFAULT_NAME,
DOMAIN,
HYPERION_RELEASES_URL,
@ -52,15 +50,15 @@ _LOGGER = logging.getLogger(__name__)
# The get_hyperion_unique_id method will create a per-entity unique id when given the
# server id, an instance number and a name.
# hass.data format
# ================
#
# hass.data[DOMAIN] = {
# <config_entry.entry_id>: {
# "ROOT_CLIENT": <Hyperion Client>,
# "ON_UNLOAD": [<callable>, ...],
# }
# }
type HyperionConfigEntry = ConfigEntry[HyperionData]
@dataclass
class HyperionData:
"""Hyperion runtime data."""
root_client: client.HyperionClient
instance_clients: dict[int, client.HyperionClient]
def get_hyperion_unique_id(server_id: str, instance: int, name: str) -> str:
@ -107,29 +105,29 @@ async def async_create_connect_hyperion_client(
@callback
def listen_for_instance_updates(
hass: HomeAssistant,
config_entry: ConfigEntry,
add_func: Callable,
remove_func: Callable,
entry: HyperionConfigEntry,
add_func: Callable[[int, str], None],
remove_func: Callable[[int], None],
) -> None:
"""Listen for instance additions/removals."""
hass.data[DOMAIN][config_entry.entry_id][CONF_ON_UNLOAD].extend(
[
async_dispatcher_connect(
hass,
SIGNAL_INSTANCE_ADD.format(config_entry.entry_id),
add_func,
),
async_dispatcher_connect(
hass,
SIGNAL_INSTANCE_REMOVE.format(config_entry.entry_id),
remove_func,
),
]
entry.async_on_unload(
async_dispatcher_connect(
hass,
SIGNAL_INSTANCE_ADD.format(entry.entry_id),
add_func,
)
)
entry.async_on_unload(
async_dispatcher_connect(
hass,
SIGNAL_INSTANCE_REMOVE.format(entry.entry_id),
remove_func,
)
)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: HyperionConfigEntry) -> bool:
"""Set up Hyperion from a config entry."""
host = entry.data[CONF_HOST]
port = entry.data[CONF_PORT]
@ -185,12 +183,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# We need 1 root client (to manage instances being removed/added) and then 1 client
# per Hyperion server instance which is shared for all entities associated with
# that instance.
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
CONF_ROOT_CLIENT: hyperion_client,
CONF_INSTANCE_CLIENTS: {},
CONF_ON_UNLOAD: [],
}
entry.runtime_data = HyperionData(
root_client=hyperion_client,
instance_clients={},
)
async def async_instances_to_clients(response: dict[str, Any]) -> None:
"""Convert instances to Hyperion clients."""
@ -203,7 +199,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
device_registry = dr.async_get(hass)
running_instances: set[int] = set()
stopped_instances: set[int] = set()
existing_instances = hass.data[DOMAIN][entry.entry_id][CONF_INSTANCE_CLIENTS]
existing_instances = entry.runtime_data.instance_clients
server_id = cast(str, entry.unique_id)
# In practice, an instance can be in 3 states as seen by this function:
@ -270,39 +266,29 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
assert hyperion_client
if hyperion_client.instances is not None:
await async_instances_to_clients_raw(hyperion_client.instances)
hass.data[DOMAIN][entry.entry_id][CONF_ON_UNLOAD].append(
entry.add_update_listener(_async_entry_updated)
)
entry.async_on_unload(entry.add_update_listener(_async_entry_updated))
return True
async def _async_entry_updated(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
async def _async_entry_updated(hass: HomeAssistant, entry: HyperionConfigEntry) -> None:
"""Handle entry updates."""
await hass.config_entries.async_reload(config_entry.entry_id)
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: HyperionConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
if unload_ok and config_entry.entry_id in hass.data[DOMAIN]:
config_data = hass.data[DOMAIN].pop(config_entry.entry_id)
for func in config_data[CONF_ON_UNLOAD]:
func()
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
# Disconnect the shared instance clients.
await asyncio.gather(
*(
config_data[CONF_INSTANCE_CLIENTS][
instance_num
].async_client_disconnect()
for instance_num in config_data[CONF_INSTANCE_CLIENTS]
inst.async_client_disconnect()
for inst in entry.runtime_data.instance_clients.values()
)
)
# Disconnect the root client.
root_client = config_data[CONF_ROOT_CLIENT]
root_client = entry.runtime_data.root_client
await root_client.async_client_disconnect()
return unload_ok

View File

@ -25,7 +25,6 @@ from homeassistant.components.camera import (
Camera,
async_get_still_stream,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import (
@ -35,12 +34,12 @@ from homeassistant.helpers.dispatcher import (
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import (
HyperionConfigEntry,
get_hyperion_device_id,
get_hyperion_unique_id,
listen_for_instance_updates,
)
from .const import (
CONF_INSTANCE_CLIENTS,
DOMAIN,
HYPERION_MANUFACTURER_NAME,
HYPERION_MODEL_NAME,
@ -53,12 +52,11 @@ IMAGE_STREAM_JPG_SENTINEL = "data:image/jpg;base64,"
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
entry: HyperionConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up a Hyperion platform from config entry."""
entry_data = hass.data[DOMAIN][config_entry.entry_id]
server_id = config_entry.unique_id
server_id = entry.unique_id
def camera_unique_id(instance_num: int) -> str:
"""Return the camera unique_id."""
@ -75,7 +73,7 @@ async def async_setup_entry(
server_id,
instance_num,
instance_name,
entry_data[CONF_INSTANCE_CLIENTS][instance_num],
entry.runtime_data.instance_clients[instance_num],
)
]
)
@ -91,7 +89,7 @@ async def async_setup_entry(
),
)
listen_for_instance_updates(hass, config_entry, instance_add, instance_remove)
listen_for_instance_updates(hass, entry, instance_add, instance_remove)
# A note on Hyperion streaming semantics:

View File

@ -3,10 +3,7 @@
CONF_AUTH_ID = "auth_id"
CONF_CREATE_TOKEN = "create_token"
CONF_INSTANCE = "instance"
CONF_INSTANCE_CLIENTS = "INSTANCE_CLIENTS"
CONF_ON_UNLOAD = "ON_UNLOAD"
CONF_PRIORITY = "priority"
CONF_ROOT_CLIENT = "ROOT_CLIENT"
CONF_EFFECT_HIDE_LIST = "effect_hide_list"
CONF_EFFECT_SHOW_LIST = "effect_show_list"

View File

@ -17,7 +17,6 @@ from homeassistant.components.light import (
LightEntity,
LightEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import (
@ -28,13 +27,13 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import color as color_util
from . import (
HyperionConfigEntry,
get_hyperion_device_id,
get_hyperion_unique_id,
listen_for_instance_updates,
)
from .const import (
CONF_EFFECT_HIDE_LIST,
CONF_INSTANCE_CLIENTS,
CONF_PRIORITY,
DEFAULT_ORIGIN,
DEFAULT_PRIORITY,
@ -74,28 +73,26 @@ ICON_EFFECT = "mdi:lava-lamp"
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
entry: HyperionConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up a Hyperion platform from config entry."""
entry_data = hass.data[DOMAIN][config_entry.entry_id]
server_id = config_entry.unique_id
server_id = entry.unique_id
@callback
def instance_add(instance_num: int, instance_name: str) -> None:
"""Add entities for a new Hyperion instance."""
assert server_id
args = (
server_id,
instance_num,
instance_name,
config_entry.options,
entry_data[CONF_INSTANCE_CLIENTS][instance_num],
)
async_add_entities(
[
HyperionLight(*args),
HyperionLight(
server_id,
instance_num,
instance_name,
entry.options,
entry.runtime_data.instance_clients[instance_num],
),
]
)
@ -110,7 +107,7 @@ async def async_setup_entry(
),
)
listen_for_instance_updates(hass, config_entry, instance_add, instance_remove)
listen_for_instance_updates(hass, entry, instance_add, instance_remove)
class HyperionLight(LightEntity):

View File

@ -19,7 +19,6 @@ from hyperion.const import (
)
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import (
@ -29,12 +28,12 @@ from homeassistant.helpers.dispatcher import (
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import (
HyperionConfigEntry,
get_hyperion_device_id,
get_hyperion_unique_id,
listen_for_instance_updates,
)
from .const import (
CONF_INSTANCE_CLIENTS,
DOMAIN,
HYPERION_MANUFACTURER_NAME,
HYPERION_MODEL_NAME,
@ -62,12 +61,11 @@ def _sensor_unique_id(server_id: str, instance_num: int, suffix: str) -> str:
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
entry: HyperionConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up a Hyperion platform from config entry."""
entry_data = hass.data[DOMAIN][config_entry.entry_id]
server_id = config_entry.unique_id
server_id = entry.unique_id
@callback
def instance_add(instance_num: int, instance_name: str) -> None:
@ -78,7 +76,7 @@ async def async_setup_entry(
server_id,
instance_num,
instance_name,
entry_data[CONF_INSTANCE_CLIENTS][instance_num],
entry.runtime_data.instance_clients[instance_num],
PRIORITY_SENSOR_DESCRIPTION,
)
]
@ -98,7 +96,7 @@ async def async_setup_entry(
),
)
listen_for_instance_updates(hass, config_entry, instance_add, instance_remove)
listen_for_instance_updates(hass, entry, instance_add, instance_remove)
class HyperionSensor(SensorEntity):

View File

@ -26,7 +26,6 @@ from hyperion.const import (
)
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
@ -38,12 +37,12 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import slugify
from . import (
HyperionConfigEntry,
get_hyperion_device_id,
get_hyperion_unique_id,
listen_for_instance_updates,
)
from .const import (
CONF_INSTANCE_CLIENTS,
DOMAIN,
HYPERION_MANUFACTURER_NAME,
HYPERION_MODEL_NAME,
@ -89,12 +88,11 @@ def _component_to_translation_key(component: str) -> str:
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
entry: HyperionConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up a Hyperion platform from config entry."""
entry_data = hass.data[DOMAIN][config_entry.entry_id]
server_id = config_entry.unique_id
server_id = entry.unique_id
@callback
def instance_add(instance_num: int, instance_name: str) -> None:
@ -106,7 +104,7 @@ async def async_setup_entry(
instance_num,
instance_name,
component,
entry_data[CONF_INSTANCE_CLIENTS][instance_num],
entry.runtime_data.instance_clients[instance_num],
)
for component in COMPONENT_SWITCHES
)
@ -123,7 +121,7 @@ async def async_setup_entry(
),
)
listen_for_instance_updates(hass, config_entry, instance_add, instance_remove)
listen_for_instance_updates(hass, entry, instance_add, instance_remove)
class HyperionComponentSwitch(SwitchEntity):