174 lines
5.6 KiB
Python
174 lines
5.6 KiB
Python
"""Support for Velbus devices."""
|
|
|
|
from __future__ import annotations
|
|
|
|
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 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(
|
|
entry.data[CONF_PORT],
|
|
cache_dir=hass.config.path(STORAGE_DIR, f"velbuscache-{entry.entry_id}"),
|
|
)
|
|
try:
|
|
await controller.connect()
|
|
except VelbusConnectionFailed as error:
|
|
raise ConfigEntryNotReady("Cannot connect to Velbus") 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 adding the new program selection
|
|
# migrate from 1.x to 2.1
|
|
if config_entry.version < 2:
|
|
# 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)
|
|
|
|
# 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])
|
|
|
|
# update the config entry
|
|
hass.config_entries.async_update_entry(config_entry, version=2, minor_version=2)
|
|
|
|
_LOGGER.error(
|
|
"Migration to version %s.%s successful",
|
|
config_entry.version,
|
|
config_entry.minor_version,
|
|
)
|
|
return True
|