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
|
@ -6,6 +6,7 @@ from collections.abc import Callable
|
|||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from itertools import chain
|
||||
from typing import cast
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
|
@ -27,10 +28,11 @@ from homeassistant.const import (
|
|||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.variance import ignore_variance
|
||||
|
||||
from .const import DOMAIN
|
||||
from .entity import (
|
||||
|
@ -40,13 +42,16 @@ from .entity import (
|
|||
)
|
||||
from .models import TeslemetryEnergyData, TeslemetryVehicleData
|
||||
|
||||
CHARGE_STATES = {
|
||||
"Starting": "starting",
|
||||
"Charging": "charging",
|
||||
"Stopped": "stopped",
|
||||
"Complete": "complete",
|
||||
"Disconnected": "disconnected",
|
||||
"NoPower": "no_power",
|
||||
}
|
||||
|
||||
@callback
|
||||
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
|
||||
SHIFT_STATES = {"P": "p", "D": "d", "R": "r", "N": "n"}
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
|
@ -57,11 +62,24 @@ class TeslemetrySensorEntityDescription(SensorEntityDescription):
|
|||
|
||||
|
||||
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(
|
||||
key="charge_state_usable_battery_level",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="charge_state_charge_energy_added",
|
||||
|
@ -96,13 +114,16 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
|
|||
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
||||
device_class=SensorDeviceClass.SPEED,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="charge_state_conn_charge_cable",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="charge_state_minutes_to_full_charge",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
key="charge_state_fast_charger_type",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=minutes_to_datetime,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="charge_state_battery_range",
|
||||
|
@ -111,12 +132,29 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
|
|||
device_class=SensorDeviceClass.DISTANCE,
|
||||
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(
|
||||
key="drive_state_speed",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
||||
device_class=SensorDeviceClass.SPEED,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda value: value or 0,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="drive_state_power",
|
||||
|
@ -125,12 +163,13 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
|
|||
device_class=SensorDeviceClass.POWER,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda value: value or 0,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="drive_state_shift_state",
|
||||
options=["p", "d", "r", "n"],
|
||||
options=list(SHIFT_STATES.values()),
|
||||
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,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
|
@ -235,14 +274,27 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
|
|||
native_unit_of_measurement=UnitOfLength.MILES,
|
||||
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",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=minutes_to_datetime,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="drive_state_active_route_destination",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
variance=1,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -322,6 +374,7 @@ ENERGY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
|
|||
device_class=SensorDeviceClass.POWER,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SensorEntityDescription(key="island_status", device_class=SensorDeviceClass.ENUM),
|
||||
)
|
||||
|
||||
WALL_CONNECTOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
|
||||
|
@ -362,7 +415,12 @@ async def async_setup_entry(
|
|||
for vehicle in data.vehicles
|
||||
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)
|
||||
for energysite in data.energysites
|
||||
for description in ENERGY_DESCRIPTIONS
|
||||
|
@ -390,17 +448,36 @@ class TeslemetryVehicleSensorEntity(TeslemetryVehicleEntity, SensorEntity):
|
|||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
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._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
|
||||
def native_value(self) -> StateType | datetime:
|
||||
def native_value(self) -> datetime | None:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self.get())
|
||||
return self._get_timestamp(self._value)
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if sensor is available."""
|
||||
return super().available and self.get() is not None
|
||||
"""Return the avaliability of the sensor."""
|
||||
return isinstance(self._value, int | float) and self._value > 0
|
||||
|
||||
|
||||
class TeslemetryEnergySensorEntity(TeslemetryEnergyEntity, SensorEntity):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue