diff --git a/.coveragerc b/.coveragerc index b394d77c267..0cd78a5eaa6 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1535,6 +1535,7 @@ omit = homeassistant/components/vicare/entity.py homeassistant/components/vicare/number.py homeassistant/components/vicare/sensor.py + homeassistant/components/vicare/types.py homeassistant/components/vicare/utils.py homeassistant/components/vicare/water_heater.py homeassistant/components/vilfo/__init__.py diff --git a/homeassistant/components/vicare/__init__.py b/homeassistant/components/vicare/__init__.py index a2b2f3ac769..eec5f097535 100644 --- a/homeassistant/components/vicare/__init__.py +++ b/homeassistant/components/vicare/__init__.py @@ -1,15 +1,13 @@ """The ViCare integration.""" from __future__ import annotations -from collections.abc import Callable, Mapping +from collections.abc import Mapping from contextlib import suppress -from dataclasses import dataclass import logging import os from typing import Any from PyViCare.PyViCare import PyViCare -from PyViCare.PyViCareDevice import Device from PyViCare.PyViCareDeviceConfig import PyViCareDeviceConfig from PyViCare.PyViCareUtils import ( PyViCareInvalidConfigurationError, @@ -22,36 +20,14 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.storage import STORAGE_DIR -from .const import ( - CONF_HEATING_TYPE, - DEFAULT_SCAN_INTERVAL, - DOMAIN, - HEATING_TYPE_TO_CREATOR_METHOD, - PLATFORMS, - VICARE_API, - VICARE_DEVICE_CONFIG, - VICARE_DEVICE_CONFIG_LIST, - HeatingType, -) +from .const import DEFAULT_CACHE_DURATION, DEVICE_LIST, DOMAIN, PLATFORMS +from .types import ViCareDevice +from .utils import get_device _LOGGER = logging.getLogger(__name__) _TOKEN_FILENAME = "vicare_token.save" -@dataclass(frozen=True) -class ViCareRequiredKeysMixin: - """Mixin for required keys.""" - - value_getter: Callable[[Device], Any] - - -@dataclass(frozen=True) -class ViCareRequiredKeysMixinWithSet(ViCareRequiredKeysMixin): - """Mixin for required keys with setter.""" - - value_setter: Callable[[Device], bool] - - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up from config entry.""" _LOGGER.debug("Setting up ViCare component") @@ -69,10 +45,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -def vicare_login(hass: HomeAssistant, entry_data: Mapping[str, Any]) -> PyViCare: +def vicare_login( + hass: HomeAssistant, + entry_data: Mapping[str, Any], + cache_duration=DEFAULT_CACHE_DURATION, +) -> PyViCare: """Login via PyVicare API.""" vicare_api = PyViCare() - vicare_api.setCacheDuration(DEFAULT_SCAN_INTERVAL) + vicare_api.setCacheDuration(cache_duration) vicare_api.initWithCredentials( entry_data[CONF_USERNAME], entry_data[CONF_PASSWORD], @@ -87,20 +67,25 @@ def setup_vicare_api(hass: HomeAssistant, entry: ConfigEntry) -> None: vicare_api = vicare_login(hass, entry.data) device_config_list = get_supported_devices(vicare_api.devices) + if (number_of_devices := len(device_config_list)) > 1: + cache_duration = DEFAULT_CACHE_DURATION * number_of_devices + _LOGGER.debug( + "Found %s devices, adjusting cache duration to %s", + number_of_devices, + cache_duration, + ) + vicare_api = vicare_login(hass, entry.data, cache_duration) + device_config_list = get_supported_devices(vicare_api.devices) for device in device_config_list: _LOGGER.debug( "Found device: %s (online: %s)", device.getModel(), str(device.isOnline()) ) - # Currently we only support a single device - device = device_config_list[0] - hass.data[DOMAIN][entry.entry_id][VICARE_DEVICE_CONFIG_LIST] = device_config_list - hass.data[DOMAIN][entry.entry_id][VICARE_DEVICE_CONFIG] = device - hass.data[DOMAIN][entry.entry_id][VICARE_API] = getattr( - device, - HEATING_TYPE_TO_CREATOR_METHOD[HeatingType(entry.data[CONF_HEATING_TYPE])], - )() + hass.data[DOMAIN][entry.entry_id][DEVICE_LIST] = [ + ViCareDevice(config=device_config, api=get_device(entry, device_config)) + for device_config in device_config_list + ] async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/vicare/binary_sensor.py b/homeassistant/components/vicare/binary_sensor.py index f3cf585b470..a78b1fe5dab 100644 --- a/homeassistant/components/vicare/binary_sensor.py +++ b/homeassistant/components/vicare/binary_sensor.py @@ -27,9 +27,9 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ViCareRequiredKeysMixin -from .const import DOMAIN, VICARE_API, VICARE_DEVICE_CONFIG +from .const import DEVICE_LIST, DOMAIN from .entity import ViCareEntity +from .types import ViCareDevice, ViCareRequiredKeysMixin from .utils import get_burners, get_circuits, get_compressors, is_supported _LOGGER = logging.getLogger(__name__) @@ -111,29 +111,28 @@ GLOBAL_SENSORS: tuple[ViCareBinarySensorEntityDescription, ...] = ( def _build_entities( - device: PyViCareDevice, - device_config: PyViCareDeviceConfig, + device_list: list[ViCareDevice], ) -> list[ViCareBinarySensor]: """Create ViCare binary sensor entities for a device.""" - entities: list[ViCareBinarySensor] = _build_entities_for_device( - device, device_config - ) - entities.extend( - _build_entities_for_component( - get_circuits(device), device_config, CIRCUIT_SENSORS + entities: list[ViCareBinarySensor] = [] + for device in device_list: + entities.extend(_build_entities_for_device(device.api, device.config)) + entities.extend( + _build_entities_for_component( + get_circuits(device.api), device.config, CIRCUIT_SENSORS + ) ) - ) - entities.extend( - _build_entities_for_component( - get_burners(device), device_config, BURNER_SENSORS + entities.extend( + _build_entities_for_component( + get_burners(device.api), device.config, BURNER_SENSORS + ) ) - ) - entities.extend( - _build_entities_for_component( - get_compressors(device), device_config, COMPRESSOR_SENSORS + entities.extend( + _build_entities_for_component( + get_compressors(device.api), device.config, COMPRESSOR_SENSORS + ) ) - ) return entities @@ -179,14 +178,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Create the ViCare binary sensor devices.""" - api = hass.data[DOMAIN][config_entry.entry_id][VICARE_API] - device_config = hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG] + device_list = hass.data[DOMAIN][config_entry.entry_id][DEVICE_LIST] async_add_entities( await hass.async_add_executor_job( _build_entities, - api, - device_config, + device_list, ) ) diff --git a/homeassistant/components/vicare/button.py b/homeassistant/components/vicare/button.py index 8f11fdf0ac5..ae32e66dff3 100644 --- a/homeassistant/components/vicare/button.py +++ b/homeassistant/components/vicare/button.py @@ -20,9 +20,9 @@ from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ViCareRequiredKeysMixinWithSet -from .const import DOMAIN, VICARE_API, VICARE_DEVICE_CONFIG +from .const import DEVICE_LIST, DOMAIN from .entity import ViCareEntity +from .types import ViCareDevice, ViCareRequiredKeysMixinWithSet from .utils import is_supported _LOGGER = logging.getLogger(__name__) @@ -48,19 +48,19 @@ BUTTON_DESCRIPTIONS: tuple[ViCareButtonEntityDescription, ...] = ( def _build_entities( - api: PyViCareDevice, - device_config: PyViCareDeviceConfig, + device_list: list[ViCareDevice], ) -> list[ViCareButton]: """Create ViCare button entities for a device.""" return [ ViCareButton( - api, - device_config, + device.api, + device.config, description, ) + for device in device_list for description in BUTTON_DESCRIPTIONS - if is_supported(description.key, description, api) + if is_supported(description.key, description, device.api) ] @@ -70,14 +70,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Create the ViCare button entities.""" - api = hass.data[DOMAIN][config_entry.entry_id][VICARE_API] - device_config = hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG] + device_list = hass.data[DOMAIN][config_entry.entry_id][DEVICE_LIST] async_add_entities( await hass.async_add_executor_job( _build_entities, - api, - device_config, + device_list, ) ) diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index ba2665ac083..20a14ee9a04 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -40,8 +40,9 @@ from homeassistant.helpers import entity_platform import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN, VICARE_API, VICARE_DEVICE_CONFIG +from .const import DEVICE_LIST, DOMAIN from .entity import ViCareEntity +from .types import ViCareDevice from .utils import get_burners, get_circuits, get_compressors _LOGGER = logging.getLogger(__name__) @@ -99,18 +100,18 @@ HA_TO_VICARE_PRESET_HEATING = { def _build_entities( - api: PyViCareDevice, - device_config: PyViCareDeviceConfig, + device_list: list[ViCareDevice], ) -> list[ViCareClimate]: """Create ViCare climate entities for a device.""" return [ ViCareClimate( - api, + device.api, circuit, - device_config, + device.config, "heating", ) - for circuit in get_circuits(api) + for device in device_list + for circuit in get_circuits(device.api) ] @@ -120,8 +121,6 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the ViCare climate platform.""" - api = hass.data[DOMAIN][config_entry.entry_id][VICARE_API] - device_config = hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG] platform = entity_platform.async_get_current_platform() @@ -131,11 +130,12 @@ async def async_setup_entry( "set_vicare_mode", ) + device_list = hass.data[DOMAIN][config_entry.entry_id][DEVICE_LIST] + async_add_entities( await hass.async_add_executor_job( _build_entities, - api, - device_config, + device_list, ) ) diff --git a/homeassistant/components/vicare/const.py b/homeassistant/components/vicare/const.py index 3ed81ab587a..a548b7772cf 100644 --- a/homeassistant/components/vicare/const.py +++ b/homeassistant/components/vicare/const.py @@ -14,15 +14,13 @@ PLATFORMS = [ Platform.WATER_HEATER, ] -VICARE_DEVICE_CONFIG = "device_conf" -VICARE_DEVICE_CONFIG_LIST = "device_config_list" -VICARE_API = "api" +DEVICE_LIST = "device_list" VICARE_NAME = "ViCare" CONF_CIRCUIT = "circuit" CONF_HEATING_TYPE = "heating_type" -DEFAULT_SCAN_INTERVAL = 60 +DEFAULT_CACHE_DURATION = 60 VICARE_CUBIC_METER = "cubicMeter" VICARE_KWH = "kilowattHour" diff --git a/homeassistant/components/vicare/diagnostics.py b/homeassistant/components/vicare/diagnostics.py index aa5d08f92d8..23a3c8640c5 100644 --- a/homeassistant/components/vicare/diagnostics.py +++ b/homeassistant/components/vicare/diagnostics.py @@ -9,7 +9,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_CLIENT_ID, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from .const import DOMAIN, VICARE_DEVICE_CONFIG_LIST +from .const import DEVICE_LIST, DOMAIN TO_REDACT = {CONF_CLIENT_ID, CONF_PASSWORD, CONF_USERNAME} @@ -18,10 +18,11 @@ async def async_get_config_entry_diagnostics( hass: HomeAssistant, entry: ConfigEntry ) -> dict[str, Any]: """Return diagnostics for a config entry.""" - # Currently we only support a single device data = [] - for device in hass.data[DOMAIN][entry.entry_id][VICARE_DEVICE_CONFIG_LIST]: - data.append(json.loads(await hass.async_add_executor_job(device.dump_secure))) + for device in hass.data[DOMAIN][entry.entry_id][DEVICE_LIST]: + data.append( + json.loads(await hass.async_add_executor_job(device.config.dump_secure)) + ) return { "entry": async_redact_data(entry.as_dict(), TO_REDACT), "data": data, diff --git a/homeassistant/components/vicare/number.py b/homeassistant/components/vicare/number.py index d4dd0437b04..25c74e890c4 100644 --- a/homeassistant/components/vicare/number.py +++ b/homeassistant/components/vicare/number.py @@ -29,9 +29,9 @@ from homeassistant.const import EntityCategory, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ViCareRequiredKeysMixin -from .const import DOMAIN, VICARE_API, VICARE_DEVICE_CONFIG +from .const import DEVICE_LIST, DOMAIN from .entity import ViCareEntity +from .types import ViCareDevice, ViCareRequiredKeysMixin from .utils import get_circuits, is_supported _LOGGER = logging.getLogger(__name__) @@ -123,18 +123,18 @@ CIRCUIT_ENTITY_DESCRIPTIONS: tuple[ViCareNumberEntityDescription, ...] = ( def _build_entities( - api: PyViCareDevice, - device_config: PyViCareDeviceConfig, + device_list: list[ViCareDevice], ) -> list[ViCareNumber]: - """Create ViCare number entities for a component.""" + """Create ViCare number entities for a device.""" return [ ViCareNumber( circuit, - device_config, + device.config, description, ) - for circuit in get_circuits(api) + for device in device_list + for circuit in get_circuits(device.api) for description in CIRCUIT_ENTITY_DESCRIPTIONS if is_supported(description.key, description, circuit) ] @@ -146,14 +146,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Create the ViCare number devices.""" - api = hass.data[DOMAIN][config_entry.entry_id][VICARE_API] - device_config = hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG] + device_list = hass.data[DOMAIN][config_entry.entry_id][DEVICE_LIST] async_add_entities( await hass.async_add_executor_job( _build_entities, - api, - device_config, + device_list, ) ) diff --git a/homeassistant/components/vicare/sensor.py b/homeassistant/components/vicare/sensor.py index a8a21c7e787..312a05e813d 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -38,16 +38,15 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ViCareRequiredKeysMixin from .const import ( + DEVICE_LIST, DOMAIN, - VICARE_API, VICARE_CUBIC_METER, - VICARE_DEVICE_CONFIG, VICARE_KWH, VICARE_UNIT_TO_UNIT_OF_MEASUREMENT, ) from .entity import ViCareEntity +from .types import ViCareDevice, ViCareRequiredKeysMixin from .utils import get_burners, get_circuits, get_compressors, is_supported _LOGGER = logging.getLogger(__name__) @@ -693,27 +692,28 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( def _build_entities( - device: PyViCareDevice, - device_config: PyViCareDeviceConfig, + device_list: list[ViCareDevice], ) -> list[ViCareSensor]: """Create ViCare sensor entities for a device.""" - entities: list[ViCareSensor] = _build_entities_for_device(device, device_config) - entities.extend( - _build_entities_for_component( - get_circuits(device), device_config, CIRCUIT_SENSORS + entities: list[ViCareSensor] = [] + for device in device_list: + entities.extend(_build_entities_for_device(device.api, device.config)) + entities.extend( + _build_entities_for_component( + get_circuits(device.api), device.config, CIRCUIT_SENSORS + ) ) - ) - entities.extend( - _build_entities_for_component( - get_burners(device), device_config, BURNER_SENSORS + entities.extend( + _build_entities_for_component( + get_burners(device.api), device.config, BURNER_SENSORS + ) ) - ) - entities.extend( - _build_entities_for_component( - get_compressors(device), device_config, COMPRESSOR_SENSORS + entities.extend( + _build_entities_for_component( + get_compressors(device.api), device.config, COMPRESSOR_SENSORS + ) ) - ) return entities @@ -759,16 +759,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Create the ViCare sensor devices.""" - api: PyViCareDevice = hass.data[DOMAIN][config_entry.entry_id][VICARE_API] - device_config: PyViCareDeviceConfig = hass.data[DOMAIN][config_entry.entry_id][ - VICARE_DEVICE_CONFIG - ] + device_list = hass.data[DOMAIN][config_entry.entry_id][DEVICE_LIST] async_add_entities( await hass.async_add_executor_job( _build_entities, - api, - device_config, + device_list, ) ) diff --git a/homeassistant/components/vicare/types.py b/homeassistant/components/vicare/types.py new file mode 100644 index 00000000000..dcb6036d919 --- /dev/null +++ b/homeassistant/components/vicare/types.py @@ -0,0 +1,29 @@ +"""Types for the ViCare integration.""" +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from PyViCare.PyViCareDevice import Device as PyViCareDevice +from PyViCare.PyViCareDeviceConfig import PyViCareDeviceConfig + + +@dataclass(frozen=True) +class ViCareDevice: + """Dataclass holding the device api and config.""" + + config: PyViCareDeviceConfig + api: PyViCareDevice + + +@dataclass(frozen=True) +class ViCareRequiredKeysMixin: + """Mixin for required keys.""" + + value_getter: Callable[[PyViCareDevice], Any] + + +@dataclass(frozen=True) +class ViCareRequiredKeysMixinWithSet(ViCareRequiredKeysMixin): + """Mixin for required keys with setter.""" + + value_setter: Callable[[PyViCareDevice], bool] diff --git a/homeassistant/components/vicare/utils.py b/homeassistant/components/vicare/utils.py index a084eee383b..649b1859442 100644 --- a/homeassistant/components/vicare/utils.py +++ b/homeassistant/components/vicare/utils.py @@ -2,16 +2,30 @@ import logging from PyViCare.PyViCareDevice import Device as PyViCareDevice +from PyViCare.PyViCareDeviceConfig import PyViCareDeviceConfig from PyViCare.PyViCareHeatingDevice import ( HeatingDeviceWithComponent as PyViCareHeatingDeviceComponent, ) from PyViCare.PyViCareUtils import PyViCareNotSupportedFeatureError -from . import ViCareRequiredKeysMixin +from homeassistant.config_entries import ConfigEntry + +from .const import CONF_HEATING_TYPE, HEATING_TYPE_TO_CREATOR_METHOD, HeatingType +from .types import ViCareRequiredKeysMixin _LOGGER = logging.getLogger(__name__) +def get_device( + entry: ConfigEntry, device_config: PyViCareDeviceConfig +) -> PyViCareDevice: + """Get device for device config.""" + return getattr( + device_config, + HEATING_TYPE_TO_CREATOR_METHOD[HeatingType(entry.data[CONF_HEATING_TYPE])], + )() + + def is_supported( name: str, entity_description: ViCareRequiredKeysMixin, diff --git a/homeassistant/components/vicare/water_heater.py b/homeassistant/components/vicare/water_heater.py index 66a90ca065b..9a8fb7eb092 100644 --- a/homeassistant/components/vicare/water_heater.py +++ b/homeassistant/components/vicare/water_heater.py @@ -24,8 +24,9 @@ from homeassistant.const import ATTR_TEMPERATURE, PRECISION_TENTHS, UnitOfTemper from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN, VICARE_API, VICARE_DEVICE_CONFIG +from .const import DEVICE_LIST, DOMAIN from .entity import ViCareEntity +from .types import ViCareDevice from .utils import get_circuits _LOGGER = logging.getLogger(__name__) @@ -61,18 +62,19 @@ HA_TO_VICARE_HVAC_DHW = { def _build_entities( - api: PyViCareDevice, - device_config: PyViCareDeviceConfig, + device_list: list[ViCareDevice], ) -> list[ViCareWater]: """Create ViCare domestic hot water entities for a device.""" + return [ ViCareWater( - api, + device.api, circuit, - device_config, + device.config, "domestic_hot_water", ) - for circuit in get_circuits(api) + for device in device_list + for circuit in get_circuits(device.api) ] @@ -82,14 +84,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the ViCare water heater platform.""" - api = hass.data[DOMAIN][config_entry.entry_id][VICARE_API] - device_config = hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG] + device_list = hass.data[DOMAIN][config_entry.entry_id][DEVICE_LIST] async_add_entities( await hass.async_add_executor_job( _build_entities, - api, - device_config, + device_list, ) )