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."""
from __future__ import annotations
from collections.abc import Awaitable, Callable
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.config_entries import ConfigEntry
@ -18,7 +19,7 @@ from .renault_hub import RenaultHub
class RenaultButtonRequiredKeysMixin:
"""Mixin for required keys."""
async_press: Callable[[RenaultButtonEntity], Awaitable]
async_press: Callable[[RenaultButtonEntity], Coroutine[Any, Any, Any]]
@dataclass
@ -56,25 +57,15 @@ class RenaultButtonEntity(RenaultEntity, ButtonEntity):
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, ...] = (
RenaultButtonEntityDescription(
async_press=_start_air_conditioner,
async_press=lambda x: x.vehicle.set_ac_start(21, None),
key="start_air_conditioner",
icon="mdi:air-conditioner",
name="Start air conditioner",
),
RenaultButtonEntityDescription(
async_press=_start_charge,
async_press=lambda x: x.vehicle.set_charge_start(),
key="start_charge",
icon="mdi:ev-station",
name="Start charge",

View File

@ -2,22 +2,48 @@
from __future__ import annotations
import asyncio
from collections.abc import Awaitable, Callable
from collections.abc import Awaitable, Callable, Coroutine
from dataclasses import dataclass
from datetime import timedelta
from datetime import datetime, timedelta
from functools import wraps
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.renault_vehicle import RenaultVehicle
from typing_extensions import Concatenate, ParamSpec
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity import DeviceInfo
from .const import DOMAIN
from .renault_coordinator import RenaultDataUpdateCoordinator
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
@ -69,11 +95,6 @@ class RenaultVehicleProxy:
"""Return a device description for device registry."""
return self._device_info
@property
def vehicle(self) -> RenaultVehicle:
"""Return the underlying vehicle."""
return self._vehicle
async def async_initialise(self) -> None:
"""Load available coordinators."""
self.coordinators = {
@ -119,6 +140,42 @@ class RenaultVehicleProxy:
)
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, ...] = (
RenaultCoordinatorDescription(

View File

@ -75,7 +75,7 @@ class RenaultSelectEntity(
async def async_select_option(self, option: str) -> None:
"""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:

View File

@ -74,7 +74,7 @@ def setup_services(hass: HomeAssistant) -> None:
proxy = get_vehicle_proxy(service_call.data)
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)
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)
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)
async def charge_set_schedules(service_call: ServiceCall) -> None:
"""Set charge schedules."""
schedules: list[dict[str, Any]] = service_call.data[ATTR_SCHEDULES]
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:
charge_schedules.update(schedule)
if TYPE_CHECKING:
assert charge_schedules.schedules is not None
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(
"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
import pytest
from renault_api.exceptions import RenaultException
from renault_api.kamereon import schemas
from renault_api.kamereon.models import ChargeSchedule
@ -27,6 +28,7 @@ from homeassistant.const import (
ATTR_SW_VERSION,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr
from .const import MOCK_VEHICLES
@ -89,15 +91,12 @@ async def test_service_set_ac_cancel(
with patch(
"renault_api.renault_vehicle.RenaultVehicle.set_ac_stop",
return_value=(
schemas.KamereonVehicleHvacStartActionDataSchema.loads(
load_fixture("renault/action.set_ac_stop.json")
)
),
side_effect=RenaultException("Didn't work"),
) as mock_action:
await hass.services.async_call(
DOMAIN, SERVICE_AC_CANCEL, service_data=data, blocking=True
)
with pytest.raises(HomeAssistantError, match="Didn't work"):
await hass.services.async_call(
DOMAIN, SERVICE_AC_CANCEL, service_data=data, blocking=True
)
assert len(mock_action.mock_calls) == 1
assert mock_action.mock_calls[0][1] == ()