Add proper exception handling to lamarzocco (#125913)

This commit is contained in:
Josef Zweck 2024-09-27 21:04:01 +02:00 committed by GitHub
parent 57e041171b
commit 2e1732fadf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 285 additions and 17 deletions

View File

@ -4,10 +4,12 @@ from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any
from lmcloud.exceptions import RequestNotSuccessful
from lmcloud.lm_machine import LaMarzoccoMachine
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import LaMarzoccoConfigEntry
@ -55,5 +57,13 @@ class LaMarzoccoButtonEntity(LaMarzoccoEntity, ButtonEntity):
async def async_press(self) -> None:
"""Press button."""
await self.entity_description.press_fn(self.coordinator.device)
try:
await self.entity_description.press_fn(self.coordinator.device)
except RequestNotSuccessful as exc:
raise HomeAssistantError(
translation_key="button_error",
translation_placeholders={
"key": self.entity_description.key,
},
) from exc
await self.coordinator.async_request_refresh()

View File

@ -11,6 +11,7 @@ from lmcloud.const import (
PhysicalKey,
PrebrewMode,
)
from lmcloud.exceptions import RequestNotSuccessful
from lmcloud.lm_machine import LaMarzoccoMachine
from lmcloud.models import LaMarzoccoMachineConfig
@ -27,6 +28,7 @@ from homeassistant.const import (
UnitOfTime,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import LaMarzoccoConfigEntry
@ -220,7 +222,18 @@ class LaMarzoccoNumberEntity(LaMarzoccoEntity, NumberEntity):
async def async_set_native_value(self, value: float) -> None:
"""Set the value."""
if value != self.native_value:
await self.entity_description.set_value_fn(self.coordinator.device, value)
try:
await self.entity_description.set_value_fn(
self.coordinator.device, value
)
except RequestNotSuccessful as exc:
raise HomeAssistantError(
translation_key="number_exception",
translation_placeholders={
"key": self.entity_description.key,
"value": str(value),
},
) from exc
self.async_write_ha_state()
@ -258,7 +271,17 @@ class LaMarzoccoKeyNumberEntity(LaMarzoccoEntity, NumberEntity):
async def async_set_native_value(self, value: float) -> None:
"""Set the value."""
if value != self.native_value:
await self.entity_description.set_value_fn(
self.coordinator.device, value, PhysicalKey(self.pyhsical_key)
)
try:
await self.entity_description.set_value_fn(
self.coordinator.device, value, PhysicalKey(self.pyhsical_key)
)
except RequestNotSuccessful as exc:
raise HomeAssistantError(
translation_key="number_exception_key",
translation_placeholders={
"key": self.entity_description.key,
"value": str(value),
"physical_key": str(self.pyhsical_key),
},
) from exc
self.async_write_ha_state()

View File

@ -5,12 +5,14 @@ from dataclasses import dataclass
from typing import Any
from lmcloud.const import MachineModel, PrebrewMode, SteamLevel
from lmcloud.exceptions import RequestNotSuccessful
from lmcloud.lm_machine import LaMarzoccoMachine
from lmcloud.models import LaMarzoccoMachineConfig
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import LaMarzoccoConfigEntry
@ -113,7 +115,16 @@ class LaMarzoccoSelectEntity(LaMarzoccoEntity, SelectEntity):
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
if option != self.current_option:
await self.entity_description.select_option_fn(
self.coordinator.device, option
)
try:
await self.entity_description.select_option_fn(
self.coordinator.device, option
)
except RequestNotSuccessful as exc:
raise HomeAssistantError(
translation_key="select_option_error",
translation_placeholders={
"key": self.entity_description.key,
"option": option,
},
) from exc
self.async_write_ha_state()

View File

@ -180,5 +180,31 @@
"title": "Unsupported gateway firmware",
"description": "Gateway firmware {gateway_version} is no longer supported by this integration, please update."
}
},
"exceptions": {
"auto_on_off_error": {
"message": "Error while setting auto on/off to {state} for {id}"
},
"button_error": {
"message": "Error while executing button {key}"
},
"number_exception": {
"message": "Error while setting value {value} for number {key}"
},
"number_exception_key": {
"message": "Error while setting value {value} for number {key}, key {physical_key}"
},
"select_option_error": {
"message": "Error while setting select option {option} for {key}"
},
"switch_on_error": {
"message": "Error while turning on switch {key}"
},
"switch_off_error": {
"message": "Error while turning off switch {key}"
},
"update_failed": {
"message": "Error while updating {key}"
}
}
}

View File

