Raise HomeAssistantError in Renault (#86071)

This commit is contained in:
epenet 2023-01-17 11:44:18 +01:00 committed by GitHub
parent 6c5f9c6fcb
commit 1918f21fb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 35 deletions

View File

@ -1,8 +1,9 @@
"""Support for Renault button entities.""" """Support for Renault button entities."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Awaitable, Callable from collections.abc import Callable, Coroutine
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -18,7 +19,7 @@ from .renault_hub import RenaultHub
class RenaultButtonRequiredKeysMixin: class RenaultButtonRequiredKeysMixin:
"""Mixin for required keys.""" """Mixin for required keys."""
async_press: Callable[[RenaultButtonEntity], Awaitable] async_press: Callable[[RenaultButtonEntity], Coroutine[Any, Any, Any]]
@dataclass @dataclass
@ -56,25 +57,15 @@ class RenaultButtonEntity(RenaultEntity, ButtonEntity):
await self.entity_description.async_press(self) await self.entity_description.async_press(self)
async def _start_charge(entity: RenaultButtonEntity) -> None:
"""Start charge on the vehicle."""
await entity.vehicle.vehicle.set_charge_start()
async def _start_air_conditioner(entity: RenaultButtonEntity) -> None:
"""Start air conditioner on the vehicle."""
await entity.vehicle.vehicle.set_ac_start(21, None)
BUTTON_TYPES: tuple[RenaultButtonEntityDescription, ...] = ( BUTTON_TYPES: tuple[RenaultButtonEntityDescription, ...] = (
RenaultButtonEntityDescription( RenaultButtonEntityDescription(
async_press=_start_air_conditioner, async_press=lambda x: x.vehicle.set_ac_start(21, None),
key="start_air_conditioner", key="start_air_conditioner",
icon="mdi:air-conditioner", icon="mdi:air-conditioner",
name="Start air conditioner", name="Start air conditioner",
), ),
RenaultButtonEntityDescription( RenaultButtonEntityDescription(
async_press=_start_charge, async_press=lambda x: x.vehicle.set_charge_start(),
key="start_charge", key="start_charge",
icon="mdi:ev-station", icon="mdi:ev-station",
name="Start charge", name="Start charge",

View File

@ -2,22 +2,48 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from collections.abc import Awaitable, Callable from collections.abc import Awaitable, Callable, Coroutine
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta from datetime import datetime, timedelta
from functools import wraps
import logging import logging
from typing import cast from typing import Any, TypeVar, cast
from renault_api.exceptions import RenaultException
from renault_api.kamereon import models from renault_api.kamereon import models
from renault_api.renault_vehicle import RenaultVehicle from renault_api.renault_vehicle import RenaultVehicle
from typing_extensions import Concatenate, ParamSpec
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from .const import DOMAIN from .const import DOMAIN
from .renault_coordinator import RenaultDataUpdateCoordinator from .renault_coordinator import RenaultDataUpdateCoordinator
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
_T = TypeVar("_T")
_P = ParamSpec("_P")
def with_error_wrapping(
func: Callable[Concatenate[RenaultVehicleProxy, _P], Awaitable[_T]]
) -> Callable[Concatenate[RenaultVehicleProxy, _P], Coroutine[Any, Any, _T]]:
"""Catch Renault errors."""
@wraps(func)
async def wrapper(
self: RenaultVehicleProxy,
*args: _P.args,
**kwargs: _P.kwargs,
) -> _T:
"""Catch RenaultException errors and raise HomeAssistantError."""
try:
return await func(self, *args, **kwargs)
except RenaultException as err:
raise HomeAssistantError(err) from err
return wrapper
@dataclass @dataclass
@ -69,11 +95,6 @@ class RenaultVehicleProxy:
"""Return a device description for device registry.""" """Return a device description for device registry."""
return self._device_info return self._device_info
@property
def vehicle(self) -> RenaultVehicle:
"""Return the underlying vehicle."""
return self._vehicle
async def async_initialise(self) -> None: async def async_initialise(self) -> None:
"""Load available coordinators.""" """Load available coordinators."""
self.coordinators = { self.coordinators = {
@ -119,6 +140,42 @@ class RenaultVehicleProxy:
) )
del self.coordinators[key] del self.coordinators[key]
@with_error_wrapping
async def set_charge_mode(
self, charge_mode: str
) -> models.KamereonVehicleChargeModeActionData:
"""Set vehicle charge mode."""
return await self._vehicle.set_charge_mode(charge_mode)
@with_error_wrapping
async def set_charge_start(self) -> models.KamereonVehicleChargingStartActionData:
"""Start vehicle charge."""
return await self._vehicle.set_charge_start()
@with_error_wrapping
async def set_ac_stop(self) -> models.KamereonVehicleHvacStartActionData:
"""Stop vehicle ac."""
return await self._vehicle.set_ac_stop()
@with_error_wrapping
async def set_ac_start(
self, temperature: float, when: datetime | None = None
) -> models.KamereonVehicleHvacStartActionData:
"""Start vehicle ac."""
return await self._vehicle.set_ac_start(temperature, when)
@with_error_wrapping
async def get_charging_settings(self) -> models.KamereonVehicleChargingSettingsData:
"""Get vehicle charging settings."""
return await self._vehicle.get_charging_settings()
@with_error_wrapping
async def set_charge_schedules(
self, schedules: list[models.ChargeSchedule]
) -> models.KamereonVehicleChargeScheduleActionData:
"""Set vehicle charge schedules."""
return await self._vehicle.set_charge_schedules(schedules)
COORDINATORS: tuple[RenaultCoordinatorDescription, ...] = ( COORDINATORS: tuple[RenaultCoordinatorDescription, ...] = (
RenaultCoordinatorDescription( RenaultCoordinatorDescription(

View File

@ -75,7 +75,7 @@ class RenaultSelectEntity(
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."""
await self.vehicle.vehicle.set_charge_mode(option) await self.vehicle.set_charge_mode(option)
def _get_charge_mode_icon(entity: RenaultSelectEntity) -> str: def _get_charge_mode_icon(entity: RenaultSelectEntity) -> str:

View File

@ -74,7 +74,7 @@ def setup_services(hass: HomeAssistant) -> None:
proxy = get_vehicle_proxy(service_call.data) proxy = get_vehicle_proxy(service_call.data)
LOGGER.debug("A/C cancel attempt") LOGGER.debug("A/C cancel attempt")
result = await proxy.vehicle.set_ac_stop() result = await proxy.set_ac_stop()
LOGGER.debug("A/C cancel result: %s", result) LOGGER.debug("A/C cancel result: %s", result)
async def ac_start(service_call: ServiceCall) -> None: async def ac_start(service_call: ServiceCall) -> None:
@ -84,21 +84,22 @@ def setup_services(hass: HomeAssistant) -> None:
proxy = get_vehicle_proxy(service_call.data) proxy = get_vehicle_proxy(service_call.data)
LOGGER.debug("A/C start attempt: %s / %s", temperature, when) LOGGER.debug("A/C start attempt: %s / %s", temperature, when)
result = await proxy.vehicle.set_ac_start(temperature, when) result = await proxy.set_ac_start(temperature, when)
LOGGER.debug("A/C start result: %s", result.raw_data) LOGGER.debug("A/C start result: %s", result.raw_data)
async def charge_set_schedules(service_call: ServiceCall) -> None: async def charge_set_schedules(service_call: ServiceCall) -> None:
"""Set charge schedules.""" """Set charge schedules."""
schedules: list[dict[str, Any]] = service_call.data[ATTR_SCHEDULES] schedules: list[dict[str, Any]] = service_call.data[ATTR_SCHEDULES]
proxy = get_vehicle_proxy(service_call.data) proxy = get_vehicle_proxy(service_call.data)
charge_schedules = await proxy.vehicle.get_charging_settings() charge_schedules = await proxy.get_charging_settings()
for schedule in schedules: for schedule in schedules:
charge_schedules.update(schedule) charge_schedules.update(schedule)
if TYPE_CHECKING: if TYPE_CHECKING:
assert charge_schedules.schedules is not None assert charge_schedules.schedules is not None
LOGGER.debug("Charge set schedules attempt: %s", schedules) LOGGER.debug("Charge set schedules attempt: %s", schedules)
result = await proxy.vehicle.set_charge_schedules(charge_schedules.schedules) result = await proxy.set_charge_schedules(charge_schedules.schedules)
LOGGER.debug("Charge set schedules result: %s", result) LOGGER.debug("Charge set schedules result: %s", result)
LOGGER.debug( LOGGER.debug(
"It may take some time before these changes are reflected in your vehicle" "It may take some time before these changes are reflected in your vehicle"

View File

@ -4,6 +4,7 @@ from datetime import datetime
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
from renault_api.exceptions import RenaultException
from renault_api.kamereon import schemas from renault_api.kamereon import schemas
from renault_api.kamereon.models import ChargeSchedule from renault_api.kamereon.models import ChargeSchedule
@ -27,6 +28,7 @@ from homeassistant.const import (
ATTR_SW_VERSION, ATTR_SW_VERSION,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from .const import MOCK_VEHICLES from .const import MOCK_VEHICLES
@ -89,15 +91,12 @@ async def test_service_set_ac_cancel(
with patch( with patch(
"renault_api.renault_vehicle.RenaultVehicle.set_ac_stop", "renault_api.renault_vehicle.RenaultVehicle.set_ac_stop",
return_value=( side_effect=RenaultException("Didn't work"),
schemas.KamereonVehicleHvacStartActionDataSchema.loads(
load_fixture("renault/action.set_ac_stop.json")
)
),
) as mock_action: ) as mock_action:
await hass.services.async_call( with pytest.raises(HomeAssistantError, match="Didn't work"):
DOMAIN, SERVICE_AC_CANCEL, service_data=data, blocking=True await hass.services.async_call(
) DOMAIN, SERVICE_AC_CANCEL, service_data=data, blocking=True
)
assert len(mock_action.mock_calls) == 1 assert len(mock_action.mock_calls) == 1
assert mock_action.mock_calls[0][1] == () assert mock_action.mock_calls[0][1] == ()