Update PyVicare to 2.13.0 (#57700)
* Update PyVicare to 2.x With PyViCare 2.8.1 a breaking change was introduced which required changes on sensor and binary_sensor platforms: - Circuit, Burner and Compressor have been separated out from the "main" device - Multiple circuits and burners allow "duplicate sensors". We add the circuit or burner number as suffix now At the same time the sensors are now created only when available: During entity creation we can check if the value is provided for the user's device. Sensors are not created by heating type anymore but instead the new API structure is reflected, providing device, burner or circuit sensors. For details of breaking changes from PyViCare 1.x to 2.x please see https://github.com/somm15/PyViCare#breaking-changes-in-version-2x * Integrate review comments * variables cleanup * Update unique ids The unique ids shall not depend on the name but on the entity description key (which should not change) and the id of the circuit, burner or device.
This commit is contained in:
parent
be4b1d15ec
commit
66ae116023
8 changed files with 661 additions and 441 deletions
|
@ -1,16 +1,12 @@
|
|||
"""The ViCare integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
import enum
|
||||
import logging
|
||||
from typing import Generic, TypeVar
|
||||
from typing import Callable
|
||||
|
||||
from PyViCare.PyViCare import PyViCare
|
||||
from PyViCare.PyViCareDevice import Device
|
||||
from PyViCare.PyViCareFuelCell import FuelCell
|
||||
from PyViCare.PyViCareGazBoiler import GazBoiler
|
||||
from PyViCare.PyViCareHeatPump import HeatPump
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
|
@ -24,42 +20,35 @@ from homeassistant.helpers import discovery
|
|||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.storage import STORAGE_DIR
|
||||
|
||||
from .const import (
|
||||
CONF_CIRCUIT,
|
||||
CONF_HEATING_TYPE,
|
||||
DEFAULT_HEATING_TYPE,
|
||||
DOMAIN,
|
||||
HEATING_TYPE_TO_CREATOR_METHOD,
|
||||
PLATFORMS,
|
||||
VICARE_API,
|
||||
VICARE_CIRCUITS,
|
||||
VICARE_DEVICE_CONFIG,
|
||||
VICARE_NAME,
|
||||
HeatingType,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = ["climate", "sensor", "binary_sensor", "water_heater"]
|
||||
|
||||
DOMAIN = "vicare"
|
||||
VICARE_API = "api"
|
||||
VICARE_NAME = "name"
|
||||
VICARE_HEATING_TYPE = "heating_type"
|
||||
|
||||
CONF_CIRCUIT = "circuit"
|
||||
CONF_HEATING_TYPE = "heating_type"
|
||||
DEFAULT_HEATING_TYPE = "generic"
|
||||
|
||||
|
||||
ApiT = TypeVar("ApiT", bound=Device)
|
||||
|
||||
|
||||
@dataclass()
|
||||
class ViCareRequiredKeysMixin(Generic[ApiT]):
|
||||
class ViCareRequiredKeysMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
value_getter: Callable[[ApiT], bool]
|
||||
|
||||
|
||||
class HeatingType(enum.Enum):
|
||||
"""Possible options for heating type."""
|
||||
|
||||
generic = "generic"
|
||||
gas = "gas"
|
||||
heatpump = "heatpump"
|
||||
fuelcell = "fuelcell"
|
||||
value_getter: Callable[[Device], bool]
|
||||
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
DOMAIN: vol.All(
|
||||
cv.deprecated(CONF_CIRCUIT),
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
|
@ -67,12 +56,15 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
vol.Optional(CONF_SCAN_INTERVAL, default=60): vol.All(
|
||||
cv.time_period, lambda value: value.total_seconds()
|
||||
),
|
||||
vol.Optional(CONF_CIRCUIT): int,
|
||||
vol.Optional(
|
||||
CONF_CIRCUIT
|
||||
): int, # Ignored: All circuits are now supported. Will be removed when switching to Setup via UI.
|
||||
vol.Optional(CONF_NAME, default="ViCare"): cv.string,
|
||||
vol.Optional(CONF_HEATING_TYPE, default=DEFAULT_HEATING_TYPE): cv.enum(
|
||||
HeatingType
|
||||
),
|
||||
vol.Optional(
|
||||
CONF_HEATING_TYPE, default=DEFAULT_HEATING_TYPE
|
||||
): cv.enum(HeatingType),
|
||||
}
|
||||
),
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
|
@ -83,34 +75,40 @@ def setup(hass, config):
|
|||
"""Create the ViCare component."""
|
||||
conf = config[DOMAIN]
|
||||
params = {"token_file": hass.config.path(STORAGE_DIR, "vicare_token.save")}
|
||||
if conf.get(CONF_CIRCUIT) is not None:
|
||||
params["circuit"] = conf[CONF_CIRCUIT]
|
||||
|
||||
params["cacheDuration"] = conf.get(CONF_SCAN_INTERVAL)
|
||||
params["client_id"] = conf.get(CONF_CLIENT_ID)
|
||||
heating_type = conf[CONF_HEATING_TYPE]
|
||||
|
||||
try:
|
||||
if heating_type == HeatingType.gas:
|
||||
vicare_api = GazBoiler(conf[CONF_USERNAME], conf[CONF_PASSWORD], **params)
|
||||
elif heating_type == HeatingType.heatpump:
|
||||
vicare_api = HeatPump(conf[CONF_USERNAME], conf[CONF_PASSWORD], **params)
|
||||
elif heating_type == HeatingType.fuelcell:
|
||||
vicare_api = FuelCell(conf[CONF_USERNAME], conf[CONF_PASSWORD], **params)
|
||||
else:
|
||||
vicare_api = Device(conf[CONF_USERNAME], conf[CONF_PASSWORD], **params)
|
||||
except AttributeError:
|
||||
_LOGGER.error(
|
||||
"Failed to create PyViCare API client. Please check your credentials"
|
||||
)
|
||||
return False
|
||||
|
||||
hass.data[DOMAIN] = {}
|
||||
hass.data[DOMAIN][VICARE_API] = vicare_api
|
||||
hass.data[DOMAIN][VICARE_NAME] = conf[CONF_NAME]
|
||||
hass.data[DOMAIN][VICARE_HEATING_TYPE] = heating_type
|
||||
setup_vicare_api(hass, conf, hass.data[DOMAIN])
|
||||
|
||||
hass.data[DOMAIN][CONF_HEATING_TYPE] = conf[CONF_HEATING_TYPE]
|
||||
|
||||
for platform in PLATFORMS:
|
||||
discovery.load_platform(hass, platform, DOMAIN, {}, config)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def setup_vicare_api(hass, conf, entity_data):
|
||||
"""Set up PyVicare API."""
|
||||
vicare_api = PyViCare()
|
||||
vicare_api.setCacheDuration(conf[CONF_SCAN_INTERVAL])
|
||||
vicare_api.initWithCredentials(
|
||||
conf[CONF_USERNAME],
|
||||
conf[CONF_PASSWORD],
|
||||
conf[CONF_CLIENT_ID],
|
||||
hass.config.path(STORAGE_DIR, "vicare_token.save"),
|
||||
)
|
||||
|
||||
device = vicare_api.devices[0]
|
||||
for device in vicare_api.devices:
|
||||
_LOGGER.info(
|
||||
"Found device: %s (online: %s)", device.getModel(), str(device.isOnline())
|
||||
)
|
||||
entity_data[VICARE_DEVICE_CONFIG] = device
|
||||
entity_data[VICARE_API] = getattr(
|
||||
device, HEATING_TYPE_TO_CREATOR_METHOD[conf[CONF_HEATING_TYPE]]
|
||||
)()
|
||||
entity_data[VICARE_CIRCUITS] = entity_data[VICARE_API].circuits
|
||||
|
|
|
@ -4,12 +4,12 @@ from __future__ import annotations
|
|||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
from PyViCare.PyViCare import PyViCareNotSupportedFeatureError, PyViCareRateLimitError
|
||||
from PyViCare.PyViCareDevice import Device
|
||||
from PyViCare.PyViCareGazBoiler import GazBoiler
|
||||
from PyViCare.PyViCareHeatPump import HeatPump
|
||||
from PyViCare.PyViCareUtils import (
|
||||
PyViCareInvalidDataError,
|
||||
PyViCareNotSupportedFeatureError,
|
||||
PyViCareRateLimitError,
|
||||
)
|
||||
import requests
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
|
@ -18,36 +18,25 @@ from homeassistant.components.binary_sensor import (
|
|||
BinarySensorEntityDescription,
|
||||
)
|
||||
|
||||
from . import (
|
||||
DOMAIN as VICARE_DOMAIN,
|
||||
VICARE_API,
|
||||
VICARE_HEATING_TYPE,
|
||||
VICARE_NAME,
|
||||
ApiT,
|
||||
HeatingType,
|
||||
ViCareRequiredKeysMixin,
|
||||
)
|
||||
from . import ViCareRequiredKeysMixin
|
||||
from .const import DOMAIN, VICARE_API, VICARE_DEVICE_CONFIG, VICARE_NAME
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SENSOR_CIRCULATION_PUMP_ACTIVE = "circulationpump_active"
|
||||
|
||||
# gas sensors
|
||||
SENSOR_BURNER_ACTIVE = "burner_active"
|
||||
|
||||
# heatpump sensors
|
||||
SENSOR_COMPRESSOR_ACTIVE = "compressor_active"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ViCareBinarySensorEntityDescription(
|
||||
BinarySensorEntityDescription, ViCareRequiredKeysMixin[ApiT]
|
||||
BinarySensorEntityDescription, ViCareRequiredKeysMixin
|
||||
):
|
||||
"""Describes ViCare binary sensor entity."""
|
||||
|
||||
|
||||
SENSOR_TYPES_GENERIC: tuple[ViCareBinarySensorEntityDescription[Device]] = (
|
||||
ViCareBinarySensorEntityDescription[Device](
|
||||
CIRCUIT_SENSORS: tuple[ViCareBinarySensorEntityDescription, ...] = (
|
||||
ViCareBinarySensorEntityDescription(
|
||||
key=SENSOR_CIRCULATION_PUMP_ACTIVE,
|
||||
name="Circulation pump active",
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
|
@ -55,80 +44,133 @@ SENSOR_TYPES_GENERIC: tuple[ViCareBinarySensorEntityDescription[Device]] = (
|
|||
),
|
||||
)
|
||||
|
||||
SENSOR_TYPES_GAS: tuple[ViCareBinarySensorEntityDescription[GazBoiler]] = (
|
||||
ViCareBinarySensorEntityDescription[GazBoiler](
|
||||
BURNER_SENSORS: tuple[ViCareBinarySensorEntityDescription, ...] = (
|
||||
ViCareBinarySensorEntityDescription(
|
||||
key=SENSOR_BURNER_ACTIVE,
|
||||
name="Burner active",
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
value_getter=lambda api: api.getBurnerActive(),
|
||||
value_getter=lambda api: api.getActive(),
|
||||
),
|
||||
)
|
||||
|
||||
SENSOR_TYPES_HEATPUMP: tuple[ViCareBinarySensorEntityDescription[HeatPump]] = (
|
||||
ViCareBinarySensorEntityDescription[HeatPump](
|
||||
COMPRESSOR_SENSORS: tuple[ViCareBinarySensorEntityDescription, ...] = (
|
||||
ViCareBinarySensorEntityDescription(
|
||||
key=SENSOR_COMPRESSOR_ACTIVE,
|
||||
name="Compressor active",
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
value_getter=lambda api: api.getCompressorActive(),
|
||||
value_getter=lambda api: api.getActive(),
|
||||
),
|
||||
)
|
||||
|
||||
SENSORS_GENERIC = [SENSOR_CIRCULATION_PUMP_ACTIVE]
|
||||
|
||||
SENSORS_BY_HEATINGTYPE = {
|
||||
HeatingType.gas: [SENSOR_BURNER_ACTIVE],
|
||||
HeatingType.heatpump: [SENSOR_COMPRESSOR_ACTIVE],
|
||||
HeatingType.fuelcell: [SENSOR_BURNER_ACTIVE],
|
||||
}
|
||||
def _build_entity(name, vicare_api, device_config, sensor):
|
||||
"""Create a ViCare binary sensor entity."""
|
||||
try:
|
||||
sensor.value_getter(vicare_api)
|
||||
_LOGGER.debug("Found entity %s", name)
|
||||
except PyViCareNotSupportedFeatureError:
|
||||
_LOGGER.info("Feature not supported %s", name)
|
||||
return None
|
||||
except AttributeError:
|
||||
_LOGGER.debug("Attribute Error %s", name)
|
||||
return None
|
||||
|
||||
return ViCareBinarySensor(
|
||||
name,
|
||||
vicare_api,
|
||||
device_config,
|
||||
sensor,
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Create the ViCare sensor devices."""
|
||||
async def _entities_from_descriptions(
|
||||
hass, name, all_devices, sensor_descriptions, iterables
|
||||
):
|
||||
"""Create entities from descriptions and list of burners/circuits."""
|
||||
for description in sensor_descriptions:
|
||||
for current in iterables:
|
||||
suffix = ""
|
||||
if len(iterables) > 1:
|
||||
suffix = f" {current.id}"
|
||||
entity = await hass.async_add_executor_job(
|
||||
_build_entity,
|
||||
f"{name} {description.name}{suffix}",
|
||||
current,
|
||||
hass.data[DOMAIN][VICARE_DEVICE_CONFIG],
|
||||
description,
|
||||
)
|
||||
if entity is not None:
|
||||
all_devices.append(entity)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Create the ViCare binary sensor devices."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
vicare_api = hass.data[VICARE_DOMAIN][VICARE_API]
|
||||
heating_type = hass.data[VICARE_DOMAIN][VICARE_HEATING_TYPE]
|
||||
name = hass.data[DOMAIN][VICARE_NAME]
|
||||
api = hass.data[DOMAIN][VICARE_API]
|
||||
|
||||
sensors = SENSORS_GENERIC.copy()
|
||||
all_devices = []
|
||||
|
||||
if heating_type != HeatingType.generic:
|
||||
sensors.extend(SENSORS_BY_HEATINGTYPE[heating_type])
|
||||
|
||||
add_entities(
|
||||
[
|
||||
ViCareBinarySensor(
|
||||
hass.data[VICARE_DOMAIN][VICARE_NAME], vicare_api, description
|
||||
)
|
||||
for description in (
|
||||
*SENSOR_TYPES_GENERIC,
|
||||
*SENSOR_TYPES_GAS,
|
||||
*SENSOR_TYPES_HEATPUMP,
|
||||
)
|
||||
if description.key in sensors
|
||||
]
|
||||
for description in CIRCUIT_SENSORS:
|
||||
for circuit in api.circuits:
|
||||
suffix = ""
|
||||
if len(api.circuits) > 1:
|
||||
suffix = f" {circuit.id}"
|
||||
entity = await hass.async_add_executor_job(
|
||||
_build_entity,
|
||||
f"{name} {description.name}{suffix}",
|
||||
circuit,
|
||||
hass.data[DOMAIN][VICARE_DEVICE_CONFIG],
|
||||
description,
|
||||
)
|
||||
if entity is not None:
|
||||
all_devices.append(entity)
|
||||
|
||||
try:
|
||||
_entities_from_descriptions(
|
||||
hass, name, all_devices, BURNER_SENSORS, api.burners
|
||||
)
|
||||
except PyViCareNotSupportedFeatureError:
|
||||
_LOGGER.info("No burners found")
|
||||
|
||||
DescriptionT = Union[
|
||||
ViCareBinarySensorEntityDescription[Device],
|
||||
ViCareBinarySensorEntityDescription[GazBoiler],
|
||||
ViCareBinarySensorEntityDescription[HeatPump],
|
||||
]
|
||||
try:
|
||||
_entities_from_descriptions(
|
||||
hass, name, all_devices, COMPRESSOR_SENSORS, api.compressors
|
||||
)
|
||||
except PyViCareNotSupportedFeatureError:
|
||||
_LOGGER.info("No compressors found")
|
||||
|
||||
async_add_entities(all_devices)
|
||||
|
||||
|
||||
class ViCareBinarySensor(BinarySensorEntity):
|
||||
"""Representation of a ViCare sensor."""
|
||||
|
||||
entity_description: DescriptionT
|
||||
entity_description: ViCareBinarySensorEntityDescription
|
||||
|
||||
def __init__(self, name, api, description: DescriptionT):
|
||||
def __init__(
|
||||
self, name, api, device_config, description: ViCareBinarySensorEntityDescription
|
||||
):
|
||||
"""Initialize the sensor."""
|
||||
self.entity_description = description
|
||||
self._attr_name = f"{name} {description.name}"
|
||||
self._attr_name = name
|
||||
self._api = api
|
||||
self.entity_description = description
|
||||
self._device_config = device_config
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device info for this device."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self._device_config.getConfig().serial)},
|
||||
"name": self._device_config.getModel(),
|
||||
"manufacturer": "Viessmann",
|
||||
"model": (DOMAIN, self._device_config.getModel()),
|
||||
}
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
|
@ -136,8 +178,13 @@ class ViCareBinarySensor(BinarySensorEntity):
|
|||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID."""
|
||||
return f"{self._api.service.id}-{self.entity_description.key}"
|
||||
"""Return unique ID for this device."""
|
||||
tmp_id = (
|
||||
f"{self._device_config.getConfig().serial}-{self.entity_description.key}"
|
||||
)
|
||||
if hasattr(self._api, "id"):
|
||||
return f"{tmp_id}-{self._api.id}"
|
||||
return tmp_id
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
|
@ -155,3 +202,5 @@ class ViCareBinarySensor(BinarySensorEntity):
|
|||
_LOGGER.error("Unable to decode data from ViCare server")
|
||||
except PyViCareRateLimitError as limit_exception:
|
||||
_LOGGER.error("Vicare API rate limit exceeded: %s", limit_exception)
|
||||
except PyViCareInvalidDataError as invalid_data_exception:
|
||||
_LOGGER.error("Invalid data from Vicare server: %s", invalid_data_exception)
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
from contextlib import suppress
|
||||
import logging
|
||||
|
||||
from PyViCare.PyViCare import PyViCareNotSupportedFeatureError, PyViCareRateLimitError
|
||||
from PyViCare.PyViCareUtils import (
|
||||
PyViCareInvalidDataError,
|
||||
PyViCareNotSupportedFeatureError,
|
||||
PyViCareRateLimitError,
|
||||
)
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
|
@ -21,12 +25,13 @@ from homeassistant.components.climate.const import (
|
|||
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
|
||||
from homeassistant.helpers import entity_platform
|
||||
|
||||
from . import (
|
||||
DOMAIN as VICARE_DOMAIN,
|
||||
from .const import (
|
||||
CONF_HEATING_TYPE,
|
||||
DOMAIN,
|
||||
VICARE_API,
|
||||
VICARE_HEATING_TYPE,
|
||||
VICARE_CIRCUITS,
|
||||
VICARE_DEVICE_CONFIG,
|
||||
VICARE_NAME,
|
||||
HeatingType,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -87,23 +92,38 @@ HA_TO_VICARE_PRESET_HEATING = {
|
|||
}
|
||||
|
||||
|
||||
def _build_entity(name, vicare_api, circuit, device_config, heating_type):
|
||||
"""Create a ViCare climate entity."""
|
||||
_LOGGER.debug("Found device %s", name)
|
||||
return ViCareClimate(name, vicare_api, device_config, circuit, heating_type)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, hass_config, async_add_entities, discovery_info=None
|
||||
):
|
||||
"""Create the ViCare climate devices."""
|
||||
# Legacy setup. Remove after configuration.yaml deprecation end
|
||||
if discovery_info is None:
|
||||
return
|
||||
vicare_api = hass.data[VICARE_DOMAIN][VICARE_API]
|
||||
heating_type = hass.data[VICARE_DOMAIN][VICARE_HEATING_TYPE]
|
||||
async_add_entities(
|
||||
[
|
||||
ViCareClimate(
|
||||
f"{hass.data[VICARE_DOMAIN][VICARE_NAME]} Heating",
|
||||
vicare_api,
|
||||
heating_type,
|
||||
)
|
||||
]
|
||||
|
||||
name = hass.data[DOMAIN][VICARE_NAME]
|
||||
all_devices = []
|
||||
|
||||
for circuit in hass.data[DOMAIN][VICARE_CIRCUITS]:
|
||||
suffix = ""
|
||||
if len(hass.data[DOMAIN][VICARE_CIRCUITS]) > 1:
|
||||
suffix = f" {circuit.id}"
|
||||
entity = _build_entity(
|
||||
f"{name} Heating{suffix}",
|
||||
hass.data[DOMAIN][VICARE_API],
|
||||
hass.data[DOMAIN][VICARE_DEVICE_CONFIG],
|
||||
circuit,
|
||||
hass.data[DOMAIN][CONF_HEATING_TYPE],
|
||||
)
|
||||
if entity is not None:
|
||||
all_devices.append(entity)
|
||||
|
||||
async_add_entities(all_devices)
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
|
||||
|
@ -121,11 +141,13 @@ async def async_setup_platform(
|
|||
class ViCareClimate(ClimateEntity):
|
||||
"""Representation of the ViCare heating climate device."""
|
||||
|
||||
def __init__(self, name, api, heating_type):
|
||||
def __init__(self, name, api, circuit, device_config, heating_type):
|
||||
"""Initialize the climate device."""
|
||||
self._name = name
|
||||
self._state = None
|
||||
self._api = api
|
||||
self._circuit = circuit
|
||||
self._device_config = device_config
|
||||
self._attributes = {}
|
||||
self._target_temperature = None
|
||||
self._current_mode = None
|
||||
|
@ -134,16 +156,31 @@ class ViCareClimate(ClimateEntity):
|
|||
self._heating_type = heating_type
|
||||
self._current_action = None
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return unique ID for this device."""
|
||||
return f"{self._device_config.getConfig().serial}-climate-{self._circuit.id}"
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device info for this device."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self._device_config.getConfig().serial)},
|
||||
"name": self._device_config.getModel(),
|
||||
"manufacturer": "Viessmann",
|
||||
"model": (DOMAIN, self._device_config.getModel()),
|
||||
}
|
||||
|
||||
def update(self):
|
||||
"""Let HA know there has been an update from the ViCare API."""
|
||||
try:
|
||||
_room_temperature = None
|
||||
with suppress(PyViCareNotSupportedFeatureError):
|
||||
_room_temperature = self._api.getRoomTemperature()
|
||||
_room_temperature = self._circuit.getRoomTemperature()
|
||||
|
||||
_supply_temperature = None
|
||||
with suppress(PyViCareNotSupportedFeatureError):
|
||||
_supply_temperature = self._api.getSupplyTemperature()
|
||||
_supply_temperature = self._circuit.getSupplyTemperature()
|
||||
|
||||
if _room_temperature is not None:
|
||||
self._current_temperature = _room_temperature
|
||||
|
@ -153,13 +190,13 @@ class ViCareClimate(ClimateEntity):
|
|||
self._current_temperature = None
|
||||
|
||||
with suppress(PyViCareNotSupportedFeatureError):
|
||||
self._current_program = self._api.getActiveProgram()
|
||||
self._current_program = self._circuit.getActiveProgram()
|
||||
|
||||
with suppress(PyViCareNotSupportedFeatureError):
|
||||
self._target_temperature = self._api.getCurrentDesiredTemperature()
|
||||
self._target_temperature = self._circuit.getCurrentDesiredTemperature()
|
||||
|
||||
with suppress(PyViCareNotSupportedFeatureError):
|
||||
self._current_mode = self._api.getActiveMode()
|
||||
self._current_mode = self._circuit.getActiveMode()
|
||||
|
||||
# Update the generic device attributes
|
||||
self._attributes = {}
|
||||
|
@ -171,26 +208,33 @@ class ViCareClimate(ClimateEntity):
|
|||
with suppress(PyViCareNotSupportedFeatureError):
|
||||
self._attributes[
|
||||
"heating_curve_slope"
|
||||
] = self._api.getHeatingCurveSlope()
|
||||
] = self._circuit.getHeatingCurveSlope()
|
||||
|
||||
with suppress(PyViCareNotSupportedFeatureError):
|
||||
self._attributes[
|
||||
"heating_curve_shift"
|
||||
] = self._api.getHeatingCurveShift()
|
||||
] = self._circuit.getHeatingCurveShift()
|
||||
|
||||
self._current_action = False
|
||||
# Update the specific device attributes
|
||||
if self._heating_type == HeatingType.gas:
|
||||
with suppress(PyViCareNotSupportedFeatureError):
|
||||
self._current_action = self._api.getBurnerActive()
|
||||
elif self._heating_type == HeatingType.heatpump:
|
||||
for burner in self._api.burners:
|
||||
self._current_action = self._current_action or burner.getActive()
|
||||
|
||||
with suppress(PyViCareNotSupportedFeatureError):
|
||||
self._current_action = self._api.getCompressorActive()
|
||||
for compressor in self._api.compressors:
|
||||
self._current_action = (
|
||||
self._current_action or compressor.getActive()
|
||||
)
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error("Unable to retrieve data from ViCare server")
|
||||
except PyViCareRateLimitError as limit_exception:
|
||||
_LOGGER.error("Vicare API rate limit exceeded: %s", limit_exception)
|
||||
except ValueError:
|
||||
_LOGGER.error("Unable to decode data from ViCare server")
|
||||
except PyViCareInvalidDataError as invalid_data_exception:
|
||||
_LOGGER.error("Invalid data from Vicare server: %s", invalid_data_exception)
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
|
@ -231,7 +275,7 @@ class ViCareClimate(ClimateEntity):
|
|||
)
|
||||
|
||||
_LOGGER.debug("Setting hvac mode to %s / %s", hvac_mode, vicare_mode)
|
||||
self._api.setMode(vicare_mode)
|
||||
self._circuit.setMode(vicare_mode)
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
|
@ -263,7 +307,7 @@ class ViCareClimate(ClimateEntity):
|
|||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperatures."""
|
||||
if (temp := kwargs.get(ATTR_TEMPERATURE)) is not None:
|
||||
self._api.setProgramTemperature(self._current_program, temp)
|
||||
self._circuit.setProgramTemperature(self._current_program, temp)
|
||||
self._target_temperature = temp
|
||||
|
||||
@property
|
||||
|
@ -285,8 +329,8 @@ class ViCareClimate(ClimateEntity):
|
|||
)
|
||||
|
||||
_LOGGER.debug("Setting preset to %s / %s", preset_mode, vicare_program)
|
||||
self._api.deactivateProgram(self._current_program)
|
||||
self._api.activateProgram(vicare_program)
|
||||
self._circuit.deactivateProgram(self._current_program)
|
||||
self._circuit.activateProgram(vicare_program)
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
|
@ -298,4 +342,4 @@ class ViCareClimate(ClimateEntity):
|
|||
if vicare_mode not in VICARE_TO_HA_HVAC_HEATING:
|
||||
raise ValueError(f"Cannot set invalid vicare mode: {vicare_mode}")
|
||||
|
||||
self._api.setMode(vicare_mode)
|
||||
self._circuit.setMode(vicare_mode)
|
||||
|
|
39
homeassistant/components/vicare/const.py
Normal file
39
homeassistant/components/vicare/const.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
"""Constants for the ViCare integration."""
|
||||
import enum
|
||||
|
||||
DOMAIN = "vicare"
|
||||
|
||||
PLATFORMS = ["climate", "sensor", "binary_sensor", "water_heater"]
|
||||
|
||||
VICARE_DEVICE_CONFIG = "device_conf"
|
||||
VICARE_API = "api"
|
||||
VICARE_NAME = "name"
|
||||
VICARE_CIRCUITS = "circuits"
|
||||
|
||||
CONF_CIRCUIT = "circuit"
|
||||
CONF_HEATING_TYPE = "heating_type"
|
||||
|
||||
DEFAULT_SCAN_INTERVAL = 60
|
||||
|
||||
|
||||
class HeatingType(enum.Enum):
|
||||
"""Possible options for heating type."""
|
||||
|
||||
auto = "auto"
|
||||
gas = "gas"
|
||||
oil = "oil"
|
||||
pellets = "pellets"
|
||||
heatpump = "heatpump"
|
||||
fuelcell = "fuelcell"
|
||||
|
||||
|
||||
DEFAULT_HEATING_TYPE = HeatingType.auto
|
||||
|
||||
HEATING_TYPE_TO_CREATOR_METHOD = {
|
||||
HeatingType.auto: "asAutoDetectDevice",
|
||||
HeatingType.gas: "asGazBoiler",
|
||||
HeatingType.fuelcell: "asFuelCell",
|
||||
HeatingType.heatpump: "asHeatPump",
|
||||
HeatingType.oil: "asOilBoiler",
|
||||
HeatingType.pellets: "asPelletsBoiler",
|
||||
}
|
|
@ -3,6 +3,6 @@
|
|||
"name": "Viessmann ViCare",
|
||||
"documentation": "https://www.home-assistant.io/integrations/vicare",
|
||||
"codeowners": ["@oischinger"],
|
||||
"requirements": ["PyViCare==1.0.0"],
|
||||
"requirements": ["PyViCare==2.13.0"],
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
||||
|
|
|
@ -4,16 +4,19 @@ from __future__ import annotations
|
|||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
from PyViCare.PyViCare import PyViCareNotSupportedFeatureError, PyViCareRateLimitError
|
||||
from PyViCare.PyViCareDevice import Device
|
||||
from PyViCare.PyViCareFuelCell import FuelCell
|
||||
from PyViCare.PyViCareGazBoiler import GazBoiler
|
||||
from PyViCare.PyViCareHeatPump import HeatPump
|
||||
from PyViCare.PyViCareUtils import (
|
||||
PyViCareInvalidDataError,
|
||||
PyViCareNotSupportedFeatureError,
|
||||
PyViCareRateLimitError,
|
||||
)
|
||||
import requests
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
|
||||
from homeassistant.components.sensor import (
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
|
@ -24,21 +27,13 @@ from homeassistant.const import (
|
|||
TEMP_CELSIUS,
|
||||
TIME_HOURS,
|
||||
)
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from . import (
|
||||
DOMAIN as VICARE_DOMAIN,
|
||||
VICARE_API,
|
||||
VICARE_HEATING_TYPE,
|
||||
VICARE_NAME,
|
||||
ApiT,
|
||||
HeatingType,
|
||||
ViCareRequiredKeysMixin,
|
||||
)
|
||||
from . import ViCareRequiredKeysMixin
|
||||
from .const import DOMAIN, VICARE_API, VICARE_DEVICE_CONFIG, VICARE_NAME
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SENSOR_TYPE_TEMPERATURE = "temperature"
|
||||
|
||||
SENSOR_OUTSIDE_TEMPERATURE = "outside_temperature"
|
||||
SENSOR_SUPPLY_TEMPERATURE = "supply_temperature"
|
||||
SENSOR_RETURN_TEMPERATURE = "return_temperature"
|
||||
|
@ -76,308 +71,340 @@ SENSOR_POWER_PRODUCTION_THIS_YEAR = "power_production_this_year"
|
|||
|
||||
|
||||
@dataclass
|
||||
class ViCareSensorEntityDescription(
|
||||
SensorEntityDescription, ViCareRequiredKeysMixin[ApiT]
|
||||
):
|
||||
class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysMixin):
|
||||
"""Describes ViCare sensor entity."""
|
||||
|
||||
|
||||
SENSOR_TYPES_GENERIC: tuple[ViCareSensorEntityDescription[Device], ...] = (
|
||||
ViCareSensorEntityDescription[Device](
|
||||
GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_OUTSIDE_TEMPERATURE,
|
||||
name="Outside Temperature",
|
||||
native_unit_of_measurement=TEMP_CELSIUS,
|
||||
value_getter=lambda api: api.getOutsideTemperature(),
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
),
|
||||
ViCareSensorEntityDescription[Device](
|
||||
key=SENSOR_SUPPLY_TEMPERATURE,
|
||||
name="Supply Temperature",
|
||||
native_unit_of_measurement=TEMP_CELSIUS,
|
||||
value_getter=lambda api: api.getSupplyTemperature(),
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
),
|
||||
)
|
||||
|
||||
SENSOR_TYPES_GAS: tuple[ViCareSensorEntityDescription[GazBoiler], ...] = (
|
||||
ViCareSensorEntityDescription[GazBoiler](
|
||||
key=SENSOR_BOILER_TEMPERATURE,
|
||||
name="Boiler Temperature",
|
||||
native_unit_of_measurement=TEMP_CELSIUS,
|
||||
value_getter=lambda api: api.getBoilerTemperature(),
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
),
|
||||
ViCareSensorEntityDescription[GazBoiler](
|
||||
key=SENSOR_BURNER_MODULATION,
|
||||
name="Burner modulation",
|
||||
icon="mdi:percent",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_getter=lambda api: api.getBurnerModulation(),
|
||||
),
|
||||
ViCareSensorEntityDescription[GazBoiler](
|
||||
key=SENSOR_DHW_GAS_CONSUMPTION_TODAY,
|
||||
name="Hot water gas consumption today",
|
||||
icon="mdi:power",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionDomesticHotWaterToday(),
|
||||
),
|
||||
ViCareSensorEntityDescription[GazBoiler](
|
||||
key=SENSOR_DHW_GAS_CONSUMPTION_THIS_WEEK,
|
||||
name="Hot water gas consumption this week",
|
||||
icon="mdi:power",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionDomesticHotWaterThisWeek(),
|
||||
),
|
||||
ViCareSensorEntityDescription[GazBoiler](
|
||||
key=SENSOR_DHW_GAS_CONSUMPTION_THIS_MONTH,
|
||||
name="Hot water gas consumption this month",
|
||||
icon="mdi:power",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionDomesticHotWaterThisMonth(),
|
||||
),
|
||||
ViCareSensorEntityDescription[GazBoiler](
|
||||
key=SENSOR_DHW_GAS_CONSUMPTION_THIS_YEAR,
|
||||
name="Hot water gas consumption this year",
|
||||
icon="mdi:power",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionDomesticHotWaterThisYear(),
|
||||
),
|
||||
ViCareSensorEntityDescription[GazBoiler](
|
||||
key=SENSOR_GAS_CONSUMPTION_TODAY,
|
||||
name="Heating gas consumption today",
|
||||
icon="mdi:power",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionHeatingToday(),
|
||||
),
|
||||
ViCareSensorEntityDescription[GazBoiler](
|
||||
key=SENSOR_GAS_CONSUMPTION_THIS_WEEK,
|
||||
name="Heating gas consumption this week",
|
||||
icon="mdi:power",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionHeatingThisWeek(),
|
||||
),
|
||||
ViCareSensorEntityDescription[GazBoiler](
|
||||
key=SENSOR_GAS_CONSUMPTION_THIS_MONTH,
|
||||
name="Heating gas consumption this month",
|
||||
icon="mdi:power",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionHeatingThisMonth(),
|
||||
),
|
||||
ViCareSensorEntityDescription[GazBoiler](
|
||||
key=SENSOR_GAS_CONSUMPTION_THIS_YEAR,
|
||||
name="Heating gas consumption this year",
|
||||
icon="mdi:power",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionHeatingThisYear(),
|
||||
),
|
||||
ViCareSensorEntityDescription[GazBoiler](
|
||||
key=SENSOR_BURNER_STARTS,
|
||||
name="Burner Starts",
|
||||
icon="mdi:counter",
|
||||
value_getter=lambda api: api.getBurnerStarts(),
|
||||
),
|
||||
ViCareSensorEntityDescription[GazBoiler](
|
||||
key=SENSOR_BURNER_HOURS,
|
||||
name="Burner Hours",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement=TIME_HOURS,
|
||||
value_getter=lambda api: api.getBurnerHours(),
|
||||
),
|
||||
)
|
||||
|
||||
SENSOR_TYPES_HEATPUMP: tuple[ViCareSensorEntityDescription[HeatPump], ...] = (
|
||||
ViCareSensorEntityDescription[HeatPump](
|
||||
key=SENSOR_COMPRESSOR_STARTS,
|
||||
name="Compressor Starts",
|
||||
icon="mdi:counter",
|
||||
value_getter=lambda api: api.getCompressorStarts(),
|
||||
),
|
||||
ViCareSensorEntityDescription[HeatPump](
|
||||
key=SENSOR_COMPRESSOR_HOURS,
|
||||
name="Compressor Hours",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement=TIME_HOURS,
|
||||
value_getter=lambda api: api.getCompressorHours(),
|
||||
),
|
||||
ViCareSensorEntityDescription[HeatPump](
|
||||
key=SENSOR_COMPRESSOR_HOURS_LOADCLASS1,
|
||||
name="Compressor Hours Load Class 1",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement=TIME_HOURS,
|
||||
value_getter=lambda api: api.getCompressorHoursLoadClass1(),
|
||||
),
|
||||
ViCareSensorEntityDescription[HeatPump](
|
||||
key=SENSOR_COMPRESSOR_HOURS_LOADCLASS2,
|
||||
name="Compressor Hours Load Class 2",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement=TIME_HOURS,
|
||||
value_getter=lambda api: api.getCompressorHoursLoadClass2(),
|
||||
),
|
||||
ViCareSensorEntityDescription[HeatPump](
|
||||
key=SENSOR_COMPRESSOR_HOURS_LOADCLASS3,
|
||||
name="Compressor Hours Load Class 3",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement=TIME_HOURS,
|
||||
value_getter=lambda api: api.getCompressorHoursLoadClass3(),
|
||||
),
|
||||
ViCareSensorEntityDescription[HeatPump](
|
||||
key=SENSOR_COMPRESSOR_HOURS_LOADCLASS4,
|
||||
name="Compressor Hours Load Class 4",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement=TIME_HOURS,
|
||||
value_getter=lambda api: api.getCompressorHoursLoadClass4(),
|
||||
),
|
||||
ViCareSensorEntityDescription[HeatPump](
|
||||
key=SENSOR_COMPRESSOR_HOURS_LOADCLASS5,
|
||||
name="Compressor Hours Load Class 5",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement=TIME_HOURS,
|
||||
value_getter=lambda api: api.getCompressorHoursLoadClass5(),
|
||||
),
|
||||
ViCareSensorEntityDescription[HeatPump](
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_RETURN_TEMPERATURE,
|
||||
name="Return Temperature",
|
||||
native_unit_of_measurement=TEMP_CELSIUS,
|
||||
value_getter=lambda api: api.getReturnTemperature(),
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
),
|
||||
)
|
||||
|
||||
SENSOR_TYPES_FUELCELL: tuple[ViCareSensorEntityDescription[FuelCell], ...] = (
|
||||
ViCareSensorEntityDescription[FuelCell](
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_BOILER_TEMPERATURE,
|
||||
name="Boiler Temperature",
|
||||
native_unit_of_measurement=TEMP_CELSIUS,
|
||||
value_getter=lambda api: api.getBoilerTemperature(),
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_DHW_GAS_CONSUMPTION_TODAY,
|
||||
name="Hot water gas consumption today",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionDomesticHotWaterToday(),
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_DHW_GAS_CONSUMPTION_THIS_WEEK,
|
||||
name="Hot water gas consumption this week",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionDomesticHotWaterThisWeek(),
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_DHW_GAS_CONSUMPTION_THIS_MONTH,
|
||||
name="Hot water gas consumption this month",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionDomesticHotWaterThisMonth(),
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_DHW_GAS_CONSUMPTION_THIS_YEAR,
|
||||
name="Hot water gas consumption this year",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionDomesticHotWaterThisYear(),
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_GAS_CONSUMPTION_TODAY,
|
||||
name="Heating gas consumption today",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionHeatingToday(),
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_GAS_CONSUMPTION_THIS_WEEK,
|
||||
name="Heating gas consumption this week",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionHeatingThisWeek(),
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_GAS_CONSUMPTION_THIS_MONTH,
|
||||
name="Heating gas consumption this month",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionHeatingThisMonth(),
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_GAS_CONSUMPTION_THIS_YEAR,
|
||||
name="Heating gas consumption this year",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getGasConsumptionHeatingThisYear(),
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_POWER_PRODUCTION_CURRENT,
|
||||
name="Power production current",
|
||||
native_unit_of_measurement=POWER_WATT,
|
||||
value_getter=lambda api: api.getPowerProductionCurrent(),
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
ViCareSensorEntityDescription[FuelCell](
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_POWER_PRODUCTION_TODAY,
|
||||
name="Power production today",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getPowerProductionToday(),
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
ViCareSensorEntityDescription[FuelCell](
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_POWER_PRODUCTION_THIS_WEEK,
|
||||
name="Power production this week",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getPowerProductionThisWeek(),
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
ViCareSensorEntityDescription[FuelCell](
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_POWER_PRODUCTION_THIS_MONTH,
|
||||
name="Power production this month",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getPowerProductionThisMonth(),
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
ViCareSensorEntityDescription[FuelCell](
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_POWER_PRODUCTION_THIS_YEAR,
|
||||
name="Power production this year",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
value_getter=lambda api: api.getPowerProductionThisYear(),
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
)
|
||||
|
||||
SENSORS_GENERIC = [SENSOR_OUTSIDE_TEMPERATURE, SENSOR_SUPPLY_TEMPERATURE]
|
||||
CIRCUIT_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_SUPPLY_TEMPERATURE,
|
||||
name="Supply Temperature",
|
||||
native_unit_of_measurement=TEMP_CELSIUS,
|
||||
value_getter=lambda api: api.getSupplyTemperature(),
|
||||
),
|
||||
)
|
||||
|
||||
SENSORS_BY_HEATINGTYPE = {
|
||||
HeatingType.gas: [
|
||||
SENSOR_BOILER_TEMPERATURE,
|
||||
SENSOR_BURNER_HOURS,
|
||||
SENSOR_BURNER_MODULATION,
|
||||
SENSOR_BURNER_STARTS,
|
||||
SENSOR_DHW_GAS_CONSUMPTION_TODAY,
|
||||
SENSOR_DHW_GAS_CONSUMPTION_THIS_WEEK,
|
||||
SENSOR_DHW_GAS_CONSUMPTION_THIS_MONTH,
|
||||
SENSOR_DHW_GAS_CONSUMPTION_THIS_YEAR,
|
||||
SENSOR_GAS_CONSUMPTION_TODAY,
|
||||
SENSOR_GAS_CONSUMPTION_THIS_WEEK,
|
||||
SENSOR_GAS_CONSUMPTION_THIS_MONTH,
|
||||
SENSOR_GAS_CONSUMPTION_THIS_YEAR,
|
||||
],
|
||||
HeatingType.heatpump: [
|
||||
SENSOR_COMPRESSOR_STARTS,
|
||||
SENSOR_COMPRESSOR_HOURS,
|
||||
SENSOR_COMPRESSOR_HOURS_LOADCLASS1,
|
||||
SENSOR_COMPRESSOR_HOURS_LOADCLASS2,
|
||||
SENSOR_COMPRESSOR_HOURS_LOADCLASS3,
|
||||
SENSOR_COMPRESSOR_HOURS_LOADCLASS4,
|
||||
SENSOR_COMPRESSOR_HOURS_LOADCLASS5,
|
||||
SENSOR_RETURN_TEMPERATURE,
|
||||
],
|
||||
HeatingType.fuelcell: [
|
||||
# gas
|
||||
SENSOR_BOILER_TEMPERATURE,
|
||||
SENSOR_BURNER_HOURS,
|
||||
SENSOR_BURNER_MODULATION,
|
||||
SENSOR_BURNER_STARTS,
|
||||
SENSOR_DHW_GAS_CONSUMPTION_TODAY,
|
||||
SENSOR_DHW_GAS_CONSUMPTION_THIS_WEEK,
|
||||
SENSOR_DHW_GAS_CONSUMPTION_THIS_MONTH,
|
||||
SENSOR_DHW_GAS_CONSUMPTION_THIS_YEAR,
|
||||
SENSOR_GAS_CONSUMPTION_TODAY,
|
||||
SENSOR_GAS_CONSUMPTION_THIS_WEEK,
|
||||
SENSOR_GAS_CONSUMPTION_THIS_MONTH,
|
||||
SENSOR_GAS_CONSUMPTION_THIS_YEAR,
|
||||
# fuel cell
|
||||
SENSOR_POWER_PRODUCTION_CURRENT,
|
||||
SENSOR_POWER_PRODUCTION_TODAY,
|
||||
SENSOR_POWER_PRODUCTION_THIS_WEEK,
|
||||
SENSOR_POWER_PRODUCTION_THIS_MONTH,
|
||||
SENSOR_POWER_PRODUCTION_THIS_YEAR,
|
||||
],
|
||||
}
|
||||
BURNER_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_BURNER_STARTS,
|
||||
name="Burner Starts",
|
||||
icon="mdi:counter",
|
||||
value_getter=lambda api: api.getStarts(),
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_BURNER_HOURS,
|
||||
name="Burner Hours",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement=TIME_HOURS,
|
||||
value_getter=lambda api: api.getHours(),
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_BURNER_MODULATION,
|
||||
name="Burner Modulation",
|
||||
icon="mdi:percent",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_getter=lambda api: api.getModulation(),
|
||||
),
|
||||
)
|
||||
|
||||
COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_COMPRESSOR_STARTS,
|
||||
name="Compressor Starts",
|
||||
icon="mdi:counter",
|
||||
value_getter=lambda api: api.getStarts(),
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_COMPRESSOR_HOURS,
|
||||
name="Compressor Hours",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement=TIME_HOURS,
|
||||
value_getter=lambda api: api.getHours(),
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_COMPRESSOR_HOURS_LOADCLASS1,
|
||||
name="Compressor Hours Load Class 1",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement=TIME_HOURS,
|
||||
value_getter=lambda api: api.getHoursLoadClass1(),
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_COMPRESSOR_HOURS_LOADCLASS2,
|
||||
name="Compressor Hours Load Class 2",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement=TIME_HOURS,
|
||||
value_getter=lambda api: api.getHoursLoadClass2(),
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_COMPRESSOR_HOURS_LOADCLASS3,
|
||||
name="Compressor Hours Load Class 3",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement=TIME_HOURS,
|
||||
value_getter=lambda api: api.getHoursLoadClass3(),
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_COMPRESSOR_HOURS_LOADCLASS4,
|
||||
name="Compressor Hours Load Class 4",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement=TIME_HOURS,
|
||||
value_getter=lambda api: api.getHoursLoadClass4(),
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key=SENSOR_COMPRESSOR_HOURS_LOADCLASS5,
|
||||
name="Compressor Hours Load Class 5",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement=TIME_HOURS,
|
||||
value_getter=lambda api: api.getHoursLoadClass5(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
def _build_entity(name, vicare_api, device_config, sensor):
|
||||
"""Create a ViCare sensor entity."""
|
||||
_LOGGER.debug("Found device %s", name)
|
||||
try:
|
||||
sensor.value_getter(vicare_api)
|
||||
_LOGGER.debug("Found entity %s", name)
|
||||
except PyViCareNotSupportedFeatureError:
|
||||
_LOGGER.info("Feature not supported %s", name)
|
||||
return None
|
||||
except AttributeError:
|
||||
_LOGGER.debug("Attribute Error %s", name)
|
||||
return None
|
||||
|
||||
return ViCareSensor(
|
||||
name,
|
||||
vicare_api,
|
||||
device_config,
|
||||
sensor,
|
||||
)
|
||||
|
||||
|
||||
async def _entities_from_descriptions(
|
||||
hass, name, all_devices, sensor_descriptions, iterables
|
||||
):
|
||||
"""Create entities from descriptions and list of burners/circuits."""
|
||||
for description in sensor_descriptions:
|
||||
for current in iterables:
|
||||
suffix = ""
|
||||
if len(iterables) > 1:
|
||||
suffix = f" {current.id}"
|
||||
entity = await hass.async_add_executor_job(
|
||||
_build_entity,
|
||||
f"{name} {description.name}{suffix}",
|
||||
current,
|
||||
hass.data[DOMAIN][VICARE_DEVICE_CONFIG],
|
||||
description,
|
||||
)
|
||||
if entity is not None:
|
||||
all_devices.append(entity)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Create the ViCare sensor devices."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
vicare_api = hass.data[VICARE_DOMAIN][VICARE_API]
|
||||
heating_type = hass.data[VICARE_DOMAIN][VICARE_HEATING_TYPE]
|
||||
name = hass.data[DOMAIN][VICARE_NAME]
|
||||
api = hass.data[DOMAIN][VICARE_API]
|
||||
|
||||
sensors = SENSORS_GENERIC.copy()
|
||||
|
||||
if heating_type != HeatingType.generic:
|
||||
sensors.extend(SENSORS_BY_HEATINGTYPE[heating_type])
|
||||
|
||||
add_entities(
|
||||
[
|
||||
ViCareSensor(hass.data[VICARE_DOMAIN][VICARE_NAME], vicare_api, description)
|
||||
for description in (
|
||||
*SENSOR_TYPES_GENERIC,
|
||||
*SENSOR_TYPES_GAS,
|
||||
*SENSOR_TYPES_HEATPUMP,
|
||||
*SENSOR_TYPES_FUELCELL,
|
||||
all_devices = []
|
||||
for description in GLOBAL_SENSORS:
|
||||
entity = await hass.async_add_executor_job(
|
||||
_build_entity,
|
||||
f"{name} {description.name}",
|
||||
api,
|
||||
hass.data[DOMAIN][VICARE_DEVICE_CONFIG],
|
||||
description,
|
||||
)
|
||||
if description.key in sensors
|
||||
]
|
||||
if entity is not None:
|
||||
all_devices.append(entity)
|
||||
|
||||
for description in CIRCUIT_SENSORS:
|
||||
for circuit in api.circuits:
|
||||
suffix = ""
|
||||
if len(api.circuits) > 1:
|
||||
suffix = f" {circuit.id}"
|
||||
entity = await hass.async_add_executor_job(
|
||||
_build_entity,
|
||||
f"{name} {description.name}{suffix}",
|
||||
circuit,
|
||||
hass.data[DOMAIN][VICARE_DEVICE_CONFIG],
|
||||
description,
|
||||
)
|
||||
if entity is not None:
|
||||
all_devices.append(entity)
|
||||
|
||||
try:
|
||||
_entities_from_descriptions(
|
||||
hass, name, all_devices, BURNER_SENSORS, api.burners
|
||||
)
|
||||
except PyViCareNotSupportedFeatureError:
|
||||
_LOGGER.info("No burners found")
|
||||
|
||||
DescriptionT = Union[
|
||||
ViCareSensorEntityDescription[Device],
|
||||
ViCareSensorEntityDescription[GazBoiler],
|
||||
ViCareSensorEntityDescription[HeatPump],
|
||||
ViCareSensorEntityDescription[FuelCell],
|
||||
]
|
||||
try:
|
||||
_entities_from_descriptions(
|
||||
hass, name, all_devices, COMPRESSOR_SENSORS, api.compressors
|
||||
)
|
||||
except PyViCareNotSupportedFeatureError:
|
||||
_LOGGER.info("No compressors found")
|
||||
|
||||
async_add_entities(all_devices)
|
||||
|
||||
|
||||
class ViCareSensor(SensorEntity):
|
||||
"""Representation of a ViCare sensor."""
|
||||
|
||||
entity_description: DescriptionT
|
||||
entity_description: ViCareSensorEntityDescription
|
||||
|
||||
def __init__(self, name, api, description: DescriptionT):
|
||||
def __init__(
|
||||
self, name, api, device_config, description: ViCareSensorEntityDescription
|
||||
):
|
||||
"""Initialize the sensor."""
|
||||
self.entity_description = description
|
||||
self._attr_name = f"{name} {description.name}"
|
||||
self._attr_name = name
|
||||
self._api = api
|
||||
self._device_config = device_config
|
||||
self._state = None
|
||||
self._last_reset = dt_util.utcnow()
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device info for this device."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self._device_config.getConfig().serial)},
|
||||
"name": self._device_config.getModel(),
|
||||
"manufacturer": "Viessmann",
|
||||
"model": (DOMAIN, self._device_config.getModel()),
|
||||
}
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
|
@ -386,16 +413,27 @@ class ViCareSensor(SensorEntity):
|
|||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID."""
|
||||
return f"{self._api.service.id}-{self.entity_description.key}"
|
||||
"""Return unique ID for this device."""
|
||||
tmp_id = (
|
||||
f"{self._device_config.getConfig().serial}-{self.entity_description.key}"
|
||||
)
|
||||
if hasattr(self._api, "id"):
|
||||
return f"{tmp_id}-{self._api.id}"
|
||||
return tmp_id
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def last_reset(self):
|
||||
"""Return the time when the sensor was last reset."""
|
||||
return self._last_reset
|
||||
|
||||
def update(self):
|
||||
"""Update state of sensor."""
|
||||
self._last_reset = dt_util.start_of_local_day()
|
||||
try:
|
||||
with suppress(PyViCareNotSupportedFeatureError):
|
||||
self._state = self.entity_description.value_getter(self._api)
|
||||
|
@ -405,3 +443,5 @@ class ViCareSensor(SensorEntity):
|
|||
_LOGGER.error("Unable to decode data from ViCare server")
|
||||
except PyViCareRateLimitError as limit_exception:
|
||||
_LOGGER.error("Vicare API rate limit exceeded: %s", limit_exception)
|
||||
except PyViCareInvalidDataError as invalid_data_exception:
|
||||
_LOGGER.error("Invalid data from Vicare server: %s", invalid_data_exception)
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
from contextlib import suppress
|
||||
import logging
|
||||
|
||||
from PyViCare.PyViCare import PyViCareNotSupportedFeatureError, PyViCareRateLimitError
|
||||
from PyViCare.PyViCareUtils import (
|
||||
PyViCareInvalidDataError,
|
||||
PyViCareNotSupportedFeatureError,
|
||||
PyViCareRateLimitError,
|
||||
)
|
||||
import requests
|
||||
|
||||
from homeassistant.components.water_heater import (
|
||||
|
@ -11,7 +15,14 @@ from homeassistant.components.water_heater import (
|
|||
)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
|
||||
|
||||
from . import DOMAIN as VICARE_DOMAIN, VICARE_API, VICARE_HEATING_TYPE, VICARE_NAME
|
||||
from .const import (
|
||||
CONF_HEATING_TYPE,
|
||||
DOMAIN,
|
||||
VICARE_API,
|
||||
VICARE_CIRCUITS,
|
||||
VICARE_DEVICE_CONFIG,
|
||||
VICARE_NAME,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -43,31 +54,53 @@ HA_TO_VICARE_HVAC_DHW = {
|
|||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
def _build_entity(name, vicare_api, circuit, device_config, heating_type):
|
||||
"""Create a ViCare water_heater entity."""
|
||||
_LOGGER.debug("Found device %s", name)
|
||||
return ViCareWater(
|
||||
name,
|
||||
vicare_api,
|
||||
circuit,
|
||||
device_config,
|
||||
heating_type,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Create the ViCare water_heater devices."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
vicare_api = hass.data[VICARE_DOMAIN][VICARE_API]
|
||||
heating_type = hass.data[VICARE_DOMAIN][VICARE_HEATING_TYPE]
|
||||
add_entities(
|
||||
[
|
||||
ViCareWater(
|
||||
f"{hass.data[VICARE_DOMAIN][VICARE_NAME]} Water",
|
||||
vicare_api,
|
||||
heating_type,
|
||||
)
|
||||
]
|
||||
|
||||
name = hass.data[DOMAIN][VICARE_NAME]
|
||||
|
||||
all_devices = []
|
||||
for circuit in hass.data[DOMAIN][VICARE_CIRCUITS]:
|
||||
suffix = ""
|
||||
if len(hass.data[DOMAIN][VICARE_CIRCUITS]) > 1:
|
||||
suffix = f" {circuit.id}"
|
||||
entity = _build_entity(
|
||||
f"{name} Water{suffix}",
|
||||
hass.data[DOMAIN][VICARE_API],
|
||||
circuit,
|
||||
hass.data[DOMAIN][VICARE_DEVICE_CONFIG],
|
||||
hass.data[DOMAIN][CONF_HEATING_TYPE],
|
||||
)
|
||||
if entity is not None:
|
||||
all_devices.append(entity)
|
||||
|
||||
async_add_entities(all_devices)
|
||||
|
||||
|
||||
class ViCareWater(WaterHeaterEntity):
|
||||
"""Representation of the ViCare domestic hot water device."""
|
||||
|
||||
def __init__(self, name, api, heating_type):
|
||||
def __init__(self, name, api, circuit, device_config, heating_type):
|
||||
"""Initialize the DHW water_heater device."""
|
||||
self._name = name
|
||||
self._state = None
|
||||
self._api = api
|
||||
self._circuit = circuit
|
||||
self._device_config = device_config
|
||||
self._attributes = {}
|
||||
self._target_temperature = None
|
||||
self._current_temperature = None
|
||||
|
@ -84,11 +117,11 @@ class ViCareWater(WaterHeaterEntity):
|
|||
|
||||
with suppress(PyViCareNotSupportedFeatureError):
|
||||
self._target_temperature = (
|
||||
self._api.getDomesticHotWaterConfiguredTemperature()
|
||||
self._api.getDomesticHotWaterDesiredTemperature()
|
||||
)
|
||||
|
||||
with suppress(PyViCareNotSupportedFeatureError):
|
||||
self._current_mode = self._api.getActiveMode()
|
||||
self._current_mode = self._circuit.getActiveMode()
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error("Unable to retrieve data from ViCare server")
|
||||
|
@ -96,6 +129,23 @@ class ViCareWater(WaterHeaterEntity):
|
|||
_LOGGER.error("Vicare API rate limit exceeded: %s", limit_exception)
|
||||
except ValueError:
|
||||
_LOGGER.error("Unable to decode data from ViCare server")
|
||||
except PyViCareInvalidDataError as invalid_data_exception:
|
||||
_LOGGER.error("Invalid data from Vicare server: %s", invalid_data_exception)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return unique ID for this device."""
|
||||
return f"{self._device_config.getConfig().serial}-water-{self._circuit.id}"
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device info for this device."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self._device_config.getConfig().serial)},
|
||||
"name": self._device_config.getModel(),
|
||||
"manufacturer": "Viessmann",
|
||||
"model": (DOMAIN, self._device_config.getModel()),
|
||||
}
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
|
|
|
@ -55,7 +55,7 @@ PyTransportNSW==0.1.1
|
|||
PyTurboJPEG==1.6.1
|
||||
|
||||
# homeassistant.components.vicare
|
||||
PyViCare==1.0.0
|
||||
PyViCare==2.13.0
|
||||
|
||||
# homeassistant.components.xiaomi_aqara
|
||||
PyXiaomiGateway==0.13.4
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue