1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-17 23:53:49 +01:00
Files
core/homeassistant/components/wiim/__init__.py
balloob-travel a0ef23097f Abort WiiM config flow when Home Assistant URL is unavailable (#166055)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-25 19:26:45 +01:00

122 lines
4.2 KiB
Python

"""The WiiM integration."""
from __future__ import annotations
from wiim.controller import WiimController
from wiim.discovery import async_create_wiim_device
from wiim.exceptions import WiimDeviceException, WiimRequestException
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import Event, HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DATA_WIIM, DOMAIN, LOGGER, PLATFORMS, UPNP_PORT, WiimConfigEntry
from .models import WiimData
from .util import InvalidHomeAssistantURLError, get_homeassistant_local_host
DEFAULT_AVAILABILITY_POLLING_INTERVAL = 60
async def async_setup_entry(hass: HomeAssistant, entry: WiimConfigEntry) -> bool:
"""Set up WiiM from a config entry.
This method owns the device connect/disconnect lifecycle.
"""
LOGGER.debug(
"Setting up WiiM entry: %s (UDN: %s, Source: %s)",
entry.title,
entry.unique_id,
entry.source,
)
# This integration maintains shared domain-level state because:
# - Multiple config entries can be loaded simultaneously.
# - All WiiM devices share a single WiimController instance
# to coordinate network communication and event handling.
# - We also maintain a global entity_id -> UDN mapping
# used for cross-entity event routing.
#
# The domain data must therefore be initialized once and reused
# across all config entries.
session = async_get_clientsession(hass)
if DATA_WIIM not in hass.data:
hass.data[DATA_WIIM] = WiimData(controller=WiimController(session))
wiim_domain_data = hass.data[DATA_WIIM]
controller = wiim_domain_data.controller
host = entry.data[CONF_HOST]
upnp_location = f"http://{host}:{UPNP_PORT}/description.xml"
try:
local_host = get_homeassistant_local_host(hass)
except InvalidHomeAssistantURLError as err:
raise ConfigEntryNotReady("Failed to determine Home Assistant URL") from err
try:
wiim_device = await async_create_wiim_device(
upnp_location,
session,
host=host,
local_host=local_host,
polling_interval=DEFAULT_AVAILABILITY_POLLING_INTERVAL,
)
except WiimRequestException as err:
raise ConfigEntryNotReady(f"HTTP API request failed for {host}: {err}") from err
except WiimDeviceException as err:
raise ConfigEntryNotReady(f"Device setup failed for {host}: {err}") from err
await controller.add_device(wiim_device)
entry.runtime_data = wiim_device
LOGGER.info(
"WiiM device %s (UDN: %s) linked to HASS. Name: '%s', HTTP: %s, UPnP Location: %s",
entry.entry_id,
wiim_device.udn,
wiim_device.name,
host,
upnp_location or "N/A",
)
async def _async_shutdown_event_handler(event: Event) -> None:
LOGGER.info(
"Home Assistant stopping, disconnecting WiiM device: %s",
wiim_device.name,
)
await wiim_device.disconnect()
entry.async_on_unload(
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, _async_shutdown_event_handler
)
)
async def _unload_entry_cleanup():
"""Cleanup when unloading the config entry.
Removes the device from the controller and disconnects it.
"""
LOGGER.debug("Running unload cleanup for %s", wiim_device.name)
await controller.remove_device(wiim_device.udn)
await wiim_device.disconnect()
entry.async_on_unload(_unload_entry_cleanup)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: WiimConfigEntry) -> bool:
"""Unload a config entry."""
LOGGER.info("Unloading WiiM entry: %s (UDN: %s)", entry.title, entry.unique_id)
if not await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
return False
if not hass.config_entries.async_loaded_entries(DOMAIN):
hass.data.pop(DATA_WIIM)
LOGGER.info("Last WiiM entry unloaded, cleaning up domain data")
return True