Use EntityDescription - renault (#55061)

* Cleanup sensor.py

* Add EntityDescription

* Add checks for state attributes

* Fix pylint

* Simplify checks

* Add icon checks

* Update data type

* Use mixin for required keys, and review class initialisation

* Add constraint to TypeVar("T")

* Enable lambda for icon handling

* Enable lambda for value handling

* Enable lambda for value handling
This commit is contained in:
epenet 2021-08-25 23:15:49 +02:00 committed by GitHub
parent 35d943ba56
commit 9315f3bdd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 529 additions and 387 deletions

View File

@ -1,22 +1,44 @@
"""Support for Renault binary sensors."""
from __future__ import annotations
from dataclasses import dataclass
from renault_api.kamereon.enums import ChargeState, PlugState
from renault_api.kamereon.models import KamereonVehicleBatteryStatusData
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_BATTERY_CHARGING,
DEVICE_CLASS_PLUG,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DOMAIN
from .renault_entities import RenaultBatteryDataEntity, RenaultDataEntity
from .renault_entities import RenaultDataEntity, RenaultEntityDescription, T
from .renault_hub import RenaultHub
@dataclass
class RenaultBinarySensorRequiredKeysMixin:
"""Mixin for required keys."""
entity_class: type[RenaultBinarySensor]
on_value: StateType
@dataclass
class RenaultBinarySensorEntityDescription(
BinarySensorEntityDescription,
RenaultEntityDescription,
RenaultBinarySensorRequiredKeysMixin,
):
"""Class describing Renault binary sensor entities."""
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
@ -24,35 +46,43 @@ async def async_setup_entry(
) -> None:
"""Set up the Renault entities from config entry."""
proxy: RenaultHub = hass.data[DOMAIN][config_entry.entry_id]
entities: list[RenaultDataEntity] = []
for vehicle in proxy.vehicles.values():
if "battery" in vehicle.coordinators:
entities.append(RenaultPluggedInSensor(vehicle, "Plugged In"))
entities.append(RenaultChargingSensor(vehicle, "Charging"))
entities: list[RenaultBinarySensor] = [
description.entity_class(vehicle, description)
for vehicle in proxy.vehicles.values()
for description in BINARY_SENSOR_TYPES
if description.coordinator in vehicle.coordinators
]
async_add_entities(entities)
class RenaultPluggedInSensor(RenaultBatteryDataEntity, BinarySensorEntity):
"""Plugged In binary sensor."""
class RenaultBinarySensor(RenaultDataEntity[T], BinarySensorEntity):
"""Mixin for binary sensor specific attributes."""
_attr_device_class = DEVICE_CLASS_PLUG
entity_description: RenaultBinarySensorEntityDescription
@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
if (not self.data) or (self.data.plugStatus is None):
return None
return self.data.get_plug_status() == PlugState.PLUGGED
return self.data == self.entity_description.on_value
class RenaultChargingSensor(RenaultBatteryDataEntity, BinarySensorEntity):
"""Charging binary sensor."""
_attr_device_class = DEVICE_CLASS_BATTERY_CHARGING
@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
if (not self.data) or (self.data.chargingStatus is None):
return None
return self.data.get_charging_status() == ChargeState.CHARGE_IN_PROGRESS
BINARY_SENSOR_TYPES: tuple[RenaultBinarySensorEntityDescription, ...] = (
RenaultBinarySensorEntityDescription(
key="plugged_in",
coordinator="battery",
data_key="plugStatus",
device_class=DEVICE_CLASS_PLUG,
entity_class=RenaultBinarySensor[KamereonVehicleBatteryStatusData],
name="Plugged In",
on_value=PlugState.PLUGGED.value,
),
RenaultBinarySensorEntityDescription(
key="charging",
coordinator="battery",
data_key="chargingStatus",
device_class=DEVICE_CLASS_BATTERY_CHARGING,
entity_class=RenaultBinarySensor[KamereonVehicleBatteryStatusData],
name="Charging",
on_value=ChargeState.CHARGE_IN_PROGRESS.value,
),
)

View File

@ -1,103 +1,73 @@
"""Base classes for Renault entities."""
from __future__ import annotations
from typing import Any, Generic, Optional, TypeVar
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any, Optional, TypeVar, cast
from renault_api.kamereon.enums import ChargeState, PlugState
from renault_api.kamereon.models import (
KamereonVehicleBatteryStatusData,
KamereonVehicleChargeModeData,
KamereonVehicleCockpitData,
KamereonVehicleHvacStatusData,
)
from renault_api.kamereon.models import KamereonVehicleDataAttributes
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import slugify
from .renault_vehicle import RenaultVehicleProxy
@dataclass
class RenaultRequiredKeysMixin:
"""Mixin for required keys."""
coordinator: str
data_key: str
@dataclass
class RenaultEntityDescription(EntityDescription, RenaultRequiredKeysMixin):
"""Class describing Renault entities."""
requires_fuel: bool | None = None
ATTR_LAST_UPDATE = "last_update"
T = TypeVar("T")
T = TypeVar("T", bound=KamereonVehicleDataAttributes)
class RenaultDataEntity(Generic[T], CoordinatorEntity[Optional[T]], Entity):
class RenaultDataEntity(CoordinatorEntity[Optional[T]], Entity):
"""Implementation of a Renault entity with a data coordinator."""
entity_description: RenaultEntityDescription
def __init__(
self, vehicle: RenaultVehicleProxy, entity_type: str, coordinator_key: str
self,
vehicle: RenaultVehicleProxy,
description: RenaultEntityDescription,
) -> None:
"""Initialise entity."""
super().__init__(vehicle.coordinators[coordinator_key])
super().__init__(vehicle.coordinators[description.coordinator])
self.vehicle = vehicle
self._entity_type = entity_type
self.entity_description = description
self._attr_device_info = self.vehicle.device_info
self._attr_name = entity_type
self._attr_unique_id = slugify(
f"{self.vehicle.details.vin}-{self._entity_type}"
self._attr_unique_id = f"{self.vehicle.details.vin}_{description.key}".lower()
@property
def data(self) -> StateType:
"""Return the state of this entity."""
if self.coordinator.data is None:
return None
return cast(
StateType, getattr(self.coordinator.data, self.entity_description.data_key)
)
@property
def available(self) -> bool:
"""Return if entity is available."""
# Data can succeed, but be empty
return super().available and self.coordinator.data is not None
@property
def data(self) -> T | None:
"""Return collected data."""
return self.coordinator.data
class RenaultBatteryDataEntity(RenaultDataEntity[KamereonVehicleBatteryStatusData]):
"""Implementation of a Renault entity with battery coordinator."""
def __init__(self, vehicle: RenaultVehicleProxy, entity_type: str) -> None:
"""Initialise entity."""
super().__init__(vehicle, entity_type, "battery")
@property
def extra_state_attributes(self) -> dict[str, Any]:
def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""Return the state attributes of this entity."""
last_update = self.data.timestamp if self.data else None
return {ATTR_LAST_UPDATE: last_update}
@property
def is_charging(self) -> bool:
"""Return charge state as boolean."""
return (
self.data is not None
and self.data.get_charging_status() == ChargeState.CHARGE_IN_PROGRESS
)
@property
def is_plugged_in(self) -> bool:
"""Return plug state as boolean."""
return (
self.data is not None and self.data.get_plug_status() == PlugState.PLUGGED
)
class RenaultChargeModeDataEntity(RenaultDataEntity[KamereonVehicleChargeModeData]):
"""Implementation of a Renault entity with charge_mode coordinator."""
def __init__(self, vehicle: RenaultVehicleProxy, entity_type: str) -> None:
"""Initialise entity."""
super().__init__(vehicle, entity_type, "charge_mode")
class RenaultCockpitDataEntity(RenaultDataEntity[KamereonVehicleCockpitData]):
"""Implementation of a Renault entity with cockpit coordinator."""
def __init__(self, vehicle: RenaultVehicleProxy, entity_type: str) -> None:
"""Initialise entity."""
super().__init__(vehicle, entity_type, "cockpit")
class RenaultHVACDataEntity(RenaultDataEntity[KamereonVehicleHvacStatusData]):
"""Implementation of a Renault entity with hvac_status coordinator."""
def __init__(self, vehicle: RenaultVehicleProxy, entity_type: str) -> None:
"""Initialise entity."""
super().__init__(vehicle, entity_type, "hvac_status")
if self.entity_description.coordinator == "battery":
last_update = (
getattr(self.coordinator.data, "timestamp")
if self.coordinator.data
else None
)
return {ATTR_LAST_UPDATE: last_update}
return None

View File

@ -1,7 +1,23 @@
"""Support for Renault sensors."""
from __future__ import annotations
from homeassistant.components.sensor import SensorEntity
from dataclasses import dataclass
from typing import Callable, cast
from renault_api.kamereon.enums import ChargeState, PlugState
from renault_api.kamereon.models import (
KamereonVehicleBatteryStatusData,
KamereonVehicleChargeModeData,
KamereonVehicleCockpitData,
KamereonVehicleHvacStatusData,
)
from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
DEVICE_CLASS_BATTERY,
@ -18,6 +34,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import (
DEVICE_CLASS_CHARGE_MODE,
@ -25,17 +42,25 @@ from .const import (
DEVICE_CLASS_PLUG_STATE,
DOMAIN,
)
from .renault_entities import (
RenaultBatteryDataEntity,
RenaultChargeModeDataEntity,
RenaultCockpitDataEntity,
RenaultDataEntity,
RenaultHVACDataEntity,
)
from .renault_entities import RenaultDataEntity, RenaultEntityDescription, T
from .renault_hub import RenaultHub
from .renault_vehicle import RenaultVehicleProxy
ATTR_BATTERY_AVAILABLE_ENERGY = "battery_available_energy"
@dataclass
class RenaultSensorRequiredKeysMixin:
"""Mixin for required keys."""
entity_class: type[RenaultSensor]
@dataclass
class RenaultSensorEntityDescription(
SensorEntityDescription, RenaultEntityDescription, RenaultSensorRequiredKeysMixin
):
"""Class describing Renault sensor entities."""
icon_lambda: Callable[[RenaultDataEntity[T]], str] | None = None
value_lambda: Callable[[RenaultDataEntity[T]], StateType] | None = None
async def async_setup_entry(
@ -45,224 +70,208 @@ async def async_setup_entry(
) -> None:
"""Set up the Renault entities from config entry."""
proxy: RenaultHub = hass.data[DOMAIN][config_entry.entry_id]
entities = get_entities(proxy)
entities: list[RenaultSensor] = [
description.entity_class(vehicle, description)
for vehicle in proxy.vehicles.values()
for description in SENSOR_TYPES
if description.coordinator in vehicle.coordinators
and (not description.requires_fuel or vehicle.details.uses_fuel())
]
async_add_entities(entities)
def get_entities(proxy: RenaultHub) -> list[RenaultDataEntity]:
"""Create Renault entities for all vehicles."""
entities = []
for vehicle in proxy.vehicles.values():
entities.extend(get_vehicle_entities(vehicle))
return entities
class RenaultSensor(RenaultDataEntity[T], SensorEntity):
"""Mixin for sensor specific attributes."""
def get_vehicle_entities(vehicle: RenaultVehicleProxy) -> list[RenaultDataEntity]:
"""Create Renault entities for single vehicle."""
entities: list[RenaultDataEntity] = []
if "cockpit" in vehicle.coordinators:
entities.append(RenaultMileageSensor(vehicle, "Mileage"))
if vehicle.details.uses_fuel():
entities.append(RenaultFuelAutonomySensor(vehicle, "Fuel Autonomy"))
entities.append(RenaultFuelQuantitySensor(vehicle, "Fuel Quantity"))
if "hvac_status" in vehicle.coordinators:
entities.append(RenaultOutsideTemperatureSensor(vehicle, "Outside Temperature"))
if "battery" in vehicle.coordinators:
entities.append(RenaultBatteryLevelSensor(vehicle, "Battery Level"))
entities.append(RenaultChargeStateSensor(vehicle, "Charge State"))
entities.append(
RenaultChargingRemainingTimeSensor(vehicle, "Charging Remaining Time")
)
entities.append(RenaultChargingPowerSensor(vehicle, "Charging Power"))
entities.append(RenaultPlugStateSensor(vehicle, "Plug State"))
entities.append(RenaultBatteryAutonomySensor(vehicle, "Battery Autonomy"))
entities.append(
RenaultBatteryAvailableEnergySensor(vehicle, "Battery Available Energy")
)
entities.append(RenaultBatteryTemperatureSensor(vehicle, "Battery Temperature"))
if "charge_mode" in vehicle.coordinators:
entities.append(RenaultChargeModeSensor(vehicle, "Charge Mode"))
return entities
class RenaultBatteryAutonomySensor(RenaultBatteryDataEntity, SensorEntity):
"""Battery autonomy sensor."""
_attr_icon = "mdi:ev-station"
_attr_native_unit_of_measurement = LENGTH_KILOMETERS
entity_description: RenaultSensorEntityDescription
@property
def native_value(self) -> int | None:
"""Return the state of this entity."""
return self.data.batteryAutonomy if self.data else None
class RenaultBatteryAvailableEnergySensor(RenaultBatteryDataEntity, SensorEntity):
"""Battery available energy sensor."""
_attr_device_class = DEVICE_CLASS_ENERGY
_attr_native_unit_of_measurement = ENERGY_KILO_WATT_HOUR
@property
def native_value(self) -> float | None:
"""Return the state of this entity."""
return self.data.batteryAvailableEnergy if self.data else None
class RenaultBatteryLevelSensor(RenaultBatteryDataEntity, SensorEntity):
"""Battery Level sensor."""
_attr_device_class = DEVICE_CLASS_BATTERY
_attr_native_unit_of_measurement = PERCENTAGE
@property
def native_value(self) -> int | None:
"""Return the state of this entity."""
return self.data.batteryLevel if self.data else None
class RenaultBatteryTemperatureSensor(RenaultBatteryDataEntity, SensorEntity):
"""Battery Temperature sensor."""
_attr_device_class = DEVICE_CLASS_TEMPERATURE
_attr_native_unit_of_measurement = TEMP_CELSIUS
@property
def native_value(self) -> int | None:
"""Return the state of this entity."""
return self.data.batteryTemperature if self.data else None
class RenaultChargeModeSensor(RenaultChargeModeDataEntity, SensorEntity):
"""Charge Mode sensor."""
_attr_device_class = DEVICE_CLASS_CHARGE_MODE
@property
def native_value(self) -> str | None:
"""Return the state of this entity."""
return self.data.chargeMode if self.data else None
@property
def icon(self) -> str:
def icon(self) -> str | None:
"""Icon handling."""
if self.data and self.data.chargeMode == "schedule_mode":
return "mdi:calendar-clock"
return "mdi:calendar-remove"
class RenaultChargeStateSensor(RenaultBatteryDataEntity, SensorEntity):
"""Charge State sensor."""
_attr_device_class = DEVICE_CLASS_CHARGE_STATE
if self.entity_description.icon_lambda is None:
return super().icon
return self.entity_description.icon_lambda(self)
@property
def native_value(self) -> str | None:
def native_value(self) -> StateType:
"""Return the state of this entity."""
charging_status = self.data.get_charging_status() if self.data else None
return charging_status.name.lower() if charging_status is not None else None
@property
def icon(self) -> str:
"""Icon handling."""
return "mdi:flash" if self.is_charging else "mdi:flash-off"
class RenaultChargingRemainingTimeSensor(RenaultBatteryDataEntity, SensorEntity):
"""Charging Remaining Time sensor."""
_attr_icon = "mdi:timer"
_attr_native_unit_of_measurement = TIME_MINUTES
@property
def native_value(self) -> int | None:
"""Return the state of this entity."""
return self.data.chargingRemainingTime if self.data else None
class RenaultChargingPowerSensor(RenaultBatteryDataEntity, SensorEntity):
"""Charging Power sensor."""
_attr_device_class = DEVICE_CLASS_POWER
_attr_native_unit_of_measurement = POWER_KILO_WATT
@property
def native_value(self) -> float | None:
"""Return the state of this entity."""
if not self.data or self.data.chargingInstantaneousPower is None:
if self.data is None:
return None
if self.vehicle.details.reports_charging_power_in_watts():
# Need to convert to kilowatts
return self.data.chargingInstantaneousPower / 1000
return self.data.chargingInstantaneousPower
if self.entity_description.value_lambda is None:
return self.data
return self.entity_description.value_lambda(self)
class RenaultFuelAutonomySensor(RenaultCockpitDataEntity, SensorEntity):
"""Fuel autonomy sensor."""
_attr_icon = "mdi:gas-station"
_attr_native_unit_of_measurement = LENGTH_KILOMETERS
@property
def native_value(self) -> int | None:
"""Return the state of this entity."""
if not self.data or self.data.fuelAutonomy is None:
return None
return round(self.data.fuelAutonomy)
def _get_formatted_charging_status(
data: KamereonVehicleBatteryStatusData,
) -> str | None:
"""Return the charging_status of this entity."""
charging_status = data.get_charging_status() if data else None
return charging_status.name.lower() if charging_status else None
class RenaultFuelQuantitySensor(RenaultCockpitDataEntity, SensorEntity):
"""Fuel quantity sensor."""
_attr_icon = "mdi:fuel"
_attr_native_unit_of_measurement = VOLUME_LITERS
@property
def native_value(self) -> int | None:
"""Return the state of this entity."""
if not self.data or self.data.fuelQuantity is None:
return None
return round(self.data.fuelQuantity)
def _get_formatted_plug_status(data: KamereonVehicleBatteryStatusData) -> str | None:
"""Return the plug_status of this entity."""
plug_status = data.get_plug_status() if data else None
return plug_status.name.lower() if plug_status else None
class RenaultMileageSensor(RenaultCockpitDataEntity, SensorEntity):
"""Mileage sensor."""
_attr_icon = "mdi:sign-direction"
_attr_native_unit_of_measurement = LENGTH_KILOMETERS
@property
def native_value(self) -> int | None:
"""Return the state of this entity."""
if not self.data or self.data.totalMileage is None:
return None
return round(self.data.totalMileage)
class RenaultOutsideTemperatureSensor(RenaultHVACDataEntity, SensorEntity):
"""HVAC Outside Temperature sensor."""
_attr_device_class = DEVICE_CLASS_TEMPERATURE
_attr_native_unit_of_measurement = TEMP_CELSIUS
@property
def native_value(self) -> float | None:
"""Return the state of this entity."""
return self.data.externalTemperature if self.data else None
class RenaultPlugStateSensor(RenaultBatteryDataEntity, SensorEntity):
"""Plug State sensor."""
_attr_device_class = DEVICE_CLASS_PLUG_STATE
@property
def native_value(self) -> str | None:
"""Return the state of this entity."""
plug_status = self.data.get_plug_status() if self.data else None
return plug_status.name.lower() if plug_status is not None else None
@property
def icon(self) -> str:
"""Icon handling."""
return "mdi:power-plug" if self.is_plugged_in else "mdi:power-plug-off"
SENSOR_TYPES: tuple[RenaultSensorEntityDescription, ...] = (
RenaultSensorEntityDescription(
key="battery_level",
coordinator="battery",
data_key="batteryLevel",
device_class=DEVICE_CLASS_BATTERY,
entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
name="Battery Level",
native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
RenaultSensorEntityDescription(
key="charge_state",
coordinator="battery",
data_key="chargingStatus",
device_class=DEVICE_CLASS_CHARGE_STATE,
entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
icon_lambda=lambda x: (
"mdi:flash"
if x.data == ChargeState.CHARGE_IN_PROGRESS.value
else "mdi:flash-off"
),
name="Charge State",
value_lambda=lambda x: (
_get_formatted_charging_status(
cast(KamereonVehicleBatteryStatusData, x.coordinator.data)
)
),
),
RenaultSensorEntityDescription(
key="charging_remaining_time",
coordinator="battery",
data_key="chargingRemainingTime",
entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
icon="mdi:timer",
name="Charging Remaining Time",
native_unit_of_measurement=TIME_MINUTES,
state_class=STATE_CLASS_MEASUREMENT,
),
RenaultSensorEntityDescription(
key="charging_power",
coordinator="battery",
data_key="chargingInstantaneousPower",
device_class=DEVICE_CLASS_POWER,
entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
name="Charging Power",
native_unit_of_measurement=POWER_KILO_WATT,
state_class=STATE_CLASS_MEASUREMENT,
value_lambda=lambda x: (
cast(float, x.data) / 1000
if x.vehicle.details.reports_charging_power_in_watts()
else x.data
),
),
RenaultSensorEntityDescription(
key="plug_state",
coordinator="battery",
data_key="plugStatus",
device_class=DEVICE_CLASS_PLUG_STATE,
entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
icon_lambda=lambda x: (
"mdi:power-plug"
if x.data == PlugState.PLUGGED.value
else "mdi:power-plug-off"
),
name="Plug State",
value_lambda=lambda x: (
_get_formatted_plug_status(
cast(KamereonVehicleBatteryStatusData, x.coordinator.data)
)
),
),
RenaultSensorEntityDescription(
key="battery_autonomy",
coordinator="battery",
data_key="batteryAutonomy",
entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
icon="mdi:ev-station",
name="Battery Autonomy",
native_unit_of_measurement=LENGTH_KILOMETERS,
state_class=STATE_CLASS_MEASUREMENT,
),
RenaultSensorEntityDescription(
key="battery_available_energy",
coordinator="battery",
data_key="batteryAvailableEnergy",
entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
device_class=DEVICE_CLASS_ENERGY,
name="Battery Available Energy",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
state_class=STATE_CLASS_MEASUREMENT,
),
RenaultSensorEntityDescription(
key="battery_temperature",
coordinator="battery",
data_key="batteryTemperature",
device_class=DEVICE_CLASS_TEMPERATURE,
entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
name="Battery Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
state_class=STATE_CLASS_MEASUREMENT,
),
RenaultSensorEntityDescription(
key="mileage",
coordinator="cockpit",
data_key="totalMileage",
entity_class=RenaultSensor[KamereonVehicleCockpitData],
icon="mdi:sign-direction",
name="Mileage",
native_unit_of_measurement=LENGTH_KILOMETERS,
state_class=STATE_CLASS_TOTAL_INCREASING,
value_lambda=lambda x: round(cast(float, x.data)),
),
RenaultSensorEntityDescription(
key="fuel_autonomy",
coordinator="cockpit",
data_key="fuelAutonomy",
entity_class=RenaultSensor[KamereonVehicleCockpitData],
icon="mdi:gas-station",
name="Fuel Autonomy",
native_unit_of_measurement=LENGTH_KILOMETERS,
state_class=STATE_CLASS_MEASUREMENT,
requires_fuel=True,
value_lambda=lambda x: round(cast(float, x.data)),
),
RenaultSensorEntityDescription(
key="fuel_quantity",
coordinator="cockpit",
data_key="fuelQuantity",
entity_class=RenaultSensor[KamereonVehicleCockpitData],
icon="mdi:fuel",
name="Fuel Quantity",
native_unit_of_measurement=VOLUME_LITERS,
state_class=STATE_CLASS_MEASUREMENT,
requires_fuel=True,
value_lambda=lambda x: round(cast(float, x.data)),
),
RenaultSensorEntityDescription(
key="outside_temperature",
coordinator="hvac_status",
device_class=DEVICE_CLASS_TEMPERATURE,
data_key="externalTemperature",
entity_class=RenaultSensor[KamereonVehicleHvacStatusData],
name="Outside Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
state_class=STATE_CLASS_MEASUREMENT,
),
RenaultSensorEntityDescription(
key="charge_mode",
coordinator="charge_mode",
data_key="chargeMode",
device_class=DEVICE_CLASS_CHARGE_MODE,
entity_class=RenaultSensor[KamereonVehicleChargeModeData],
icon_lambda=lambda x: (
"mdi:calendar-clock" if x.data == "schedule_mode" else "mdi:calendar-remove"
),
name="Charge Mode",
),
)

View File

@ -12,8 +12,17 @@ from homeassistant.components.renault.const import (
DEVICE_CLASS_PLUG_STATE,
DOMAIN,
)
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.renault.renault_entities import ATTR_LAST_UPDATE
from homeassistant.components.sensor import (
ATTR_STATE_CLASS,
DOMAIN as SENSOR_DOMAIN,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
)
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ICON,
ATTR_UNIT_OF_MEASUREMENT,
CONF_PASSWORD,
CONF_USERNAME,
DEVICE_CLASS_BATTERY,
@ -32,6 +41,14 @@ from homeassistant.const import (
VOLUME_LITERS,
)
CHECK_ATTRIBUTES = (
ATTR_DEVICE_CLASS,
ATTR_ICON,
ATTR_LAST_UPDATE,
ATTR_STATE_CLASS,
ATTR_UNIT_OF_MEASUREMENT,
)
# Mock config data to be used across multiple tests
MOCK_CONFIG = {
CONF_USERNAME: "email@test.com",
@ -66,13 +83,15 @@ MOCK_VEHICLES = {
"entity_id": "binary_sensor.plugged_in",
"unique_id": "vf1aaaaa555777999_plugged_in",
"result": STATE_ON,
"class": DEVICE_CLASS_PLUG,
ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
},
{
"entity_id": "binary_sensor.charging",
"unique_id": "vf1aaaaa555777999_charging",
"result": STATE_ON,
"class": DEVICE_CLASS_BATTERY_CHARGING,
ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY_CHARGING,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
},
],
SENSOR_DOMAIN: [
@ -80,72 +99,94 @@ MOCK_VEHICLES = {
"entity_id": "sensor.battery_autonomy",
"unique_id": "vf1aaaaa555777999_battery_autonomy",
"result": "141",
"unit": LENGTH_KILOMETERS,
ATTR_ICON: "mdi:ev-station",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS,
},
{
"entity_id": "sensor.battery_available_energy",
"unique_id": "vf1aaaaa555777999_battery_available_energy",
"result": "31",
"unit": ENERGY_KILO_WATT_HOUR,
"class": DEVICE_CLASS_ENERGY,
ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR,
},
{
"entity_id": "sensor.battery_level",
"unique_id": "vf1aaaaa555777999_battery_level",
"result": "60",
"unit": PERCENTAGE,
"class": DEVICE_CLASS_BATTERY,
ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE,
},
{
"entity_id": "sensor.battery_temperature",
"unique_id": "vf1aaaaa555777999_battery_temperature",
"result": "20",
"unit": TEMP_CELSIUS,
"class": DEVICE_CLASS_TEMPERATURE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
},
{
"entity_id": "sensor.charge_mode",
"unique_id": "vf1aaaaa555777999_charge_mode",
"result": "always",
"class": DEVICE_CLASS_CHARGE_MODE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_CHARGE_MODE,
ATTR_ICON: "mdi:calendar-remove",
},
{
"entity_id": "sensor.charge_state",
"unique_id": "vf1aaaaa555777999_charge_state",
"result": "charge_in_progress",
"class": DEVICE_CLASS_CHARGE_STATE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_CHARGE_STATE,
ATTR_ICON: "mdi:flash",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
},
{
"entity_id": "sensor.charging_power",
"unique_id": "vf1aaaaa555777999_charging_power",
"result": "0.027",
"unit": POWER_KILO_WATT,
"class": DEVICE_CLASS_POWER,
ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: POWER_KILO_WATT,
},
{
"entity_id": "sensor.charging_remaining_time",
"unique_id": "vf1aaaaa555777999_charging_remaining_time",
"result": "145",
"unit": TIME_MINUTES,
ATTR_ICON: "mdi:timer",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: TIME_MINUTES,
},
{
"entity_id": "sensor.mileage",
"unique_id": "vf1aaaaa555777999_mileage",
"result": "49114",
"unit": LENGTH_KILOMETERS,
ATTR_ICON: "mdi:sign-direction",
ATTR_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING,
ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS,
},
{
"entity_id": "sensor.outside_temperature",
"unique_id": "vf1aaaaa555777999_outside_temperature",
"result": "8.0",
"unit": TEMP_CELSIUS,
"class": DEVICE_CLASS_TEMPERATURE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
},
{
"entity_id": "sensor.plug_state",
"unique_id": "vf1aaaaa555777999_plug_state",
"result": "plugged",
"class": DEVICE_CLASS_PLUG_STATE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG_STATE,
ATTR_ICON: "mdi:power-plug",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
},
],
},
@ -173,13 +214,15 @@ MOCK_VEHICLES = {
"entity_id": "binary_sensor.plugged_in",
"unique_id": "vf1aaaaa555777999_plugged_in",
"result": STATE_OFF,
"class": DEVICE_CLASS_PLUG,
ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG,
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
},
{
"entity_id": "binary_sensor.charging",
"unique_id": "vf1aaaaa555777999_charging",
"result": STATE_OFF,
"class": DEVICE_CLASS_BATTERY_CHARGING,
ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY_CHARGING,
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
},
],
SENSOR_DOMAIN: [
@ -187,65 +230,86 @@ MOCK_VEHICLES = {
"entity_id": "sensor.battery_autonomy",
"unique_id": "vf1aaaaa555777999_battery_autonomy",
"result": "128",
"unit": LENGTH_KILOMETERS,
ATTR_ICON: "mdi:ev-station",
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS,
},
{
"entity_id": "sensor.battery_available_energy",
"unique_id": "vf1aaaaa555777999_battery_available_energy",
"result": "0",
"unit": ENERGY_KILO_WATT_HOUR,
"class": DEVICE_CLASS_ENERGY,
ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY,
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR,
},
{
"entity_id": "sensor.battery_level",
"unique_id": "vf1aaaaa555777999_battery_level",
"result": "50",
"unit": PERCENTAGE,
"class": DEVICE_CLASS_BATTERY,
ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY,
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE,
},
{
"entity_id": "sensor.battery_temperature",
"unique_id": "vf1aaaaa555777999_battery_temperature",
"result": STATE_UNKNOWN,
"unit": TEMP_CELSIUS,
"class": DEVICE_CLASS_TEMPERATURE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
},
{
"entity_id": "sensor.charge_mode",
"unique_id": "vf1aaaaa555777999_charge_mode",
"result": "schedule_mode",
"class": DEVICE_CLASS_CHARGE_MODE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_CHARGE_MODE,
ATTR_ICON: "mdi:calendar-clock",
},
{
"entity_id": "sensor.charge_state",
"unique_id": "vf1aaaaa555777999_charge_state",
"result": "charge_error",
"class": DEVICE_CLASS_CHARGE_STATE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_CHARGE_STATE,
ATTR_ICON: "mdi:flash-off",
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
},
{
"entity_id": "sensor.charging_power",
"unique_id": "vf1aaaaa555777999_charging_power",
"result": STATE_UNKNOWN,
"unit": POWER_KILO_WATT,
"class": DEVICE_CLASS_POWER,
ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER,
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: POWER_KILO_WATT,
},
{
"entity_id": "sensor.charging_remaining_time",
"unique_id": "vf1aaaaa555777999_charging_remaining_time",
"result": STATE_UNKNOWN,
"unit": TIME_MINUTES,
ATTR_ICON: "mdi:timer",
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: TIME_MINUTES,
},
{
"entity_id": "sensor.mileage",
"unique_id": "vf1aaaaa555777999_mileage",
"result": "49114",
"unit": LENGTH_KILOMETERS,
ATTR_ICON: "mdi:sign-direction",
ATTR_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING,
ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS,
},
{
"entity_id": "sensor.plug_state",
"unique_id": "vf1aaaaa555777999_plug_state",
"result": "unplugged",
"class": DEVICE_CLASS_PLUG_STATE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG_STATE,
ATTR_ICON: "mdi:power-plug-off",
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
},
],
},
@ -273,13 +337,15 @@ MOCK_VEHICLES = {
"entity_id": "binary_sensor.plugged_in",
"unique_id": "vf1aaaaa555777123_plugged_in",
"result": STATE_ON,
"class": DEVICE_CLASS_PLUG,
ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
},
{
"entity_id": "binary_sensor.charging",
"unique_id": "vf1aaaaa555777123_charging",
"result": STATE_ON,
"class": DEVICE_CLASS_BATTERY_CHARGING,
ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY_CHARGING,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
},
],
SENSOR_DOMAIN: [
@ -287,77 +353,102 @@ MOCK_VEHICLES = {
"entity_id": "sensor.battery_autonomy",
"unique_id": "vf1aaaaa555777123_battery_autonomy",
"result": "141",
"unit": LENGTH_KILOMETERS,
ATTR_ICON: "mdi:ev-station",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS,
},
{
"entity_id": "sensor.battery_available_energy",
"unique_id": "vf1aaaaa555777123_battery_available_energy",
"result": "31",
"unit": ENERGY_KILO_WATT_HOUR,
"class": DEVICE_CLASS_ENERGY,
ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR,
},
{
"entity_id": "sensor.battery_level",
"unique_id": "vf1aaaaa555777123_battery_level",
"result": "60",
"unit": PERCENTAGE,
"class": DEVICE_CLASS_BATTERY,
ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE,
},
{
"entity_id": "sensor.battery_temperature",
"unique_id": "vf1aaaaa555777123_battery_temperature",
"result": "20",
"unit": TEMP_CELSIUS,
"class": DEVICE_CLASS_TEMPERATURE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
},
{
"entity_id": "sensor.charge_mode",
"unique_id": "vf1aaaaa555777123_charge_mode",
"result": "always",
"class": DEVICE_CLASS_CHARGE_MODE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_CHARGE_MODE,
ATTR_ICON: "mdi:calendar-remove",
},
{
"entity_id": "sensor.charge_state",
"unique_id": "vf1aaaaa555777123_charge_state",
"result": "charge_in_progress",
"class": DEVICE_CLASS_CHARGE_STATE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_CHARGE_STATE,
ATTR_ICON: "mdi:flash",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
},
{
"entity_id": "sensor.charging_power",
"unique_id": "vf1aaaaa555777123_charging_power",
"result": "27.0",
"unit": POWER_KILO_WATT,
"class": DEVICE_CLASS_POWER,
ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: POWER_KILO_WATT,
},
{
"entity_id": "sensor.charging_remaining_time",
"unique_id": "vf1aaaaa555777123_charging_remaining_time",
"result": "145",
"unit": TIME_MINUTES,
ATTR_ICON: "mdi:timer",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: TIME_MINUTES,
},
{
"entity_id": "sensor.fuel_autonomy",
"unique_id": "vf1aaaaa555777123_fuel_autonomy",
"result": "35",
"unit": LENGTH_KILOMETERS,
ATTR_ICON: "mdi:gas-station",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS,
},
{
"entity_id": "sensor.fuel_quantity",
"unique_id": "vf1aaaaa555777123_fuel_quantity",
"result": "3",
"unit": VOLUME_LITERS,
ATTR_ICON: "mdi:fuel",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: VOLUME_LITERS,
},
{
"entity_id": "sensor.mileage",
"unique_id": "vf1aaaaa555777123_mileage",
"result": "5567",
"unit": LENGTH_KILOMETERS,
ATTR_ICON: "mdi:sign-direction",
ATTR_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING,
ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS,
},
{
"entity_id": "sensor.plug_state",
"unique_id": "vf1aaaaa555777123_plug_state",
"result": "plugged",
"class": DEVICE_CLASS_PLUG_STATE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG_STATE,
ATTR_ICON: "mdi:power-plug",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
},
],
},
@ -382,19 +473,25 @@ MOCK_VEHICLES = {
"entity_id": "sensor.fuel_autonomy",
"unique_id": "vf1aaaaa555777123_fuel_autonomy",
"result": "35",
"unit": LENGTH_KILOMETERS,
ATTR_ICON: "mdi:gas-station",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS,
},
{
"entity_id": "sensor.fuel_quantity",
"unique_id": "vf1aaaaa555777123_fuel_quantity",
"result": "3",
"unit": VOLUME_LITERS,
ATTR_ICON: "mdi:fuel",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: VOLUME_LITERS,
},
{
"entity_id": "sensor.mileage",
"unique_id": "vf1aaaaa555777123_mileage",
"result": "5567",
"unit": LENGTH_KILOMETERS,
ATTR_ICON: "mdi:sign-direction",
ATTR_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING,
ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS,
},
],
},

View File

@ -5,6 +5,7 @@ import pytest
from renault_api.kamereon import exceptions
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.renault.renault_entities import ATTR_LAST_UPDATE
from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE
from homeassistant.setup import async_setup_component
@ -14,7 +15,7 @@ from . import (
setup_renault_integration_vehicle_with_no_data,
setup_renault_integration_vehicle_with_side_effect,
)
from .const import MOCK_VEHICLES
from .const import CHECK_ATTRIBUTES, MOCK_VEHICLES
from tests.common import mock_device_registry, mock_registry
@ -40,10 +41,10 @@ async def test_binary_sensors(hass, vehicle_type):
registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None
assert registry_entry.unique_id == expected_entity["unique_id"]
assert registry_entry.unit_of_measurement == expected_entity.get("unit")
assert registry_entry.device_class == expected_entity.get("class")
state = hass.states.get(entity_id)
assert state.state == expected_entity["result"]
for attr in CHECK_ATTRIBUTES:
assert state.attributes.get(attr) == expected_entity.get(attr)
@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys())
@ -67,10 +68,13 @@ async def test_binary_sensor_empty(hass, vehicle_type):
registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None
assert registry_entry.unique_id == expected_entity["unique_id"]
assert registry_entry.unit_of_measurement == expected_entity.get("unit")
assert registry_entry.device_class == expected_entity.get("class")
state = hass.states.get(entity_id)
assert state.state == STATE_OFF
for attr in CHECK_ATTRIBUTES:
if attr == ATTR_LAST_UPDATE:
assert state.attributes.get(attr) is None
else:
assert state.attributes.get(attr) == expected_entity.get(attr)
@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys())
@ -101,10 +105,13 @@ async def test_binary_sensor_errors(hass, vehicle_type):
registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None
assert registry_entry.unique_id == expected_entity["unique_id"]
assert registry_entry.unit_of_measurement == expected_entity.get("unit")
assert registry_entry.device_class == expected_entity.get("class")
state = hass.states.get(entity_id)
assert state.state == STATE_UNAVAILABLE
for attr in CHECK_ATTRIBUTES:
if attr == ATTR_LAST_UPDATE:
assert state.attributes.get(attr) is None
else:
assert state.attributes.get(attr) == expected_entity.get(attr)
async def test_binary_sensor_access_denied(hass):

View File

@ -4,8 +4,20 @@ from unittest.mock import patch
import pytest
from renault_api.kamereon import exceptions
from homeassistant.components.renault.const import (
DEVICE_CLASS_CHARGE_MODE,
DEVICE_CLASS_CHARGE_STATE,
DEVICE_CLASS_PLUG_STATE,
)
from homeassistant.components.renault.renault_entities import ATTR_LAST_UPDATE
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ICON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import State
from homeassistant.setup import async_setup_component
from . import (
@ -14,11 +26,28 @@ from . import (
setup_renault_integration_vehicle_with_no_data,
setup_renault_integration_vehicle_with_side_effect,
)
from .const import MOCK_VEHICLES
from .const import CHECK_ATTRIBUTES, MOCK_VEHICLES
from tests.common import mock_device_registry, mock_registry
def check_inactive_attribute(state: State, attr: str, expected_entity: dict):
"""Check attribute for icon for inactive sensors."""
if attr == ATTR_LAST_UPDATE:
assert state.attributes.get(attr) is None
elif attr == ATTR_ICON:
if expected_entity.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_CHARGE_MODE:
assert state.attributes.get(ATTR_ICON) == "mdi:calendar-remove"
elif expected_entity.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_CHARGE_STATE:
assert state.attributes.get(ATTR_ICON) == "mdi:flash-off"
elif expected_entity.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_PLUG_STATE:
assert state.attributes.get(ATTR_ICON) == "mdi:power-plug-off"
else:
assert state.attributes.get(ATTR_ICON) == expected_entity.get(ATTR_ICON)
else:
assert state.attributes.get(attr) == expected_entity.get(attr)
@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys())
async def test_sensors(hass, vehicle_type):
"""Test for Renault sensors."""
@ -40,10 +69,10 @@ async def test_sensors(hass, vehicle_type):
registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None
assert registry_entry.unique_id == expected_entity["unique_id"]
assert registry_entry.unit_of_measurement == expected_entity.get("unit")
assert registry_entry.device_class == expected_entity.get("class")
state = hass.states.get(entity_id)
assert state.state == expected_entity["result"]
for attr in CHECK_ATTRIBUTES:
assert state.attributes.get(attr) == expected_entity.get(attr)
@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys())
@ -67,10 +96,10 @@ async def test_sensor_empty(hass, vehicle_type):
registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None
assert registry_entry.unique_id == expected_entity["unique_id"]
assert registry_entry.unit_of_measurement == expected_entity.get("unit")
assert registry_entry.device_class == expected_entity.get("class")
state = hass.states.get(entity_id)
assert state.state == STATE_UNKNOWN
for attr in CHECK_ATTRIBUTES:
check_inactive_attribute(state, attr, expected_entity)
@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys())
@ -101,10 +130,10 @@ async def test_sensor_errors(hass, vehicle_type):
registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None
assert registry_entry.unique_id == expected_entity["unique_id"]
assert registry_entry.unit_of_measurement == expected_entity.get("unit")
assert registry_entry.device_class == expected_entity.get("class")
state = hass.states.get(entity_id)
assert state.state == STATE_UNAVAILABLE
for attr in CHECK_ATTRIBUTES:
check_inactive_attribute(state, attr, expected_entity)
async def test_sensor_access_denied(hass):