1
0
mirror of https://github.com/home-assistant/core.git synced 2026-07-02 04:06:41 +01:00
Files
core/homeassistant/components/velbus/__init__.py
T

179 lines
5.8 KiB
Python

"""Support for Velbus devices."""
import asyncio
from dataclasses import dataclass
import logging
import os
import shutil
from velbusaio.controller import Velbus
from velbusaio.exceptions import VelbusConnectionFailed
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PORT, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady, PlatformNotReady
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.storage import STORAGE_DIR
from homeassistant.helpers.typing import ConfigType
from .const import CONF_VLP_FILE, DOMAIN
from .services import async_setup_services
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.CLIMATE,
Platform.COVER,
Platform.LIGHT,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
]
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
type VelbusConfigEntry = ConfigEntry[VelbusData]
@dataclass
class VelbusData:
"""Runtime data for the Velbus config entry."""
controller: Velbus
scan_task: asyncio.Task
async def velbus_scan_task(
controller: Velbus, hass: HomeAssistant, entry_id: str
) -> None:
"""Task to offload the long running scan."""
try:
await controller.start()
except ConnectionError as ex:
raise PlatformNotReady(
f"Connection error while connecting to Velbus {entry_id}: {ex}"
) from ex
# create all modules
dev_reg = dr.async_get(hass)
for module in controller.get_modules().values():
dev_reg.async_get_or_create(
config_entry_id=entry_id,
identifiers={
(DOMAIN, str(module.get_addresses()[0])),
},
manufacturer="Velleman",
model=module.get_type_name(),
model_id=str(module.get_type()),
name=f"{module.get_name()} ({module.get_type_name()})",
sw_version=module.get_sw_version(),
serial_number=module.get_serial(),
)
def _migrate_device_identifiers(hass: HomeAssistant, entry_id: str) -> None:
"""Migrate old device identifiers."""
dev_reg = dr.async_get(hass)
devices: list[dr.DeviceEntry] = dr.async_entries_for_config_entry(dev_reg, entry_id)
for device in devices:
old_identifier = list(next(iter(device.identifiers)))
if len(old_identifier) > 2:
new_identifier = {(old_identifier.pop(0), old_identifier.pop(0))}
_LOGGER.debug(
"migrate identifier '%s' to '%s'", device.identifiers, new_identifier
)
dev_reg.async_update_device(device.id, new_identifiers=new_identifier)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the actions for the Velbus component."""
async_setup_services(hass)
return True
async def async_setup_entry(hass: HomeAssistant, entry: VelbusConfigEntry) -> bool:
"""Establish connection with velbus."""
controller = Velbus(
dsn=entry.data[CONF_PORT],
cache_dir=hass.config.path(STORAGE_DIR, f"velbuscache-{entry.entry_id}"),
vlp_file=entry.data.get(CONF_VLP_FILE),
)
try:
await controller.connect()
except VelbusConnectionFailed as error:
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="connection_failed",
) from error
task = hass.async_create_task(velbus_scan_task(controller, hass, entry.entry_id))
entry.runtime_data = VelbusData(controller=controller, scan_task=task)
_migrate_device_identifiers(hass, entry.entry_id)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: VelbusConfigEntry) -> bool:
"""Unload (close) the velbus connection."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
await entry.runtime_data.controller.stop()
return unload_ok
async def async_remove_entry(hass: HomeAssistant, entry: VelbusConfigEntry) -> None:
"""Remove the velbus entry, so we also have to cleanup the cache dir."""
await hass.async_add_executor_job(
shutil.rmtree,
hass.config.path(STORAGE_DIR, f"velbuscache-{entry.entry_id}"),
)
async def async_migrate_entry(
hass: HomeAssistant, config_entry: VelbusConfigEntry
) -> bool:
"""Migrate old entry."""
_LOGGER.error(
"Migrating from version %s.%s", config_entry.version, config_entry.minor_version
)
# This is the config entry migration for swapping the
# usb unique id to the serial number
# migrate from 2.1 to 2.2
if (
config_entry.version < 3
and config_entry.minor_version == 1
and config_entry.unique_id is not None
):
# not all velbus devices have a unique id, so handle this correctly
parts = config_entry.unique_id.split("_")
# old one should have 4 item
if len(parts) == 4:
hass.config_entries.async_update_entry(config_entry, unique_id=parts[1])
# This is the config entry migration for adding the new program selection
# migrate from < 2 to 2.1
# This is the config entry migration for adding the new properties
# migrate from < 3 to 3.2
if config_entry.version < 3:
# clean the velbusCache
cache_path = hass.config.path(
STORAGE_DIR, f"velbuscache-{config_entry.entry_id}/"
)
if os.path.isdir(cache_path):
await hass.async_add_executor_job(shutil.rmtree, cache_path)
# update the config entry
hass.config_entries.async_update_entry(config_entry, version=3, minor_version=2)
_LOGGER.error(
"Migration to version %s.%s successful",
config_entry.version,
config_entry.minor_version,
)
return True