diff --git a/homeassistant/components/teslemetry/__init__.py b/homeassistant/components/teslemetry/__init__.py index 7b46caf2b43..5d9a757b9e6 100644 --- a/homeassistant/components/teslemetry/__init__.py +++ b/homeassistant/components/teslemetry/__init__.py @@ -241,7 +241,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) - ) # 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) if stream: diff --git a/homeassistant/components/teslemetry/icons.json b/homeassistant/components/teslemetry/icons.json index 242dccf90ec..edd5d404499 100644 --- a/homeassistant/components/teslemetry/icons.json +++ b/homeassistant/components/teslemetry/icons.json @@ -415,6 +415,9 @@ "brick_voltage_min": { "default": "mdi:battery-low" }, + "credit_balance": { + "default": "mdi:credit-card" + }, "cruise_follow_distance": { "default": "mdi:car-cruise-control" }, diff --git a/homeassistant/components/teslemetry/models.py b/homeassistant/components/teslemetry/models.py index 4f0d26a1cba..51eed97227e 100644 --- a/homeassistant/components/teslemetry/models.py +++ b/homeassistant/components/teslemetry/models.py @@ -28,6 +28,7 @@ class TeslemetryData: vehicles: list[TeslemetryVehicleData] energysites: list[TeslemetryEnergyData] scopes: list[Scope] + stream: TeslemetryStream @dataclass diff --git a/homeassistant/components/teslemetry/sensor.py b/homeassistant/components/teslemetry/sensor.py index ee1dddf4774..ab075d18132 100644 --- a/homeassistant/components/teslemetry/sensor.py +++ b/homeassistant/components/teslemetry/sensor.py @@ -6,7 +6,7 @@ from collections.abc import Callable from dataclasses import dataclass from datetime import datetime, timedelta -from teslemetry_stream import TeslemetryStreamVehicle +from teslemetry_stream import TeslemetryStream, TeslemetryStreamVehicle from homeassistant.components.sensor import ( RestoreSensor, @@ -45,7 +45,7 @@ from .entity import ( TeslemetryVehicleStreamEntity, TeslemetryWallConnectorEntity, ) -from .models import TeslemetryEnergyData, TeslemetryVehicleData +from .models import TeslemetryData, TeslemetryEnergyData, TeslemetryVehicleData PARALLEL_UPDATES = 0 @@ -1618,6 +1618,12 @@ async def async_setup_entry( if energysite.history_coordinator is not None ) + entities.append( + TeslemetryCreditBalanceSensor( + entry.unique_id or entry.entry_id, entry.runtime_data + ) + ) + async_add_entities(entities) @@ -1825,3 +1831,33 @@ class TeslemetryEnergyHistorySensorEntity(TeslemetryEnergyHistoryEntity, SensorE def _async_update_attrs(self) -> None: """Update the attributes of the sensor.""" 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() diff --git a/homeassistant/components/teslemetry/strings.json b/homeassistant/components/teslemetry/strings.json index cd20cde6293..fb68e045b37 100644 --- a/homeassistant/components/teslemetry/strings.json +++ b/homeassistant/components/teslemetry/strings.json @@ -490,6 +490,10 @@ "climate_state_passenger_temp_setting": { "name": "Passenger temperature setting" }, + "credit_balance": { + "name": "Teslemetry credits", + "unit_of_measurement": "credits" + }, "drive_state_active_route_destination": { "name": "Destination" }, diff --git a/tests/components/teslemetry/snapshots/test_sensor.ambr b/tests/components/teslemetry/snapshots/test_sensor.ambr index 3b860039b03..13d87dbe88b 100644 --- a/tests/components/teslemetry/snapshots/test_sensor.ambr +++ b/tests/components/teslemetry/snapshots/test_sensor.ambr @@ -2424,6 +2424,75 @@ 'state': '0', }) # --- +# name: test_sensors[sensor.teslemetry_credits-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.teslemetry_credits', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + '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': , + 'unit_of_measurement': 'credits', + }), + 'context': , + 'entity_id': 'sensor.teslemetry_credits', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.teslemetry_credits-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Teslemetry credits', + 'state_class': , + 'unit_of_measurement': 'credits', + }), + 'context': , + 'entity_id': 'sensor.teslemetry_credits', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- # name: test_sensors[sensor.test_battery_level-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -5054,6 +5123,9 @@ 'state': 'disconnected', }) # --- +# name: test_sensors_streaming[sensor.teslemetry_credits-state] + '1980' +# --- # name: test_sensors_streaming[sensor.test_battery_level-state] '90' # --- diff --git a/tests/components/teslemetry/test_sensor.py b/tests/components/teslemetry/test_sensor.py index 213811f6ea0..f50dc93bde4 100644 --- a/tests/components/teslemetry/test_sensor.py +++ b/tests/components/teslemetry/test_sensor.py @@ -73,6 +73,12 @@ async def test_sensors_streaming( Signal.TIME_TO_FULL_CHARGE: 0.166666667, Signal.MINUTES_TO_ARRIVAL: None, }, + "credits": { + "type": "wake_up", + "cost": 20, + "name": "wake_up", + "balance": 1980, + }, "createdAt": "2024-10-04T10:45:17.537Z", } ) @@ -91,6 +97,7 @@ async def test_sensors_streaming( "sensor.test_charge_cable", "sensor.test_time_to_full_charge", "sensor.test_time_to_arrival", + "sensor.teslemetry_credits", ): state = hass.states.get(entity_id) assert state.state == snapshot(name=f"{entity_id}-state")