Add credit balance sensor to Teslemetry (#144365)

* Add credits

* Credits string and icon

* Add test

* tests and fixes

* Add units

* Update homeassistant/components/teslemetry/sensor.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Update snapshot with unit

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Brett Adams 2025-05-14 20:21:45 +10:00 committed by GitHub
parent 9a06584a1d
commit 8ccedd4064
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 126 additions and 3 deletions

View File

@ -241,7 +241,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
) )
# Setup Platforms # Setup Platforms
entry.runtime_data = TeslemetryData(vehicles, energysites, scopes) entry.runtime_data = TeslemetryData(vehicles, energysites, scopes, stream)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
if stream: if stream:

View File

@ -415,6 +415,9 @@
"brick_voltage_min": { "brick_voltage_min": {
"default": "mdi:battery-low" "default": "mdi:battery-low"
}, },
"credit_balance": {
"default": "mdi:credit-card"
},
"cruise_follow_distance": { "cruise_follow_distance": {
"default": "mdi:car-cruise-control" "default": "mdi:car-cruise-control"
}, },

View File

@ -28,6 +28,7 @@ class TeslemetryData:
vehicles: list[TeslemetryVehicleData] vehicles: list[TeslemetryVehicleData]
energysites: list[TeslemetryEnergyData] energysites: list[TeslemetryEnergyData]
scopes: list[Scope] scopes: list[Scope]
stream: TeslemetryStream
@dataclass @dataclass

View File

@ -6,7 +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 teslemetry_stream import TeslemetryStreamVehicle from teslemetry_stream import TeslemetryStream, TeslemetryStreamVehicle
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
RestoreSensor, RestoreSensor,
@ -45,7 +45,7 @@ from .entity import (
TeslemetryVehicleStreamEntity, TeslemetryVehicleStreamEntity,
TeslemetryWallConnectorEntity, TeslemetryWallConnectorEntity,
) )
from .models import TeslemetryEnergyData, TeslemetryVehicleData from .models import TeslemetryData, TeslemetryEnergyData, TeslemetryVehicleData
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -1618,6 +1618,12 @@ async def async_setup_entry(
if energysite.history_coordinator is not None if energysite.history_coordinator is not None
) )
entities.append(
TeslemetryCreditBalanceSensor(
entry.unique_id or entry.entry_id, entry.runtime_data
)
)
async_add_entities(entities) async_add_entities(entities)
@ -1825,3 +1831,33 @@ class TeslemetryEnergyHistorySensorEntity(TeslemetryEnergyHistoryEntity, SensorE
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> None:
"""Update the attributes of the sensor.""" """Update the attributes of the sensor."""
self._attr_native_value = self._value self._attr_native_value = self._value
class TeslemetryCreditBalanceSensor(RestoreSensor):
"""Entity for Teslemetry Credit balance."""
_attr_has_entity_name = True
stream: TeslemetryStream
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_suggested_display_precision = 0
def __init__(self, uid: str, data: TeslemetryData) -> None:
"""Initialize common aspects of a Teslemetry entity."""
self._attr_translation_key = "credit_balance"
self._attr_unique_id = f"{uid}_credit_balance"
self.stream = data.stream
async def async_added_to_hass(self) -> None:
"""Handle entity which will be added."""
await super().async_added_to_hass()
if (sensor_data := await self.async_get_last_sensor_data()) is not None:
self._attr_native_value = sensor_data.native_value
self.async_on_remove(self.stream.listen_Balance(self._async_update))
def _async_update(self, value: int) -> None:
"""Handle updated data from the coordinator."""
self._attr_native_value = value
self.async_write_ha_state()

View File

@ -490,6 +490,10 @@
"climate_state_passenger_temp_setting": { "climate_state_passenger_temp_setting": {
"name": "Passenger temperature setting" "name": "Passenger temperature setting"
}, },
"credit_balance": {
"name": "Teslemetry credits",
"unit_of_measurement": "credits"
},
"drive_state_active_route_destination": { "drive_state_active_route_destination": {
"name": "Destination" "name": "Destination"
}, },

View File

@ -2424,6 +2424,75 @@
'state': '0', 'state': '0',
}) })
# --- # ---
# name: test_sensors[sensor.teslemetry_credits-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.teslemetry_credits',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 0,
}),
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Teslemetry credits',
'platform': 'teslemetry',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'credit_balance',
'unique_id': 'abc-123_credit_balance',
'unit_of_measurement': 'credits',
})
# ---
# name: test_sensors[sensor.teslemetry_credits-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Teslemetry credits',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': 'credits',
}),
'context': <ANY>,
'entity_id': 'sensor.teslemetry_credits',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_sensors[sensor.teslemetry_credits-statealt]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Teslemetry credits',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': 'credits',
}),
'context': <ANY>,
'entity_id': 'sensor.teslemetry_credits',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_sensors[sensor.test_battery_level-entry] # name: test_sensors[sensor.test_battery_level-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
@ -5054,6 +5123,9 @@
'state': 'disconnected', 'state': 'disconnected',
}) })
# --- # ---
# name: test_sensors_streaming[sensor.teslemetry_credits-state]
'1980'
# ---
# name: test_sensors_streaming[sensor.test_battery_level-state] # name: test_sensors_streaming[sensor.test_battery_level-state]
'90' '90'
# --- # ---

View File

@ -73,6 +73,12 @@ async def test_sensors_streaming(
Signal.TIME_TO_FULL_CHARGE: 0.166666667, Signal.TIME_TO_FULL_CHARGE: 0.166666667,
Signal.MINUTES_TO_ARRIVAL: None, Signal.MINUTES_TO_ARRIVAL: None,
}, },
"credits": {
"type": "wake_up",
"cost": 20,
"name": "wake_up",
"balance": 1980,
},
"createdAt": "2024-10-04T10:45:17.537Z", "createdAt": "2024-10-04T10:45:17.537Z",
} }
) )
@ -91,6 +97,7 @@ async def test_sensors_streaming(
"sensor.test_charge_cable", "sensor.test_charge_cable",
"sensor.test_time_to_full_charge", "sensor.test_time_to_full_charge",
"sensor.test_time_to_arrival", "sensor.test_time_to_arrival",
"sensor.teslemetry_credits",
): ):
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state.state == snapshot(name=f"{entity_id}-state") assert state.state == snapshot(name=f"{entity_id}-state")