mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Add energy site coordinator to Teslemetry (#117184)
* Add energy site coordinator * Add missing string * Add another missing string * Aprettier
This commit is contained in:
parent
55c4ba12f6
commit
62d70b1b10
@ -20,6 +20,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
|
||||
|
||||
from .const import DOMAIN, MODELS
|
||||
from .coordinator import (
|
||||
TeslemetryEnergySiteInfoCoordinator,
|
||||
TeslemetryEnergySiteLiveCoordinator,
|
||||
TeslemetryVehicleDataCoordinator,
|
||||
)
|
||||
@ -83,6 +84,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
site_id = product["energy_site_id"]
|
||||
api = EnergySpecific(teslemetry.energy, site_id)
|
||||
live_coordinator = TeslemetryEnergySiteLiveCoordinator(hass, api)
|
||||
info_coordinator = TeslemetryEnergySiteInfoCoordinator(hass, api, product)
|
||||
device = DeviceInfo(
|
||||
identifiers={(DOMAIN, str(site_id))},
|
||||
manufacturer="Tesla",
|
||||
@ -94,6 +96,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
TeslemetryEnergyData(
|
||||
api=api,
|
||||
live_coordinator=live_coordinator,
|
||||
info_coordinator=info_coordinator,
|
||||
id=site_id,
|
||||
device=device,
|
||||
)
|
||||
@ -109,6 +112,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
energysite.live_coordinator.async_config_entry_first_refresh()
|
||||
for energysite in energysites
|
||||
),
|
||||
*(
|
||||
energysite.info_coordinator.async_config_entry_first_refresh()
|
||||
for energysite in energysites
|
||||
),
|
||||
)
|
||||
|
||||
# Setup Platforms
|
||||
|
@ -111,3 +111,32 @@ class TeslemetryEnergySiteLiveCoordinator(DataUpdateCoordinator[dict[str, Any]])
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class TeslemetryEnergySiteInfoCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
"""Class to manage fetching energy site info from the Teslemetry API."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, api: EnergySpecific, product: dict) -> None:
|
||||
"""Initialize Teslemetry Energy Info coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
LOGGER,
|
||||
name="Teslemetry Energy Site Info",
|
||||
update_interval=ENERGY_INFO_INTERVAL,
|
||||
)
|
||||
self.api = api
|
||||
self.data = product
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Update energy site data using Teslemetry API."""
|
||||
|
||||
try:
|
||||
data = (await self.api.site_info())["response"]
|
||||
except InvalidToken as e:
|
||||
raise ConfigEntryAuthFailed from e
|
||||
except SubscriptionRequired as e:
|
||||
raise ConfigEntryAuthFailed from e
|
||||
except TeslaFleetError as e:
|
||||
raise UpdateFailed(e.message) from e
|
||||
|
||||
return flatten(data)
|
||||
|
@ -13,6 +13,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, LOGGER, TeslemetryState
|
||||
from .coordinator import (
|
||||
TeslemetryEnergySiteInfoCoordinator,
|
||||
TeslemetryEnergySiteLiveCoordinator,
|
||||
TeslemetryVehicleDataCoordinator,
|
||||
)
|
||||
@ -21,7 +22,9 @@ from .models import TeslemetryEnergyData, TeslemetryVehicleData
|
||||
|
||||
class TeslemetryEntity(
|
||||
CoordinatorEntity[
|
||||
TeslemetryVehicleDataCoordinator | TeslemetryEnergySiteLiveCoordinator
|
||||
TeslemetryVehicleDataCoordinator
|
||||
| TeslemetryEnergySiteLiveCoordinator
|
||||
| TeslemetryEnergySiteInfoCoordinator
|
||||
]
|
||||
):
|
||||
"""Parent class for all Teslemetry entities."""
|
||||
@ -31,7 +34,8 @@ class TeslemetryEntity(
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: TeslemetryVehicleDataCoordinator
|
||||
| TeslemetryEnergySiteLiveCoordinator,
|
||||
| TeslemetryEnergySiteLiveCoordinator
|
||||
| TeslemetryEnergySiteInfoCoordinator,
|
||||
api: VehicleSpecific | EnergySpecific,
|
||||
key: str,
|
||||
) -> None:
|
||||
@ -172,6 +176,21 @@ class TeslemetryEnergyLiveEntity(TeslemetryEntity):
|
||||
super().__init__(data.live_coordinator, data.api, key)
|
||||
|
||||
|
||||
class TeslemetryEnergyInfoEntity(TeslemetryEntity):
|
||||
"""Parent class for Teslemetry Energy Site Info Entities."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: TeslemetryEnergyData,
|
||||
key: str,
|
||||
) -> None:
|
||||
"""Initialize common aspects of a Teslemetry Energy Site Info entity."""
|
||||
self._attr_unique_id = f"{data.id}-{key}"
|
||||
self._attr_device_info = data.device
|
||||
|
||||
super().__init__(data.info_coordinator, data.api, key)
|
||||
|
||||
|
||||
class TeslemetryWallConnectorEntity(
|
||||
TeslemetryEntity, CoordinatorEntity[TeslemetryEnergySiteLiveCoordinator]
|
||||
):
|
||||
|
@ -11,6 +11,7 @@ from tesla_fleet_api.const import Scope
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
|
||||
from .coordinator import (
|
||||
TeslemetryEnergySiteInfoCoordinator,
|
||||
TeslemetryEnergySiteLiveCoordinator,
|
||||
TeslemetryVehicleDataCoordinator,
|
||||
)
|
||||
@ -42,5 +43,6 @@ class TeslemetryEnergyData:
|
||||
|
||||
api: EnergySpecific
|
||||
live_coordinator: TeslemetryEnergySiteLiveCoordinator
|
||||
info_coordinator: TeslemetryEnergySiteInfoCoordinator
|
||||
id: int
|
||||
device: DeviceInfo
|
||||
|
@ -36,6 +36,7 @@ from homeassistant.util.variance import ignore_variance
|
||||
|
||||
from .const import DOMAIN
|
||||
from .entity import (
|
||||
TeslemetryEnergyInfoEntity,
|
||||
TeslemetryEnergyLiveEntity,
|
||||
TeslemetryVehicleEntity,
|
||||
TeslemetryWallConnectorEntity,
|
||||
@ -401,6 +402,16 @@ WALL_CONNECTOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
|
||||
),
|
||||
)
|
||||
|
||||
ENERGY_INFO_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key="vpp_backup_reserve_percent",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
),
|
||||
SensorEntityDescription(key="version"),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
@ -432,6 +443,12 @@ async def async_setup_entry(
|
||||
for din in energysite.live_coordinator.data.get("wall_connectors", {})
|
||||
for description in WALL_CONNECTOR_DESCRIPTIONS
|
||||
),
|
||||
( # Add energy site info
|
||||
TeslemetryEnergyInfoSensorEntity(energysite, description)
|
||||
for energysite in data.energysites
|
||||
for description in ENERGY_INFO_DESCRIPTIONS
|
||||
if description.key in energysite.info_coordinator.data
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@ -527,3 +544,23 @@ class TeslemetryWallConnectorSensorEntity(TeslemetryWallConnectorEntity, SensorE
|
||||
"""Update the attributes of the sensor."""
|
||||
self._attr_available = not self.is_none
|
||||
self._attr_native_value = self._value
|
||||
|
||||
|
||||
class TeslemetryEnergyInfoSensorEntity(TeslemetryEnergyInfoEntity, SensorEntity):
|
||||
"""Base class for Teslemetry energy site metric sensors."""
|
||||
|
||||
entity_description: SensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: TeslemetryEnergyData,
|
||||
description: SensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
self.entity_description = description
|
||||
super().__init__(data, description.key)
|
||||
|
||||
def _async_update_attrs(self) -> None:
|
||||
"""Update the attributes of the sensor."""
|
||||
self._attr_available = not self.is_none
|
||||
self._attr_native_value = self._value
|
||||
|
@ -166,9 +166,15 @@
|
||||
"vehicle_state_tpms_pressure_rr": {
|
||||
"name": "Tire pressure rear right"
|
||||
},
|
||||
"version": {
|
||||
"name": "version"
|
||||
},
|
||||
"vin": {
|
||||
"name": "Vehicle"
|
||||
},
|
||||
"vpp_backup_reserve_percent": {
|
||||
"name": "VPP backup reserve"
|
||||
},
|
||||
"wall_connector_fault_state": {
|
||||
"name": "Fault state code"
|
||||
},
|
||||
|
@ -714,6 +714,128 @@
|
||||
'state': '40.727',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_version-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.energy_site_version',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'version',
|
||||
'platform': 'teslemetry',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'version',
|
||||
'unique_id': '123456-version',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_version-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Energy Site version',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.energy_site_version',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '23.44.0 eb113390',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_version-statealt]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Energy Site version',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.energy_site_version',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '23.44.0 eb113390',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_vpp_backup_reserve-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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.energy_site_vpp_backup_reserve',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'VPP backup reserve',
|
||||
'platform': 'teslemetry',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'vpp_backup_reserve_percent',
|
||||
'unique_id': '123456-vpp_backup_reserve_percent',
|
||||
'unit_of_measurement': '%',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_vpp_backup_reserve-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'battery',
|
||||
'friendly_name': 'Energy Site VPP backup reserve',
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.energy_site_vpp_backup_reserve',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_vpp_backup_reserve-statealt]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'battery',
|
||||
'friendly_name': 'Energy Site VPP backup reserve',
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.energy_site_vpp_backup_reserve',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.test_battery_level-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
@ -82,3 +82,14 @@ async def test_energy_live_refresh_error(
|
||||
mock_live_status.side_effect = side_effect
|
||||
entry = await setup_platform(hass)
|
||||
assert entry.state is state
|
||||
|
||||
|
||||
# Test Energy Site Coordinator
|
||||
@pytest.mark.parametrize(("side_effect", "state"), ERRORS)
|
||||
async def test_energy_site_refresh_error(
|
||||
hass: HomeAssistant, mock_site_info, side_effect, state
|
||||
) -> None:
|
||||
"""Test coordinator refresh with an error."""
|
||||
mock_site_info.side_effect = side_effect
|
||||
entry = await setup_platform(hass)
|
||||
assert entry.state is state
|
||||
|
Loading…
x
Reference in New Issue
Block a user