Reolink translate errors (#132301)

This commit is contained in:
starkillerOG 2024-12-18 20:22:33 +01:00 committed by GitHub
parent c8f050ecbc
commit 19e6867f1a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 331 additions and 152 deletions

View File

@ -73,7 +73,9 @@ async def async_setup_entry(
) as err: ) as err:
await host.stop() await host.stop()
raise ConfigEntryNotReady( raise ConfigEntryNotReady(
f"Error while trying to setup {host.api.host}:{host.api.port}: {err!s}" translation_domain=DOMAIN,
translation_key="config_entry_not_ready",
translation_placeholders={"host": host.api.host, "err": str(err)},
) from err ) from err
except BaseException: except BaseException:
await host.stop() await host.stop()

View File

@ -7,7 +7,6 @@ from dataclasses import dataclass
from typing import Any from typing import Any
from reolink_aio.api import GuardEnum, Host, PtzEnum from reolink_aio.api import GuardEnum, Host, PtzEnum
from reolink_aio.exceptions import ReolinkError
import voluptuous as vol import voluptuous as vol
from homeassistant.components.button import ( from homeassistant.components.button import (
@ -18,7 +17,6 @@ from homeassistant.components.button import (
from homeassistant.components.camera import CameraEntityFeature from homeassistant.components.camera import CameraEntityFeature
from homeassistant.const import EntityCategory from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import ( from homeassistant.helpers.entity_platform import (
AddEntitiesCallback, AddEntitiesCallback,
@ -31,7 +29,7 @@ from .entity import (
ReolinkHostCoordinatorEntity, ReolinkHostCoordinatorEntity,
ReolinkHostEntityDescription, ReolinkHostEntityDescription,
) )
from .util import ReolinkConfigEntry, ReolinkData from .util import ReolinkConfigEntry, ReolinkData, raise_translated_error
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
ATTR_SPEED = "speed" ATTR_SPEED = "speed"
@ -205,22 +203,18 @@ class ReolinkButtonEntity(ReolinkChannelCoordinatorEntity, ButtonEntity):
): ):
self._attr_supported_features = SUPPORT_PTZ_SPEED self._attr_supported_features = SUPPORT_PTZ_SPEED
@raise_translated_error
async def async_press(self) -> None: async def async_press(self) -> None:
"""Execute the button action.""" """Execute the button action."""
try:
await self.entity_description.method(self._host.api, self._channel) await self.entity_description.method(self._host.api, self._channel)
except ReolinkError as err:
raise HomeAssistantError(err) from err
@raise_translated_error
async def async_ptz_move(self, **kwargs: Any) -> None: async def async_ptz_move(self, **kwargs: Any) -> None:
"""PTZ move with speed.""" """PTZ move with speed."""
speed = kwargs[ATTR_SPEED] speed = kwargs[ATTR_SPEED]
try:
await self._host.api.set_ptz_command( await self._host.api.set_ptz_command(
self._channel, command=self.entity_description.ptz_cmd, speed=speed self._channel, command=self.entity_description.ptz_cmd, speed=speed
) )
except ReolinkError as err:
raise HomeAssistantError(err) from err
class ReolinkHostButtonEntity(ReolinkHostCoordinatorEntity, ButtonEntity): class ReolinkHostButtonEntity(ReolinkHostCoordinatorEntity, ButtonEntity):
@ -237,9 +231,7 @@ class ReolinkHostButtonEntity(ReolinkHostCoordinatorEntity, ButtonEntity):
self.entity_description = entity_description self.entity_description = entity_description
super().__init__(reolink_data) super().__init__(reolink_data)
@raise_translated_error
async def async_press(self) -> None: async def async_press(self) -> None:
"""Execute the button action.""" """Execute the button action."""
try:
await self.entity_description.method(self._host.api) await self.entity_description.method(self._host.api)
except ReolinkError as err:
raise HomeAssistantError(err) from err

View File

@ -6,7 +6,6 @@ from dataclasses import dataclass
import logging import logging
from reolink_aio.api import DUAL_LENS_MODELS from reolink_aio.api import DUAL_LENS_MODELS
from reolink_aio.exceptions import ReolinkError
from homeassistant.components.camera import ( from homeassistant.components.camera import (
Camera, Camera,
@ -14,11 +13,10 @@ from homeassistant.components.camera import (
CameraEntityFeature, CameraEntityFeature,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription
from .util import ReolinkConfigEntry, ReolinkData from .util import ReolinkConfigEntry, ReolinkData, raise_translated_error
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -142,13 +140,11 @@ class ReolinkCamera(ReolinkChannelCoordinatorEntity, Camera):
self._channel, self.entity_description.stream self._channel, self.entity_description.stream
) )
@raise_translated_error
async def async_camera_image( async def async_camera_image(
self, width: int | None = None, height: int | None = None self, width: int | None = None, height: int | None = None
) -> bytes | None: ) -> bytes | None:
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
try:
return await self._host.api.get_snapshot( return await self._host.api.get_snapshot(
self._channel, self.entity_description.stream self._channel, self.entity_description.stream
) )
except ReolinkError as err:
raise HomeAssistantError(err) from err

View File

@ -7,7 +7,6 @@ from dataclasses import dataclass
from typing import Any from typing import Any
from reolink_aio.api import Host from reolink_aio.api import Host
from reolink_aio.exceptions import InvalidParameterError, ReolinkError
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
@ -17,7 +16,6 @@ from homeassistant.components.light import (
) )
from homeassistant.const import EntityCategory from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .entity import ( from .entity import (
@ -26,7 +24,7 @@ from .entity import (
ReolinkHostCoordinatorEntity, ReolinkHostCoordinatorEntity,
ReolinkHostEntityDescription, ReolinkHostEntityDescription,
) )
from .util import ReolinkConfigEntry, ReolinkData from .util import ReolinkConfigEntry, ReolinkData, raise_translated_error
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -154,37 +152,28 @@ class ReolinkLightEntity(ReolinkChannelCoordinatorEntity, LightEntity):
return round(255 * bright_pct / 100.0) return round(255 * bright_pct / 100.0)
@raise_translated_error
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn light off.""" """Turn light off."""
try:
await self.entity_description.turn_on_off_fn( await self.entity_description.turn_on_off_fn(
self._host.api, self._channel, False self._host.api, self._channel, False
) )
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()
@raise_translated_error
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn light on.""" """Turn light on."""
if ( if (
brightness := kwargs.get(ATTR_BRIGHTNESS) brightness := kwargs.get(ATTR_BRIGHTNESS)
) is not None and self.entity_description.set_brightness_fn is not None: ) is not None and self.entity_description.set_brightness_fn is not None:
brightness_pct = int(brightness / 255.0 * 100) brightness_pct = int(brightness / 255.0 * 100)
try:
await self.entity_description.set_brightness_fn( await self.entity_description.set_brightness_fn(
self._host.api, self._channel, brightness_pct self._host.api, self._channel, brightness_pct
) )
except InvalidParameterError as err:
raise ServiceValidationError(err) from err
except ReolinkError as err:
raise HomeAssistantError(err) from err
try:
await self.entity_description.turn_on_off_fn( await self.entity_description.turn_on_off_fn(
self._host.api, self._channel, True self._host.api, self._channel, True
) )
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()
@ -209,18 +198,14 @@ class ReolinkHostLightEntity(ReolinkHostCoordinatorEntity, LightEntity):
"""Return true if light is on.""" """Return true if light is on."""
return self.entity_description.is_on_fn(self._host.api) return self.entity_description.is_on_fn(self._host.api)
@raise_translated_error
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn light off.""" """Turn light off."""
try:
await self.entity_description.turn_on_off_fn(self._host.api, False) await self.entity_description.turn_on_off_fn(self._host.api, False)
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()
@raise_translated_error
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn light on.""" """Turn light on."""
try:
await self.entity_description.turn_on_off_fn(self._host.api, True) await self.entity_description.turn_on_off_fn(self._host.api, True)
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()

View File

@ -7,7 +7,6 @@ from dataclasses import dataclass
from typing import Any from typing import Any
from reolink_aio.api import Chime, Host from reolink_aio.api import Chime, Host
from reolink_aio.exceptions import InvalidParameterError, ReolinkError
from homeassistant.components.number import ( from homeassistant.components.number import (
NumberEntity, NumberEntity,
@ -16,7 +15,6 @@ from homeassistant.components.number import (
) )
from homeassistant.const import EntityCategory, UnitOfTime from homeassistant.const import EntityCategory, UnitOfTime
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .entity import ( from .entity import (
@ -27,7 +25,7 @@ from .entity import (
ReolinkHostCoordinatorEntity, ReolinkHostCoordinatorEntity,
ReolinkHostEntityDescription, ReolinkHostEntityDescription,
) )
from .util import ReolinkConfigEntry, ReolinkData from .util import ReolinkConfigEntry, ReolinkData, raise_translated_error
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -589,14 +587,10 @@ class ReolinkNumberEntity(ReolinkChannelCoordinatorEntity, NumberEntity):
"""State of the number entity.""" """State of the number entity."""
return self.entity_description.value(self._host.api, self._channel) return self.entity_description.value(self._host.api, self._channel)
@raise_translated_error
async def async_set_native_value(self, value: float) -> None: async def async_set_native_value(self, value: float) -> None:
"""Update the current value.""" """Update the current value."""
try:
await self.entity_description.method(self._host.api, self._channel, value) await self.entity_description.method(self._host.api, self._channel, value)
except InvalidParameterError as err:
raise ServiceValidationError(err) from err
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()
@ -621,14 +615,10 @@ class ReolinkHostNumberEntity(ReolinkHostCoordinatorEntity, NumberEntity):
"""State of the number entity.""" """State of the number entity."""
return self.entity_description.value(self._host.api) return self.entity_description.value(self._host.api)
@raise_translated_error
async def async_set_native_value(self, value: float) -> None: async def async_set_native_value(self, value: float) -> None:
"""Update the current value.""" """Update the current value."""
try:
await self.entity_description.method(self._host.api, value) await self.entity_description.method(self._host.api, value)
except InvalidParameterError as err:
raise ServiceValidationError(err) from err
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()
@ -654,12 +644,8 @@ class ReolinkChimeNumberEntity(ReolinkChimeCoordinatorEntity, NumberEntity):
"""State of the number entity.""" """State of the number entity."""
return self.entity_description.value(self._chime) return self.entity_description.value(self._chime)
@raise_translated_error
async def async_set_native_value(self, value: float) -> None: async def async_set_native_value(self, value: float) -> None:
"""Update the current value.""" """Update the current value."""
try:
await self.entity_description.method(self._chime, value) await self.entity_description.method(self._chime, value)
except InvalidParameterError as err:
raise ServiceValidationError(err) from err
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()

View File

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

View File

@ -19,12 +19,10 @@ from reolink_aio.api import (
StatusLedEnum, StatusLedEnum,
TrackMethodEnum, TrackMethodEnum,
) )
from reolink_aio.exceptions import InvalidParameterError, ReolinkError
from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.const import EntityCategory, UnitOfDataRate, UnitOfFrequency from homeassistant.const import EntityCategory, UnitOfDataRate, UnitOfFrequency
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .entity import ( from .entity import (
@ -33,7 +31,7 @@ from .entity import (
ReolinkChimeCoordinatorEntity, ReolinkChimeCoordinatorEntity,
ReolinkChimeEntityDescription, ReolinkChimeEntityDescription,
) )
from .util import ReolinkConfigEntry, ReolinkData from .util import ReolinkConfigEntry, ReolinkData, raise_translated_error
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -354,14 +352,10 @@ class ReolinkSelectEntity(ReolinkChannelCoordinatorEntity, SelectEntity):
self._log_error = True self._log_error = True
return option return option
@raise_translated_error
async def async_select_option(self, option: str) -> None: async def async_select_option(self, option: str) -> None:
"""Change the selected option.""" """Change the selected option."""
try:
await self.entity_description.method(self._host.api, self._channel, option) await self.entity_description.method(self._host.api, self._channel, option)
except InvalidParameterError as err:
raise ServiceValidationError(err) from err
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()
@ -396,12 +390,8 @@ class ReolinkChimeSelectEntity(ReolinkChimeCoordinatorEntity, SelectEntity):
self._log_error = True self._log_error = True
return option return option
@raise_translated_error
async def async_select_option(self, option: str) -> None: async def async_select_option(self, option: str) -> None:
"""Change the selected option.""" """Change the selected option."""
try:
await self.entity_description.method(self._chime, option) await self.entity_description.method(self._chime, option)
except InvalidParameterError as err:
raise ServiceValidationError(err) from err
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()

View File

@ -4,18 +4,17 @@ from __future__ import annotations
from reolink_aio.api import Chime from reolink_aio.api import Chime
from reolink_aio.enums import ChimeToneEnum from reolink_aio.enums import ChimeToneEnum
from reolink_aio.exceptions import InvalidParameterError, ReolinkError
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_DEVICE_ID from homeassistant.const import ATTR_DEVICE_ID
from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from .const import DOMAIN from .const import DOMAIN
from .host import ReolinkHost from .host import ReolinkHost
from .util import get_device_uid_and_ch from .util import get_device_uid_and_ch, raise_translated_error
ATTR_RINGTONE = "ringtone" ATTR_RINGTONE = "ringtone"
@ -24,6 +23,7 @@ ATTR_RINGTONE = "ringtone"
def async_setup_services(hass: HomeAssistant) -> None: def async_setup_services(hass: HomeAssistant) -> None:
"""Set up Reolink services.""" """Set up Reolink services."""
@raise_translated_error
async def async_play_chime(service_call: ServiceCall) -> None: async def async_play_chime(service_call: ServiceCall) -> None:
"""Play a ringtone.""" """Play a ringtone."""
service_data = service_call.data service_data = service_call.data
@ -58,12 +58,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
) )
ringtone = service_data[ATTR_RINGTONE] ringtone = service_data[ATTR_RINGTONE]
try:
await chime.play(ChimeToneEnum[ringtone].value) await chime.play(ChimeToneEnum[ringtone].value)
except InvalidParameterError as err:
raise ServiceValidationError(err) from err
except ReolinkError as err:
raise HomeAssistantError(err) from err
hass.services.async_register( hass.services.async_register(
DOMAIN, DOMAIN,

View File

@ -5,8 +5,6 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any
from reolink_aio.exceptions import InvalidParameterError, ReolinkError
from homeassistant.components.siren import ( from homeassistant.components.siren import (
ATTR_DURATION, ATTR_DURATION,
ATTR_VOLUME_LEVEL, ATTR_VOLUME_LEVEL,
@ -15,11 +13,10 @@ from homeassistant.components.siren import (
SirenEntityFeature, SirenEntityFeature,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription
from .util import ReolinkConfigEntry, ReolinkData from .util import ReolinkConfigEntry, ReolinkData, raise_translated_error
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -77,26 +74,15 @@ class ReolinkSirenEntity(ReolinkChannelCoordinatorEntity, SirenEntity):
self.entity_description = entity_description self.entity_description = entity_description
super().__init__(reolink_data, channel) super().__init__(reolink_data, channel)
@raise_translated_error
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the siren.""" """Turn on the siren."""
if (volume := kwargs.get(ATTR_VOLUME_LEVEL)) is not None: if (volume := kwargs.get(ATTR_VOLUME_LEVEL)) is not None:
try:
await self._host.api.set_volume(self._channel, int(volume * 100)) await self._host.api.set_volume(self._channel, int(volume * 100))
except InvalidParameterError as err:
raise ServiceValidationError(err) from err
except ReolinkError as err:
raise HomeAssistantError(err) from err
duration = kwargs.get(ATTR_DURATION) duration = kwargs.get(ATTR_DURATION)
try:
await self._host.api.set_siren(self._channel, True, duration) await self._host.api.set_siren(self._channel, True, duration)
except InvalidParameterError as err:
raise ServiceValidationError(err) from err
except ReolinkError as err:
raise HomeAssistantError(err) from err
@raise_translated_error
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the siren.""" """Turn off the siren."""
try:
await self._host.api.set_siren(self._channel, False, None) await self._host.api.set_siren(self._channel, False, None)
except ReolinkError as err:
raise HomeAssistantError(err) from err

View File

@ -55,6 +55,45 @@
}, },
"service_not_chime": { "service_not_chime": {
"message": "Reolink play_chime error: {device_name} is not a chime" "message": "Reolink play_chime error: {device_name} is not a chime"
},
"invalid_parameter": {
"message": "Invalid input parameter: {err}"
},
"api_error": {
"message": "The device responded with a error: {err}"
},
"invalid_content_type": {
"message": "Received a different content type than expected: {err}"
},
"invalid_credentials": {
"message": "Invalid credentials: {err}"
},
"login_error": {
"message": "Error during login attempt: {err}"
},
"no_data": {
"message": "Device returned no data: {err}"
},
"unexpected_data": {
"message": "Device returned unexpected data: {err}"
},
"not_supported": {
"message": "Function not supported by this device: {err}"
},
"subscription_error": {
"message": "Error during ONVIF subscription: {err}"
},
"connection_error": {
"message": "Could not connect to the device: {err}"
},
"timeout": {
"message": "Timeout waiting on a response: {err}"
},
"firmware_install_error": {
"message": "Error trying to update Reolink firmware: {err}"
},
"config_entry_not_ready": {
"message": "Error while trying to setup {host}: {err}"
} }
}, },
"issues": { "issues": {

View File

@ -7,12 +7,10 @@ from dataclasses import dataclass
from typing import Any from typing import Any
from reolink_aio.api import Chime, Host from reolink_aio.api import Chime, Host
from reolink_aio.exceptions import ReolinkError
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.const import EntityCategory from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er, issue_registry as ir from homeassistant.helpers import entity_registry as er, issue_registry as ir
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -25,7 +23,7 @@ from .entity import (
ReolinkHostCoordinatorEntity, ReolinkHostCoordinatorEntity,
ReolinkHostEntityDescription, ReolinkHostEntityDescription,
) )
from .util import ReolinkConfigEntry, ReolinkData from .util import ReolinkConfigEntry, ReolinkData, raise_translated_error
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -430,20 +428,16 @@ class ReolinkSwitchEntity(ReolinkChannelCoordinatorEntity, SwitchEntity):
"""Return true if switch is on.""" """Return true if switch is on."""
return self.entity_description.value(self._host.api, self._channel) return self.entity_description.value(self._host.api, self._channel)
@raise_translated_error
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on.""" """Turn the entity on."""
try:
await self.entity_description.method(self._host.api, self._channel, True) await self.entity_description.method(self._host.api, self._channel, True)
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()
@raise_translated_error
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off.""" """Turn the entity off."""
try:
await self.entity_description.method(self._host.api, self._channel, False) await self.entity_description.method(self._host.api, self._channel, False)
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()
@ -466,20 +460,16 @@ class ReolinkNVRSwitchEntity(ReolinkHostCoordinatorEntity, SwitchEntity):
"""Return true if switch is on.""" """Return true if switch is on."""
return self.entity_description.value(self._host.api) return self.entity_description.value(self._host.api)
@raise_translated_error
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on.""" """Turn the entity on."""
try:
await self.entity_description.method(self._host.api, True) await self.entity_description.method(self._host.api, True)
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()
@raise_translated_error
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off.""" """Turn the entity off."""
try:
await self.entity_description.method(self._host.api, False) await self.entity_description.method(self._host.api, False)
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()
@ -503,18 +493,14 @@ class ReolinkChimeSwitchEntity(ReolinkChimeCoordinatorEntity, SwitchEntity):
"""Return true if switch is on.""" """Return true if switch is on."""
return self.entity_description.value(self._chime) return self.entity_description.value(self._chime)
@raise_translated_error
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on.""" """Turn the entity on."""
try:
await self.entity_description.method(self._chime, True) await self.entity_description.method(self._chime, True)
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()
@raise_translated_error
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off.""" """Turn the entity off."""
try:
await self.entity_description.method(self._chime, False) await self.entity_description.method(self._chime, False)
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state() self.async_write_ha_state()

View File

@ -24,6 +24,7 @@ from homeassistant.helpers.update_coordinator import (
) )
from . import DEVICE_UPDATE_INTERVAL from . import DEVICE_UPDATE_INTERVAL
from .const import DOMAIN
from .entity import ( from .entity import (
ReolinkChannelCoordinatorEntity, ReolinkChannelCoordinatorEntity,
ReolinkChannelEntityDescription, ReolinkChannelEntityDescription,
@ -196,7 +197,9 @@ class ReolinkUpdateBaseEntity(
await self._host.api.update_firmware(self._channel) await self._host.api.update_firmware(self._channel)
except ReolinkError as err: except ReolinkError as err:
raise HomeAssistantError( raise HomeAssistantError(
f"Error trying to update Reolink firmware: {err}" translation_domain=DOMAIN,
translation_key="firmware_install_error",
translation_placeholders={"err": str(err)},
) from err ) from err
finally: finally:
self.async_write_ha_state() self.async_write_ha_state()

View File

@ -2,10 +2,28 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Awaitable, Callable, Coroutine
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, ParamSpec, TypeVar
from reolink_aio.exceptions import (
ApiError,
CredentialsInvalidError,
InvalidContentTypeError,
InvalidParameterError,
LoginError,
NoDataError,
NotSupportedError,
ReolinkConnectionError,
ReolinkError,
ReolinkTimeoutError,
SubscriptionError,
UnexpectedDataError,
)
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
@ -53,3 +71,89 @@ def get_device_uid_and_ch(
else: else:
ch = host.api.channel_for_uid(device_uid[1]) ch = host.api.channel_for_uid(device_uid[1])
return (device_uid, ch, is_chime) return (device_uid, ch, is_chime)
T = TypeVar("T")
P = ParamSpec("P")
# Decorators
def raise_translated_error(
func: Callable[P, Awaitable[T]],
) -> Callable[P, Coroutine[Any, Any, T]]:
"""Wrap a reolink-aio function to translate any potential errors."""
async def decorator_raise_translated_error(*args: P.args, **kwargs: P.kwargs) -> T:
"""Try a reolink-aio function and translate any potential errors."""
try:
return await func(*args, **kwargs)
except InvalidParameterError as err:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_parameter",
translation_placeholders={"err": str(err)},
) from err
except ApiError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="api_error",
translation_placeholders={"err": str(err)},
) from err
except InvalidContentTypeError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="invalid_content_type",
translation_placeholders={"err": str(err)},
) from err
except CredentialsInvalidError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="invalid_credentials",
translation_placeholders={"err": str(err)},
) from err
except LoginError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="login_error",
translation_placeholders={"err": str(err)},
) from err
except NoDataError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="no_data",
translation_placeholders={"err": str(err)},
) from err
except UnexpectedDataError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="unexpected_data",
translation_placeholders={"err": str(err)},
) from err
except NotSupportedError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="not_supported",
translation_placeholders={"err": str(err)},
) from err
except SubscriptionError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="subscription_error",
translation_placeholders={"err": str(err)},
) from err
except ReolinkConnectionError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="connection_error",
translation_placeholders={"err": str(err)},
) from err
except ReolinkTimeoutError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="timeout",
translation_placeholders={"err": str(err)},
) from err
except ReolinkError as err:
raise HomeAssistantError(err) from err
return decorator_raise_translated_error

