Add additional sensors to Teslemetry (#112555)
* Add more sensors * Fix coverage * Dont do this rename yet * Fix case * Update snapshot * Add icons * Remove unused icons * Update snapshot * Remove last_value logic from TimeSensor * Apply suggestions from code review Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Update constant case * Remove useless test * Add refresh test back * Add assertion to post coordinator refresh --------- Co-authored-by: G Johansson <goran.johansson@shiftit.se>
This commit is contained in:
parent
470ef554d7
commit
219cb7a788
9 changed files with 1788 additions and 658 deletions
|
@ -48,6 +48,11 @@ class TeslemetryVehicleEntity(CoordinatorEntity[TeslemetryVehicleDataCoordinator
|
||||||
serial_number=vehicle.vin,
|
serial_number=vehicle.vin,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _value(self) -> Any | None:
|
||||||
|
"""Return a specific value from coordinator data."""
|
||||||
|
return self.coordinator.data.get(self.key)
|
||||||
|
|
||||||
async def wake_up_if_asleep(self) -> None:
|
async def wake_up_if_asleep(self) -> None:
|
||||||
"""Wake up the vehicle if its asleep."""
|
"""Wake up the vehicle if its asleep."""
|
||||||
async with self._wakelock:
|
async with self._wakelock:
|
||||||
|
|
|
@ -18,6 +18,15 @@
|
||||||
"battery_power": {
|
"battery_power": {
|
||||||
"default": "mdi:home-battery"
|
"default": "mdi:home-battery"
|
||||||
},
|
},
|
||||||
|
"charge_state_charging_state": {
|
||||||
|
"default": "mdi:ev-station",
|
||||||
|
"state": {
|
||||||
|
"disconnected": "mdi:connection",
|
||||||
|
"no_power": "mdi:power-plug-off-outline",
|
||||||
|
"starting": "mdi:play-circle",
|
||||||
|
"stopped": "mdi:stop-circle"
|
||||||
|
}
|
||||||
|
},
|
||||||
"drive_state_active_route_destination": {
|
"drive_state_active_route_destination": {
|
||||||
"default": "mdi:routes"
|
"default": "mdi:routes"
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,6 +6,7 @@ from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
|
@ -27,10 +28,11 @@ from homeassistant.const import (
|
||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
UnitOfTime,
|
UnitOfTime,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
from homeassistant.util.variance import ignore_variance
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .entity import (
|
from .entity import (
|
||||||
|
@ -40,13 +42,16 @@ from .entity import (
|
||||||
)
|
)
|
||||||
from .models import TeslemetryEnergyData, TeslemetryVehicleData
|
from .models import TeslemetryEnergyData, TeslemetryVehicleData
|
||||||
|
|
||||||
|
CHARGE_STATES = {
|
||||||
|
"Starting": "starting",
|
||||||
|
"Charging": "charging",
|
||||||
|
"Stopped": "stopped",
|
||||||
|
"Complete": "complete",
|
||||||
|
"Disconnected": "disconnected",
|
||||||
|
"NoPower": "no_power",
|
||||||
|
}
|
||||||
|
|
||||||
@callback
|
SHIFT_STATES = {"P": "p", "D": "d", "R": "r", "N": "n"}
|
||||||
def minutes_to_datetime(value: StateType) -> datetime | None:
|
|
||||||
"""Convert relative minutes into absolute datetime."""
|
|
||||||
if isinstance(value, (int, float)) and value > 0:
|
|
||||||
return dt_util.now() + timedelta(minutes=value)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
@ -57,11 +62,24 @@ class TeslemetrySensorEntityDescription(SensorEntityDescription):
|
||||||
|
|
||||||
|
|
||||||
VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
|
VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
|
||||||
|
TeslemetrySensorEntityDescription(
|
||||||
|
key="charge_state_charging_state",
|
||||||
|
options=list(CHARGE_STATES.values()),
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
value_fn=lambda value: CHARGE_STATES.get(cast(str, value)),
|
||||||
|
),
|
||||||
|
TeslemetrySensorEntityDescription(
|
||||||
|
key="charge_state_battery_level",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
device_class=SensorDeviceClass.BATTERY,
|
||||||
|
),
|
||||||
TeslemetrySensorEntityDescription(
|
TeslemetrySensorEntityDescription(
|
||||||
key="charge_state_usable_battery_level",
|
key="charge_state_usable_battery_level",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
device_class=SensorDeviceClass.BATTERY,
|
device_class=SensorDeviceClass.BATTERY,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
TeslemetrySensorEntityDescription(
|
TeslemetrySensorEntityDescription(
|
||||||
key="charge_state_charge_energy_added",
|
key="charge_state_charge_energy_added",
|
||||||
|
@ -96,13 +114,16 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
|
||||||
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
||||||
device_class=SensorDeviceClass.SPEED,
|
device_class=SensorDeviceClass.SPEED,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
),
|
||||||
|
TeslemetrySensorEntityDescription(
|
||||||
|
key="charge_state_conn_charge_cable",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
TeslemetrySensorEntityDescription(
|
TeslemetrySensorEntityDescription(
|
||||||
key="charge_state_minutes_to_full_charge",
|
key="charge_state_fast_charger_type",
|
||||||
device_class=SensorDeviceClass.TIMESTAMP,
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
value_fn=minutes_to_datetime,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
TeslemetrySensorEntityDescription(
|
TeslemetrySensorEntityDescription(
|
||||||
key="charge_state_battery_range",
|
key="charge_state_battery_range",
|
||||||
|
@ -111,12 +132,29 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
|
||||||
device_class=SensorDeviceClass.DISTANCE,
|
device_class=SensorDeviceClass.DISTANCE,
|
||||||
suggested_display_precision=1,
|
suggested_display_precision=1,
|
||||||
),
|
),
|
||||||
|
TeslemetrySensorEntityDescription(
|
||||||
|
key="charge_state_est_battery_range",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfLength.MILES,
|
||||||
|
device_class=SensorDeviceClass.DISTANCE,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
TeslemetrySensorEntityDescription(
|
||||||
|
key="charge_state_ideal_battery_range",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfLength.MILES,
|
||||||
|
device_class=SensorDeviceClass.DISTANCE,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
TeslemetrySensorEntityDescription(
|
TeslemetrySensorEntityDescription(
|
||||||
key="drive_state_speed",
|
key="drive_state_speed",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
||||||
device_class=SensorDeviceClass.SPEED,
|
device_class=SensorDeviceClass.SPEED,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
|
value_fn=lambda value: value or 0,
|
||||||
),
|
),
|
||||||
TeslemetrySensorEntityDescription(
|
TeslemetrySensorEntityDescription(
|
||||||
key="drive_state_power",
|
key="drive_state_power",
|
||||||
|
@ -125,12 +163,13 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
|
value_fn=lambda value: value or 0,
|
||||||
),
|
),
|
||||||
TeslemetrySensorEntityDescription(
|
TeslemetrySensorEntityDescription(
|
||||||
key="drive_state_shift_state",
|
key="drive_state_shift_state",
|
||||||
options=["p", "d", "r", "n"],
|
options=list(SHIFT_STATES.values()),
|
||||||
device_class=SensorDeviceClass.ENUM,
|
device_class=SensorDeviceClass.ENUM,
|
||||||
value_fn=lambda x: x.lower() if isinstance(x, str) else x,
|
value_fn=lambda x: SHIFT_STATES.get(str(x), "p"),
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
TeslemetrySensorEntityDescription(
|
TeslemetrySensorEntityDescription(
|
||||||
|
@ -235,14 +274,27 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
|
||||||
native_unit_of_measurement=UnitOfLength.MILES,
|
native_unit_of_measurement=UnitOfLength.MILES,
|
||||||
device_class=SensorDeviceClass.DISTANCE,
|
device_class=SensorDeviceClass.DISTANCE,
|
||||||
),
|
),
|
||||||
TeslemetrySensorEntityDescription(
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class TeslemetryTimeEntityDescription(SensorEntityDescription):
|
||||||
|
"""Describes Teslemetry Sensor entity."""
|
||||||
|
|
||||||
|
variance: int
|
||||||
|
|
||||||
|
|
||||||
|
VEHICLE_TIME_DESCRIPTIONS: tuple[TeslemetryTimeEntityDescription, ...] = (
|
||||||
|
TeslemetryTimeEntityDescription(
|
||||||
|
key="charge_state_minutes_to_full_charge",
|
||||||
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
variance=4,
|
||||||
|
),
|
||||||
|
TeslemetryTimeEntityDescription(
|
||||||
key="drive_state_active_route_minutes_to_arrival",
|
key="drive_state_active_route_minutes_to_arrival",
|
||||||
device_class=SensorDeviceClass.TIMESTAMP,
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
value_fn=minutes_to_datetime,
|
variance=1,
|
||||||
),
|
|
||||||
TeslemetrySensorEntityDescription(
|
|
||||||
key="drive_state_active_route_destination",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -322,6 +374,7 @@ ENERGY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
|
SensorEntityDescription(key="island_status", device_class=SensorDeviceClass.ENUM),
|
||||||
)
|
)
|
||||||
|
|
||||||
WALL_CONNECTOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
|
WALL_CONNECTOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
|
||||||
|
@ -362,7 +415,12 @@ async def async_setup_entry(
|
||||||
for vehicle in data.vehicles
|
for vehicle in data.vehicles
|
||||||
for description in VEHICLE_DESCRIPTIONS
|
for description in VEHICLE_DESCRIPTIONS
|
||||||
),
|
),
|
||||||
( # Add energy sites
|
( # Add vehicles time sensors
|
||||||
|
TeslemetryVehicleTimeSensorEntity(vehicle, description)
|
||||||
|
for vehicle in data.vehicles
|
||||||
|
for description in VEHICLE_TIME_DESCRIPTIONS
|
||||||
|
),
|
||||||
|
( # Add energy site live
|
||||||
TeslemetryEnergySensorEntity(energysite, description)
|
TeslemetryEnergySensorEntity(energysite, description)
|
||||||
for energysite in data.energysites
|
for energysite in data.energysites
|
||||||
for description in ENERGY_DESCRIPTIONS
|
for description in ENERGY_DESCRIPTIONS
|
||||||
|
@ -390,17 +448,36 @@ class TeslemetryVehicleSensorEntity(TeslemetryVehicleEntity, SensorEntity):
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
super().__init__(vehicle, description.key)
|
super().__init__(vehicle, description.key)
|
||||||
|
|
||||||
|
|
||||||
|
class TeslemetryVehicleTimeSensorEntity(TeslemetryVehicleEntity, SensorEntity):
|
||||||
|
"""Base class for Teslemetry vehicle metric sensors."""
|
||||||
|
|
||||||
|
entity_description: TeslemetryTimeEntityDescription
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
data: TeslemetryVehicleData,
|
||||||
|
description: TeslemetryTimeEntityDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the sensor."""
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
|
self._get_timestamp = ignore_variance(
|
||||||
|
func=lambda value: dt_util.now() + timedelta(minutes=value),
|
||||||
|
ignored_variance=timedelta(minutes=description.variance),
|
||||||
|
)
|
||||||
|
|
||||||
|
super().__init__(data, description.key)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> StateType | datetime:
|
def native_value(self) -> datetime | None:
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return self.entity_description.value_fn(self.get())
|
return self._get_timestamp(self._value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return if sensor is available."""
|
"""Return the avaliability of the sensor."""
|
||||||
return super().available and self.get() is not None
|
return isinstance(self._value, int | float) and self._value > 0
|
||||||
|
|
||||||
|
|
||||||
class TeslemetryEnergySensorEntity(TeslemetryEnergyEntity, SensorEntity):
|
class TeslemetryEnergySensorEntity(TeslemetryEnergyEntity, SensorEntity):
|
||||||
|
|
|
@ -32,32 +32,85 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"charge_state_usable_battery_level": {
|
"battery_power": {
|
||||||
"name": "Battery level"
|
"name": "Battery power"
|
||||||
|
},
|
||||||
|
"charge_state_battery_range": {
|
||||||
|
"name": "Battery range"
|
||||||
|
},
|
||||||
|
"charge_state_est_battery_range": {
|
||||||
|
"name": "Estimate battery range"
|
||||||
|
},
|
||||||
|
"charge_state_ideal_battery_range": {
|
||||||
|
"name": "Ideal battery range"
|
||||||
},
|
},
|
||||||
"charge_state_charge_energy_added": {
|
"charge_state_charge_energy_added": {
|
||||||
"name": "Charge energy added"
|
"name": "Charge energy added"
|
||||||
},
|
},
|
||||||
|
"charge_state_charge_rate": {
|
||||||
|
"name": "Charge rate"
|
||||||
|
},
|
||||||
|
"charge_state_charger_actual_current": {
|
||||||
|
"name": "Charger current"
|
||||||
|
},
|
||||||
"charge_state_charger_power": {
|
"charge_state_charger_power": {
|
||||||
"name": "Charger power"
|
"name": "Charger power"
|
||||||
},
|
},
|
||||||
"charge_state_charger_voltage": {
|
"charge_state_charger_voltage": {
|
||||||
"name": "Charger voltage"
|
"name": "Charger voltage"
|
||||||
},
|
},
|
||||||
"charge_state_charger_actual_current": {
|
"charge_state_conn_charge_cable": {
|
||||||
"name": "Charger current"
|
"name": "Charge cable"
|
||||||
},
|
},
|
||||||
"charge_state_charge_rate": {
|
"charge_state_fast_charger_type": {
|
||||||
"name": "Charge rate"
|
"name": "Fast charger type"
|
||||||
},
|
},
|
||||||
"charge_state_battery_range": {
|
"charge_state_charging_state": {
|
||||||
"name": "Battery range"
|
"name": "Charging",
|
||||||
|
"state": {
|
||||||
|
"starting": "Starting",
|
||||||
|
"charging": "Charging",
|
||||||
|
"disconnected": "Disconnected",
|
||||||
|
"stopped": "Stopped",
|
||||||
|
"complete": "Complete",
|
||||||
|
"no_power": "No power"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"charge_state_minutes_to_full_charge": {
|
"charge_state_minutes_to_full_charge": {
|
||||||
"name": "Time to full charge"
|
"name": "Time to full charge"
|
||||||
},
|
},
|
||||||
"drive_state_speed": {
|
"charge_state_battery_level": {
|
||||||
"name": "Speed"
|
"name": "Battery level"
|
||||||
|
},
|
||||||
|
"charge_state_usable_battery_level": {
|
||||||
|
"name": "Usable battery level"
|
||||||
|
},
|
||||||
|
"climate_state_driver_temp_setting": {
|
||||||
|
"name": "Driver temperature setting"
|
||||||
|
},
|
||||||
|
"climate_state_inside_temp": {
|
||||||
|
"name": "Inside temperature"
|
||||||
|
},
|
||||||
|
"climate_state_outside_temp": {
|
||||||
|
"name": "Outside temperature"
|
||||||
|
},
|
||||||
|
"climate_state_passenger_temp_setting": {
|
||||||
|
"name": "Passenger temperature setting"
|
||||||
|
},
|
||||||
|
"drive_state_active_route_destination": {
|
||||||
|
"name": "Destination"
|
||||||
|
},
|
||||||
|
"drive_state_active_route_energy_at_arrival": {
|
||||||
|
"name": "State of charge at arrival"
|
||||||
|
},
|
||||||
|
"drive_state_active_route_miles_to_arrival": {
|
||||||
|
"name": "Distance to arrival"
|
||||||
|
},
|
||||||
|
"drive_state_active_route_minutes_to_arrival": {
|
||||||
|
"name": "Time to arrival"
|
||||||
|
},
|
||||||
|
"drive_state_active_route_traffic_minutes_delay": {
|
||||||
|
"name": "Traffic delay"
|
||||||
},
|
},
|
||||||
"drive_state_power": {
|
"drive_state_power": {
|
||||||
"name": "Power"
|
"name": "Power"
|
||||||
|
@ -65,12 +118,39 @@
|
||||||
"drive_state_shift_state": {
|
"drive_state_shift_state": {
|
||||||
"name": "Shift state",
|
"name": "Shift state",
|
||||||
"state": {
|
"state": {
|
||||||
"p": "Park",
|
|
||||||
"d": "Drive",
|
"d": "Drive",
|
||||||
"r": "Reverse",
|
"n": "Neutral",
|
||||||
"n": "Neutral"
|
"p": "Park",
|
||||||
|
"r": "Reverse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"drive_state_speed": {
|
||||||
|
"name": "Speed"
|
||||||
|
},
|
||||||
|
"energy_left": {
|
||||||
|
"name": "Energy left"
|
||||||
|
},
|
||||||
|
"generator_power": {
|
||||||
|
"name": "Generator power"
|
||||||
|
},
|
||||||
|
"grid_power": {
|
||||||
|
"name": "Grid power"
|
||||||
|
},
|
||||||
|
"grid_services_power": {
|
||||||
|
"name": "Grid services power"
|
||||||
|
},
|
||||||
|
"load_power": {
|
||||||
|
"name": "Load power"
|
||||||
|
},
|
||||||
|
"percentage_charged": {
|
||||||
|
"name": "Percentage charged"
|
||||||
|
},
|
||||||
|
"solar_power": {
|
||||||
|
"name": "Solar power"
|
||||||
|
},
|
||||||
|
"total_pack_energy": {
|
||||||
|
"name": "Total pack energy"
|
||||||
|
},
|
||||||
"vehicle_state_odometer": {
|
"vehicle_state_odometer": {
|
||||||
"name": "Odometer"
|
"name": "Odometer"
|
||||||
},
|
},
|
||||||
|
@ -86,62 +166,8 @@
|
||||||
"vehicle_state_tpms_pressure_rr": {
|
"vehicle_state_tpms_pressure_rr": {
|
||||||
"name": "Tire pressure rear right"
|
"name": "Tire pressure rear right"
|
||||||
},
|
},
|
||||||
"climate_state_inside_temp": {
|
"vin": {
|
||||||
"name": "Inside temperature"
|
"name": "Vehicle"
|
||||||
},
|
|
||||||
"climate_state_outside_temp": {
|
|
||||||
"name": "Outside temperature"
|
|
||||||
},
|
|
||||||
"climate_state_driver_temp_setting": {
|
|
||||||
"name": "Driver temperature setting"
|
|
||||||
},
|
|
||||||
"climate_state_passenger_temp_setting": {
|
|
||||||
"name": "Passenger temperature setting"
|
|
||||||
},
|
|
||||||
"drive_state_active_route_traffic_minutes_delay": {
|
|
||||||
"name": "Traffic delay"
|
|
||||||
},
|
|
||||||
"drive_state_active_route_energy_at_arrival": {
|
|
||||||
"name": "State of charge at arrival"
|
|
||||||
},
|
|
||||||
"drive_state_active_route_miles_to_arrival": {
|
|
||||||
"name": "Distance to arrival"
|
|
||||||
},
|
|
||||||
"drive_state_active_route_minutes_to_arrival": {
|
|
||||||
"name": "Time to arrival"
|
|
||||||
},
|
|
||||||
"drive_state_active_route_destination": {
|
|
||||||
"name": "Destination"
|
|
||||||
},
|
|
||||||
"solar_power": {
|
|
||||||
"name": "Solar power"
|
|
||||||
},
|
|
||||||
"energy_left": {
|
|
||||||
"name": "Energy left"
|
|
||||||
},
|
|
||||||
"total_pack_energy": {
|
|
||||||
"name": "Total pack energy"
|
|
||||||
},
|
|
||||||
"percentage_charged": {
|
|
||||||
"name": "Percentage charged"
|
|
||||||
},
|
|
||||||
"battery_power": {
|
|
||||||
"name": "Battery power"
|
|
||||||
},
|
|
||||||
"load_power": {
|
|
||||||
"name": "Load power"
|
|
||||||
},
|
|
||||||
"grid_power": {
|
|
||||||
"name": "Grid power"
|
|
||||||
},
|
|
||||||
"grid_services_power": {
|
|
||||||
"name": "Grid services power"
|
|
||||||
},
|
|
||||||
"generator_power": {
|
|
||||||
"name": "Generator power"
|
|
||||||
},
|
|
||||||
"wall_connector_state": {
|
|
||||||
"name": "State code"
|
|
||||||
},
|
},
|
||||||
"wall_connector_fault_state": {
|
"wall_connector_fault_state": {
|
||||||
"name": "Fault state code"
|
"name": "Fault state code"
|
||||||
|
@ -149,8 +175,8 @@
|
||||||
"wall_connector_power": {
|
"wall_connector_power": {
|
||||||
"name": "Power"
|
"name": "Power"
|
||||||
},
|
},
|
||||||
"vin": {
|
"wall_connector_state": {
|
||||||
"name": "Vehicle"
|
"name": "State code"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,3 +48,18 @@ def assert_entities(
|
||||||
assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry")
|
assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry")
|
||||||
assert (state := hass.states.get(entity_entry.entity_id))
|
assert (state := hass.states.get(entity_entry.entity_id))
|
||||||
assert state == snapshot(name=f"{entity_entry.entity_id}-state")
|
assert state == snapshot(name=f"{entity_entry.entity_id}-state")
|
||||||
|
|
||||||
|
|
||||||
|
def assert_entities_alt(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry_id: str,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
) -> None:
|
||||||
|
"""Test that all entities match their alt snapshot."""
|
||||||
|
entity_entries = er.async_entries_for_config_entry(entity_registry, entry_id)
|
||||||
|
|
||||||
|
assert entity_entries
|
||||||
|
for entity_entry in entity_entries:
|
||||||
|
assert (state := hass.states.get(entity_entry.entity_id))
|
||||||
|
assert state == snapshot(name=f"{entity_entry.entity_id}-statealt")
|
||||||
|
|
|
@ -12,6 +12,7 @@ WAKE_UP_ASLEEP = {"response": {"state": TeslemetryState.ASLEEP}, "error": None}
|
||||||
|
|
||||||
PRODUCTS = load_json_object_fixture("products.json", DOMAIN)
|
PRODUCTS = load_json_object_fixture("products.json", DOMAIN)
|
||||||
VEHICLE_DATA = load_json_object_fixture("vehicle_data.json", DOMAIN)
|
VEHICLE_DATA = load_json_object_fixture("vehicle_data.json", DOMAIN)
|
||||||
|
VEHICLE_DATA_ALT = load_json_object_fixture("vehicle_data_alt.json", DOMAIN)
|
||||||
LIVE_STATUS = load_json_object_fixture("live_status.json", DOMAIN)
|
LIVE_STATUS = load_json_object_fixture("live_status.json", DOMAIN)
|
||||||
|
|
||||||
RESPONSE_OK = {"response": {}, "error": None}
|
RESPONSE_OK = {"response": {}, "error": None}
|
||||||
|
|
279
tests/components/teslemetry/fixtures/vehicle_data_alt.json
Normal file
279
tests/components/teslemetry/fixtures/vehicle_data_alt.json
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"id": 1234,
|
||||||
|
"user_id": 1234,
|
||||||
|
"vehicle_id": 1234,
|
||||||
|
"vin": "VINVINVIN",
|
||||||
|
"color": null,
|
||||||
|
"access_type": "OWNER",
|
||||||
|
"granular_access": {
|
||||||
|
"hide_private": false
|
||||||
|
},
|
||||||
|
"tokens": ["abc", "def"],
|
||||||
|
"state": "online",
|
||||||
|
"in_service": false,
|
||||||
|
"id_s": "1234",
|
||||||
|
"calendar_enabled": true,
|
||||||
|
"api_version": 71,
|
||||||
|
"backseat_token": null,
|
||||||
|
"backseat_token_updated_at": null,
|
||||||
|
"ble_autopair_enrolled": false,
|
||||||
|
"charge_state": {
|
||||||
|
"battery_heater_on": false,
|
||||||
|
"battery_level": 77,
|
||||||
|
"battery_range": 266.87,
|
||||||
|
"charge_amps": 16,
|
||||||
|
"charge_current_request": 16,
|
||||||
|
"charge_current_request_max": 16,
|
||||||
|
"charge_enable_request": true,
|
||||||
|
"charge_energy_added": 0,
|
||||||
|
"charge_limit_soc": 80,
|
||||||
|
"charge_limit_soc_max": 100,
|
||||||
|
"charge_limit_soc_min": 50,
|
||||||
|
"charge_limit_soc_std": 80,
|
||||||
|
"charge_miles_added_ideal": 0,
|
||||||
|
"charge_miles_added_rated": 0,
|
||||||
|
"charge_port_cold_weather_mode": false,
|
||||||
|
"charge_port_color": "<invalid>",
|
||||||
|
"charge_port_door_open": true,
|
||||||
|
"charge_port_latch": "Engaged",
|
||||||
|
"charge_rate": 0,
|
||||||
|
"charger_actual_current": 0,
|
||||||
|
"charger_phases": null,
|
||||||
|
"charger_pilot_current": 16,
|
||||||
|
"charger_power": 0,
|
||||||
|
"charger_voltage": 2,
|
||||||
|
"charging_state": "Stopped",
|
||||||
|
"conn_charge_cable": "IEC",
|
||||||
|
"est_battery_range": 275.04,
|
||||||
|
"fast_charger_brand": "<invalid>",
|
||||||
|
"fast_charger_present": false,
|
||||||
|
"fast_charger_type": "ACSingleWireCAN",
|
||||||
|
"ideal_battery_range": 266.87,
|
||||||
|
"max_range_charge_counter": 0,
|
||||||
|
"minutes_to_full_charge": "bad value",
|
||||||
|
"not_enough_power_to_heat": null,
|
||||||
|
"off_peak_charging_enabled": false,
|
||||||
|
"off_peak_charging_times": "all_week",
|
||||||
|
"off_peak_hours_end_time": 900,
|
||||||
|
"preconditioning_enabled": false,
|
||||||
|
"preconditioning_times": "all_week",
|
||||||
|
"scheduled_charging_mode": "Off",
|
||||||
|
"scheduled_charging_pending": false,
|
||||||
|
"scheduled_charging_start_time": null,
|
||||||
|
"scheduled_charging_start_time_app": 600,
|
||||||
|
"scheduled_departure_time": 1704837600,
|
||||||
|
"scheduled_departure_time_minutes": 480,
|
||||||
|
"supercharger_session_trip_planner": false,
|
||||||
|
"time_to_full_charge": null,
|
||||||
|
"timestamp": null,
|
||||||
|
"trip_charging": false,
|
||||||
|
"usable_battery_level": 77,
|
||||||
|
"user_charge_enable_request": null
|
||||||
|
},
|
||||||
|
"climate_state": {
|
||||||
|
"allow_cabin_overheat_protection": true,
|
||||||
|
"auto_seat_climate_left": false,
|
||||||
|
"auto_seat_climate_right": false,
|
||||||
|
"auto_steering_wheel_heat": false,
|
||||||
|
"battery_heater": false,
|
||||||
|
"battery_heater_no_power": null,
|
||||||
|
"cabin_overheat_protection": "Off",
|
||||||
|
"cabin_overheat_protection_actively_cooling": false,
|
||||||
|
"climate_keeper_mode": "off",
|
||||||
|
"cop_activation_temperature": "High",
|
||||||
|
"defrost_mode": 0,
|
||||||
|
"driver_temp_setting": 22,
|
||||||
|
"fan_status": 0,
|
||||||
|
"hvac_auto_request": "On",
|
||||||
|
"inside_temp": 29.8,
|
||||||
|
"is_auto_conditioning_on": false,
|
||||||
|
"is_climate_on": false,
|
||||||
|
"is_front_defroster_on": false,
|
||||||
|
"is_preconditioning": false,
|
||||||
|
"is_rear_defroster_on": false,
|
||||||
|
"left_temp_direction": 251,
|
||||||
|
"max_avail_temp": 28,
|
||||||
|
"min_avail_temp": 15,
|
||||||
|
"outside_temp": 30,
|
||||||
|
"passenger_temp_setting": 22,
|
||||||
|
"remote_heater_control_enabled": false,
|
||||||
|
"right_temp_direction": 251,
|
||||||
|
"seat_heater_left": 0,
|
||||||
|
"seat_heater_rear_center": 0,
|
||||||
|
"seat_heater_rear_left": 0,
|
||||||
|
"seat_heater_rear_right": 0,
|
||||||
|
"seat_heater_right": 0,
|
||||||
|
"side_mirror_heaters": false,
|
||||||
|
"steering_wheel_heat_level": 0,
|
||||||
|
"steering_wheel_heater": false,
|
||||||
|
"supports_fan_only_cabin_overheat_protection": true,
|
||||||
|
"timestamp": 1705707520649,
|
||||||
|
"wiper_blade_heater": false
|
||||||
|
},
|
||||||
|
"drive_state": {
|
||||||
|
"active_route_latitude": 30.2226265,
|
||||||
|
"active_route_longitude": -97.6236871,
|
||||||
|
"active_route_miles_to_arrival": 0,
|
||||||
|
"active_route_minutes_to_arrival": 0,
|
||||||
|
"active_route_traffic_minutes_delay": 0,
|
||||||
|
"gps_as_of": 1701129612,
|
||||||
|
"heading": 185,
|
||||||
|
"latitude": -30.222626,
|
||||||
|
"longitude": -97.6236871,
|
||||||
|
"native_latitude": -30.222626,
|
||||||
|
"native_location_supported": 1,
|
||||||
|
"native_longitude": -97.6236871,
|
||||||
|
"native_type": "wgs",
|
||||||
|
"power": -7,
|
||||||
|
"shift_state": null,
|
||||||
|
"speed": null,
|
||||||
|
"timestamp": 1705707520649
|
||||||
|
},
|
||||||
|
"gui_settings": {
|
||||||
|
"gui_24_hour_time": false,
|
||||||
|
"gui_charge_rate_units": "kW",
|
||||||
|
"gui_distance_units": "km/hr",
|
||||||
|
"gui_range_display": "Rated",
|
||||||
|
"gui_temperature_units": "C",
|
||||||
|
"gui_tirepressure_units": "Psi",
|
||||||
|
"show_range_units": false,
|
||||||
|
"timestamp": 1705707520649
|
||||||
|
},
|
||||||
|
"vehicle_config": {
|
||||||
|
"aux_park_lamps": "Eu",
|
||||||
|
"badge_version": 1,
|
||||||
|
"can_accept_navigation_requests": true,
|
||||||
|
"can_actuate_trunks": true,
|
||||||
|
"car_special_type": "base",
|
||||||
|
"car_type": "model3",
|
||||||
|
"charge_port_type": "CCS",
|
||||||
|
"cop_user_set_temp_supported": false,
|
||||||
|
"dashcam_clip_save_supported": true,
|
||||||
|
"default_charge_to_max": false,
|
||||||
|
"driver_assist": "TeslaAP3",
|
||||||
|
"ece_restrictions": false,
|
||||||
|
"efficiency_package": "M32021",
|
||||||
|
"eu_vehicle": true,
|
||||||
|
"exterior_color": "DeepBlue",
|
||||||
|
"exterior_trim": "Black",
|
||||||
|
"exterior_trim_override": "",
|
||||||
|
"has_air_suspension": false,
|
||||||
|
"has_ludicrous_mode": false,
|
||||||
|
"has_seat_cooling": false,
|
||||||
|
"headlamp_type": "Global",
|
||||||
|
"interior_trim_type": "White2",
|
||||||
|
"key_version": 2,
|
||||||
|
"motorized_charge_port": true,
|
||||||
|
"paint_color_override": "0,9,25,0.7,0.04",
|
||||||
|
"performance_package": "Base",
|
||||||
|
"plg": true,
|
||||||
|
"pws": true,
|
||||||
|
"rear_drive_unit": "PM216MOSFET",
|
||||||
|
"rear_seat_heaters": 1,
|
||||||
|
"rear_seat_type": 0,
|
||||||
|
"rhd": true,
|
||||||
|
"roof_color": "RoofColorGlass",
|
||||||
|
"seat_type": null,
|
||||||
|
"spoiler_type": "None",
|
||||||
|
"sun_roof_installed": null,
|
||||||
|
"supports_qr_pairing": false,
|
||||||
|
"third_row_seats": "None",
|
||||||
|
"timestamp": 1705707520649,
|
||||||
|
"trim_badging": "74d",
|
||||||
|
"use_range_badging": true,
|
||||||
|
"utc_offset": 36000,
|
||||||
|
"webcam_selfie_supported": true,
|
||||||
|
"webcam_supported": true,
|
||||||
|
"wheel_type": "Pinwheel18CapKit"
|
||||||
|
},
|
||||||
|
"vehicle_state": {
|
||||||
|
"api_version": 71,
|
||||||
|
"autopark_state_v2": "unavailable",
|
||||||
|
"calendar_supported": true,
|
||||||
|
"car_version": "2023.44.30.8 06f534d46010",
|
||||||
|
"center_display_state": 0,
|
||||||
|
"dashcam_clip_save_available": true,
|
||||||
|
"dashcam_state": "Recording",
|
||||||
|
"df": 0,
|
||||||
|
"dr": 0,
|
||||||
|
"fd_window": 0,
|
||||||
|
"feature_bitmask": "fbdffbff,187f",
|
||||||
|
"fp_window": 0,
|
||||||
|
"ft": 0,
|
||||||
|
"is_user_present": false,
|
||||||
|
"locked": false,
|
||||||
|
"media_info": {
|
||||||
|
"audio_volume": 2.6667,
|
||||||
|
"audio_volume_increment": 0.333333,
|
||||||
|
"audio_volume_max": 10.333333,
|
||||||
|
"media_playback_status": "Stopped",
|
||||||
|
"now_playing_album": "",
|
||||||
|
"now_playing_artist": "",
|
||||||
|
"now_playing_duration": 0,
|
||||||
|
"now_playing_elapsed": 0,
|
||||||
|
"now_playing_source": "Spotify",
|
||||||
|
"now_playing_station": "",
|
||||||
|
"now_playing_title": ""
|
||||||
|
},
|
||||||
|
"media_state": {
|
||||||
|
"remote_control_enabled": true
|
||||||
|
},
|
||||||
|
"notifications_supported": true,
|
||||||
|
"odometer": 6481.019282,
|
||||||
|
"parsed_calendar_supported": true,
|
||||||
|
"pf": 0,
|
||||||
|
"pr": 0,
|
||||||
|
"rd_window": 0,
|
||||||
|
"remote_start": false,
|
||||||
|
"remote_start_enabled": true,
|
||||||
|
"remote_start_supported": true,
|
||||||
|
"rp_window": 0,
|
||||||
|
"rt": 0,
|
||||||
|
"santa_mode": 0,
|
||||||
|
"sentry_mode": false,
|
||||||
|
"sentry_mode_available": true,
|
||||||
|
"service_mode": false,
|
||||||
|
"service_mode_plus": false,
|
||||||
|
"software_update": {
|
||||||
|
"download_perc": 0,
|
||||||
|
"expected_duration_sec": 2700,
|
||||||
|
"install_perc": 1,
|
||||||
|
"status": "",
|
||||||
|
"version": " "
|
||||||
|
},
|
||||||
|
"speed_limit_mode": {
|
||||||
|
"active": false,
|
||||||
|
"current_limit_mph": 69,
|
||||||
|
"max_limit_mph": 120,
|
||||||
|
"min_limit_mph": 50,
|
||||||
|
"pin_code_set": true
|
||||||
|
},
|
||||||
|
"timestamp": 1705707520649,
|
||||||
|
"tpms_hard_warning_fl": false,
|
||||||
|
"tpms_hard_warning_fr": false,
|
||||||
|
"tpms_hard_warning_rl": false,
|
||||||
|
"tpms_hard_warning_rr": false,
|
||||||
|
"tpms_last_seen_pressure_time_fl": 1705700812,
|
||||||
|
"tpms_last_seen_pressure_time_fr": 1705700793,
|
||||||
|
"tpms_last_seen_pressure_time_rl": 1705700794,
|
||||||
|
"tpms_last_seen_pressure_time_rr": 1705700823,
|
||||||
|
"tpms_pressure_fl": 2.775,
|
||||||
|
"tpms_pressure_fr": 2.8,
|
||||||
|
"tpms_pressure_rl": 2.775,
|
||||||
|
"tpms_pressure_rr": 2.775,
|
||||||
|
"tpms_rcp_front_value": 2.9,
|
||||||
|
"tpms_rcp_rear_value": 2.9,
|
||||||
|
"tpms_soft_warning_fl": false,
|
||||||
|
"tpms_soft_warning_fr": false,
|
||||||
|
"tpms_soft_warning_rl": false,
|
||||||
|
"tpms_soft_warning_rr": false,
|
||||||
|
"valet_mode": false,
|
||||||
|
"valet_pin_needed": false,
|
||||||
|
"vehicle_name": "Test",
|
||||||
|
"vehicle_self_test_progress": 0,
|
||||||
|
"vehicle_self_test_requested": false,
|
||||||
|
"webcam_available": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,14 +1,19 @@
|
||||||
"""Test the Teslemetry sensor platform."""
|
"""Test the Teslemetry sensor platform."""
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant.components.teslemetry.coordinator import SYNC_INTERVAL
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
from . import assert_entities, setup_platform
|
from . import assert_entities, assert_entities_alt, setup_platform
|
||||||
|
from .const import VEHICLE_DATA_ALT
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||||
|
@ -17,6 +22,7 @@ async def test_sensors(
|
||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
freezer: FrozenDateTimeFactory,
|
freezer: FrozenDateTimeFactory,
|
||||||
|
mock_vehicle_data,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Tests that the sensor entities are correct."""
|
"""Tests that the sensor entities are correct."""
|
||||||
|
|
||||||
|
@ -25,3 +31,11 @@ async def test_sensors(
|
||||||
entry = await setup_platform(hass, [Platform.SENSOR])
|
entry = await setup_platform(hass, [Platform.SENSOR])
|
||||||
|
|
||||||
assert_entities(hass, entry.entry_id, entity_registry, snapshot)
|
assert_entities(hass, entry.entry_id, entity_registry, snapshot)
|
||||||
|
|
||||||
|
# Coordinator refresh
|
||||||
|
mock_vehicle_data.return_value = VEHICLE_DATA_ALT
|
||||||
|
freezer.tick(timedelta(seconds=SYNC_INTERVAL))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert_entities_alt(hass, entry.entry_id, entity_registry, snapshot)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue