Home Assistant Unofficial Reference 2024.12.1
renault_vehicle.py
Go to the documentation of this file.
1 """Proxy to handle account communication with Renault servers."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from collections.abc import Awaitable, Callable, Coroutine
7 from dataclasses import dataclass
8 from datetime import datetime, timedelta
9 from functools import wraps
10 import logging
11 from typing import Any, Concatenate, cast
12 
13 from renault_api.exceptions import RenaultException
14 from renault_api.kamereon import models
15 from renault_api.renault_vehicle import RenaultVehicle
16 
17 from homeassistant.core import HomeAssistant
18 from homeassistant.exceptions import HomeAssistantError
19 from homeassistant.helpers.device_registry import DeviceInfo
20 
21 from .const import DOMAIN
22 from .coordinator import RenaultDataUpdateCoordinator
23 
24 LOGGER = logging.getLogger(__name__)
25 
26 
27 def with_error_wrapping[**_P, _R](
28  func: Callable[Concatenate[RenaultVehicleProxy, _P], Awaitable[_R]],
29 ) -> Callable[Concatenate[RenaultVehicleProxy, _P], Coroutine[Any, Any, _R]]:
30  """Catch Renault errors."""
31 
32  @wraps(func)
33  async def wrapper(
34  self: RenaultVehicleProxy,
35  *args: _P.args,
36  **kwargs: _P.kwargs,
37  ) -> _R:
38  """Catch RenaultException errors and raise HomeAssistantError."""
39  try:
40  return await func(self, *args, **kwargs)
41  except RenaultException as err:
42  raise HomeAssistantError(err) from err
43 
44  return wrapper
45 
46 
47 @dataclass
49  """Class describing Renault coordinators."""
50 
51  endpoint: str
52  key: str
53  update_method: Callable[
54  [RenaultVehicle],
55  Callable[[], Awaitable[models.KamereonVehicleDataAttributes]],
56  ]
57  # Optional keys
58  requires_electricity: bool = False
59 
60 
62  """Handle vehicle communication with Renault servers."""
63 
64  def __init__(
65  self,
66  hass: HomeAssistant,
67  vehicle: RenaultVehicle,
68  details: models.KamereonVehicleDetails,
69  scan_interval: timedelta,
70  ) -> None:
71  """Initialise vehicle proxy."""
72  self.hasshass = hass
73  self._vehicle_vehicle = vehicle
74  self._details_details = details
75  self._device_info_device_info = DeviceInfo(
76  identifiers={(DOMAIN, cast(str, details.vin))},
77  manufacturer=(details.get_brand_label() or "").capitalize(),
78  model=(details.get_model_label() or "").capitalize(),
79  model_id=(details.get_model_code() or ""),
80  name=details.registrationNumber or "",
81  )
82  self.coordinatorscoordinators: dict[str, RenaultDataUpdateCoordinator] = {}
83  self.hvac_target_temperaturehvac_target_temperature = 21
84  self._scan_interval_scan_interval = scan_interval
85 
86  @property
87  def details(self) -> models.KamereonVehicleDetails:
88  """Return the specs of the vehicle."""
89  return self._details_details
90 
91  @property
92  def device_info(self) -> DeviceInfo:
93  """Return a device description for device registry."""
94  return self._device_info_device_info
95 
96  async def async_initialise(self) -> None:
97  """Load available coordinators."""
98  self.coordinatorscoordinators = {
100  self.hasshass,
101  LOGGER,
102  # Name of the data. For logging purposes.
103  name=f"{self.details.vin} {coord.key}",
104  update_method=coord.update_method(self._vehicle_vehicle),
105  # Polling interval. Will only be polled if there are subscribers.
106  update_interval=self._scan_interval_scan_interval,
107  )
108  for coord in COORDINATORS
109  if (
110  self.detailsdetails.supports_endpoint(coord.endpoint)
111  and (not coord.requires_electricity or self.detailsdetails.uses_electricity())
112  )
113  }
114  # Check all coordinators
115  await asyncio.gather(
116  *(
117  coordinator.async_config_entry_first_refresh()
118  for coordinator in self.coordinatorscoordinators.values()
119  )
120  )
121  for key in list(self.coordinatorscoordinators):
122  # list() to avoid Runtime iteration error
123  coordinator = self.coordinatorscoordinators[key]
124  if coordinator.not_supported:
125  # Remove endpoint as it is not supported for this vehicle.
126  LOGGER.warning(
127  "Ignoring endpoint %s as it is not supported: %s",
128  coordinator.name,
129  coordinator.last_exception,
130  )
131  del self.coordinatorscoordinators[key]
132  elif coordinator.access_denied:
133  # Remove endpoint as it is denied for this vehicle.
134  LOGGER.warning(
135  "Ignoring endpoint %s as it is denied: %s",
136  coordinator.name,
137  coordinator.last_exception,
138  )
139  del self.coordinatorscoordinators[key]
140 
141  @with_error_wrapping
142  async def set_charge_mode(
143  self, charge_mode: str
144  ) -> models.KamereonVehicleChargeModeActionData:
145  """Set vehicle charge mode."""
146  return await self._vehicle_vehicle.set_charge_mode(charge_mode)
147 
148  @with_error_wrapping
149  async def set_charge_start(self) -> models.KamereonVehicleChargingStartActionData:
150  """Start vehicle charge."""
151  return await self._vehicle_vehicle.set_charge_start()
152 
153  @with_error_wrapping
154  async def set_charge_stop(self) -> models.KamereonVehicleChargingStartActionData:
155  """Stop vehicle charge."""
156  return await self._vehicle_vehicle.set_charge_stop()
157 
158  @with_error_wrapping
159  async def set_ac_stop(self) -> models.KamereonVehicleHvacStartActionData:
160  """Stop vehicle ac."""
161  return await self._vehicle_vehicle.set_ac_stop()
162 
163  @with_error_wrapping
164  async def set_ac_start(
165  self, temperature: float, when: datetime | None = None
166  ) -> models.KamereonVehicleHvacStartActionData:
167  """Start vehicle ac."""
168  return await self._vehicle_vehicle.set_ac_start(temperature, when)
169 
170  @with_error_wrapping
171  async def get_hvac_settings(self) -> models.KamereonVehicleHvacSettingsData:
172  """Get vehicle hvac settings."""
173  return await self._vehicle_vehicle.get_hvac_settings()
174 
175  @with_error_wrapping
177  self, schedules: list[models.HvacSchedule]
178  ) -> models.KamereonVehicleHvacScheduleActionData:
179  """Set vehicle hvac schedules."""
180  return await self._vehicle_vehicle.set_hvac_schedules(schedules)
181 
182  @with_error_wrapping
183  async def get_charging_settings(self) -> models.KamereonVehicleChargingSettingsData:
184  """Get vehicle charging settings."""
185  return await self._vehicle_vehicle.get_charging_settings()
186 
187  @with_error_wrapping
189  self, schedules: list[models.ChargeSchedule]
190  ) -> models.KamereonVehicleChargeScheduleActionData:
191  """Set vehicle charge schedules."""
192  return await self._vehicle_vehicle.set_charge_schedules(schedules)
193 
194 
195 COORDINATORS: tuple[RenaultCoordinatorDescription, ...] = (
197  endpoint="cockpit",
198  key="cockpit",
199  update_method=lambda x: x.get_cockpit,
200  ),
202  endpoint="hvac-status",
203  key="hvac_status",
204  update_method=lambda x: x.get_hvac_status,
205  ),
207  endpoint="location",
208  key="location",
209  update_method=lambda x: x.get_location,
210  ),
212  endpoint="battery-status",
213  key="battery",
214  requires_electricity=True,
215  update_method=lambda x: x.get_battery_status,
216  ),
218  endpoint="charge-mode",
219  key="charge_mode",
220  requires_electricity=True,
221  update_method=lambda x: x.get_charge_mode,
222  ),
224  endpoint="lock-status",
225  key="lock_status",
226  update_method=lambda x: x.get_lock_status,
227  ),
229  endpoint="res-state",
230  key="res_state",
231  update_method=lambda x: x.get_res_state,
232  ),
233 )
models.KamereonVehicleHvacSettingsData get_hvac_settings(self)
models.KamereonVehicleHvacScheduleActionData set_hvac_schedules(self, list[models.HvacSchedule] schedules)
models.KamereonVehicleChargingStartActionData set_charge_stop(self)
models.KamereonVehicleChargingStartActionData set_charge_start(self)
models.KamereonVehicleChargingSettingsData get_charging_settings(self)
models.KamereonVehicleChargeModeActionData set_charge_mode(self, str charge_mode)
models.KamereonVehicleChargeScheduleActionData set_charge_schedules(self, list[models.ChargeSchedule] schedules)
models.KamereonVehicleHvacStartActionData set_ac_stop(self)
None __init__(self, HomeAssistant hass, RenaultVehicle vehicle, models.KamereonVehicleDetails details, timedelta scan_interval)
models.KamereonVehicleHvacStartActionData set_ac_start(self, float temperature, datetime|None when=None)