diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index fec6395c890..f8426bc4130 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -2,6 +2,8 @@ from __future__ import annotations +import asyncio +from dataclasses import dataclass import logging import os import shutil @@ -34,6 +36,16 @@ PLATFORMS = [ 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 + connect_task: asyncio.Task + async def velbus_connect_task( controller: Velbus, hass: HomeAssistant, entry_id: str @@ -67,19 +79,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_setup_entry(hass: HomeAssistant, entry: VelbusConfigEntry) -> bool: """Establish connection with velbus.""" - hass.data.setdefault(DOMAIN, {}) - controller = Velbus( entry.data[CONF_PORT], cache_dir=hass.config.path(STORAGE_DIR, f"velbuscache-{entry.entry_id}"), ) - hass.data[DOMAIN][entry.entry_id] = {} - hass.data[DOMAIN][entry.entry_id]["cntrl"] = controller - hass.data[DOMAIN][entry.entry_id]["tsk"] = hass.async_create_task( - velbus_connect_task(controller, hass, entry.entry_id) - ) + task = hass.async_create_task(velbus_connect_task(controller, hass, entry.entry_id)) + entry.runtime_data = VelbusData(controller=controller, connect_task=task) _migrate_device_identifiers(hass, entry.entry_id) @@ -88,17 +95,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +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 hass.data[DOMAIN][entry.entry_id]["cntrl"].stop() - hass.data[DOMAIN].pop(entry.entry_id) - if not hass.data[DOMAIN]: - hass.data.pop(DOMAIN) + await entry.runtime_data.controller.stop() return unload_ok -async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: +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, @@ -106,7 +110,9 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: ) -async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: +async def async_migrate_entry( + hass: HomeAssistant, config_entry: VelbusConfigEntry +) -> bool: """Migrate old entry.""" _LOGGER.debug("Migrating from version %s", config_entry.version) cache_path = hass.config.path(STORAGE_DIR, f"velbuscache-{config_entry.entry_id}/") diff --git a/homeassistant/components/velbus/binary_sensor.py b/homeassistant/components/velbus/binary_sensor.py index 5f363c1a035..dd65ff7d50d 100644 --- a/homeassistant/components/velbus/binary_sensor.py +++ b/homeassistant/components/velbus/binary_sensor.py @@ -3,24 +3,23 @@ from velbusaio.channels import Button as VelbusButton from homeassistant.components.binary_sensor import BinarySensorEntity -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN +from . import VelbusConfigEntry from .entity import VelbusEntity async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: VelbusConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Velbus switch based on config_entry.""" - await hass.data[DOMAIN][entry.entry_id]["tsk"] - cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] + await entry.runtime_data.connect_task async_add_entities( - VelbusBinarySensor(channel) for channel in cntrl.get_all("binary_sensor") + VelbusBinarySensor(channel) + for channel in entry.runtime_data.controller.get_all_binary_sensor() ) diff --git a/homeassistant/components/velbus/button.py b/homeassistant/components/velbus/button.py index bd5b81d67a0..2b908c188b8 100644 --- a/homeassistant/components/velbus/button.py +++ b/homeassistant/components/velbus/button.py @@ -8,24 +8,25 @@ from velbusaio.channels import ( ) from homeassistant.components.button import ButtonEntity -from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN +from . import VelbusConfigEntry from .entity import VelbusEntity, api_call async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: VelbusConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Velbus switch based on config_entry.""" - await hass.data[DOMAIN][entry.entry_id]["tsk"] - cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - async_add_entities(VelbusButton(channel) for channel in cntrl.get_all("button")) + await entry.runtime_data.connect_task + async_add_entities( + VelbusButton(channel) + for channel in entry.runtime_data.controller.get_all_button() + ) class VelbusButton(VelbusEntity, ButtonEntity): diff --git a/homeassistant/components/velbus/climate.py b/homeassistant/components/velbus/climate.py index 18142482539..fa8391d4199 100644 --- a/homeassistant/components/velbus/climate.py +++ b/homeassistant/components/velbus/climate.py @@ -11,25 +11,27 @@ from homeassistant.components.climate import ( ClimateEntityFeature, HVACMode, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers.entity_platform import AddEntitiesCallback +from . import VelbusConfigEntry from .const import DOMAIN, PRESET_MODES from .entity import VelbusEntity, api_call async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: VelbusConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Velbus switch based on config_entry.""" - await hass.data[DOMAIN][entry.entry_id]["tsk"] - cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - async_add_entities(VelbusClimate(channel) for channel in cntrl.get_all("climate")) + await entry.runtime_data.connect_task + async_add_entities( + VelbusClimate(channel) + for channel in entry.runtime_data.controller.get_all_climate() + ) class VelbusClimate(VelbusEntity, ClimateEntity): diff --git a/homeassistant/components/velbus/cover.py b/homeassistant/components/velbus/cover.py index 8b9d927f3d7..7850e7b1895 100644 --- a/homeassistant/components/velbus/cover.py +++ b/homeassistant/components/velbus/cover.py @@ -11,23 +11,24 @@ from homeassistant.components.cover import ( CoverEntity, CoverEntityFeature, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN +from . import VelbusConfigEntry from .entity import VelbusEntity, api_call async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: VelbusConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Velbus switch based on config_entry.""" - await hass.data[DOMAIN][entry.entry_id]["tsk"] - cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - async_add_entities(VelbusCover(channel) for channel in cntrl.get_all("cover")) + await entry.runtime_data.connect_task + async_add_entities( + VelbusCover(channel) + for channel in entry.runtime_data.controller.get_all_cover() + ) class VelbusCover(VelbusEntity, CoverEntity): diff --git a/homeassistant/components/velbus/diagnostics.py b/homeassistant/components/velbus/diagnostics.py index f7e29e2f57e..75b7669edec 100644 --- a/homeassistant/components/velbus/diagnostics.py +++ b/homeassistant/components/velbus/diagnostics.py @@ -7,18 +7,17 @@ from typing import Any from velbusaio.channels import Channel as VelbusChannel from velbusaio.module import Module as VelbusModule -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntry -from .const import DOMAIN +from . import VelbusConfigEntry async def async_get_config_entry_diagnostics( - hass: HomeAssistant, entry: ConfigEntry + hass: HomeAssistant, entry: VelbusConfigEntry ) -> dict[str, Any]: """Return diagnostics for a config entry.""" - controller = hass.data[DOMAIN][entry.entry_id]["cntrl"] + controller = entry.runtime_data.controller data: dict[str, Any] = {"entry": entry.as_dict(), "modules": []} for module in controller.get_modules().values(): data["modules"].append(_build_module_diagnostics_info(module)) @@ -26,10 +25,10 @@ async def async_get_config_entry_diagnostics( async def async_get_device_diagnostics( - hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry + hass: HomeAssistant, entry: VelbusConfigEntry, device: DeviceEntry ) -> dict[str, Any]: """Return diagnostics for a device entry.""" - controller = hass.data[DOMAIN][entry.entry_id]["cntrl"] + controller = entry.runtime_data.controller channel = list(next(iter(device.identifiers)))[1] modules = controller.get_modules() return _build_module_diagnostics_info(modules[int(channel)]) diff --git a/homeassistant/components/velbus/light.py b/homeassistant/components/velbus/light.py index 7145576be6a..0df4f70d753 100644 --- a/homeassistant/components/velbus/light.py +++ b/homeassistant/components/velbus/light.py @@ -20,28 +20,30 @@ from homeassistant.components.light import ( LightEntity, LightEntityFeature, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN +from . import VelbusConfigEntry from .entity import VelbusEntity, api_call async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: VelbusConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Velbus switch based on config_entry.""" - await hass.data[DOMAIN][entry.entry_id]["tsk"] - cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] + await entry.runtime_data.connect_task entities: list[Entity] = [ - VelbusLight(channel) for channel in cntrl.get_all("light") + VelbusLight(channel) + for channel in entry.runtime_data.controller.get_all_light() ] - entities.extend(VelbusButtonLight(channel) for channel in cntrl.get_all("led")) + entities.extend( + VelbusButtonLight(channel) + for channel in entry.runtime_data.controller.get_all_led() + ) async_add_entities(entities) diff --git a/homeassistant/components/velbus/quality_scale.yaml b/homeassistant/components/velbus/quality_scale.yaml index adea896a1c6..68fe5ead781 100644 --- a/homeassistant/components/velbus/quality_scale.yaml +++ b/homeassistant/components/velbus/quality_scale.yaml @@ -23,7 +23,7 @@ rules: entity-event-setup: todo entity-unique-id: done has-entity-name: todo - runtime-data: todo + runtime-data: done test-before-configure: done test-before-setup: todo unique-config-entry: diff --git a/homeassistant/components/velbus/select.py b/homeassistant/components/velbus/select.py index 7eecb85fc47..f0ad509270c 100644 --- a/homeassistant/components/velbus/select.py +++ b/homeassistant/components/velbus/select.py @@ -3,24 +3,25 @@ from velbusaio.channels import SelectedProgram from homeassistant.components.select import SelectEntity -from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN +from . import VelbusConfigEntry from .entity import VelbusEntity, api_call async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: VelbusConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Velbus select based on config_entry.""" - await hass.data[DOMAIN][entry.entry_id]["tsk"] - cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - async_add_entities(VelbusSelect(channel) for channel in cntrl.get_all("select")) + await entry.runtime_data.connect_task + async_add_entities( + VelbusSelect(channel) + for channel in entry.runtime_data.controller.get_all_select() + ) class VelbusSelect(VelbusEntity, SelectEntity): diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index b765eebcddc..598287839c1 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -9,24 +9,22 @@ from homeassistant.components.sensor import ( SensorEntity, SensorStateClass, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN +from . import VelbusConfigEntry from .entity import VelbusEntity async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: VelbusConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Velbus switch based on config_entry.""" - await hass.data[DOMAIN][entry.entry_id]["tsk"] - cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] + await entry.runtime_data.connect_task entities = [] - for channel in cntrl.get_all("sensor"): + for channel in entry.runtime_data.controller.get_all_sensor(): entities.append(VelbusSensor(channel)) if channel.is_counter_channel(): entities.append(VelbusSensor(channel, True)) diff --git a/homeassistant/components/velbus/services.py b/homeassistant/components/velbus/services.py index 83633eb66bc..3f0b1bd6cdb 100644 --- a/homeassistant/components/velbus/services.py +++ b/homeassistant/components/velbus/services.py @@ -5,6 +5,7 @@ from __future__ import annotations from contextlib import suppress import os import shutil +from typing import TYPE_CHECKING import voluptuous as vol @@ -13,6 +14,9 @@ from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.helpers import config_validation as cv from homeassistant.helpers.storage import STORAGE_DIR +if TYPE_CHECKING: + from . import VelbusConfigEntry + from .const import ( CONF_INTERFACE, CONF_MEMO_TEXT, @@ -35,20 +39,32 @@ def setup_services(hass: HomeAssistant) -> None: "The interface provided is not defined as a port in a Velbus integration" ) + def get_config_entry(interface: str) -> VelbusConfigEntry | None: + for config_entry in hass.config_entries.async_entries(DOMAIN): + if "port" in config_entry.data and config_entry.data["port"] == interface: + return config_entry + return None + async def scan(call: ServiceCall) -> None: - await hass.data[DOMAIN][call.data[CONF_INTERFACE]]["cntrl"].scan() + """Handle a scan service call.""" + entry = get_config_entry(call.data[CONF_INTERFACE]) + if entry: + await entry.runtime_data.controller.scan() async def syn_clock(call: ServiceCall) -> None: - await hass.data[DOMAIN][call.data[CONF_INTERFACE]]["cntrl"].sync_clock() + """Handle a sync clock service call.""" + entry = get_config_entry(call.data[CONF_INTERFACE]) + if entry: + await entry.runtime_data.controller.sync_clock() async def set_memo_text(call: ServiceCall) -> None: """Handle Memo Text service call.""" - memo_text = call.data[CONF_MEMO_TEXT] - await ( - hass.data[DOMAIN][call.data[CONF_INTERFACE]]["cntrl"] - .get_module(call.data[CONF_ADDRESS]) - .set_memo_text(memo_text.async_render()) - ) + entry = get_config_entry(call.data[CONF_INTERFACE]) + if entry: + memo_text = call.data[CONF_MEMO_TEXT] + module = entry.runtime_data.controller.get_module(call.data[CONF_ADDRESS]) + if module: + await module.set_memo_text(memo_text.async_render()) async def clear_cache(call: ServiceCall) -> None: """Handle a clear cache service call.""" diff --git a/homeassistant/components/velbus/switch.py b/homeassistant/components/velbus/switch.py index 1e6014b8d90..f3bd009d25e 100644 --- a/homeassistant/components/velbus/switch.py +++ b/homeassistant/components/velbus/switch.py @@ -5,23 +5,24 @@ from typing import Any from velbusaio.channels import Relay as VelbusRelay from homeassistant.components.switch import SwitchEntity -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN +from . import VelbusConfigEntry from .entity import VelbusEntity, api_call async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: VelbusConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Velbus switch based on config_entry.""" - await hass.data[DOMAIN][entry.entry_id]["tsk"] - cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - async_add_entities(VelbusSwitch(channel) for channel in cntrl.get_all("switch")) + await entry.runtime_data.connect_task + async_add_entities( + VelbusSwitch(channel) + for channel in entry.runtime_data.controller.get_all_switch() + ) class VelbusSwitch(VelbusEntity, SwitchEntity):