Add error handling to Tailwind service methods (#106463)

This commit is contained in:
Franck Nijhof 2023-12-27 10:53:31 +01:00 committed by GitHub
parent 68ac4717dc
commit a78ecb3895
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 219 additions and 15 deletions

View File

@ -5,7 +5,7 @@ from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any
from gotailwind import Tailwind
from gotailwind import Tailwind, TailwindError
from homeassistant.components.button import (
ButtonDeviceClass,
@ -15,6 +15,7 @@ from homeassistant.components.button import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
@ -62,4 +63,11 @@ class TailwindButtonEntity(TailwindEntity, ButtonEntity):
async def async_press(self) -> None:
"""Trigger button press on the Tailwind device."""
try:
await self.entity_description.press_fn(self.coordinator.tailwind)
except TailwindError as exc:
raise HomeAssistantError(
str(exc),
translation_domain=DOMAIN,
translation_key="communication_error",
) from exc

View File

@ -3,7 +3,13 @@ from __future__ import annotations
from typing import Any
from gotailwind import TailwindDoorOperationCommand, TailwindDoorState
from gotailwind import (
TailwindDoorDisabledError,
TailwindDoorLockedOutError,
TailwindDoorOperationCommand,
TailwindDoorState,
TailwindError,
)
from homeassistant.components.cover import (
CoverDeviceClass,
@ -12,6 +18,7 @@ from homeassistant.components.cover import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
@ -56,10 +63,30 @@ class TailwindDoorCoverEntity(TailwindDoorEntity, CoverEntity):
"""
self._attr_is_opening = True
self.async_write_ha_state()
try:
await self.coordinator.tailwind.operate(
door=self.coordinator.data.doors[self.door_id],
operation=TailwindDoorOperationCommand.OPEN,
)
except TailwindDoorDisabledError as exc:
raise HomeAssistantError(
str(exc),
translation_domain=DOMAIN,
translation_key="door_disabled",
) from exc
except TailwindDoorLockedOutError as exc:
raise HomeAssistantError(
str(exc),
translation_domain=DOMAIN,
translation_key="door_locked_out",
) from exc
except TailwindError as exc:
raise HomeAssistantError(
str(exc),
translation_domain=DOMAIN,
translation_key="communication_error",
) from exc
finally:
self._attr_is_opening = False
await self.coordinator.async_request_refresh()
@ -71,9 +98,28 @@ class TailwindDoorCoverEntity(TailwindDoorEntity, CoverEntity):
"""
self._attr_is_closing = True
self.async_write_ha_state()
try:
await self.coordinator.tailwind.operate(
door=self.coordinator.data.doors[self.door_id],
operation=TailwindDoorOperationCommand.CLOSE,
)
except TailwindDoorDisabledError as exc:
raise HomeAssistantError(
str(exc),
translation_domain=DOMAIN,
translation_key="door_disabled",
) from exc
except TailwindDoorLockedOutError as exc:
raise HomeAssistantError(
str(exc),
translation_domain=DOMAIN,
translation_key="door_locked_out",
) from exc
except TailwindError as exc:
raise HomeAssistantError(
str(exc),
translation_domain=DOMAIN,
translation_key="communication_error",
) from exc
self._attr_is_closing = False
await self.coordinator.async_request_refresh()

View File

@ -5,12 +5,13 @@ from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any
from gotailwind import Tailwind, TailwindDeviceStatus
from gotailwind import Tailwind, TailwindDeviceStatus, TailwindError
from homeassistant.components.number import NumberEntity, NumberEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
@ -72,5 +73,12 @@ class TailwindNumberEntity(TailwindEntity, NumberEntity):
async def async_set_native_value(self, value: float) -> None:
"""Change to new number value."""
try:
await self.entity_description.set_value_fn(self.coordinator.tailwind, value)
except TailwindError as exc:
raise HomeAssistantError(
str(exc),
translation_domain=DOMAIN,
translation_key="communication_error",
) from exc
await self.coordinator.async_request_refresh()

View File

@ -60,5 +60,16 @@
"name": "Status LED brightness"
}
}
},
"exceptions": {
"communication_error": {
"message": "An error occurred while communicating with the Tailwind device."
},
"door_disabled": {
"message": "The door is disabled and cannot be operated."
},
"door_locked_out": {
"message": "The door is locked out and cannot be operated."
}
}
}

View File

