mirror of
https://github.com/home-assistant/core.git
synced 2025-11-09 02:49:40 +00:00
211 lines
7.0 KiB
Python
211 lines
7.0 KiB
Python
"""The KNX integration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
from pathlib import Path
|
|
from typing import Final
|
|
|
|
import voluptuous as vol
|
|
from xknx.exceptions import XKNXException
|
|
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.const import Platform
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.exceptions import ConfigEntryNotReady
|
|
from homeassistant.helpers.device_registry import DeviceEntry
|
|
from homeassistant.helpers.reload import async_integration_yaml_config
|
|
from homeassistant.helpers.storage import STORAGE_DIR
|
|
from homeassistant.helpers.typing import ConfigType
|
|
|
|
from .const import (
|
|
CONF_KNX_EXPOSE,
|
|
CONF_KNX_KNXKEY_FILENAME,
|
|
DATA_HASS_CONFIG,
|
|
DOMAIN,
|
|
KNX_MODULE_KEY,
|
|
SUPPORTED_PLATFORMS_UI,
|
|
SUPPORTED_PLATFORMS_YAML,
|
|
)
|
|
from .expose import create_knx_exposure
|
|
from .knx_module import KNXModule
|
|
from .project import STORAGE_KEY as PROJECT_STORAGE_KEY
|
|
from .schema import (
|
|
BinarySensorSchema,
|
|
ButtonSchema,
|
|
ClimateSchema,
|
|
CoverSchema,
|
|
DateSchema,
|
|
DateTimeSchema,
|
|
EventSchema,
|
|
ExposeSchema,
|
|
FanSchema,
|
|
LightSchema,
|
|
NotifySchema,
|
|
NumberSchema,
|
|
SceneSchema,
|
|
SelectSchema,
|
|
SensorSchema,
|
|
SwitchSchema,
|
|
TextSchema,
|
|
TimeSchema,
|
|
WeatherSchema,
|
|
)
|
|
from .services import async_setup_services
|
|
from .storage.config_store import STORAGE_KEY as CONFIG_STORAGE_KEY
|
|
from .telegrams import STORAGE_KEY as TELEGRAMS_STORAGE_KEY
|
|
from .websocket import register_panel
|
|
|
|
_KNX_YAML_CONFIG: Final = "knx_yaml_config"
|
|
|
|
CONFIG_SCHEMA = vol.Schema(
|
|
{
|
|
DOMAIN: vol.All(
|
|
vol.Schema(
|
|
{
|
|
**EventSchema.SCHEMA,
|
|
**ExposeSchema.platform_node(),
|
|
**BinarySensorSchema.platform_node(),
|
|
**ButtonSchema.platform_node(),
|
|
**ClimateSchema.platform_node(),
|
|
**CoverSchema.platform_node(),
|
|
**DateSchema.platform_node(),
|
|
**DateTimeSchema.platform_node(),
|
|
**FanSchema.platform_node(),
|
|
**LightSchema.platform_node(),
|
|
**NotifySchema.platform_node(),
|
|
**NumberSchema.platform_node(),
|
|
**SceneSchema.platform_node(),
|
|
**SelectSchema.platform_node(),
|
|
**SensorSchema.platform_node(),
|
|
**SwitchSchema.platform_node(),
|
|
**TextSchema.platform_node(),
|
|
**TimeSchema.platform_node(),
|
|
**WeatherSchema.platform_node(),
|
|
}
|
|
),
|
|
)
|
|
},
|
|
extra=vol.ALLOW_EXTRA,
|
|
)
|
|
|
|
|
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|
"""Start the KNX integration."""
|
|
hass.data[DATA_HASS_CONFIG] = config
|
|
if (conf := config.get(DOMAIN)) is not None:
|
|
hass.data[_KNX_YAML_CONFIG] = dict(conf)
|
|
|
|
async_setup_services(hass)
|
|
return True
|
|
|
|
|
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
"""Load a config entry."""
|
|
# `_KNX_YAML_CONFIG` is only set in async_setup.
|
|
# It's None when reloading the integration or no `knx` key in configuration.yaml
|
|
config = hass.data.pop(_KNX_YAML_CONFIG, None)
|
|
if config is None:
|
|
_conf = await async_integration_yaml_config(hass, DOMAIN)
|
|
if not _conf or DOMAIN not in _conf:
|
|
# generate defaults
|
|
config = CONFIG_SCHEMA({DOMAIN: {}})[DOMAIN]
|
|
else:
|
|
config = _conf[DOMAIN]
|
|
try:
|
|
knx_module = KNXModule(hass, config, entry)
|
|
await knx_module.start()
|
|
except XKNXException as ex:
|
|
raise ConfigEntryNotReady from ex
|
|
|
|
hass.data[KNX_MODULE_KEY] = knx_module
|
|
|
|
if CONF_KNX_EXPOSE in config:
|
|
for expose_config in config[CONF_KNX_EXPOSE]:
|
|
knx_module.exposures.append(
|
|
create_knx_exposure(hass, knx_module.xknx, expose_config)
|
|
)
|
|
configured_platforms_yaml = {
|
|
platform for platform in SUPPORTED_PLATFORMS_YAML if platform in config
|
|
}
|
|
await hass.config_entries.async_forward_entry_setups(
|
|
entry,
|
|
{
|
|
Platform.SENSOR, # always forward sensor for system entities (telegram counter, etc.)
|
|
*SUPPORTED_PLATFORMS_UI, # forward all platforms that support UI entity management
|
|
*configured_platforms_yaml, # forward yaml-only managed platforms on demand,
|
|
},
|
|
)
|
|
|
|
await register_panel(hass)
|
|
|
|
return True
|
|
|
|
|
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
"""Unloading the KNX platforms."""
|
|
knx_module = hass.data.get(KNX_MODULE_KEY)
|
|
if not knx_module:
|
|
# if not loaded directly return
|
|
return True
|
|
|
|
for exposure in knx_module.exposures:
|
|
exposure.async_remove()
|
|
|
|
configured_platforms_yaml = {
|
|
platform
|
|
for platform in SUPPORTED_PLATFORMS_YAML
|
|
if platform in knx_module.config_yaml
|
|
}
|
|
unload_ok = await hass.config_entries.async_unload_platforms(
|
|
entry,
|
|
{
|
|
Platform.SENSOR, # always unload system entities (telegram counter, etc.)
|
|
*SUPPORTED_PLATFORMS_UI, # unload all platforms that support UI entity management
|
|
*configured_platforms_yaml, # unload yaml-only managed platforms if configured,
|
|
},
|
|
)
|
|
if unload_ok:
|
|
await knx_module.stop()
|
|
hass.data.pop(DOMAIN)
|
|
|
|
return unload_ok
|
|
|
|
|
|
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
"""Remove a config entry."""
|
|
|
|
def remove_files(storage_dir: Path, knxkeys_filename: str | None) -> None:
|
|
"""Remove KNX files."""
|
|
if knxkeys_filename is not None:
|
|
with contextlib.suppress(FileNotFoundError):
|
|
(storage_dir / knxkeys_filename).unlink()
|
|
with contextlib.suppress(FileNotFoundError):
|
|
(storage_dir / CONFIG_STORAGE_KEY).unlink()
|
|
with contextlib.suppress(FileNotFoundError):
|
|
(storage_dir / PROJECT_STORAGE_KEY).unlink()
|
|
with contextlib.suppress(FileNotFoundError):
|
|
(storage_dir / TELEGRAMS_STORAGE_KEY).unlink()
|
|
with contextlib.suppress(FileNotFoundError, OSError):
|
|
(storage_dir / DOMAIN).rmdir()
|
|
|
|
storage_dir = Path(hass.config.path(STORAGE_DIR))
|
|
knxkeys_filename = entry.data.get(CONF_KNX_KNXKEY_FILENAME)
|
|
await hass.async_add_executor_job(remove_files, storage_dir, knxkeys_filename)
|
|
|
|
|
|
async def async_remove_config_entry_device(
|
|
hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry
|
|
) -> bool:
|
|
"""Remove a config entry from a device."""
|
|
knx_module = hass.data[KNX_MODULE_KEY]
|
|
if not device_entry.identifiers.isdisjoint(
|
|
knx_module.interface_device.device_info["identifiers"]
|
|
):
|
|
# can not remove interface device
|
|
return False
|
|
for entity in knx_module.config_store.get_entity_entries():
|
|
if entity.device_id == device_entry.id:
|
|
await knx_module.config_store.delete_entity(entity.entity_id)
|
|
return True
|