@ -5,12 +5,14 @@ from dataclasses import dataclass
from typing import Any
from lmcloud.const import BoilerType
from lmcloud.exceptions import RequestNotSuccessful
from lmcloud.lm_machine import LaMarzoccoMachine
from lmcloud.models import LaMarzoccoMachineConfig
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import LaMarzoccoConfigEntry
@ -77,12 +79,24 @@ class LaMarzoccoSwitchEntity(LaMarzoccoEntity, SwitchEntity):
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn device on."""
await self.entity_description.control_fn(self.coordinator.device, True)
try:
await self.entity_description.control_fn(self.coordinator.device, True)
except RequestNotSuccessful as exc:
raise HomeAssistantError(
translation_key="switch_on_error",
translation_placeholders={"key": self.entity_description.key},
) from exc
self.async_write_ha_state()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn device off."""
await self.entity_description.control_fn(self.coordinator.device, False)
try:
await self.entity_description.control_fn(self.coordinator.device, False)
except RequestNotSuccessful as exc:
raise HomeAssistantError(
translation_key="switch_off_error",
translation_placeholders={"name": self.entity_description.key},
) from exc
self.async_write_ha_state()
@property
@ -114,7 +128,13 @@ class LaMarzoccoAutoOnOffSwitchEntity(LaMarzoccoBaseEntity, SwitchEntity):
self._identifier
]
wake_up_sleep_entry.enabled = state
await self.coordinator.device.set_wake_up_sleep(wake_up_sleep_entry)
try:
await self.coordinator.device.set_wake_up_sleep(wake_up_sleep_entry)
except RequestNotSuccessful as exc:
raise HomeAssistantError(
translation_key="auto_on_off_error",
translation_placeholders={"id": self._identifier, "state": str(state)},
) from exc
self.async_write_ha_state()
async def async_turn_on(self, **kwargs: Any) -> None:

View File

