Fix time to arrival to timestamp in Tessie (#109172)

* Fix time to arrival

* Update snapshot

* Freeze time for snapshot

* Fix docstring

* Add available_fn

* Update snapshot

* Dont use variance for full charge

* Remove unrelated changes

* Revert snapshot

* Rename hours_to_datetime
This commit is contained in:
Brett Adams 2024-02-01 06:52:58 +10:00 committed by GitHub
parent 552d14204d
commit 1584f02e71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 69 additions and 56 deletions

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import cast
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
@ -29,6 +30,7 @@ from homeassistant.core import HomeAssistant, callback
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 .coordinator import TessieStateUpdateCoordinator from .coordinator import TessieStateUpdateCoordinator
@ -36,8 +38,8 @@ from .entity import TessieEntity
@callback @callback
def hours_to_datetime(value: StateType) -> datetime | None: def minutes_to_datetime(value: StateType) -> datetime | None:
"""Convert relative hours into absolute datetime.""" """Convert relative minutes into absolute datetime."""
if isinstance(value, (int, float)) and value > 0: if isinstance(value, (int, float)) and value > 0:
return dt_util.now() + timedelta(minutes=value) return dt_util.now() + timedelta(minutes=value)
return None return None
@ -48,6 +50,7 @@ class TessieSensorEntityDescription(SensorEntityDescription):
"""Describes Tessie Sensor entity.""" """Describes Tessie Sensor entity."""
value_fn: Callable[[StateType], StateType | datetime] = lambda x: x value_fn: Callable[[StateType], StateType | datetime] = lambda x: x
available_fn: Callable[[StateType], bool] = lambda _: True
DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = ( DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
@ -95,7 +98,7 @@ DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
key="charge_state_minutes_to_full_charge", key="charge_state_minutes_to_full_charge",
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
value_fn=hours_to_datetime, value_fn=minutes_to_datetime,
), ),
TessieSensorEntityDescription( TessieSensorEntityDescription(
key="charge_state_battery_range", key="charge_state_battery_range",
@ -219,9 +222,12 @@ DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
), ),
TessieSensorEntityDescription( TessieSensorEntityDescription(
key="drive_state_active_route_minutes_to_arrival", key="drive_state_active_route_minutes_to_arrival",
state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.TIMESTAMP,
native_unit_of_measurement=UnitOfTime.MINUTES, value_fn=ignore_variance(
device_class=SensorDeviceClass.DURATION, lambda value: dt_util.now() + timedelta(minutes=cast(float, value)),
timedelta(seconds=30),
),
available_fn=lambda x: x is not None,
), ),
TessieSensorEntityDescription( TessieSensorEntityDescription(
key="drive_state_active_route_destination", key="drive_state_active_route_destination",
@ -262,3 +268,8 @@ class TessieSensorEntity(TessieEntity, SensorEntity):
def native_value(self) -> StateType | datetime: def native_value(self) -> StateType | datetime:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self.entity_description.value_fn(self.get()) return self.entity_description.value_fn(self.get())
@property
def available(self) -> bool:
"""Return if sensor is available."""
return super().available and self.entity_description.available_fn(self.get())

View File

@ -142,7 +142,7 @@
"drive_state_active_route_miles_to_arrival": { "drive_state_active_route_miles_to_arrival": {
"name": "Distance to arrival" "name": "Distance to arrival"
}, },
"drive_state_active_route_time_to_arrival": { "drive_state_active_route_minutes_to_arrival": {
"name": "Time to arrival" "name": "Time to arrival"
}, },
"drive_state_active_route_destination": { "drive_state_active_route_destination": {

View File

@ -493,54 +493,6 @@
'state': '22.5', 'state': '22.5',
}) })
# --- # ---
# name: test_sensors[sensor.test_duration-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.test_duration',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
'original_icon': None,
'original_name': 'Duration',
'platform': 'tessie',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'drive_state_active_route_minutes_to_arrival',
'unique_id': 'VINVINVIN-drive_state_active_route_minutes_to_arrival',
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
})
# ---
# name: test_sensors[sensor.test_duration-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'duration',
'friendly_name': 'Test Duration',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
'context': <ANY>,
'entity_id': 'sensor.test_duration',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '59.2',
})
# ---
# name: test_sensors[sensor.test_inside_temperature-entry] # name: test_sensors[sensor.test_inside_temperature-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
@ -953,6 +905,50 @@
'state': '65', 'state': '65',
}) })
# --- # ---
# name: test_sensors[sensor.test_time_to_arrival-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.test_time_to_arrival',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'original_icon': None,
'original_name': 'Time to arrival',
'platform': 'tessie',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'drive_state_active_route_minutes_to_arrival',
'unique_id': 'VINVINVIN-drive_state_active_route_minutes_to_arrival',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[sensor.test_time_to_arrival-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'timestamp',
'friendly_name': 'Test Time to arrival',
}),
'context': <ANY>,
'entity_id': 'sensor.test_time_to_arrival',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '2024-01-01T00:59:12+00:00',
})
# ---
# name: test_sensors[sensor.test_time_to_full_charge-entry] # name: test_sensors[sensor.test_time_to_full_charge-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({

View File

@ -1,4 +1,5 @@
"""Test the Tessie sensor platform.""" """Test the Tessie sensor platform."""
from freezegun.api import FrozenDateTimeFactory
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from homeassistant.const import Platform from homeassistant.const import Platform
@ -9,10 +10,15 @@ from .common import assert_entities, setup_platform
async def test_sensors( async def test_sensors(
hass: HomeAssistant, snapshot: SnapshotAssertion, entity_registry: er.EntityRegistry hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
freezer: FrozenDateTimeFactory,
) -> None: ) -> None:
"""Tests that the sensor entities are correct.""" """Tests that the sensor entities are correct."""
freezer.move_to("2024-01-01 00:00:00+00:00")
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)