mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +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 .const import DOMAIN, MODELS
|
||||||
from .coordinator import (
|
from .coordinator import (
|
||||||
|
TeslemetryEnergySiteInfoCoordinator,
|
||||||
TeslemetryEnergySiteLiveCoordinator,
|
TeslemetryEnergySiteLiveCoordinator,
|
||||||
TeslemetryVehicleDataCoordinator,
|
TeslemetryVehicleDataCoordinator,
|
||||||
)
|
)
|
||||||
@ -83,6 +84,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
site_id = product["energy_site_id"]
|
site_id = product["energy_site_id"]
|
||||||
api = EnergySpecific(teslemetry.energy, site_id)
|
api = EnergySpecific(teslemetry.energy, site_id)
|
||||||
live_coordinator = TeslemetryEnergySiteLiveCoordinator(hass, api)
|
live_coordinator = TeslemetryEnergySiteLiveCoordinator(hass, api)
|
||||||
|
info_coordinator = TeslemetryEnergySiteInfoCoordinator(hass, api, product)
|
||||||
device = DeviceInfo(
|
device = DeviceInfo(
|
||||||
identifiers={(DOMAIN, str(site_id))},
|
identifiers={(DOMAIN, str(site_id))},
|
||||||
manufacturer="Tesla",
|
manufacturer="Tesla",
|
||||||
@ -94,6 +96,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
TeslemetryEnergyData(
|
TeslemetryEnergyData(
|
||||||
api=api,
|
api=api,
|
||||||
live_coordinator=live_coordinator,
|
live_coordinator=live_coordinator,
|
||||||
|
info_coordinator=info_coordinator,
|
||||||
id=site_id,
|
id=site_id,
|
||||||
device=device,
|
device=device,
|
||||||
)
|
)
|
||||||
@ -109,6 +112,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
energysite.live_coordinator.async_config_entry_first_refresh()
|
energysite.live_coordinator.async_config_entry_first_refresh()
|
||||||
for energysite in energysites
|
for energysite in energysites
|
||||||
),
|
),
|
||||||
|
*(
|
||||||
|
energysite.info_coordinator.async_config_entry_first_refresh()
|
||||||
|
for energysite in energysites
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Setup Platforms
|
# Setup Platforms
|
||||||
|
@ -111,3 +111,32 @@ class TeslemetryEnergySiteLiveCoordinator(DataUpdateCoordinator[dict[str, Any]])
|
|||||||
}
|
}
|
||||||
|
|
||||||
return data
|
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 .const import DOMAIN, LOGGER, TeslemetryState
|
||||||
from .coordinator import (
|
from .coordinator import (
|
||||||
|
TeslemetryEnergySiteInfoCoordinator,
|
||||||
TeslemetryEnergySiteLiveCoordinator,
|
TeslemetryEnergySiteLiveCoordinator,
|
||||||
TeslemetryVehicleDataCoordinator,
|
TeslemetryVehicleDataCoordinator,
|
||||||
)
|
)
|
||||||
@ -21,7 +22,9 @@ from .models import TeslemetryEnergyData, TeslemetryVehicleData
|
|||||||
|
|
||||||
class TeslemetryEntity(
|
class TeslemetryEntity(
|
||||||
CoordinatorEntity[
|
CoordinatorEntity[
|
||||||
TeslemetryVehicleDataCoordinator | TeslemetryEnergySiteLiveCoordinator
|
TeslemetryVehicleDataCoordinator
|
||||||
|
| TeslemetryEnergySiteLiveCoordinator
|
||||||
|
| TeslemetryEnergySiteInfoCoordinator
|
||||||
]
|
]
|
||||||
):
|
):
|
||||||
"""Parent class for all Teslemetry entities."""
|
"""Parent class for all Teslemetry entities."""
|
||||||
@ -31,7 +34,8 @@ class TeslemetryEntity(
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: TeslemetryVehicleDataCoordinator
|
coordinator: TeslemetryVehicleDataCoordinator
|
||||||
| TeslemetryEnergySiteLiveCoordinator,
|
| TeslemetryEnergySiteLiveCoordinator
|
||||||
|
| TeslemetryEnergySiteInfoCoordinator,
|
||||||
api: VehicleSpecific | EnergySpecific,
|
api: VehicleSpecific | EnergySpecific,
|
||||||
key: str,
|
key: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -172,6 +176,21 @@ class TeslemetryEnergyLiveEntity(TeslemetryEntity):
|
|||||||
super().__init__(data.live_coordinator, data.api, key)
|
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(
|
class TeslemetryWallConnectorEntity(
|
||||||
TeslemetryEntity, CoordinatorEntity[TeslemetryEnergySiteLiveCoordinator]
|
TeslemetryEntity, CoordinatorEntity[TeslemetryEnergySiteLiveCoordinator]
|
||||||
):
|
):
|
||||||
|
@ -11,6 +11,7 @@ from tesla_fleet_api.const import Scope
|
|||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
|
|
||||||
from .coordinator import (
|
from .coordinator import (
|
||||||
|
TeslemetryEnergySiteInfoCoordinator,
|
||||||
TeslemetryEnergySiteLiveCoordinator,
|
TeslemetryEnergySiteLiveCoordinator,
|
||||||
TeslemetryVehicleDataCoordinator,
|
TeslemetryVehicleDataCoordinator,
|
||||||
)
|
)
|
||||||
@ -42,5 +43,6 @@ class TeslemetryEnergyData:
|
|||||||
|
|
||||||
api: EnergySpecific
|
api: EnergySpecific
|
||||||
live_coordinator: TeslemetryEnergySiteLiveCoordinator
|
live_coordinator: TeslemetryEnergySiteLiveCoordinator
|
||||||
|
info_coordinator: TeslemetryEnergySiteInfoCoordinator
|
||||||
id: int
|
id: int
|
||||||
device: DeviceInfo
|
device: DeviceInfo
|
||||||
|
@ -36,6 +36,7 @@ from homeassistant.util.variance import ignore_variance
|
|||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .entity import (
|
from .entity import (
|
||||||
|
TeslemetryEnergyInfoEntity,
|
||||||
TeslemetryEnergyLiveEntity,
|
TeslemetryEnergyLiveEntity,
|
||||||
TeslemetryVehicleEntity,
|
TeslemetryVehicleEntity,
|
||||||
TeslemetryWallConnectorEntity,
|
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(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
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 din in energysite.live_coordinator.data.get("wall_connectors", {})
|
||||||
for description in WALL_CONNECTOR_DESCRIPTIONS
|
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."""
|
"""Update the attributes of the sensor."""
|
||||||
self._attr_available = not self.is_none
|
self._attr_available = not self.is_none
|
||||||
self._attr_native_value = self._value
|
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": {
|
"vehicle_state_tpms_pressure_rr": {
|
||||||
"name": "Tire pressure rear right"
|
"name": "Tire pressure rear right"
|
||||||
},
|
},
|
||||||
|
"version": {
|
||||||
|
"name": "version"
|
||||||
|
},
|
||||||
"vin": {
|
"vin": {
|
||||||
"name": "Vehicle"
|
"name": "Vehicle"
|
||||||
},
|
},
|
||||||
|
"vpp_backup_reserve_percent": {
|
||||||
|
"name": "VPP backup reserve"
|
||||||
|
},
|
||||||
"wall_connector_fault_state": {
|
"wall_connector_fault_state": {
|
||||||
"name": "Fault state code"
|
"name": "Fault state code"
|
||||||
},
|
},
|
||||||
|
@ -714,6 +714,128 @@
|
|||||||
'state': '40.727',
|
'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]
|
# name: test_sensors[sensor.test_battery_level-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
@ -82,3 +82,14 @@ async def test_energy_live_refresh_error(
|
|||||||
mock_live_status.side_effect = side_effect
|
mock_live_status.side_effect = side_effect
|
||||||
entry = await setup_platform(hass)
|
entry = await setup_platform(hass)
|
||||||
assert entry.state is state
|
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