@ -4,6 +4,7 @@ from dataclasses import dataclass
from typing import Any
from lmcloud.const import FirmwareType
from lmcloud.exceptions import RequestNotSuccessful
from homeassistant.components.update import (
UpdateDeviceClass,
@ -94,10 +95,23 @@ class LaMarzoccoUpdateEntity(LaMarzoccoEntity, UpdateEntity):
"""Install an update."""
self._attr_in_progress = True
self.async_write_ha_state()
success = await self.coordinator.device.update_firmware(
self.entity_description.component
)
try:
success = await self.coordinator.device.update_firmware(
self.entity_description.component
)
except RequestNotSuccessful as exc:
raise HomeAssistantError(
translation_key="update_failed",
translation_placeholders={
"key": self.entity_description.key,
},
) from exc
if not success:
raise HomeAssistantError("Update failed")
raise HomeAssistantError(
translation_key="update_failed",
translation_placeholders={
"key": self.entity_description.key,
},
)
self._attr_in_progress = False
await self.coordinator.async_request_refresh()

View File

@ -2,12 +2,14 @@
from unittest.mock import MagicMock
from lmcloud.exceptions import RequestNotSuccessful
import pytest
from syrupy import SnapshotAssertion
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
pytestmark = pytest.mark.usefixtures("init_integration")
@ -42,3 +44,26 @@ async def test_start_backflush(
assert len(mock_lamarzocco.start_backflush.mock_calls) == 1
mock_lamarzocco.start_backflush.assert_called_once()
async def test_button_error(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
) -> None:
"""Test the La Marzocco button error."""
serial_number = mock_lamarzocco.serial_number
state = hass.states.get(f"button.{serial_number}_start_backflush")
assert state
mock_lamarzocco.start_backflush.side_effect = RequestNotSuccessful("Boom.")
with pytest.raises(HomeAssistantError) as exc_info:
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{
ATTR_ENTITY_ID: f"button.{serial_number}_start_backflush",
},
blocking=True,
)
assert exc_info.value.translation_key == "button_error"

View File

@ -9,6 +9,7 @@ from lmcloud.const import (
PhysicalKey,
PrebrewMode,
)
from lmcloud.exceptions import RequestNotSuccessful
import pytest
from syrupy import SnapshotAssertion
@ -19,6 +20,7 @@ from homeassistant.components.number import (
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er
from . import async_init_integration
@ -379,3 +381,46 @@ async def test_not_existing_key_entities(
for key in range(1, KEYS_PER_MODEL[MachineModel.GS3_AV] + 1):
state = hass.states.get(f"number.{serial_number}_{entity}_key_{key}")
assert state is None
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_number_error(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test number entities raise error on service call."""
await async_init_integration(hass, mock_config_entry)
serial_number = mock_lamarzocco.serial_number
state = hass.states.get(f"number.{serial_number}_coffee_target_temperature")
assert state
mock_lamarzocco.set_temp.side_effect = RequestNotSuccessful("Boom")
with pytest.raises(HomeAssistantError) as exc_info:
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: f"number.{serial_number}_coffee_target_temperature",
ATTR_VALUE: 94,
},
blocking=True,
)
assert exc_info.value.translation_key == "number_exception"
state = hass.states.get(f"number.{serial_number}_dose_key_1")
assert state
mock_lamarzocco.set_dose.side_effect = RequestNotSuccessful("Boom")
with pytest.raises(HomeAssistantError) as exc_info:
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: f"number.{serial_number}_dose_key_1",
ATTR_VALUE: 99,
},
blocking=True,
)
assert exc_info.value.translation_key == "number_exception_key"

View File

@ -3,6 +3,7 @@
from unittest.mock import MagicMock
from lmcloud.const import MachineModel, PrebrewMode, SteamLevel
from lmcloud.exceptions import RequestNotSuccessful
import pytest
from syrupy import SnapshotAssertion
@ -13,6 +14,7 @@ from homeassistant.components.select import (
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
pytestmark = pytest.mark.usefixtures("init_integration")
@ -117,3 +119,29 @@ async def test_pre_brew_infusion_select_none(
state = hass.states.get(f"select.{serial_number}_prebrew_infusion_mode")
assert state is None
async def test_select_errors(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
) -> None:
"""Test select errors."""
serial_number = mock_lamarzocco.serial_number
state = hass.states.get(f"select.{serial_number}_prebrew_infusion_mode")
assert state
mock_lamarzocco.set_prebrew_mode.side_effect = RequestNotSuccessful("Boom")
# Test setting invalid option
with pytest.raises(HomeAssistantError) as exc_info:
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: f"select.{serial_number}_prebrew_infusion_mode",
ATTR_OPTION: "prebrew",
},
blocking=True,
)
assert exc_info.value.translation_key == "select_option_error"

View File

@ -2,6 +2,7 @@
from unittest.mock import MagicMock
from lmcloud.exceptions import RequestNotSuccessful
import pytest
from syrupy import SnapshotAssertion
@ -12,6 +13,7 @@ from homeassistant.components.switch import (
)
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
from . import WAKE_UP_SLEEP_ENTRY_IDS, async_init_integration
@ -158,3 +160,56 @@ async def test_auto_on_off_switches(
)
wake_up_sleep_entry.enabled = True
mock_lamarzocco.set_wake_up_sleep.assert_called_with(wake_up_sleep_entry)
async def test_switch_exceptions(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the La Marzocco switches."""
await async_init_integration(hass, mock_config_entry)
serial_number = mock_lamarzocco.serial_number
state = hass.states.get(f"switch.{serial_number}")
assert state
mock_lamarzocco.set_power.side_effect = RequestNotSuccessful("Boom")
with pytest.raises(HomeAssistantError) as exc_info:
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{
ATTR_ENTITY_ID: f"switch.{serial_number}",
},
blocking=True,
)
assert exc_info.value.translation_key == "switch_off_error"
with pytest.raises(HomeAssistantError) as exc_info:
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: f"switch.{serial_number}",
},
blocking=True,
)
assert exc_info.value.translation_key == "switch_on_error"
state = hass.states.get(f"switch.{serial_number}_auto_on_off_os2oswx")
assert state
mock_lamarzocco.set_wake_up_sleep.side_effect = RequestNotSuccessful("Boom")
with pytest.raises(HomeAssistantError) as exc_info:
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{
ATTR_ENTITY_ID: f"switch.{serial_number}_auto_on_off_os2oswx",
},
blocking=True,
)
assert exc_info.value.translation_key == "auto_on_off_error"

View File

@ -3,6 +3,7 @@
from unittest.mock import MagicMock
from lmcloud.const import FirmwareType
from lmcloud.exceptions import RequestNotSuccessful
import pytest
from syrupy import SnapshotAssertion
@ -54,17 +55,26 @@ async def test_update_entites(
mock_lamarzocco.update_firmware.assert_called_once_with(component)
@pytest.mark.parametrize(
("attr", "value"),
[
("side_effect", RequestNotSuccessful("Boom")),
("return_value", False),
],
)
async def test_update_error(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
attr: str,
value: bool | Exception,
) -> None:
"""Test error during update."""
state = hass.states.get(f"update.{mock_lamarzocco.serial_number}_machine_firmware")
assert state
mock_lamarzocco.update_firmware.return_value = False
setattr(mock_lamarzocco.update_firmware, attr, value)
with pytest.raises(HomeAssistantError, match="Update failed"):
with pytest.raises(HomeAssistantError) as exc_info:
await hass.services.async_call(
UPDATE_DOMAIN,
SERVICE_INSTALL,
@ -73,3 +83,4 @@ async def test_update_error(
},
blocking=True,
)
assert exc_info.value.translation_key == "update_failed"