Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions homeassistant/components/tessie/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,24 @@ def __init__(
| TessieEnergySiteLiveCoordinator
| TessieEnergyHistoryCoordinator,
key: str,
data_key: str | None = None,
) -> None:
"""Initialize common aspects of a Tessie entity."""

self.key = key
self.data_key = data_key or key
self._attr_translation_key = key
super().__init__(coordinator)
self._async_update_attrs()

@property
def _value(self) -> Any:
"""Return value from coordinator data."""
return self.coordinator.data.get(self.key)
return self.coordinator.data.get(self.data_key)

def get(self, key: str | None = None, default: Any | None = None) -> Any:
"""Return a specific value from coordinator data."""
return self.coordinator.data.get(key or self.key, default)
return self.coordinator.data.get(key or self.data_key, default)

def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
Expand All @@ -76,6 +78,7 @@ def __init__(
self,
vehicle: TessieVehicleData,
key: str,
data_key: str | None = None,
) -> None:
"""Initialize common aspects of a Tessie vehicle entity."""
self.vin = vehicle.vin
Expand All @@ -84,12 +87,12 @@ def __init__(
self._attr_unique_id = f"{vehicle.vin}-{key}"
self._attr_device_info = vehicle.device

super().__init__(vehicle.data_coordinator, key)
super().__init__(vehicle.data_coordinator, key, data_key)

@property
def _value(self) -> Any:
"""Return value from coordinator data."""
return self.coordinator.data.get(self.key)
return self.coordinator.data.get(self.data_key)

def set(self, *args: Any) -> None:
"""Set a value in coordinator data."""
Expand Down Expand Up @@ -133,13 +136,14 @@ def __init__(
data: TessieEnergyData,
coordinator: TessieEnergySiteInfoCoordinator | TessieEnergySiteLiveCoordinator,
key: str,
data_key: str | None = None,
) -> None:
"""Initialize common aspects of a Tessie energy site entity."""
self.api = data.api
self._attr_unique_id = f"{data.id}-{key}"
self._attr_device_info = data.device

super().__init__(coordinator, key)
super().__init__(coordinator, key, data_key)


class TessieBatteryEntity(TessieBaseEntity):
Expand All @@ -149,13 +153,14 @@ def __init__(
self,
vehicle: TessieVehicleData,
key: str,
data_key: str | None = None,
) -> None:
"""Initialize common aspects of a Tessie battery health entity."""
self.vin = vehicle.vin
self._attr_unique_id = f"{vehicle.vin}-{key}"
self._attr_device_info = vehicle.device

super().__init__(vehicle.battery_coordinator, key)
super().__init__(vehicle.battery_coordinator, key, data_key)


class TessieEnergyHistoryEntity(TessieBaseEntity):
Expand All @@ -165,13 +170,14 @@ def __init__(
self,
data: TessieEnergyData,
key: str,
data_key: str | None = None,
) -> None:
"""Initialize common aspects of a Tessie energy history entity."""
self.api = data.api
self._attr_unique_id = f"{data.id}-{key}"
self._attr_device_info = data.device
assert data.history_coordinator
super().__init__(data.history_coordinator, key)
super().__init__(data.history_coordinator, key, data_key)


class TessieWallConnectorEntity(TessieBaseEntity):
Expand All @@ -182,6 +188,7 @@ def __init__(
data: TessieEnergyData,
din: str,
key: str,
data_key: str | None = None,
) -> None:
"""Initialize common aspects of a Teslemetry entity."""
self.din = din
Expand All @@ -194,7 +201,7 @@ def __init__(
serial_number=din.rsplit("-", maxsplit=1)[-1],
)
assert data.live_coordinator
super().__init__(data.live_coordinator, key)
super().__init__(data.live_coordinator, key, data_key)

@property
def _value(self) -> int:
Expand Down
48 changes: 17 additions & 31 deletions homeassistant/components/tessie/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
TessieWallConnectorStates,
)
from .entity import (
TessieBatteryEntity,
TessieEnergyEntity,
TessieEnergyHistoryEntity,
TessieEntity,
Expand All @@ -62,6 +61,7 @@ def minutes_to_datetime(value: StateType) -> datetime | None:
class TessieSensorEntityDescription(SensorEntityDescription):
"""Describes Tessie Sensor entity."""

data_key: str | None = None
value_fn: Callable[[StateType], StateType | datetime] = lambda x: x
available_fn: Callable[[StateType], bool] = lambda _: True

Expand Down Expand Up @@ -142,6 +142,14 @@ class TessieSensorEntityDescription(SensorEntityDescription):
suggested_display_precision=1,
entity_registry_enabled_default=False,
),
TessieSensorEntityDescription(
key="phantom_drain_percent",
data_key="charge_state_phantom_drain_percent",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=2,
),
TessieSensorEntityDescription(
key="charge_state_energy_remaining",
state_class=SensorStateClass.MEASUREMENT,
Expand Down Expand Up @@ -292,15 +300,9 @@ class TessieSensorEntityDescription(SensorEntityDescription):


BATTERY_DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
TessieSensorEntityDescription(
key="phantom_drain_percent",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=2,
),
TessieSensorEntityDescription(
key="lifetime_energy_used",
data_key="charge_state_lifetime_energy_used",
state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
Expand All @@ -309,6 +311,7 @@ class TessieSensorEntityDescription(SensorEntityDescription):
),
TessieSensorEntityDescription(
key="pack_current",
data_key="charge_state_pack_current",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
device_class=SensorDeviceClass.CURRENT,
Expand All @@ -317,6 +320,7 @@ class TessieSensorEntityDescription(SensorEntityDescription):
),
TessieSensorEntityDescription(
key="pack_voltage",
data_key="charge_state_pack_voltage",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
device_class=SensorDeviceClass.VOLTAGE,
Expand All @@ -325,6 +329,7 @@ class TessieSensorEntityDescription(SensorEntityDescription):
),
TessieSensorEntityDescription(
key="module_temp_min",
data_key="charge_state_module_temp_min",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
Expand All @@ -333,6 +338,7 @@ class TessieSensorEntityDescription(SensorEntityDescription):
),
TessieSensorEntityDescription(
key="module_temp_max",
data_key="charge_state_module_temp_max",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
Expand Down Expand Up @@ -494,11 +500,10 @@ async def async_setup_entry(
for vehicle in entry.runtime_data.vehicles
for description in DESCRIPTIONS
),
( # Add vehicle battery health
TessieBatteryHealthSensorEntity(vehicle, description)
( # Add vehicle battery health from state endpoint
TessieVehicleSensorEntity(vehicle, description)
for vehicle in entry.runtime_data.vehicles
for description in BATTERY_DESCRIPTIONS
Comment on lines +503 to 506
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard battery-health entities on available state keys

This change creates all BATTERY_DESCRIPTIONS sensors from data_coordinator without checking whether the mapped data_key exists. Because vehicle entities are seeded from last_state, keys like charge_state_pack_current/charge_state_pack_voltage/charge_state_phantom_drain_percent can be missing at setup (as reflected by the existing vehicles.json fixture), so these sensors now start as unknown and can stay that way for vehicles that never expose those fields. Previously, battery sensors were gated on available battery data, so this is a startup/availability regression.

Useful? React with 👍 / 👎.

if description.key in vehicle.battery_coordinator.data
),
( # Add energy site info
TessieEnergyInfoSensorEntity(energysite, description)
Expand Down Expand Up @@ -545,7 +550,7 @@ def __init__(
) -> None:
"""Initialize the sensor."""
self.entity_description = description
super().__init__(vehicle, description.key)
super().__init__(vehicle, description.key, description.data_key)

@property
def native_value(self) -> StateType | datetime:
Expand All @@ -558,25 +563,6 @@ def available(self) -> bool:
return super().available and self.entity_description.available_fn(self.get())


class TessieBatteryHealthSensorEntity(TessieBatteryEntity, SensorEntity):
"""Sensor entity for Tessie battery health data."""

entity_description: TessieSensorEntityDescription

def __init__(
self,
vehicle: TessieVehicleData,
description: TessieSensorEntityDescription,
) -> None:
"""Initialize the sensor."""
self.entity_description = description
super().__init__(vehicle, description.key)

def _async_update_attrs(self) -> None:
"""Update the attributes of the sensor."""
self._attr_native_value = self.entity_description.value_fn(self._value)


class TessieEnergyLiveSensorEntity(TessieEnergyEntity, SensorEntity):
"""Base class for Tessie energy site sensor entity."""

Expand Down
6 changes: 6 additions & 0 deletions tests/components/tessie/fixtures/online.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@
"off_peak_charging_enabled": false,
"off_peak_charging_times": "all_week",
"off_peak_hours_end_time": 900,
"lifetime_energy_used": 12345.6,
"module_temp_max": 24,
"module_temp_min": 22.5,
"pack_current": -0.6,
"pack_voltage": 390.1,
"phantom_drain_percent": 0.5,
"preconditioning_enabled": false,
"preconditioning_times": "all_week",
"scheduled_charging_mode": "StartAt",
Expand Down
Loading