Skip to content

Commit

Permalink
Add support for multiple devices linked to a Viessmann account (#96044)
Browse files Browse the repository at this point in the history
* care about all devices

* use first device for diagnostics

* update constants

* handle multiple devices

* handle multiple devices

* handle multiple devices

* handle multiple devices

* handle multiple devices

* code style

* code style

* code style

* code style

* code style

* remove unused import

* remove unused import

* use has_entity_name and add serial to device name

* use has_entity_name and add serial to device name

* use has_entity_name and add serial to device name

* use has_entity_name and add serial to device name

* use has_entity_name and add serial to device name

* remove unused constant

* Update const.py

* Update binary_sensor.py

* change format

* change format

* fix line duplication

* fix line duplication

* change format

* fix typo

* use serial in device name if multiple devices are found

* add common base class

* use base class

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update sensor.py

* Update binary_sensor.py

* correct import

* use base class

* fix cdestyle findings

* fix pylint findings

* fix mypy findings

* fix codestyle finidings

* move has_entity_name to base class

* Revert "fix mypy findings"

This reverts commit 2d78801.

* fix type issue

* move multiple device handling

* fix import

* remove special handling for device name

* extract api getter

* Update __init__.py

* Update __init__.py

* Update entity.py

* Update button.py

* Update binary_sensor.py

* Update climate.py

* Update sensor.py

* Update water_heater.py

* Apply suggestions from code review

Co-authored-by: Joost Lekkerkerker <[email protected]>

* Update __init__.py

* fix mypy & black

* move get_device to utils

* rename const

* Apply suggestions from code review

Co-authored-by: Robert Resch <[email protected]>

* store device in config entry

* extract types

* fix diagnostics

* handle new platform

* handle api rate limit

* add types

* add types

* rename

* add types

* ignore gateways for now

* Update .coveragerc

* adjust types

* fix merge issues

* rename

* Update types.py

* fix type

* add test method

* simplify

* ignore unused devices

* Apply suggestions from code review

Co-authored-by: Robert Resch <[email protected]>

* fix findings

* handle unsupported devices

* Apply suggestions from code review

Co-authored-by: Robert Resch <[email protected]>

* Update types.py

* fix format

* adjust variable naming

* Update conftest.py

* Update conftest.py

* remove kw_only

* Apply suggestions from code review

* Update __init__.py

* Update binary_sensor.py

* Update button.py

* Update climate.py

* Update const.py

* Update diagnostics.py

* Update number.py

* Update sensor.py

* Update types.py

* Update water_heater.py

* fix comment

* Apply suggestions from code review

Co-authored-by: Erik Montnemery <[email protected]>

---------

Co-authored-by: Joost Lekkerkerker <[email protected]>
Co-authored-by: Robert Resch <[email protected]>
Co-authored-by: Erik Montnemery <[email protected]>
  • Loading branch information
4 people authored Feb 15, 2024
1 parent fd0f093 commit 47cbe8f
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 136 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
61 changes: 23 additions & 38 deletions homeassistant/components/vicare/__init__.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -22,36 +20,14 @@
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")
Expand All @@ -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],
Expand All @@ -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:
Expand Down
43 changes: 20 additions & 23 deletions homeassistant/components/vicare/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
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__)
Expand Down Expand Up @@ -111,29 +111,28 @@ class 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


Expand Down Expand Up @@ -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,
)
)

Expand Down
20 changes: 9 additions & 11 deletions homeassistant/components/vicare/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
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__)
Expand All @@ -48,19 +48,19 @@ class 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)
]


Expand All @@ -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,
)
)

Expand Down
20 changes: 10 additions & 10 deletions homeassistant/components/vicare/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@
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__)
Expand Down Expand Up @@ -99,18 +100,18 @@


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)
]


Expand All @@ -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()

Expand All @@ -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,
)
)

Expand Down
6 changes: 2 additions & 4 deletions homeassistant/components/vicare/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@
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"
Expand Down
9 changes: 5 additions & 4 deletions homeassistant/components/vicare/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
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}

Expand All @@ -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,
Expand Down
20 changes: 9 additions & 11 deletions homeassistant/components/vicare/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
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__)
Expand Down Expand Up @@ -123,18 +123,18 @@ class ViCareNumberEntityDescription(NumberEntityDescription, ViCareRequiredKeysM


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)
]
Expand All @@ -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,
)
)

Expand Down
Loading

0 comments on commit 47cbe8f

Please sign in to comment.