@ -1,12 +1,15 @@
"""Tests for button entities provided by the Tailwind integration."""
from unittest.mock import MagicMock
from gotailwind import TailwindError
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
from homeassistant.components.tailwind.const import DOMAIN
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er
pytestmark = [
@ -46,3 +49,17 @@ async def test_number_entities(
assert (state := hass.states.get(state.entity_id))
assert state.state == "2023-12-17T15:25:00+00:00"
# Test error handling
mock_tailwind.identify.side_effect = TailwindError("Some error")
with pytest.raises(HomeAssistantError, match="Some error") as excinfo:
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: state.entity_id},
blocking=True,
)
assert excinfo.value.translation_domain == DOMAIN
assert excinfo.value.translation_key == "communication_error"

View File

@ -1,7 +1,12 @@
"""Tests for cover entities provided by the Tailwind integration."""
from unittest.mock import ANY, MagicMock
from gotailwind import TailwindDoorOperationCommand
from gotailwind import (
TailwindDoorDisabledError,
TailwindDoorLockedOutError,
TailwindDoorOperationCommand,
TailwindError,
)
import pytest
from syrupy.assertion import SnapshotAssertion
@ -10,8 +15,10 @@ from homeassistant.components.cover import (
SERVICE_CLOSE_COVER,
SERVICE_OPEN_COVER,
)
from homeassistant.components.tailwind.const import DOMAIN
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er
pytestmark = pytest.mark.usefixtures("init_integration")
@ -74,3 +81,90 @@ async def test_cover_operations(
mock_tailwind.operate.assert_called_with(
door=ANY, operation=TailwindDoorOperationCommand.CLOSE
)
# Test door disabled error handling
mock_tailwind.operate.side_effect = TailwindDoorDisabledError("Door disabled")
with pytest.raises(HomeAssistantError, match="Door disabled") as excinfo:
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{
ATTR_ENTITY_ID: "cover.door_1",
},
blocking=True,
)
assert excinfo.value.translation_domain == DOMAIN
assert excinfo.value.translation_key == "door_disabled"
with pytest.raises(HomeAssistantError, match="Door disabled") as excinfo:
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{
ATTR_ENTITY_ID: "cover.door_1",
},
blocking=True,
)
assert excinfo.value.translation_domain == DOMAIN
assert excinfo.value.translation_key == "door_disabled"
# Test door locked out error handling
mock_tailwind.operate.side_effect = TailwindDoorLockedOutError("Door locked out")
with pytest.raises(HomeAssistantError, match="Door locked out") as excinfo:
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{
ATTR_ENTITY_ID: "cover.door_1",
},
blocking=True,
)
assert excinfo.value.translation_domain == DOMAIN
assert excinfo.value.translation_key == "door_locked_out"
with pytest.raises(HomeAssistantError, match="Door locked out") as excinfo:
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{
ATTR_ENTITY_ID: "cover.door_1",
},
blocking=True,
)
assert excinfo.value.translation_domain == DOMAIN
assert excinfo.value.translation_key == "door_locked_out"
# Test door error handling
mock_tailwind.operate.side_effect = TailwindError("Some error")
with pytest.raises(HomeAssistantError, match="Some error") as excinfo:
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{
ATTR_ENTITY_ID: "cover.door_1",
},
blocking=True,
)
assert excinfo.value.translation_domain == DOMAIN
assert excinfo.value.translation_key == "communication_error"
with pytest.raises(HomeAssistantError, match="Some error") as excinfo:
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{
ATTR_ENTITY_ID: "cover.door_1",
},
blocking=True,
)
assert excinfo.value.translation_domain == DOMAIN
assert excinfo.value.translation_key == "communication_error"

View File

@ -1,13 +1,16 @@
"""Tests for number entities provided by the Tailwind integration."""
from unittest.mock import MagicMock
from gotailwind import TailwindError
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components import number
from homeassistant.components.number import ATTR_VALUE, SERVICE_SET_VALUE
from homeassistant.components.tailwind.const import DOMAIN
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er
pytestmark = pytest.mark.usefixtures("init_integration")
@ -44,3 +47,20 @@ async def test_number_entities(
assert len(mock_tailwind.status_led.mock_calls) == 1
mock_tailwind.status_led.assert_called_with(brightness=42)
# Test error handling
mock_tailwind.status_led.side_effect = TailwindError("Some error")
with pytest.raises(HomeAssistantError, match="Some error") as excinfo:
await hass.services.async_call(
number.DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: state.entity_id,
ATTR_VALUE: 42,
},
blocking=True,
)
assert excinfo.value.translation_domain == DOMAIN
assert excinfo.value.translation_key == "communication_error"