hass-core/homeassistant/components/vicare/binary_sensor.py
Hans Oischinger 66ae116023
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.
2021-10-25 13:43:43 +02:00

206 lines
6.3 KiB
Python

"""Viessmann ViCare sensor device."""
from __future__ import annotations
from contextlib import suppress
from dataclasses import dataclass
import logging
from PyViCare.PyViCareUtils import (
PyViCareInvalidDataError,
PyViCareNotSupportedFeatureError,
PyViCareRateLimitError,
)
import requests
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_POWER,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from . import ViCareRequiredKeysMixin
from .const import DOMAIN, VICARE_API, VICARE_DEVICE_CONFIG, VICARE_NAME
_LOGGER = logging.getLogger(__name__)
SENSOR_CIRCULATION_PUMP_ACTIVE = "circulationpump_active"
SENSOR_BURNER_ACTIVE = "burner_active"
SENSOR_COMPRESSOR_ACTIVE = "compressor_active"
@dataclass
class ViCareBinarySensorEntityDescription(
BinarySensorEntityDescription, ViCareRequiredKeysMixin
):
"""Describes ViCare binary sensor entity."""
CIRCUIT_SENSORS: tuple[ViCareBinarySensorEntityDescription, ...] = (
ViCareBinarySensorEntityDescription(
key=SENSOR_CIRCULATION_PUMP_ACTIVE,
name="Circulation pump active",
device_class=DEVICE_CLASS_POWER,
value_getter=lambda api: api.getCirculationPumpActive(),
),
)
BURNER_SENSORS: tuple[ViCareBinarySensorEntityDescription, ...] = (
ViCareBinarySensorEntityDescription(
key=SENSOR_BURNER_ACTIVE,
name="Burner active",
device_class=DEVICE_CLASS_POWER,
value_getter=lambda api: api.getActive(),
),
)
COMPRESSOR_SENSORS: tuple[ViCareBinarySensorEntityDescription, ...] = (
ViCareBinarySensorEntityDescription(
key=SENSOR_COMPRESSOR_ACTIVE,
name="Compressor active",
device_class=DEVICE_CLASS_POWER,
value_getter=lambda api: api.getActive(),
),
)
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,
)
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
name = hass.data[DOMAIN][VICARE_NAME]
api = hass.data[DOMAIN][VICARE_API]
all_devices = []
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")
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: ViCareBinarySensorEntityDescription
def __init__(
self, name, api, device_config, description: ViCareBinarySensorEntityDescription
):
"""Initialize the sensor."""
self.entity_description = description
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."""
return self._state is not None
@property
def unique_id(self):
"""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):
"""Return the state of the sensor."""
return self._state
def update(self):
"""Update state of sensor."""
try:
with suppress(PyViCareNotSupportedFeatureError):
self._state = self.entity_description.value_getter(self._api)
except requests.exceptions.ConnectionError:
_LOGGER.error("Unable to retrieve data from ViCare server")
except ValueError:
_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)