core/homeassistant/components/daikin/__init__.py

185 lines
6.0 KiB
Python

"""Platform for the Daikin AC."""
from __future__ import annotations
import asyncio
import logging
from aiohttp import ClientConnectionError
from pydaikin.daikin_base import Appliance
from pydaikin.factory import DaikinFactory
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_API_KEY,
CONF_HOST,
CONF_PASSWORD,
CONF_UUID,
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from .const import DOMAIN, KEY_MAC, TIMEOUT
from .coordinator import DaikinCoordinator
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.SWITCH]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Establish connection with Daikin."""
conf = entry.data
# For backwards compat, set unique ID
if entry.unique_id is None or ".local" in entry.unique_id:
hass.config_entries.async_update_entry(entry, unique_id=conf[KEY_MAC])
session = async_get_clientsession(hass)
host = conf[CONF_HOST]
try:
async with asyncio.timeout(TIMEOUT):
device: Appliance = await DaikinFactory(
host,
session,
key=entry.data.get(CONF_API_KEY),
uuid=entry.data.get(CONF_UUID),
password=entry.data.get(CONF_PASSWORD),
)
_LOGGER.debug("Connection to %s successful", host)
except TimeoutError as err:
_LOGGER.debug("Connection to %s timed out in 60 seconds", host)
raise ConfigEntryNotReady from err
except ClientConnectionError as err:
_LOGGER.debug("ClientConnectionError to %s", host)
raise ConfigEntryNotReady from err
coordinator = DaikinCoordinator(hass, device)
await coordinator.async_config_entry_first_refresh()
await async_migrate_unique_id(hass, entry, device)
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
if not hass.data[DOMAIN]:
hass.data.pop(DOMAIN)
return unload_ok
async def async_migrate_unique_id(
hass: HomeAssistant, config_entry: ConfigEntry, device: Appliance
) -> None:
"""Migrate old entry."""
dev_reg = dr.async_get(hass)
ent_reg = er.async_get(hass)
old_unique_id = config_entry.unique_id
new_unique_id = device.mac
new_mac = dr.format_mac(new_unique_id)
new_name = device.values.get("name", "Daikin AC")
@callback
def _update_unique_id(entity_entry: er.RegistryEntry) -> dict[str, str] | None:
"""Update unique ID of entity entry."""
return update_unique_id(entity_entry, new_unique_id)
if new_unique_id == old_unique_id:
return
duplicate = dev_reg.async_get_device(
connections={(CONNECTION_NETWORK_MAC, new_mac)}, identifiers=None
)
# Remove duplicated device
if duplicate is not None:
if config_entry.entry_id in duplicate.config_entries:
_LOGGER.debug(
"Removing duplicated device %s",
duplicate.name,
)
# The automatic cleanup in entity registry is scheduled as a task, remove
# the entities manually to avoid unique_id collision when the entities
# are migrated.
duplicate_entities = er.async_entries_for_device(
ent_reg, duplicate.id, True
)
for entity in duplicate_entities:
if entity.config_entry_id == config_entry.entry_id:
ent_reg.async_remove(entity.entity_id)
dev_reg.async_update_device(
duplicate.id, remove_config_entry_id=config_entry.entry_id
)
# Migrate devices
for device_entry in dr.async_entries_for_config_entry(
dev_reg, config_entry.entry_id
):
for connection in device_entry.connections:
if connection[1] == old_unique_id:
new_connections = {(CONNECTION_NETWORK_MAC, new_mac)}
_LOGGER.debug(
"Migrating device %s connections to %s",
device_entry.name,
new_connections,
)
dev_reg.async_update_device(
device_entry.id,
merge_connections=new_connections,
)
if device_entry.name is None:
_LOGGER.debug(
"Migrating device name to %s",
new_name,
)
dev_reg.async_update_device(
device_entry.id,
name=new_name,
)
# Migrate entities
await er.async_migrate_entries(hass, config_entry.entry_id, _update_unique_id)
new_data = {**config_entry.data, KEY_MAC: dr.format_mac(new_unique_id)}
hass.config_entries.async_update_entry(
config_entry, unique_id=new_unique_id, data=new_data
)
@callback
def update_unique_id(
entity_entry: er.RegistryEntry, unique_id: str
) -> dict[str, str] | None:
"""Update unique ID of entity entry."""
if entity_entry.unique_id.startswith(unique_id):
# Already correct, nothing to do
return None
unique_id_parts = entity_entry.unique_id.split("-")
unique_id_parts[0] = unique_id
entity_new_unique_id = "-".join(unique_id_parts)
_LOGGER.debug(
"Migrating entity %s from %s to new id %s",
entity_entry.entity_id,
entity_entry.unique_id,
entity_new_unique_id,
)
return {"new_unique_id": entity_new_unique_id}