mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Add energy history coordinator and sensors to Teslemetry (#126166)
* start * More * fix init * Update requirements_all.txt * Update requirements_test_all.txt * Add Tests * Add missing fixture * first refresh history * Fix mock_energy_history * Remove failures prop * Update test_init.py * Actually add the sensors * Add more icons * suggested_display_precision * Fix updated_once * Fix fixture * Review changes * Apply suggestions from code review Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Remove init data * Update homeassistant/components/teslemetry/coordinator.py * ruff --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
4c0fb04f61
commit
5186605cec
@ -23,6 +23,7 @@ from homeassistant.helpers.typing import ConfigType
|
|||||||
|
|
||||||
from .const import DOMAIN, LOGGER, MODELS
|
from .const import DOMAIN, LOGGER, MODELS
|
||||||
from .coordinator import (
|
from .coordinator import (
|
||||||
|
TeslemetryEnergyHistoryCoordinator,
|
||||||
TeslemetryEnergySiteInfoCoordinator,
|
TeslemetryEnergySiteInfoCoordinator,
|
||||||
TeslemetryEnergySiteLiveCoordinator,
|
TeslemetryEnergySiteLiveCoordinator,
|
||||||
TeslemetryVehicleDataCoordinator,
|
TeslemetryVehicleDataCoordinator,
|
||||||
@ -120,8 +121,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
api = EnergySpecific(teslemetry.energy, site_id)
|
api = EnergySpecific(teslemetry.energy, site_id)
|
||||||
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",
|
||||||
@ -133,8 +132,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
|||||||
energysites.append(
|
energysites.append(
|
||||||
TeslemetryEnergyData(
|
TeslemetryEnergyData(
|
||||||
api=api,
|
api=api,
|
||||||
live_coordinator=live_coordinator,
|
live_coordinator=TeslemetryEnergySiteLiveCoordinator(hass, api),
|
||||||
info_coordinator=info_coordinator,
|
info_coordinator=TeslemetryEnergySiteInfoCoordinator(
|
||||||
|
hass, api, product
|
||||||
|
),
|
||||||
|
history_coordinator=TeslemetryEnergyHistoryCoordinator(hass, api),
|
||||||
id=site_id,
|
id=site_id,
|
||||||
device=device,
|
device=device,
|
||||||
)
|
)
|
||||||
@ -154,6 +156,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
|||||||
energysite.info_coordinator.async_config_entry_first_refresh()
|
energysite.info_coordinator.async_config_entry_first_refresh()
|
||||||
for energysite in energysites
|
for energysite in energysites
|
||||||
),
|
),
|
||||||
|
*(
|
||||||
|
energysite.history_coordinator.async_config_entry_first_refresh()
|
||||||
|
for energysite in energysites
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add energy device models
|
# Add energy device models
|
||||||
|
@ -16,6 +16,30 @@ MODELS = {
|
|||||||
"Y": "Model Y",
|
"Y": "Model Y",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ENERGY_HISTORY_FIELDS = [
|
||||||
|
"solar_energy_exported",
|
||||||
|
"generator_energy_exported",
|
||||||
|
"grid_energy_imported",
|
||||||
|
"grid_services_energy_imported",
|
||||||
|
"grid_services_energy_exported",
|
||||||
|
"grid_energy_exported_from_solar",
|
||||||
|
"grid_energy_exported_from_generator",
|
||||||
|
"grid_energy_exported_from_battery",
|
||||||
|
"battery_energy_exported",
|
||||||
|
"battery_energy_imported_from_grid",
|
||||||
|
"battery_energy_imported_from_solar",
|
||||||
|
"battery_energy_imported_from_generator",
|
||||||
|
"consumer_energy_imported_from_grid",
|
||||||
|
"consumer_energy_imported_from_solar",
|
||||||
|
"consumer_energy_imported_from_battery",
|
||||||
|
"consumer_energy_imported_from_generator",
|
||||||
|
"total_home_usage",
|
||||||
|
"total_battery_charge",
|
||||||
|
"total_battery_discharge",
|
||||||
|
"total_solar_generation",
|
||||||
|
"total_grid_energy_exported",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class TeslemetryState(StrEnum):
|
class TeslemetryState(StrEnum):
|
||||||
"""Teslemetry Vehicle States."""
|
"""Teslemetry Vehicle States."""
|
||||||
|
@ -4,7 +4,7 @@ from datetime import datetime, timedelta
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from tesla_fleet_api import EnergySpecific, VehicleSpecific
|
from tesla_fleet_api import EnergySpecific, VehicleSpecific
|
||||||
from tesla_fleet_api.const import VehicleDataEndpoint
|
from tesla_fleet_api.const import TeslaEnergyPeriod, VehicleDataEndpoint
|
||||||
from tesla_fleet_api.exceptions import (
|
from tesla_fleet_api.exceptions import (
|
||||||
Forbidden,
|
Forbidden,
|
||||||
InvalidToken,
|
InvalidToken,
|
||||||
@ -17,12 +17,13 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import LOGGER, TeslemetryState
|
from .const import ENERGY_HISTORY_FIELDS, LOGGER, TeslemetryState
|
||||||
|
|
||||||
VEHICLE_INTERVAL = timedelta(seconds=30)
|
VEHICLE_INTERVAL = timedelta(seconds=30)
|
||||||
VEHICLE_WAIT = timedelta(minutes=15)
|
VEHICLE_WAIT = timedelta(minutes=15)
|
||||||
ENERGY_LIVE_INTERVAL = timedelta(seconds=30)
|
ENERGY_LIVE_INTERVAL = timedelta(seconds=30)
|
||||||
ENERGY_INFO_INTERVAL = timedelta(seconds=30)
|
ENERGY_INFO_INTERVAL = timedelta(seconds=30)
|
||||||
|
ENERGY_HISTORY_INTERVAL = timedelta(seconds=60)
|
||||||
|
|
||||||
ENDPOINTS = [
|
ENDPOINTS = [
|
||||||
VehicleDataEndpoint.CHARGE_STATE,
|
VehicleDataEndpoint.CHARGE_STATE,
|
||||||
@ -178,3 +179,39 @@ class TeslemetryEnergySiteInfoCoordinator(DataUpdateCoordinator[dict[str, Any]])
|
|||||||
raise UpdateFailed(e.message) from e
|
raise UpdateFailed(e.message) from e
|
||||||
|
|
||||||
return flatten(data)
|
return flatten(data)
|
||||||
|
|
||||||
|
|
||||||
|
class TeslemetryEnergyHistoryCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
|
"""Class to manage fetching energy site info from the Teslemetry API."""
|
||||||
|
|
||||||
|
updated_once: bool
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant, api: EnergySpecific) -> None:
|
||||||
|
"""Initialize Teslemetry Energy Info coordinator."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
LOGGER,
|
||||||
|
name=f"Teslemetry Energy History {api.energy_site_id}",
|
||||||
|
update_interval=ENERGY_HISTORY_INTERVAL,
|
||||||
|
)
|
||||||
|
self.api = api
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> dict[str, Any]:
|
||||||
|
"""Update energy site data using Teslemetry API."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = (await self.api.energy_history(TeslaEnergyPeriod.DAY))["response"]
|
||||||
|
except (InvalidToken, Forbidden, SubscriptionRequired) as e:
|
||||||
|
raise ConfigEntryAuthFailed from e
|
||||||
|
except TeslaFleetError as e:
|
||||||
|
raise UpdateFailed(e.message) from e
|
||||||
|
|
||||||
|
self.updated_once = True
|
||||||
|
|
||||||
|
# Add all time periods together
|
||||||
|
output = {key: 0 for key in ENERGY_HISTORY_FIELDS}
|
||||||
|
for period in data.get("time_series", []):
|
||||||
|
for key in ENERGY_HISTORY_FIELDS:
|
||||||
|
output[key] += period.get(key, 0)
|
||||||
|
|
||||||
|
return output
|
||||||
|
@ -11,6 +11,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
|||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import (
|
from .coordinator import (
|
||||||
|
TeslemetryEnergyHistoryCoordinator,
|
||||||
TeslemetryEnergySiteInfoCoordinator,
|
TeslemetryEnergySiteInfoCoordinator,
|
||||||
TeslemetryEnergySiteLiveCoordinator,
|
TeslemetryEnergySiteLiveCoordinator,
|
||||||
TeslemetryVehicleDataCoordinator,
|
TeslemetryVehicleDataCoordinator,
|
||||||
@ -22,6 +23,7 @@ from .models import TeslemetryEnergyData, TeslemetryVehicleData
|
|||||||
class TeslemetryEntity(
|
class TeslemetryEntity(
|
||||||
CoordinatorEntity[
|
CoordinatorEntity[
|
||||||
TeslemetryVehicleDataCoordinator
|
TeslemetryVehicleDataCoordinator
|
||||||
|
| TeslemetryEnergyHistoryCoordinator
|
||||||
| TeslemetryEnergySiteLiveCoordinator
|
| TeslemetryEnergySiteLiveCoordinator
|
||||||
| TeslemetryEnergySiteInfoCoordinator
|
| TeslemetryEnergySiteInfoCoordinator
|
||||||
]
|
]
|
||||||
@ -33,6 +35,7 @@ class TeslemetryEntity(
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: TeslemetryVehicleDataCoordinator
|
coordinator: TeslemetryVehicleDataCoordinator
|
||||||
|
| TeslemetryEnergyHistoryCoordinator
|
||||||
| TeslemetryEnergySiteLiveCoordinator
|
| TeslemetryEnergySiteLiveCoordinator
|
||||||
| TeslemetryEnergySiteInfoCoordinator,
|
| TeslemetryEnergySiteInfoCoordinator,
|
||||||
api: VehicleSpecific | EnergySpecific,
|
api: VehicleSpecific | EnergySpecific,
|
||||||
@ -148,6 +151,21 @@ class TeslemetryEnergyInfoEntity(TeslemetryEntity):
|
|||||||
super().__init__(data.info_coordinator, data.api, key)
|
super().__init__(data.info_coordinator, data.api, key)
|
||||||
|
|
||||||
|
|
||||||
|
class TeslemetryEnergyHistoryEntity(TeslemetryEntity):
|
||||||
|
"""Parent class for Teslemetry Energy History 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.history_coordinator, data.api, key)
|
||||||
|
|
||||||
|
|
||||||
class TeslemetryWallConnectorEntity(
|
class TeslemetryWallConnectorEntity(
|
||||||
TeslemetryEntity, CoordinatorEntity[TeslemetryEnergySiteLiveCoordinator]
|
TeslemetryEntity, CoordinatorEntity[TeslemetryEnergySiteLiveCoordinator]
|
||||||
):
|
):
|
||||||
|
@ -219,6 +219,69 @@
|
|||||||
},
|
},
|
||||||
"wall_connector_state": {
|
"wall_connector_state": {
|
||||||
"default": "mdi:ev-station"
|
"default": "mdi:ev-station"
|
||||||
|
},
|
||||||
|
"total_home_usage": {
|
||||||
|
"default": "mdi:home-lightning-bolt"
|
||||||
|
},
|
||||||
|
"total_battery_charge": {
|
||||||
|
"default": "mdi:battery-arrow-up"
|
||||||
|
},
|
||||||
|
"total_battery_discharge": {
|
||||||
|
"default": "mdi:battery-arrow-down"
|
||||||
|
},
|
||||||
|
"total_solar_production": {
|
||||||
|
"default": "mdi:solar-power-variant"
|
||||||
|
},
|
||||||
|
"grid_energy_imported": {
|
||||||
|
"default": "mdi:transmission-tower-import"
|
||||||
|
},
|
||||||
|
"total_grid_energy_exported": {
|
||||||
|
"default": "mdi:transmission-tower-export"
|
||||||
|
},
|
||||||
|
"solar_energy_exported": {
|
||||||
|
"default": "mdi:solar-power-variant"
|
||||||
|
},
|
||||||
|
"generator_energy_exported": {
|
||||||
|
"default": "mdi:generator-stationary"
|
||||||
|
},
|
||||||
|
"grid_services_energy_imported": {
|
||||||
|
"default": "mdi:transmission-tower-import"
|
||||||
|
},
|
||||||
|
"grid_services_energy_exported": {
|
||||||
|
"default": "mdi:transmission-tower-export"
|
||||||
|
},
|
||||||
|
"grid_energy_exported_from_solar": {
|
||||||
|
"default": "mdi:solar-power"
|
||||||
|
},
|
||||||
|
"grid_energy_exported_from_generator": {
|
||||||
|
"default": "mdi:generator-stationary"
|
||||||
|
},
|
||||||
|
"grid_energy_exported_from_battery": {
|
||||||
|
"default": "mdi:battery-arrow-down"
|
||||||
|
},
|
||||||
|
"battery_energy_exported": {
|
||||||
|
"default": "mdi:battery-arrow-down"
|
||||||
|
},
|
||||||
|
"battery_energy_imported_from_grid": {
|
||||||
|
"default": "mdi:transmission-tower-import"
|
||||||
|
},
|
||||||
|
"battery_energy_imported_from_solar": {
|
||||||
|
"default": "mdi:solar-power"
|
||||||
|
},
|
||||||
|
"battery_energy_imported_from_generator": {
|
||||||
|
"default": "mdi:generator-stationary"
|
||||||
|
},
|
||||||
|
"consumer_energy_imported_from_grid": {
|
||||||
|
"default": "mdi:transmission-tower-import"
|
||||||
|
},
|
||||||
|
"consumer_energy_imported_from_solar": {
|
||||||
|
"default": "mdi:solar-power"
|
||||||
|
},
|
||||||
|
"consumer_energy_imported_from_battery": {
|
||||||
|
"default": "mdi:home-battery"
|
||||||
|
},
|
||||||
|
"consumer_energy_imported_from_generator": {
|
||||||
|
"default": "mdi:generator-stationary"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"switch": {
|
"switch": {
|
||||||
|
@ -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 (
|
||||||
|
TeslemetryEnergyHistoryCoordinator,
|
||||||
TeslemetryEnergySiteInfoCoordinator,
|
TeslemetryEnergySiteInfoCoordinator,
|
||||||
TeslemetryEnergySiteLiveCoordinator,
|
TeslemetryEnergySiteLiveCoordinator,
|
||||||
TeslemetryVehicleDataCoordinator,
|
TeslemetryVehicleDataCoordinator,
|
||||||
@ -44,5 +45,6 @@ class TeslemetryEnergyData:
|
|||||||
api: EnergySpecific
|
api: EnergySpecific
|
||||||
live_coordinator: TeslemetryEnergySiteLiveCoordinator
|
live_coordinator: TeslemetryEnergySiteLiveCoordinator
|
||||||
info_coordinator: TeslemetryEnergySiteInfoCoordinator
|
info_coordinator: TeslemetryEnergySiteInfoCoordinator
|
||||||
|
history_coordinator: TeslemetryEnergyHistoryCoordinator
|
||||||
id: int
|
id: int
|
||||||
device: DeviceInfo
|
device: DeviceInfo
|
||||||
|
@ -34,7 +34,9 @@ from homeassistant.util import dt as dt_util
|
|||||||
from homeassistant.util.variance import ignore_variance
|
from homeassistant.util.variance import ignore_variance
|
||||||
|
|
||||||
from . import TeslemetryConfigEntry
|
from . import TeslemetryConfigEntry
|
||||||
|
from .const import ENERGY_HISTORY_FIELDS
|
||||||
from .entity import (
|
from .entity import (
|
||||||
|
TeslemetryEnergyHistoryEntity,
|
||||||
TeslemetryEnergyInfoEntity,
|
TeslemetryEnergyInfoEntity,
|
||||||
TeslemetryEnergyLiveEntity,
|
TeslemetryEnergyLiveEntity,
|
||||||
TeslemetryVehicleEntity,
|
TeslemetryVehicleEntity,
|
||||||
@ -414,6 +416,21 @@ ENERGY_INFO_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
|
|||||||
SensorEntityDescription(key="version"),
|
SensorEntityDescription(key="version"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ENERGY_HISTORY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = tuple(
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=key,
|
||||||
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
|
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||||
|
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
|
entity_registry_enabled_default=(
|
||||||
|
key.startswith("total") or key == "grid_energy_imported"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for key in ENERGY_HISTORY_FIELDS
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -451,6 +468,13 @@ async def async_setup_entry(
|
|||||||
for description in ENERGY_INFO_DESCRIPTIONS
|
for description in ENERGY_INFO_DESCRIPTIONS
|
||||||
if description.key in energysite.info_coordinator.data
|
if description.key in energysite.info_coordinator.data
|
||||||
),
|
),
|
||||||
|
( # Add energy history sensor
|
||||||
|
TeslemetryEnergyHistorySensorEntity(energysite, description)
|
||||||
|
for energysite in entry.runtime_data.energysites
|
||||||
|
for description in ENERGY_HISTORY_DESCRIPTIONS
|
||||||
|
if energysite.info_coordinator.data.get("components_battery")
|
||||||
|
or energysite.info_coordinator.data.get("components_solar")
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -566,3 +590,22 @@ class TeslemetryEnergyInfoSensorEntity(TeslemetryEnergyInfoEntity, SensorEntity)
|
|||||||
"""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 TeslemetryEnergyHistorySensorEntity(TeslemetryEnergyHistoryEntity, SensorEntity):
|
||||||
|
"""Base class for Tesla Fleet 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_native_value = self._value
|
||||||
|
@ -436,6 +436,69 @@
|
|||||||
},
|
},
|
||||||
"wall_connector_state": {
|
"wall_connector_state": {
|
||||||
"name": "State code"
|
"name": "State code"
|
||||||
|
},
|
||||||
|
"solar_energy_exported": {
|
||||||
|
"name": "Solar exported"
|
||||||
|
},
|
||||||
|
"generator_energy_exported": {
|
||||||
|
"name": "Generator exported"
|
||||||
|
},
|
||||||
|
"grid_energy_imported": {
|
||||||
|
"name": "Grid imported"
|
||||||
|
},
|
||||||
|
"grid_services_energy_imported": {
|
||||||
|
"name": "Grid services imported"
|
||||||
|
},
|
||||||
|
"grid_services_energy_exported": {
|
||||||
|
"name": "Grid services exported"
|
||||||
|
},
|
||||||
|
"grid_energy_exported_from_solar": {
|
||||||
|
"name": "Grid exported from solar"
|
||||||
|
},
|
||||||
|
"grid_energy_exported_from_generator": {
|
||||||
|
"name": "Grid exported from generator"
|
||||||
|
},
|
||||||
|
"grid_energy_exported_from_battery": {
|
||||||
|
"name": "Grid exported from battery"
|
||||||
|
},
|
||||||
|
"battery_energy_exported": {
|
||||||
|
"name": "Battery exported"
|
||||||
|
},
|
||||||
|
"battery_energy_imported_from_grid": {
|
||||||
|
"name": "Battery imported from grid"
|
||||||
|
},
|
||||||
|
"battery_energy_imported_from_solar": {
|
||||||
|
"name": "Battery imported from solar"
|
||||||
|
},
|
||||||
|
"battery_energy_imported_from_generator": {
|
||||||
|
"name": "Battery imported from generator"
|
||||||
|
},
|
||||||
|
"consumer_energy_imported_from_grid": {
|
||||||
|
"name": "Consumer imported from grid"
|
||||||
|
},
|
||||||
|
"consumer_energy_imported_from_solar": {
|
||||||
|
"name": "Consumer imported from solar"
|
||||||
|
},
|
||||||
|
"consumer_energy_imported_from_battery": {
|
||||||
|
"name": "Consumer imported from battery"
|
||||||
|
},
|
||||||
|
"consumer_energy_imported_from_generator": {
|
||||||
|
"name": "Consumer imported from generator"
|
||||||
|
},
|
||||||
|
"total_home_usage": {
|
||||||
|
"name": "Home usage"
|
||||||
|
},
|
||||||
|
"total_battery_charge": {
|
||||||
|
"name": "Battery charged"
|
||||||
|
},
|
||||||
|
"total_battery_discharge": {
|
||||||
|
"name": "Battery discharged"
|
||||||
|
},
|
||||||
|
"total_solar_generation": {
|
||||||
|
"name": "Solar generated"
|
||||||
|
},
|
||||||
|
"total_grid_energy_exported": {
|
||||||
|
"name": "Grid exported"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"switch": {
|
"switch": {
|
||||||
|
@ -10,6 +10,7 @@ import pytest
|
|||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
COMMAND_OK,
|
COMMAND_OK,
|
||||||
|
ENERGY_HISTORY,
|
||||||
LIVE_STATUS,
|
LIVE_STATUS,
|
||||||
METADATA,
|
METADATA,
|
||||||
PRODUCTS,
|
PRODUCTS,
|
||||||
@ -95,3 +96,13 @@ def mock_site_info():
|
|||||||
side_effect=lambda: deepcopy(SITE_INFO),
|
side_effect=lambda: deepcopy(SITE_INFO),
|
||||||
) as mock_live_status:
|
) as mock_live_status:
|
||||||
yield mock_live_status
|
yield mock_live_status
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_energy_history():
|
||||||
|
"""Mock Teslemetry Energy Specific site_info method."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.teslemetry.EnergySpecific.energy_history",
|
||||||
|
return_value=ENERGY_HISTORY,
|
||||||
|
) as mock_live_status:
|
||||||
|
yield mock_live_status
|
||||||
|
@ -15,6 +15,7 @@ VEHICLE_DATA = load_json_object_fixture("vehicle_data.json", DOMAIN)
|
|||||||
VEHICLE_DATA_ALT = load_json_object_fixture("vehicle_data_alt.json", DOMAIN)
|
VEHICLE_DATA_ALT = load_json_object_fixture("vehicle_data_alt.json", DOMAIN)
|
||||||
LIVE_STATUS = load_json_object_fixture("live_status.json", DOMAIN)
|
LIVE_STATUS = load_json_object_fixture("live_status.json", DOMAIN)
|
||||||
SITE_INFO = load_json_object_fixture("site_info.json", DOMAIN)
|
SITE_INFO = load_json_object_fixture("site_info.json", DOMAIN)
|
||||||
|
ENERGY_HISTORY = load_json_object_fixture("energy_history.json", DOMAIN)
|
||||||
|
|
||||||
COMMAND_OK = {"response": {"result": True, "reason": ""}}
|
COMMAND_OK = {"response": {"result": True, "reason": ""}}
|
||||||
COMMAND_REASON = {"response": {"result": False, "reason": "already closed"}}
|
COMMAND_REASON = {"response": {"result": False, "reason": "already closed"}}
|
||||||
|
55
tests/components/teslemetry/fixtures/energy_history.json
Normal file
55
tests/components/teslemetry/fixtures/energy_history.json
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"serial_number": "xxxxxx",
|
||||||
|
"period": "day",
|
||||||
|
"installation_time_zone": "Australia/Brisbane",
|
||||||
|
"time_series": [
|
||||||
|
{
|
||||||
|
"timestamp": "2024-09-18T00:00:00+10:00",
|
||||||
|
"solar_energy_exported": 0,
|
||||||
|
"generator_energy_exported": 0,
|
||||||
|
"grid_energy_imported": 0,
|
||||||
|
"grid_services_energy_imported": 0,
|
||||||
|
"grid_services_energy_exported": 0,
|
||||||
|
"grid_energy_exported_from_solar": 0,
|
||||||
|
"grid_energy_exported_from_generator": 0,
|
||||||
|
"grid_energy_exported_from_battery": 0,
|
||||||
|
"battery_energy_exported": 36,
|
||||||
|
"battery_energy_imported_from_grid": 0,
|
||||||
|
"battery_energy_imported_from_solar": 0,
|
||||||
|
"battery_energy_imported_from_generator": 0,
|
||||||
|
"consumer_energy_imported_from_grid": 0,
|
||||||
|
"consumer_energy_imported_from_solar": 0,
|
||||||
|
"consumer_energy_imported_from_battery": 36,
|
||||||
|
"consumer_energy_imported_from_generator": 0,
|
||||||
|
"raw_timestamp": "2024-09-18T00:00:00+10:00",
|
||||||
|
"total_home_usage": 36,
|
||||||
|
"total_battery_discharge": 36
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2024-09-18T08:45:00+10:00",
|
||||||
|
"solar_energy_exported": 724,
|
||||||
|
"generator_energy_exported": 0,
|
||||||
|
"grid_energy_imported": 0,
|
||||||
|
"grid_services_energy_imported": 0,
|
||||||
|
"grid_services_energy_exported": 0,
|
||||||
|
"grid_energy_exported_from_solar": 2,
|
||||||
|
"grid_energy_exported_from_generator": 0,
|
||||||
|
"grid_energy_exported_from_battery": 0,
|
||||||
|
"battery_energy_exported": 0,
|
||||||
|
"battery_energy_imported_from_grid": 0,
|
||||||
|
"battery_energy_imported_from_solar": 684,
|
||||||
|
"battery_energy_imported_from_generator": 0,
|
||||||
|
"consumer_energy_imported_from_grid": 0,
|
||||||
|
"consumer_energy_imported_from_solar": 38,
|
||||||
|
"consumer_energy_imported_from_battery": 0,
|
||||||
|
"consumer_energy_imported_from_generator": 0,
|
||||||
|
"raw_timestamp": "2024-09-18T08:45:00+10:00",
|
||||||
|
"total_home_usage": 38,
|
||||||
|
"total_solar_generation": 724,
|
||||||
|
"total_battery_charge": 684,
|
||||||
|
"total_grid_energy_exported": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -188,3 +188,17 @@ async def test_energy_site_refresh_error(
|
|||||||
mock_site_info.side_effect = side_effect
|
mock_site_info.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 History Coordinator
|
||||||
|
@pytest.mark.parametrize(("side_effect", "state"), ERRORS)
|
||||||
|
async def test_energy_history_refresh_error(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_energy_history: AsyncMock,
|
||||||
|
side_effect: TeslaFleetError,
|
||||||
|
state: ConfigEntryState,
|
||||||
|
) -> None:
|
||||||
|
"""Test coordinator refresh with an error."""
|
||||||
|
mock_energy_history.side_effect = side_effect
|
||||||
|
entry = await setup_platform(hass)
|
||||||
|
assert entry.state is state
|
||||||
|
Loading…
x
Reference in New Issue
Block a user