View File

@ -0,0 +1,115 @@
"""Test the Reolink util functions."""
from unittest.mock import MagicMock, patch
import pytest
from reolink_aio.exceptions import (
ApiError,
CredentialsInvalidError,
InvalidContentTypeError,
InvalidParameterError,
LoginError,
NoDataError,
NotSupportedError,
ReolinkConnectionError,
ReolinkError,
ReolinkTimeoutError,
SubscriptionError,
UnexpectedDataError,
)
from homeassistant.components.number import (
ATTR_VALUE,
DOMAIN as NUMBER_DOMAIN,
SERVICE_SET_VALUE,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from .conftest import TEST_NVR_NAME
from tests.common import MockConfigEntry
@pytest.mark.parametrize(
("side_effect", "expected"),
[
(
ApiError("Test error"),
HomeAssistantError,
),
(
CredentialsInvalidError("Test error"),
HomeAssistantError,
),
(
InvalidContentTypeError("Test error"),
HomeAssistantError,
),
(
InvalidParameterError("Test error"),
ServiceValidationError,
),
(
LoginError("Test error"),
HomeAssistantError,
),
(
NoDataError("Test error"),
HomeAssistantError,
),
(
NotSupportedError("Test error"),
HomeAssistantError,
),
(
ReolinkConnectionError("Test error"),
HomeAssistantError,
),
(
ReolinkError("Test error"),
HomeAssistantError,
),
(
ReolinkTimeoutError("Test error"),
HomeAssistantError,
),
(
SubscriptionError("Test error"),
HomeAssistantError,
),
(
UnexpectedDataError("Test error"),
HomeAssistantError,
),
],
)
async def test_try_function(
hass: HomeAssistant,
config_entry: MockConfigEntry,
reolink_connect: MagicMock,
side_effect: ReolinkError,
expected: Exception,
) -> None:
"""Test try_function error translations using number entity."""
reolink_connect.volume.return_value = 80
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.NUMBER]):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
entity_id = f"{Platform.NUMBER}.{TEST_NVR_NAME}_volume"
reolink_connect.set_volume.side_effect = side_effect
with pytest.raises(expected):
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
{ATTR_ENTITY_ID: entity_id, ATTR_VALUE: 50},
blocking=True,
)
reolink_connect.set_volume.reset_mock(side_effect=True)