Add exception handler and exception translations to eheimdigital (#145476)

* Add exception handler and exception translations to eheimdigital

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
Sid 2025-05-26 15:05:11 +02:00 committed by GitHub
parent 5202bbb6af
commit 6ddc2193d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 65 additions and 43 deletions

View File

@ -4,7 +4,7 @@ from typing import Any
from eheimdigital.device import EheimDigitalDevice
from eheimdigital.heater import EheimDigitalHeater
from eheimdigital.types import EheimDigitalClientError, HeaterMode, HeaterUnit
from eheimdigital.types import HeaterMode, HeaterUnit
from homeassistant.components.climate import (
PRESET_NONE,
@ -20,12 +20,11 @@ from homeassistant.const import (
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import HEATER_BIO_MODE, HEATER_PRESET_TO_HEATER_MODE, HEATER_SMART_MODE
from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator
from .entity import EheimDigitalEntity
from .entity import EheimDigitalEntity, exception_handler
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
@ -83,34 +82,28 @@ class EheimDigitalHeaterClimate(EheimDigitalEntity[EheimDigitalHeater], ClimateE
self._attr_unique_id = self._device_address
self._async_update_attrs()
@exception_handler
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode."""
try:
if preset_mode in HEATER_PRESET_TO_HEATER_MODE:
await self._device.set_operation_mode(
HEATER_PRESET_TO_HEATER_MODE[preset_mode]
)
except EheimDigitalClientError as err:
raise HomeAssistantError from err
if preset_mode in HEATER_PRESET_TO_HEATER_MODE:
await self._device.set_operation_mode(
HEATER_PRESET_TO_HEATER_MODE[preset_mode]
)
@exception_handler
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set a new temperature."""
try:
if ATTR_TEMPERATURE in kwargs:
await self._device.set_target_temperature(kwargs[ATTR_TEMPERATURE])
except EheimDigitalClientError as err:
raise HomeAssistantError from err
if ATTR_TEMPERATURE in kwargs:
await self._device.set_target_temperature(kwargs[ATTR_TEMPERATURE])
@exception_handler
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the heating mode."""
try:
match hvac_mode:
case HVACMode.OFF:
await self._device.set_active(active=False)
case HVACMode.AUTO:
await self._device.set_active(active=True)
except EheimDigitalClientError as err:
raise HomeAssistantError from err
match hvac_mode:
case HVACMode.OFF:
await self._device.set_active(active=False)
case HVACMode.AUTO:
await self._device.set_active(active=True)
def _async_update_attrs(self) -> None:
if self._device.temperature_unit == HeaterUnit.CELSIUS:

View File

@ -1,12 +1,15 @@
"""Base entity for EHEIM Digital."""
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING
from collections.abc import Callable, Coroutine
from typing import TYPE_CHECKING, Any, Concatenate
from eheimdigital.device import EheimDigitalDevice
from eheimdigital.types import EheimDigitalClientError
from homeassistant.const import CONF_HOST
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -51,3 +54,24 @@ class EheimDigitalEntity[_DeviceT: EheimDigitalDevice](
"""Update attributes when the coordinator updates."""
self._async_update_attrs()
super()._handle_coordinator_update()
def exception_handler[_EntityT: EheimDigitalEntity[EheimDigitalDevice], **_P](
func: Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, Any]],
) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]:
"""Decorate AirGradient calls to handle exceptions.
A decorator that wraps the passed in function, catches AirGradient errors.
"""
async def handler(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) -> None:
try:
await func(self, *args, **kwargs)
except EheimDigitalClientError as error:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="communication_error",
translation_placeholders={"error": str(error)},
) from error
return handler

View File

@ -4,7 +4,7 @@ from typing import Any
from eheimdigital.classic_led_ctrl import EheimDigitalClassicLEDControl
from eheimdigital.device import EheimDigitalDevice
from eheimdigital.types import EheimDigitalClientError, LightMode
from eheimdigital.types import LightMode
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
@ -15,13 +15,12 @@ from homeassistant.components.light import (
LightEntityFeature,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.color import brightness_to_value, value_to_brightness
from .const import EFFECT_DAYCL_MODE, EFFECT_TO_LIGHT_MODE
from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator
from .entity import EheimDigitalEntity
from .entity import EheimDigitalEntity, exception_handler
BRIGHTNESS_SCALE = (1, 100)
@ -88,6 +87,7 @@ class EheimDigitalClassicLEDControlLight(
"""Return whether the entity is available."""
return super().available and self._device.light_level[self._channel] is not None
@exception_handler
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the light."""
if ATTR_EFFECT in kwargs:
@ -96,22 +96,17 @@ class EheimDigitalClassicLEDControlLight(
if ATTR_BRIGHTNESS in kwargs:
if self._device.light_mode == LightMode.DAYCL_MODE:
await self._device.set_light_mode(LightMode.MAN_MODE)
try:
await self._device.turn_on(
int(brightness_to_value(BRIGHTNESS_SCALE, kwargs[ATTR_BRIGHTNESS])),
self._channel,
)
except EheimDigitalClientError as err:
raise HomeAssistantError from err
await self._device.turn_on(
int(brightness_to_value(BRIGHTNESS_SCALE, kwargs[ATTR_BRIGHTNESS])),
self._channel,
)
@exception_handler
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the light."""
if self._device.light_mode == LightMode.DAYCL_MODE:
await self._device.set_light_mode(LightMode.MAN_MODE)
try:
await self._device.turn_off(self._channel)
except EheimDigitalClientError as err:
raise HomeAssistantError from err
await self._device.turn_off(self._channel)
def _async_update_attrs(self) -> None:
light_level = self._device.light_level[self._channel]

View File

@ -26,7 +26,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator
from .entity import EheimDigitalEntity
from .entity import EheimDigitalEntity, exception_handler
PARALLEL_UPDATES = 0
@ -182,6 +182,7 @@ class EheimDigitalNumber(
self._attr_unique_id = f"{self._device_address}_{description.key}"
@override
@exception_handler
async def async_set_native_value(self, value: float) -> None:
return await self.entity_description.set_value_fn(self._device, value)

View File

@ -58,7 +58,7 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: todo
exception-translations: done
icon-translations: todo
reconfiguration-flow: todo
repair-issues: todo

View File

@ -13,7 +13,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator
from .entity import EheimDigitalEntity
from .entity import EheimDigitalEntity, exception_handler
PARALLEL_UPDATES = 0
@ -94,6 +94,7 @@ class EheimDigitalSelect(
self._attr_unique_id = f"{self._device_address}_{description.key}"
@override
@exception_handler
async def async_select_option(self, option: str) -> None:
return await self.entity_description.set_value_fn(self._device, option)

View File

@ -101,5 +101,10 @@
"name": "Night start time"
}
}
},
"exceptions": {
"communication_error": {
"message": "An error occurred while communicating with the EHEIM Digital hub: {error}"
}
}
}

View File

@ -10,7 +10,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator
from .entity import EheimDigitalEntity
from .entity import EheimDigitalEntity, exception_handler
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
@ -58,10 +58,12 @@ class EheimDigitalClassicVarioSwitch(
self._async_update_attrs()
@override
@exception_handler
async def async_turn_off(self, **kwargs: Any) -> None:
await self._device.set_active(active=False)
@override
@exception_handler
async def async_turn_on(self, **kwargs: Any) -> None:
await self._device.set_active(active=True)

View File

@ -15,7 +15,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator
from .entity import EheimDigitalEntity
from .entity import EheimDigitalEntity, exception_handler
PARALLEL_UPDATES = 0
@ -122,6 +122,7 @@ class EheimDigitalTime(
self._attr_unique_id = f"{device.mac_address}_{description.key}"
@override
@exception_handler
async def async_set_value(self, value: time) -> None:
"""Change the time."""
return await self.entity_description.set_value_fn(self._device, value)