Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Renault sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from datetime import datetime
8 from typing import TYPE_CHECKING, Any, Generic, cast
9 
10 from renault_api.kamereon.models import (
11  KamereonVehicleBatteryStatusData,
12  KamereonVehicleCockpitData,
13  KamereonVehicleHvacStatusData,
14  KamereonVehicleLocationData,
15  KamereonVehicleResStateData,
16 )
17 
19  SensorDeviceClass,
20  SensorEntity,
21  SensorEntityDescription,
22  SensorStateClass,
23 )
24 from homeassistant.const import (
25  PERCENTAGE,
26  UnitOfEnergy,
27  UnitOfLength,
28  UnitOfPower,
29  UnitOfTemperature,
30  UnitOfTime,
31  UnitOfVolume,
32 )
33 from homeassistant.core import HomeAssistant
34 from homeassistant.helpers.entity_platform import AddEntitiesCallback
35 from homeassistant.helpers.typing import StateType
36 from homeassistant.util.dt import as_utc, parse_datetime
37 
38 from . import RenaultConfigEntry
39 from .coordinator import T
40 from .entity import RenaultDataEntity, RenaultDataEntityDescription
41 from .renault_vehicle import RenaultVehicleProxy
42 
43 
44 @dataclass(frozen=True, kw_only=True)
46  SensorEntityDescription, RenaultDataEntityDescription, Generic[T]
47 ):
48  """Class describing Renault sensor entities."""
49 
50  data_key: str
51  entity_class: type[RenaultSensor[T]]
52  condition_lambda: Callable[[RenaultVehicleProxy], bool] | None = None
53  requires_fuel: bool = False
54  value_lambda: Callable[[RenaultSensor[T]], StateType | datetime] | None = None
55 
56 
58  hass: HomeAssistant,
59  config_entry: RenaultConfigEntry,
60  async_add_entities: AddEntitiesCallback,
61 ) -> None:
62  """Set up the Renault entities from config entry."""
63  entities: list[RenaultSensor[Any]] = [
64  description.entity_class(vehicle, description)
65  for vehicle in config_entry.runtime_data.vehicles.values()
66  for description in SENSOR_TYPES
67  if description.coordinator in vehicle.coordinators
68  and (not description.requires_fuel or vehicle.details.uses_fuel())
69  and (not description.condition_lambda or description.condition_lambda(vehicle))
70  ]
71  async_add_entities(entities)
72 
73 
74 class RenaultSensor(RenaultDataEntity[T], SensorEntity):
75  """Mixin for sensor specific attributes."""
76 
77  entity_description: RenaultSensorEntityDescription[T]
78 
79  @property
80  def data(self) -> StateType:
81  """Return the state of this entity."""
82  return self._get_data_attr_get_data_attr(self.entity_descriptionentity_description.data_key)
83 
84  @property
85  def native_value(self) -> StateType | datetime:
86  """Return the state of this entity."""
87  if self.datadatadata is None:
88  return None
89  if self.entity_descriptionentity_description.value_lambda is None:
90  return self.datadatadata
91  return self.entity_descriptionentity_description.value_lambda(self)
92 
93 
94 def _get_charging_power(entity: RenaultSensor[T]) -> StateType:
95  """Return the charging_power of this entity."""
96  return cast(float, entity.data) / 1000
97 
98 
99 def _get_charge_state_formatted(entity: RenaultSensor[T]) -> str | None:
100  """Return the charging_status of this entity."""
101  data = cast(KamereonVehicleBatteryStatusData, entity.coordinator.data)
102  charging_status = data.get_charging_status() if data else None
103  return charging_status.name.lower() if charging_status else None
104 
105 
106 def _get_plug_state_formatted(entity: RenaultSensor[T]) -> str | None:
107  """Return the plug_status of this entity."""
108  data = cast(KamereonVehicleBatteryStatusData, entity.coordinator.data)
109  plug_status = data.get_plug_status() if data else None
110  return plug_status.name.lower() if plug_status else None
111 
112 
113 def _get_rounded_value(entity: RenaultSensor[T]) -> float:
114  """Return the rounded value of this entity."""
115  return round(cast(float, entity.data))
116 
117 
118 def _get_utc_value(entity: RenaultSensor[T]) -> datetime:
119  """Return the UTC value of this entity."""
120  original_dt = parse_datetime(cast(str, entity.data))
121  if TYPE_CHECKING:
122  assert original_dt is not None
123  return as_utc(original_dt)
124 
125 
126 SENSOR_TYPES: tuple[RenaultSensorEntityDescription[Any], ...] = (
128  key="battery_level",
129  coordinator="battery",
130  data_key="batteryLevel",
131  device_class=SensorDeviceClass.BATTERY,
132  entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
133  native_unit_of_measurement=PERCENTAGE,
134  state_class=SensorStateClass.MEASUREMENT,
135  ),
137  key="charge_state",
138  coordinator="battery",
139  data_key="chargingStatus",
140  translation_key="charge_state",
141  device_class=SensorDeviceClass.ENUM,
142  entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
143  options=[
144  "not_in_charge",
145  "waiting_for_a_planned_charge",
146  "charge_ended",
147  "waiting_for_current_charge",
148  "energy_flap_opened",
149  "charge_in_progress",
150  "charge_error",
151  "unavailable",
152  ],
153  value_lambda=_get_charge_state_formatted,
154  ),
156  key="charging_remaining_time",
157  coordinator="battery",
158  data_key="chargingRemainingTime",
159  device_class=SensorDeviceClass.DURATION,
160  entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
161  native_unit_of_measurement=UnitOfTime.MINUTES,
162  state_class=SensorStateClass.MEASUREMENT,
163  translation_key="charging_remaining_time",
164  ),
166  # For vehicles that DO NOT report charging power in watts, this seems to
167  # correspond to the maximum power that would be admissible by the car based
168  # on the battery state, regardless of the type of charger.
169  key="charging_power",
170  condition_lambda=lambda a: not a.details.reports_charging_power_in_watts(),
171  coordinator="battery",
172  data_key="chargingInstantaneousPower",
173  device_class=SensorDeviceClass.POWER,
174  entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
175  native_unit_of_measurement=UnitOfPower.KILO_WATT,
176  state_class=SensorStateClass.MEASUREMENT,
177  translation_key="admissible_charging_power",
178  ),
180  # For vehicles that DO report charging power in watts, this is the power
181  # effectively being transferred to the car.
182  key="charging_power",
183  condition_lambda=lambda a: a.details.reports_charging_power_in_watts(),
184  coordinator="battery",
185  data_key="chargingInstantaneousPower",
186  device_class=SensorDeviceClass.POWER,
187  entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
188  native_unit_of_measurement=UnitOfPower.KILO_WATT,
189  state_class=SensorStateClass.MEASUREMENT,
190  value_lambda=_get_charging_power,
191  translation_key="charging_power",
192  ),
194  key="plug_state",
195  coordinator="battery",
196  data_key="plugStatus",
197  translation_key="plug_state",
198  device_class=SensorDeviceClass.ENUM,
199  entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
200  options=[
201  "unplugged",
202  "plugged",
203  "plugged_waiting_for_charge",
204  "plug_error",
205  "plug_unknown",
206  ],
207  value_lambda=_get_plug_state_formatted,
208  ),
210  key="battery_autonomy",
211  coordinator="battery",
212  data_key="batteryAutonomy",
213  device_class=SensorDeviceClass.DISTANCE,
214  entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
215  native_unit_of_measurement=UnitOfLength.KILOMETERS,
216  state_class=SensorStateClass.MEASUREMENT,
217  translation_key="battery_autonomy",
218  ),
220  key="battery_available_energy",
221  coordinator="battery",
222  data_key="batteryAvailableEnergy",
223  entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
224  device_class=SensorDeviceClass.ENERGY,
225  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
226  state_class=SensorStateClass.TOTAL,
227  translation_key="battery_available_energy",
228  ),
230  key="battery_temperature",
231  coordinator="battery",
232  data_key="batteryTemperature",
233  device_class=SensorDeviceClass.TEMPERATURE,
234  entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
235  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
236  state_class=SensorStateClass.MEASUREMENT,
237  translation_key="battery_temperature",
238  ),
240  key="battery_last_activity",
241  coordinator="battery",
242  device_class=SensorDeviceClass.TIMESTAMP,
243  data_key="timestamp",
244  entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
245  entity_registry_enabled_default=False,
246  value_lambda=_get_utc_value,
247  translation_key="battery_last_activity",
248  ),
250  key="mileage",
251  coordinator="cockpit",
252  data_key="totalMileage",
253  device_class=SensorDeviceClass.DISTANCE,
254  entity_class=RenaultSensor[KamereonVehicleCockpitData],
255  native_unit_of_measurement=UnitOfLength.KILOMETERS,
256  state_class=SensorStateClass.TOTAL_INCREASING,
257  value_lambda=_get_rounded_value,
258  translation_key="mileage",
259  ),
261  key="fuel_autonomy",
262  coordinator="cockpit",
263  data_key="fuelAutonomy",
264  device_class=SensorDeviceClass.DISTANCE,
265  entity_class=RenaultSensor[KamereonVehicleCockpitData],
266  native_unit_of_measurement=UnitOfLength.KILOMETERS,
267  state_class=SensorStateClass.MEASUREMENT,
268  requires_fuel=True,
269  value_lambda=_get_rounded_value,
270  translation_key="fuel_autonomy",
271  ),
273  key="fuel_quantity",
274  coordinator="cockpit",
275  data_key="fuelQuantity",
276  device_class=SensorDeviceClass.VOLUME,
277  entity_class=RenaultSensor[KamereonVehicleCockpitData],
278  native_unit_of_measurement=UnitOfVolume.LITERS,
279  state_class=SensorStateClass.TOTAL,
280  requires_fuel=True,
281  value_lambda=_get_rounded_value,
282  translation_key="fuel_quantity",
283  ),
285  key="outside_temperature",
286  coordinator="hvac_status",
287  device_class=SensorDeviceClass.TEMPERATURE,
288  data_key="externalTemperature",
289  entity_class=RenaultSensor[KamereonVehicleHvacStatusData],
290  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
291  state_class=SensorStateClass.MEASUREMENT,
292  translation_key="outside_temperature",
293  ),
295  key="hvac_soc_threshold",
296  coordinator="hvac_status",
297  data_key="socThreshold",
298  entity_class=RenaultSensor[KamereonVehicleHvacStatusData],
299  native_unit_of_measurement=PERCENTAGE,
300  translation_key="hvac_soc_threshold",
301  ),
303  key="hvac_last_activity",
304  coordinator="hvac_status",
305  device_class=SensorDeviceClass.TIMESTAMP,
306  data_key="lastUpdateTime",
307  entity_class=RenaultSensor[KamereonVehicleHvacStatusData],
308  entity_registry_enabled_default=False,
309  translation_key="hvac_last_activity",
310  value_lambda=_get_utc_value,
311  ),
313  key="location_last_activity",
314  coordinator="location",
315  device_class=SensorDeviceClass.TIMESTAMP,
316  data_key="lastUpdateTime",
317  entity_class=RenaultSensor[KamereonVehicleLocationData],
318  entity_registry_enabled_default=False,
319  translation_key="location_last_activity",
320  value_lambda=_get_utc_value,
321  ),
323  key="res_state",
324  coordinator="res_state",
325  data_key="details",
326  entity_class=RenaultSensor[KamereonVehicleResStateData],
327  translation_key="res_state",
328  ),
330  key="res_state_code",
331  coordinator="res_state",
332  data_key="code",
333  entity_class=RenaultSensor[KamereonVehicleResStateData],
334  entity_registry_enabled_default=False,
335  translation_key="res_state_code",
336  ),
337 )
datetime|None parse_datetime(str|None value)
Definition: sensor.py:138
float _get_rounded_value(RenaultSensor[T] entity)
Definition: sensor.py:113
None async_setup_entry(HomeAssistant hass, RenaultConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:61
StateType _get_charging_power(RenaultSensor[T] entity)
Definition: sensor.py:94
datetime _get_utc_value(RenaultSensor[T] entity)
Definition: sensor.py:118
str|None _get_plug_state_formatted(RenaultSensor[T] entity)
Definition: sensor.py:106
str|None _get_charge_state_formatted(RenaultSensor[T] entity)
Definition: sensor.py:99
dt.datetime as_utc(dt.datetime dattim)
Definition: dt.py:132