Files
core/homeassistant/components/vicare/entity.py
T

121 lines
4.5 KiB
Python

"""Entities for the ViCare integration."""
from collections.abc import Generator
from contextlib import contextmanager
import logging
from typing import override
from PyViCare.PyViCareDevice import Device as PyViCareDevice
from PyViCare.PyViCareDeviceConfig import PyViCareDeviceConfig
from PyViCare.PyViCareHeatingDevice import (
HeatingDeviceWithComponent as PyViCareHeatingDeviceComponent,
)
from PyViCare.PyViCareUtils import (
PyViCareDeviceCommunicationError,
PyViCareInternalServerError,
PyViCareInvalidDataError,
PyViCareRateLimitError,
)
from requests.exceptions import ConnectionError as RequestConnectionError
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity
from .const import DOMAIN, VIESSMANN_DEVELOPER_PORTAL
_LOGGER = logging.getLogger(__name__)
class ViCareEntity(Entity):
"""Base class for ViCare entities."""
_attr_has_entity_name = True
@contextmanager
def vicare_api_handler(self) -> Generator[None]:
"""Handle common ViCare API errors."""
try:
yield
except RequestConnectionError:
_LOGGER.error("Unable to retrieve data from ViCare server")
except ValueError:
_LOGGER.error("Unable to decode data from ViCare server")
except PyViCareRateLimitError as err:
_LOGGER.error("ViCare API rate limit exceeded: %s", err)
except PyViCareInvalidDataError as err:
_LOGGER.error("Invalid data from ViCare server: %s", err)
except PyViCareDeviceCommunicationError as err:
_LOGGER.warning("Device communication error: %s", err)
except PyViCareInternalServerError as err:
_LOGGER.warning("ViCare server error: %s", err)
def __init__(
self,
unique_id_suffix: str,
device_serial: str | None,
device_config: PyViCareDeviceConfig,
device: PyViCareDevice,
component: PyViCareHeatingDeviceComponent | None = None,
) -> None:
"""Initialize the entity."""
gateway_serial = device_config.getConfig().serial
device_id = device_config.getId()
model = device_config.getModel().replace("_", " ")
identifier = (
f"{gateway_serial}_{device_serial.replace('-', '_')}"
if device_serial is not None
else f"{gateway_serial}_{device_id}"
)
self._api: PyViCareDevice | PyViCareHeatingDeviceComponent = component or device
self._attr_unique_id = f"{identifier}-{unique_id_suffix}"
if component:
self._attr_unique_id += f"-{component.id}"
self._gateway_serial = gateway_serial
self._device_serial = device_serial
self._device_identifier = identifier
self._model = model
@property
@override
def device_info(self) -> DeviceInfo:
"""Return device info, resolving the zigbee gateway link at add time."""
device_info = DeviceInfo(
identifiers={(DOMAIN, self._device_identifier)},
name=self._model,
manufacturer="Viessmann",
model=self._model,
configuration_url=VIESSMANN_DEVELOPER_PORTAL,
)
device_serial = self._device_serial
if device_serial and device_serial.startswith("zigbee-"):
parts = device_serial.split("-", 2)
if len(parts) == 3:
_, zigbee_ieee, _ = parts
config_entry = self.platform.config_entry
assert config_entry is not None
# Link best effort: the gateway may be absent (its main device
# was omitted or its serial could not be retrieved), in which
# case the channel stays unlinked rather than aborting setup.
gateway_device = dr.async_get(self.hass).async_get_device_by_identifier(
(DOMAIN, f"{self._gateway_serial}_zigbee_{zigbee_ieee}"),
config_entry.entry_id,
)
if gateway_device is not None:
device_info["via_device_id"] = gateway_device.id
elif (
len(parts) == 2
and len(zigbee_ieee := device_serial.removeprefix("zigbee-")) == 16
):
device_info["serial_number"] = "-".join(
zigbee_ieee.upper()[i : i + 2] for i in range(0, 16, 2)
)
else:
device_info["serial_number"] = device_serial
return device_info