"""Component to embed TP-Link smart home devices.""" from __future__ import annotations import asyncio from datetime import timedelta from typing import Any from kasa import SmartDevice, SmartDeviceException from kasa.discover import Discover from homeassistant import config_entries from homeassistant.components import network from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_HOST, CONF_MAC, CONF_NAME, EVENT_HOMEASSISTANT_STARTED, ) from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType from .const import DOMAIN, PLATFORMS from .coordinator import TPLinkDataUpdateCoordinator DISCOVERY_INTERVAL = timedelta(minutes=15) @callback def async_trigger_discovery( hass: HomeAssistant, discovered_devices: dict[str, SmartDevice], ) -> None: """Trigger config flows for discovered devices.""" for formatted_mac, device in discovered_devices.items(): hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data={ CONF_NAME: device.alias, CONF_HOST: device.host, CONF_MAC: formatted_mac, }, ) ) async def async_discover_devices(hass: HomeAssistant) -> dict[str, SmartDevice]: """Discover TPLink devices on configured network interfaces.""" broadcast_addresses = await network.async_get_ipv4_broadcast_addresses(hass) tasks = [Discover.discover(target=str(address)) for address in broadcast_addresses] discovered_devices: dict[str, SmartDevice] = {} for device_list in await asyncio.gather(*tasks): for device in device_list.values(): discovered_devices[dr.format_mac(device.mac)] = device return discovered_devices async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the TP-Link component.""" hass.data[DOMAIN] = {} if discovered_devices := await async_discover_devices(hass): async_trigger_discovery(hass, discovered_devices) async def _async_discovery(*_: Any) -> None: if discovered := await async_discover_devices(hass): async_trigger_discovery(hass, discovered) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _async_discovery) async_track_time_interval(hass, _async_discovery, DISCOVERY_INTERVAL) return True async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up TPLink from a config entry.""" try: device: SmartDevice = await Discover.discover_single(entry.data[CONF_HOST]) except SmartDeviceException as ex: raise ConfigEntryNotReady from ex hass.data[DOMAIN][entry.entry_id] = TPLinkDataUpdateCoordinator(hass, device) hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" hass_data: dict[str, Any] = hass.data[DOMAIN] device: SmartDevice = hass_data[entry.entry_id].device if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): hass_data.pop(entry.entry_id) await device.protocol.close() return unload_ok def legacy_device_id(device: SmartDevice) -> str: """Convert the device id so it matches what was used in the original version.""" device_id: str = device.device_id # Plugs are prefixed with the mac in python-kasa but not # in pyHS100 so we need to strip off the mac if "_" not in device_id: return device_id return device_id.split("_")[1]