mirror of
https://github.com/home-assistant/core.git
synced 2025-11-30 04:58:01 +00:00
155 lines
5.0 KiB
Python
155 lines
5.0 KiB
Python
"""Support for esphome devices."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from aioesphomeapi import APIClient, APIConnectionError
|
|
|
|
from homeassistant.components import zeroconf
|
|
from homeassistant.components.bluetooth import async_remove_scanner
|
|
from homeassistant.const import (
|
|
CONF_HOST,
|
|
CONF_PASSWORD,
|
|
CONF_PORT,
|
|
__version__ as ha_version,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers import config_validation as cv
|
|
from homeassistant.helpers.issue_registry import async_delete_issue
|
|
from homeassistant.helpers.typing import ConfigType
|
|
|
|
from . import assist_satellite, dashboard, ffmpeg_proxy
|
|
from .const import CONF_BLUETOOTH_MAC_ADDRESS, CONF_NOISE_PSK, DOMAIN
|
|
from .domain_data import DomainData
|
|
from .encryption_key_storage import async_get_encryption_key_storage
|
|
from .entry_data import ESPHomeConfigEntry, RuntimeEntryData
|
|
from .manager import DEVICE_CONFLICT_ISSUE_FORMAT, ESPHomeManager, cleanup_instance
|
|
from .websocket_api import async_setup as async_setup_websocket_api
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
|
|
|
CLIENT_INFO = f"Home Assistant {ha_version}"
|
|
|
|
|
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|
"""Set up the esphome component."""
|
|
ffmpeg_proxy.async_setup(hass)
|
|
await assist_satellite.async_setup(hass)
|
|
await dashboard.async_setup(hass)
|
|
async_setup_websocket_api(hass)
|
|
return True
|
|
|
|
|
|
async def async_setup_entry(hass: HomeAssistant, entry: ESPHomeConfigEntry) -> bool:
|
|
"""Set up the esphome component."""
|
|
host: str = entry.data[CONF_HOST]
|
|
port: int = entry.data[CONF_PORT]
|
|
password: str | None = entry.data[CONF_PASSWORD]
|
|
noise_psk: str | None = entry.data.get(CONF_NOISE_PSK)
|
|
|
|
zeroconf_instance = await zeroconf.async_get_instance(hass)
|
|
|
|
cli = APIClient(
|
|
host,
|
|
port,
|
|
password,
|
|
client_info=CLIENT_INFO,
|
|
zeroconf_instance=zeroconf_instance,
|
|
noise_psk=noise_psk,
|
|
timezone=hass.config.time_zone,
|
|
)
|
|
|
|
domain_data = DomainData.get(hass)
|
|
entry_data = RuntimeEntryData(
|
|
client=cli,
|
|
entry_id=entry.entry_id,
|
|
title=entry.title,
|
|
store=domain_data.get_or_create_store(hass, entry),
|
|
original_options=dict(entry.options),
|
|
)
|
|
entry.runtime_data = entry_data
|
|
|
|
manager = ESPHomeManager(
|
|
hass, entry, host, password, cli, zeroconf_instance, domain_data
|
|
)
|
|
await manager.async_start()
|
|
|
|
return True
|
|
|
|
|
|
async def async_unload_entry(hass: HomeAssistant, entry: ESPHomeConfigEntry) -> bool:
|
|
"""Unload an esphome config entry."""
|
|
unload_ok = await hass.config_entries.async_unload_platforms(
|
|
entry, entry.runtime_data.loaded_platforms
|
|
)
|
|
if unload_ok:
|
|
await cleanup_instance(entry)
|
|
return unload_ok
|
|
|
|
|
|
async def async_remove_entry(hass: HomeAssistant, entry: ESPHomeConfigEntry) -> None:
|
|
"""Remove an esphome config entry."""
|
|
if bluetooth_mac_address := entry.data.get(CONF_BLUETOOTH_MAC_ADDRESS):
|
|
async_remove_scanner(hass, bluetooth_mac_address.upper())
|
|
async_delete_issue(
|
|
hass, DOMAIN, DEVICE_CONFLICT_ISSUE_FORMAT.format(entry.entry_id)
|
|
)
|
|
await DomainData.get(hass).get_or_create_store(hass, entry).async_remove()
|
|
|
|
await _async_clear_dynamic_encryption_key(hass, entry)
|
|
|
|
|
|
async def _async_clear_dynamic_encryption_key(
|
|
hass: HomeAssistant, entry: ESPHomeConfigEntry
|
|
) -> None:
|
|
"""Clear the dynamic encryption key on the device and from storage."""
|
|
if entry.unique_id is None or entry.data.get(CONF_NOISE_PSK) is None:
|
|
return
|
|
|
|
# Only clear the key if it's stored in our storage, meaning it was
|
|
# dynamically generated by us and not user-provided
|
|
storage = await async_get_encryption_key_storage(hass)
|
|
if await storage.async_get_key(entry.unique_id) is None:
|
|
return
|
|
|
|
host: str = entry.data[CONF_HOST]
|
|
port: int = entry.data[CONF_PORT]
|
|
password: str | None = entry.data[CONF_PASSWORD]
|
|
noise_psk: str | None = entry.data.get(CONF_NOISE_PSK)
|
|
|
|
zeroconf_instance = await zeroconf.async_get_instance(hass)
|
|
|
|
cli = APIClient(
|
|
host,
|
|
port,
|
|
password,
|
|
client_info=CLIENT_INFO,
|
|
zeroconf_instance=zeroconf_instance,
|
|
noise_psk=noise_psk,
|
|
timezone=hass.config.time_zone,
|
|
)
|
|
|
|
try:
|
|
await cli.connect()
|
|
# Clear the encryption key on the device by passing an empty key
|
|
if not await cli.noise_encryption_set_key(b""):
|
|
_LOGGER.debug(
|
|
"Could not clear dynamic encryption key for ESPHome device %s: Device rejected key removal",
|
|
entry.unique_id,
|
|
)
|
|
return
|
|
except APIConnectionError as exc:
|
|
_LOGGER.debug(
|
|
"Could not connect to ESPHome device %s to clear dynamic encryption key: %s",
|
|
entry.unique_id,
|
|
exc,
|
|
)
|
|
return
|
|
finally:
|
|
await cli.disconnect()
|
|
|
|
await storage.async_remove_key(entry.unique_id)
|