mirror of
https://github.com/home-assistant/core.git
synced 2026-10-07 23:09:19 -04:00
Stream energy history totals in Teslemetry (#182529)
This commit is contained in:
@@ -112,6 +112,7 @@ STREAM_TOPICS: Final = (
|
||||
SseTopic.LIVE_STATUS,
|
||||
SseTopic.SITE_INFO,
|
||||
SseTopic.TARIFF_CONTENT_V2,
|
||||
SseTopic.ENERGY_TOTALS,
|
||||
)
|
||||
|
||||
|
||||
@@ -703,10 +704,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
||||
for vehicle in vehicles
|
||||
if vehicle.poll
|
||||
),
|
||||
*(
|
||||
energysite.info_coordinator.async_config_entry_first_refresh()
|
||||
for energysite in energysites
|
||||
),
|
||||
*(_async_refresh_energy_site(energysite) for energysite in energysites),
|
||||
)
|
||||
|
||||
# Setup energy devices with models, versions, and listeners
|
||||
@@ -817,6 +815,8 @@ def create_handle_energy_stream_connection(
|
||||
if energysite.live_coordinator is not None:
|
||||
energysite.live_coordinator.async_set_update_error(error)
|
||||
energysite.info_coordinator.async_set_update_error(error)
|
||||
if energysite.history_coordinator is not None:
|
||||
energysite.history_coordinator.async_set_update_error(error)
|
||||
|
||||
return handle_connection
|
||||
|
||||
@@ -890,14 +890,27 @@ async def _async_setup_energy_site(
|
||||
)
|
||||
|
||||
history_coordinator = (
|
||||
TeslemetryEnergyHistoryCoordinator(hass, entry, energy_site)
|
||||
if powerwall
|
||||
else None
|
||||
TeslemetryEnergyHistoryCoordinator(hass, entry, site_id) if powerwall else None
|
||||
)
|
||||
if history_coordinator is not None:
|
||||
entry.async_on_unload(
|
||||
stream_energysite.listen_EnergyTotals(
|
||||
history_coordinator.handle_stream_update
|
||||
)
|
||||
)
|
||||
|
||||
return live_coordinator, info_coordinator, history_coordinator
|
||||
|
||||
|
||||
async def _async_refresh_energy_site(energysite: TeslemetryEnergyData) -> None:
|
||||
"""Cold read the site info, then resolve the site timezone it carries."""
|
||||
await energysite.info_coordinator.async_config_entry_first_refresh()
|
||||
if energysite.history_coordinator is not None:
|
||||
await energysite.history_coordinator.async_set_time_zone(
|
||||
energysite.info_coordinator.data.get("installation_time_zone")
|
||||
)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -> bool:
|
||||
"""Unload Teslemetry Config."""
|
||||
unloaded = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"""Teslemetry Data Coordinator."""
|
||||
|
||||
from datetime import timedelta
|
||||
from dataclasses import asdict
|
||||
from datetime import date, datetime, time, timedelta, tzinfo
|
||||
from typing import TYPE_CHECKING, Any, override
|
||||
|
||||
from tesla_fleet_api.const import TeslaEnergyPeriod, VehicleDataEndpoint
|
||||
from tesla_fleet_api.const import VehicleDataEndpoint
|
||||
from tesla_fleet_api.exceptions import (
|
||||
GatewayTimeout,
|
||||
InsufficientCredits,
|
||||
@@ -16,15 +17,17 @@ from tesla_fleet_api.exceptions import (
|
||||
TeslaFleetError,
|
||||
)
|
||||
from tesla_fleet_api.teslemetry import EnergySite, Teslemetry, Vehicle
|
||||
from teslemetry_stream.const import EnergyTotalsEvent
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import TeslemetryConfigEntry
|
||||
|
||||
from .const import DOMAIN, ENERGY_HISTORY_FIELDS, LOGGER
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .helpers import async_update_device_sw_version, flatten
|
||||
|
||||
RETRY_EXCEPTIONS = (
|
||||
@@ -45,9 +48,12 @@ def _get_retry_after(e: TeslaFleetError) -> float:
|
||||
|
||||
VEHICLE_INTERVAL = timedelta(seconds=60)
|
||||
VEHICLE_WAIT = timedelta(minutes=15)
|
||||
ENERGY_HISTORY_INTERVAL = timedelta(seconds=60)
|
||||
METADATA_INTERVAL = timedelta(hours=1)
|
||||
|
||||
# Start of the day the energy history totals cover. Kept out of
|
||||
# ENERGY_HISTORY_FIELDS, which is the list of keys that become sensors.
|
||||
PERIOD_START = "_period_start"
|
||||
|
||||
# Keys within tariff_content_v2 kept as nested dicts rather than flattened,
|
||||
# since entities and calendars read them as whole structures.
|
||||
TARIFF_SKIP_KEYS = ["daily_charges", "demand_charges", "energy_charges", "seasons"]
|
||||
@@ -330,7 +336,11 @@ class TeslemetryEnergySiteInfoCoordinator(DataUpdateCoordinator[dict[str, Any]])
|
||||
|
||||
|
||||
class TeslemetryEnergyHistoryCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
"""Class to manage fetching energy site info from the Teslemetry API."""
|
||||
"""Class to manage energy site history totals from the Teslemetry stream.
|
||||
|
||||
The server sums each day's periods itself and publishes cumulative
|
||||
``energy_totals``; there is no REST poll and no local accumulation.
|
||||
"""
|
||||
|
||||
config_entry: TeslemetryConfigEntry
|
||||
|
||||
@@ -338,54 +348,49 @@ class TeslemetryEnergyHistoryCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: TeslemetryConfigEntry,
|
||||
api: EnergySite,
|
||||
site_id: int,
|
||||
) -> None:
|
||||
"""Initialize Teslemetry Energy Info coordinator."""
|
||||
"""Initialize Teslemetry Energy History coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
LOGGER,
|
||||
config_entry=config_entry,
|
||||
name=f"Teslemetry Energy History {api.energy_site_id}",
|
||||
update_interval=ENERGY_HISTORY_INTERVAL,
|
||||
name=f"Teslemetry Energy History {site_id}",
|
||||
)
|
||||
self.api = api
|
||||
self.site_id = site_id
|
||||
self.time_zone: tzinfo | None = None
|
||||
self.data = {}
|
||||
|
||||
async def async_set_time_zone(self, name: str | None) -> None:
|
||||
"""Resolve the site's installation timezone."""
|
||||
if not name:
|
||||
return
|
||||
if (zone := await dt_util.async_get_time_zone(name)) is None:
|
||||
LOGGER.warning(
|
||||
"Unknown timezone %s for energy site %s, falling back to the Home Assistant timezone",
|
||||
name,
|
||||
self.site_id,
|
||||
)
|
||||
return
|
||||
self.time_zone = zone
|
||||
|
||||
@override
|
||||
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, SubscriptionRequired, LoginRequired) as e:
|
||||
raise ConfigEntryAuthFailed from e
|
||||
except RETRY_EXCEPTIONS as e:
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_failed",
|
||||
translation_placeholders={"message": e.message},
|
||||
retry_after=_get_retry_after(e),
|
||||
) from e
|
||||
except TeslaFleetError as e:
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_failed",
|
||||
translation_placeholders={"message": e.message},
|
||||
) from e
|
||||
"""Return the current totals; there is nothing to fetch.
|
||||
|
||||
if not data or not isinstance(data.get("time_series"), list):
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_failed_invalid_data",
|
||||
)
|
||||
Only reached through the generic entity update service, which must not
|
||||
fail on a coordinator the stream alone feeds.
|
||||
"""
|
||||
return self.data
|
||||
|
||||
# Add all time periods together
|
||||
output = dict.fromkeys(ENERGY_HISTORY_FIELDS, None)
|
||||
for period in data.get("time_series", []):
|
||||
for key in ENERGY_HISTORY_FIELDS:
|
||||
if key in period:
|
||||
if output[key] is None:
|
||||
output[key] = period[key]
|
||||
else:
|
||||
output[key] += period[key]
|
||||
|
||||
return output
|
||||
def handle_stream_update(self, event: EnergyTotalsEvent) -> None:
|
||||
"""Handle an energy_totals document from the stream."""
|
||||
data: dict[str, Any] = asdict(event.totals)
|
||||
# The server finalises a day after the site's local midnight, so a late
|
||||
# event must stay on the day it reports rather than the current one.
|
||||
data[PERIOD_START] = datetime.combine(
|
||||
date.fromisoformat(event.date),
|
||||
time(),
|
||||
tzinfo=self.time_zone or dt_util.get_default_time_zone(),
|
||||
)
|
||||
self.async_set_updated_data(data)
|
||||
|
||||
@@ -40,6 +40,7 @@ from homeassistant.util.variance import ignore_variance
|
||||
|
||||
from . import TeslemetryConfigEntry
|
||||
from .const import ENERGY_HISTORY_FIELDS
|
||||
from .coordinator import PERIOD_START
|
||||
from .entity import (
|
||||
TeslemetryEnergyHistoryEntity,
|
||||
TeslemetryEnergyInfoEntity,
|
||||
@@ -1653,7 +1654,7 @@ ENERGY_HISTORY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = tuple(
|
||||
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
suggested_display_precision=2,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
entity_registry_enabled_default=(
|
||||
key.startswith("total") or key == "grid_energy_imported"
|
||||
),
|
||||
@@ -1966,6 +1967,7 @@ class TeslemetryEnergyHistorySensorEntity(TeslemetryEnergyHistoryEntity, SensorE
|
||||
def _async_update_attrs(self) -> None:
|
||||
"""Update the attributes of the sensor."""
|
||||
self._attr_native_value = self._value
|
||||
self._attr_last_reset = self.coordinator.data.get(PERIOD_START)
|
||||
|
||||
|
||||
class TeslemetryCreditBalanceSensor(RestoreSensor):
|
||||
|
||||
@@ -1289,9 +1289,6 @@
|
||||
"update_failed_insufficient_credits": {
|
||||
"message": "Teslemetry account has insufficient command credits, pausing updates until credits are added"
|
||||
},
|
||||
"update_failed_invalid_data": {
|
||||
"message": "Received invalid data from API"
|
||||
},
|
||||
"wake_up_failed": {
|
||||
"message": "Failed to wake up vehicle: {message}"
|
||||
},
|
||||
|
||||
@@ -6,13 +6,15 @@ from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from teslemetry_stream.const import EnergyTotalsEvent
|
||||
from teslemetry_stream.stream import recursive_match
|
||||
|
||||
from homeassistant.components.teslemetry.const import TOKEN_URL
|
||||
|
||||
from .const import (
|
||||
COMMAND_OK,
|
||||
ENERGY_HISTORY,
|
||||
ENERGY_TOTALS,
|
||||
ENERGY_TOTALS_DATE,
|
||||
LIVE_STATUS,
|
||||
METADATA,
|
||||
METADATA_ENERGY,
|
||||
@@ -142,16 +144,6 @@ def mock_site_info():
|
||||
yield mock_live_status
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_energy_history():
|
||||
"""Mock Teslemetry Energy Specific site_info method."""
|
||||
with patch(
|
||||
"tesla_fleet_api.tesla.energysite.EnergySite.energy_history",
|
||||
return_value=ENERGY_HISTORY,
|
||||
) as mock_live_status:
|
||||
yield mock_live_status
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_stream_listen():
|
||||
"""Mock Teslemetry Stream listen method."""
|
||||
@@ -252,6 +244,43 @@ def mock_energy_info_stream() -> Generator[MagicMock]:
|
||||
yield mock_listen
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_energy_totals_stream() -> Generator[MagicMock]:
|
||||
"""Capture the callback the integration registers for energy_totals events."""
|
||||
with patch(
|
||||
"teslemetry_stream.TeslemetryStreamEnergySite.listen_EnergyTotals",
|
||||
) as mock_listen:
|
||||
callbacks: list = []
|
||||
|
||||
def side_effect(callback):
|
||||
callbacks.append(callback)
|
||||
return MagicMock()
|
||||
|
||||
def send(
|
||||
totals: dict[str, float | None] | None = None,
|
||||
*,
|
||||
date: str = ENERGY_TOTALS_DATE,
|
||||
created_at: str = "2024-09-18T08:50:00.000Z",
|
||||
is_cache: bool = False,
|
||||
) -> None:
|
||||
"""Deliver a wire event through the library's own parsing."""
|
||||
event = EnergyTotalsEvent.from_dict(
|
||||
{
|
||||
"id": 123456,
|
||||
"date": date,
|
||||
"createdAt": created_at,
|
||||
"isCache": is_cache,
|
||||
"totals": ENERGY_TOTALS if totals is None else totals,
|
||||
}
|
||||
)
|
||||
for callback in callbacks:
|
||||
callback(event)
|
||||
|
||||
mock_listen.side_effect = side_effect
|
||||
mock_listen.send = send
|
||||
yield mock_listen
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_energy_tariff_stream() -> Generator[MagicMock]:
|
||||
"""Capture the callback the integration registers for tariff events."""
|
||||
|
||||
@@ -29,8 +29,39 @@ SITE_INFO_WEEK_CROSSING = load_json_object_fixture(
|
||||
"site_info_week_crossing.json", DOMAIN
|
||||
)
|
||||
SITE_INFO_MULTI_SEASON = load_json_object_fixture("site_info_multi_season.json", DOMAIN)
|
||||
ENERGY_HISTORY = load_json_object_fixture("energy_history.json", DOMAIN)
|
||||
ENERGY_HISTORY_EMPTY = load_json_object_fixture("energy_history_empty.json", DOMAIN)
|
||||
|
||||
# The site-local day the streamed energy_totals fixtures below belong to. The
|
||||
# site timezone site_info.json declares is deliberately not the test machine's.
|
||||
ENERGY_TOTALS_DATE = "2024-09-18"
|
||||
|
||||
# A valid, non-empty day: the server sums every period itself and sends 0 for a
|
||||
# field that never appeared.
|
||||
ENERGY_TOTALS = {
|
||||
"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": 36,
|
||||
"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": 36,
|
||||
"consumer_energy_imported_from_generator": 0,
|
||||
"total_home_usage": 74,
|
||||
"total_battery_charge": 684,
|
||||
"total_battery_discharge": 36,
|
||||
"total_solar_generation": 724,
|
||||
"total_grid_energy_exported": 2,
|
||||
}
|
||||
|
||||
# An empty or malformed day: every field comes back null.
|
||||
ENERGY_TOTALS_NULL = dict.fromkeys(ENERGY_TOTALS)
|
||||
|
||||
COMMAND_OK = {"response": {"result": True, "reason": ""}}
|
||||
COMMAND_REASON = {"response": {"result": False, "reason": "already closed"}}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"response": {
|
||||
"serial_number": "xxxxxx",
|
||||
"period": "day",
|
||||
"installation_time_zone": "Australia/Brisbane",
|
||||
"time_series": null
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@
|
||||
},
|
||||
"nameplate_power": 15000,
|
||||
"nameplate_energy": 40500,
|
||||
"installation_time_zone": "",
|
||||
"installation_time_zone": "Australia/Brisbane",
|
||||
"max_site_meter_power_ac": 1000000000,
|
||||
"min_site_meter_power_ac": -1000000000,
|
||||
"tariff_content_v2": {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
'energysites': list([
|
||||
dict({
|
||||
'history': dict({
|
||||
'_period_start': '2024-09-18T00:00:00+10:00',
|
||||
'battery_energy_exported': 36,
|
||||
'battery_energy_imported_from_generator': 0,
|
||||
'battery_energy_imported_from_grid': 0,
|
||||
@@ -114,7 +115,7 @@
|
||||
'default_real_mode': 'self_consumption',
|
||||
'id': '1233-abcd',
|
||||
'installation_date': '**REDACTED**',
|
||||
'installation_time_zone': '',
|
||||
'installation_time_zone': 'Australia/Brisbane',
|
||||
'max_site_meter_power_ac': 1000000000,
|
||||
'min_site_meter_power_ac': -1000000000,
|
||||
'nameplate_energy': 40500,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -49,7 +49,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Battery charged',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -57,7 +58,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.684',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_battery_charged-statealt]
|
||||
@@ -65,7 +66,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Battery charged',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -83,7 +85,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -126,7 +128,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Battery discharged',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -134,7 +137,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.036',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_battery_discharged-statealt]
|
||||
@@ -142,7 +145,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Battery discharged',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -160,7 +164,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -203,7 +207,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Battery exported',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -211,7 +216,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.036',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_battery_exported-statealt]
|
||||
@@ -219,7 +224,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Battery exported',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -237,7 +243,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -280,7 +286,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Battery imported from generator',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -288,7 +295,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_battery_imported_from_generator-statealt]
|
||||
@@ -296,7 +303,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Battery imported from generator',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -314,7 +322,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -357,7 +365,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Battery imported from grid',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -365,7 +374,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_battery_imported_from_grid-statealt]
|
||||
@@ -373,7 +382,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Battery imported from grid',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -391,7 +401,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -434,7 +444,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Battery imported from solar',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -442,7 +453,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.684',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_battery_imported_from_solar-statealt]
|
||||
@@ -450,7 +461,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Battery imported from solar',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -545,7 +557,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -588,7 +600,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Consumer imported from battery',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -596,7 +609,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.036',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_consumer_imported_from_battery-statealt]
|
||||
@@ -604,7 +617,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Consumer imported from battery',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -622,7 +636,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -665,7 +679,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Consumer imported from generator',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -673,7 +688,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_consumer_imported_from_generator-statealt]
|
||||
@@ -681,7 +696,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Consumer imported from generator',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -699,7 +715,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -742,7 +758,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Consumer imported from grid',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -750,7 +767,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_consumer_imported_from_grid-statealt]
|
||||
@@ -758,7 +775,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Consumer imported from grid',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -776,7 +794,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -819,7 +837,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Consumer imported from solar',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -827,7 +846,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.038',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_consumer_imported_from_solar-statealt]
|
||||
@@ -835,7 +854,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Consumer imported from solar',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -930,7 +950,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -973,7 +993,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Generator exported',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -981,7 +1002,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_generator_exported-statealt]
|
||||
@@ -989,7 +1010,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Generator exported',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1084,7 +1106,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1127,7 +1149,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Grid exported',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1135,7 +1158,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.002',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_grid_exported-statealt]
|
||||
@@ -1143,7 +1166,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Grid exported',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1161,7 +1185,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1204,7 +1228,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Grid exported from battery',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1212,7 +1237,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_grid_exported_from_battery-statealt]
|
||||
@@ -1220,7 +1245,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Grid exported from battery',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1238,7 +1264,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1281,7 +1307,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Grid exported from generator',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1289,7 +1316,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_grid_exported_from_generator-statealt]
|
||||
@@ -1297,7 +1324,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Grid exported from generator',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1315,7 +1343,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1358,7 +1386,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Grid exported from solar',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1366,7 +1395,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.002',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_grid_exported_from_solar-statealt]
|
||||
@@ -1374,7 +1403,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Grid exported from solar',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1392,7 +1422,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1435,7 +1465,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Grid imported',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1443,7 +1474,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_grid_imported-statealt]
|
||||
@@ -1451,7 +1482,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Grid imported',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1546,7 +1578,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1589,7 +1621,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Grid services exported',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1597,7 +1630,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_grid_services_exported-statealt]
|
||||
@@ -1605,7 +1638,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Grid services exported',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1623,7 +1657,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1666,7 +1700,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Grid services imported',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1674,7 +1709,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_grid_services_imported-statealt]
|
||||
@@ -1682,7 +1717,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Grid services imported',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1777,7 +1813,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1820,7 +1856,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Home usage',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1828,7 +1865,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.074',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_home_usage-statealt]
|
||||
@@ -1836,7 +1873,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Home usage',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -2092,7 +2130,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -2135,7 +2173,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Solar exported',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -2143,7 +2182,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.724',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_solar_exported-statealt]
|
||||
@@ -2151,7 +2190,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Solar exported',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -2169,7 +2209,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -2212,7 +2252,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Solar generated',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -2220,7 +2261,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '0.724',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_solar_generated-statealt]
|
||||
@@ -2228,7 +2269,8 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'energy',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Energy Site Solar generated',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
<SensorEntityStateAttribute.LAST_RESET: 'last_reset'>: '2024-09-18T00:00:00+10:00',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL: 'total'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Test the Telemetry Diagnostics."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
@@ -21,6 +21,7 @@ async def test_diagnostics(
|
||||
snapshot: SnapshotAssertion,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
mock_legacy: AsyncMock,
|
||||
mock_energy_totals_stream: MagicMock,
|
||||
) -> None:
|
||||
"""Test diagnostics."""
|
||||
|
||||
@@ -31,5 +32,8 @@ async def test_diagnostics(
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_energy_totals_stream.send()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
diag = await get_diagnostics_for_config_entry(hass, hass_client, entry)
|
||||
assert diag == snapshot
|
||||
|
||||
@@ -52,7 +52,6 @@ from homeassistant.components.teslemetry.const import (
|
||||
|
||||
# Coordinator constants
|
||||
from homeassistant.components.teslemetry.coordinator import (
|
||||
ENERGY_HISTORY_INTERVAL,
|
||||
INSUFFICIENT_CREDITS_RETRY_AFTER,
|
||||
METADATA_INTERVAL,
|
||||
VEHICLE_INTERVAL,
|
||||
@@ -89,7 +88,6 @@ from homeassistant.helpers.update_coordinator import UpdateFailed
|
||||
from . import mock_config_entry, setup_platform
|
||||
from .const import (
|
||||
CONFIG_V1,
|
||||
ENERGY_HISTORY,
|
||||
LIVE_STATUS,
|
||||
METADATA,
|
||||
METADATA_NOSCOPE,
|
||||
@@ -652,54 +650,6 @@ async def test_live_status_coordinator_retry_exceptions(
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("exception", "expected_retry_after"), RETRY_EXCEPTIONS)
|
||||
async def test_energy_history_coordinator_retry_exceptions(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
mock_energy_history: AsyncMock,
|
||||
exception: TeslaFleetError,
|
||||
expected_retry_after: float,
|
||||
) -> None:
|
||||
"""Test energy history coordinator raises UpdateFailed with retry_after."""
|
||||
call_count = 0
|
||||
|
||||
def energy_history_side_effect(*args, **kwargs):
|
||||
nonlocal call_count
|
||||
call_count += 1
|
||||
if call_count == 1:
|
||||
raise exception # First call raises exception
|
||||
return ENERGY_HISTORY # Subsequent calls succeed
|
||||
|
||||
mock_energy_history.side_effect = energy_history_side_effect
|
||||
|
||||
entry = await setup_platform(hass)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
# Energy history doesn't have first_refresh during setup
|
||||
assert call_count == 0
|
||||
|
||||
# Trigger first coordinator refresh - this will raise the exception
|
||||
freezer.tick(ENERGY_HISTORY_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# API was called exactly once (no manual retry loop)
|
||||
assert call_count == 1
|
||||
# Entry stays loaded - UpdateFailed with retry_after doesn't break the entry
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
# The coordinator staggers its scheduling deliberately, so these ticks
|
||||
# bracket retry_after with a margin either side rather than sitting on it.
|
||||
freezer.tick(timedelta(seconds=expected_retry_after - 2))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert call_count == 1
|
||||
|
||||
freezer.tick(timedelta(seconds=3))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert call_count == 2
|
||||
|
||||
|
||||
async def test_live_status_auth_error(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
@@ -1093,33 +1043,6 @@ async def test_live_status_coordinator_refresh_error(
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"side_effect",
|
||||
[
|
||||
[InvalidToken],
|
||||
[TeslaFleetError],
|
||||
[ENERGY_HISTORY, {"response": {}}],
|
||||
],
|
||||
)
|
||||
async def test_energy_history_coordinator_refresh_errors(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
mock_energy_history: AsyncMock,
|
||||
side_effect: list,
|
||||
) -> None:
|
||||
"""Test energy history coordinator handles errors during refresh."""
|
||||
mock_energy_history.side_effect = side_effect
|
||||
|
||||
entry = await setup_platform(hass)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
freezer.tick(ENERGY_HISTORY_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
async def test_dynamic_device_discovery_triggers_reload(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
@@ -1826,6 +1749,7 @@ def test_stream_topic_allowlist() -> None:
|
||||
"live_status",
|
||||
"site_info",
|
||||
"tariff_content_v2",
|
||||
"energy_totals",
|
||||
]
|
||||
|
||||
|
||||
@@ -1835,18 +1759,22 @@ async def test_energy_stream_no_recurring_rest_polling(
|
||||
mock_live_status: AsyncMock,
|
||||
mock_site_info: AsyncMock,
|
||||
) -> None:
|
||||
"""The live/info REST cold reads happen once and do not recur."""
|
||||
await setup_platform(hass, [Platform.SENSOR])
|
||||
assert mock_live_status.call_count == 1
|
||||
assert mock_site_info.call_count == 1
|
||||
"""The live/info REST cold reads happen once, and history never reads at all."""
|
||||
with patch(
|
||||
"tesla_fleet_api.tesla.energysite.EnergySite.energy_history"
|
||||
) as mock_energy_history:
|
||||
await setup_platform(hass, [Platform.SENSOR])
|
||||
assert mock_live_status.call_count == 1
|
||||
assert mock_site_info.call_count == 1
|
||||
|
||||
# Advancing well past the old 30-second poll intervals triggers no REST reads.
|
||||
freezer.tick(ENERGY_HISTORY_INTERVAL * 2)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
# Advancing well past the old poll intervals triggers no REST reads.
|
||||
freezer.tick(timedelta(minutes=5))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_live_status.call_count == 1
|
||||
assert mock_site_info.call_count == 1
|
||||
mock_energy_history.assert_not_called()
|
||||
|
||||
|
||||
async def test_energy_stream_unload_unsubscribes_and_closes_stream(
|
||||
@@ -1856,6 +1784,7 @@ async def test_energy_stream_unload_unsubscribes_and_closes_stream(
|
||||
live_unsub = MagicMock()
|
||||
info_unsub = MagicMock()
|
||||
tariff_unsub = MagicMock()
|
||||
totals_unsub = MagicMock()
|
||||
|
||||
with (
|
||||
patch(
|
||||
@@ -1870,6 +1799,10 @@ async def test_energy_stream_unload_unsubscribes_and_closes_stream(
|
||||
"teslemetry_stream.TeslemetryStreamEnergySite.listen_TariffContentV2",
|
||||
return_value=tariff_unsub,
|
||||
),
|
||||
patch(
|
||||
"teslemetry_stream.TeslemetryStreamEnergySite.listen_EnergyTotals",
|
||||
return_value=totals_unsub,
|
||||
),
|
||||
patch("teslemetry_stream.TeslemetryStream.close") as mock_close,
|
||||
):
|
||||
entry = await setup_platform(hass, [Platform.SENSOR])
|
||||
@@ -1881,6 +1814,7 @@ async def test_energy_stream_unload_unsubscribes_and_closes_stream(
|
||||
live_unsub.assert_called_once()
|
||||
info_unsub.assert_called_once()
|
||||
tariff_unsub.assert_called_once()
|
||||
totals_unsub.assert_called_once()
|
||||
mock_close.assert_called_once()
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
"""Test the Teslemetry sensor platform."""
|
||||
|
||||
from copy import deepcopy
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
from teslemetry_stream import Signal
|
||||
from teslemetry_stream.const import EnergyHistoryTotals
|
||||
|
||||
from homeassistant.components.teslemetry.const import DOMAIN
|
||||
from homeassistant.components.homeassistant import (
|
||||
DOMAIN as HOMEASSISTANT_DOMAIN,
|
||||
SERVICE_UPDATE_ENTITY,
|
||||
)
|
||||
from homeassistant.components.teslemetry.const import DOMAIN, ENERGY_HISTORY_FIELDS
|
||||
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
EntityCategory,
|
||||
@@ -19,13 +26,16 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import assert_entities, assert_entities_alt, setup_platform
|
||||
from .const import (
|
||||
ENERGY_HISTORY_EMPTY,
|
||||
ENERGY_TOTALS,
|
||||
ENERGY_TOTALS_NULL,
|
||||
LIVE_STATUS,
|
||||
METADATA,
|
||||
PRODUCTS,
|
||||
SITE_INFO,
|
||||
VEHICLE_DATA_ALT,
|
||||
)
|
||||
|
||||
@@ -34,6 +44,12 @@ from tests.common import async_fire_time_changed
|
||||
# VIN used across the Teslemetry test fixtures.
|
||||
VEHICLE_VIN = "LRW3F7EK4NC700000"
|
||||
|
||||
ENERGY_HISTORY_ENTITY = "sensor.energy_site_battery_discharged"
|
||||
# Midnight in Australia/Brisbane, the timezone site_info.json declares. Home
|
||||
# Assistant runs on US/Pacific in tests, so a last_reset derived from its clock
|
||||
# or from the event's created_at cannot produce this.
|
||||
SITE_MIDNIGHT = "2024-09-18T00:00:00+10:00"
|
||||
|
||||
|
||||
def _products_with_driver_assist(driver_assist: str) -> dict:
|
||||
"""Return a products response with the vehicle's driver-assist capability set."""
|
||||
@@ -103,6 +119,7 @@ async def test_sensors(
|
||||
freezer: FrozenDateTimeFactory,
|
||||
mock_vehicle_data: AsyncMock,
|
||||
mock_legacy: AsyncMock,
|
||||
mock_energy_totals_stream: MagicMock,
|
||||
) -> None:
|
||||
"""Tests that the sensor entities with the legacy polling are correct."""
|
||||
|
||||
@@ -112,6 +129,9 @@ async def test_sensors(
|
||||
|
||||
entry = await setup_platform(hass, [Platform.SENSOR])
|
||||
|
||||
mock_energy_totals_stream.send()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert_entities(hass, entry.entry_id, entity_registry, snapshot)
|
||||
|
||||
# Coordinator refresh
|
||||
@@ -491,26 +511,169 @@ async def test_streaming_enum_none_clears_state(
|
||||
assert hass.states.get(entity_id).state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_energy_history_no_time_series(
|
||||
def test_energy_history_fields_match_stream_totals() -> None:
|
||||
"""The sensor key list mirrors the totals the stream event carries."""
|
||||
assert list(EnergyHistoryTotals.__dataclass_fields__) == ENERGY_HISTORY_FIELDS
|
||||
|
||||
|
||||
async def test_energy_history_stream_updates(
|
||||
hass: HomeAssistant,
|
||||
mock_energy_totals_stream: MagicMock,
|
||||
) -> None:
|
||||
"""A streamed energy_totals event drives the history sensors and their last_reset."""
|
||||
await setup_platform(hass, [Platform.SENSOR])
|
||||
|
||||
# Nothing has streamed yet, so the sensors are available but hold no value.
|
||||
assert (state := hass.states.get(ENERGY_HISTORY_ENTITY))
|
||||
assert state.state == STATE_UNKNOWN
|
||||
assert "last_reset" not in state.attributes
|
||||
|
||||
mock_energy_totals_stream.send()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (state := hass.states.get(ENERGY_HISTORY_ENTITY))
|
||||
assert state.state == "0.036"
|
||||
assert state.attributes["state_class"] == "total"
|
||||
assert state.attributes["last_reset"] == SITE_MIDNIGHT
|
||||
assert hass.states.get("sensor.energy_site_solar_generated").state == "0.724"
|
||||
|
||||
|
||||
async def test_energy_history_late_event_keeps_previous_day(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
mock_energy_history: AsyncMock,
|
||||
mock_energy_totals_stream: MagicMock,
|
||||
) -> None:
|
||||
"""Test energy history coordinator when time_series is not a list."""
|
||||
# Mock energy history to return data without time_series as a list
|
||||
"""A late event finalising the previous day keeps that day's last_reset.
|
||||
|
||||
entry = await setup_platform(hass, [Platform.SENSOR])
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
The server finalises a day after the site's local midnight. Deriving
|
||||
last_reset from the current day instead would start a second cycle and bank
|
||||
the whole of the previous day again.
|
||||
"""
|
||||
# 00:05 on the 19th in Brisbane, so "today" at the site is no longer the 18th.
|
||||
freezer.move_to("2024-09-18T14:05:00+00:00")
|
||||
await setup_platform(hass, [Platform.SENSOR])
|
||||
|
||||
entity_id = "sensor.energy_site_battery_discharged"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_UNKNOWN
|
||||
mock_energy_totals_stream.send(date="2024-09-18")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_energy_history.return_value = ENERGY_HISTORY_EMPTY
|
||||
assert (state := hass.states.get(ENERGY_HISTORY_ENTITY))
|
||||
assert state.attributes["last_reset"] == SITE_MIDNIGHT
|
||||
|
||||
freezer.tick(VEHICLE_INTERVAL)
|
||||
|
||||
async def test_energy_history_cache_snapshot_populates_sensors(
|
||||
hass: HomeAssistant,
|
||||
mock_energy_totals_stream: MagicMock,
|
||||
) -> None:
|
||||
"""The connect-time snapshot is applied exactly like a live update."""
|
||||
await setup_platform(hass, [Platform.SENSOR])
|
||||
|
||||
mock_energy_totals_stream.send(is_cache=True)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (state := hass.states.get(ENERGY_HISTORY_ENTITY))
|
||||
assert state.state == "0.036"
|
||||
assert state.attributes["last_reset"] == SITE_MIDNIGHT
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("totals", "expected"),
|
||||
[
|
||||
pytest.param(ENERGY_TOTALS, "0.0", id="absent_field_sent_as_zero"),
|
||||
pytest.param(ENERGY_TOTALS_NULL, STATE_UNKNOWN, id="empty_day_sent_as_null"),
|
||||
],
|
||||
)
|
||||
async def test_energy_history_zero_and_null_totals(
|
||||
hass: HomeAssistant,
|
||||
mock_energy_totals_stream: MagicMock,
|
||||
totals: dict[str, float | None],
|
||||
expected: str,
|
||||
) -> None:
|
||||
"""A zero total reads as zero and a null total reads as unknown, never unavailable."""
|
||||
await setup_platform(hass, [Platform.SENSOR])
|
||||
|
||||
mock_energy_totals_stream.send(totals)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (state := hass.states.get("sensor.energy_site_grid_imported"))
|
||||
assert state.state == expected
|
||||
assert state.attributes["last_reset"] == SITE_MIDNIGHT
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"installation_time_zone",
|
||||
[pytest.param("", id="not_provided"), pytest.param("Mars/Olympus", id="unknown")],
|
||||
)
|
||||
async def test_energy_history_time_zone_fallback(
|
||||
hass: HomeAssistant,
|
||||
mock_site_info: AsyncMock,
|
||||
mock_energy_totals_stream: MagicMock,
|
||||
installation_time_zone: str,
|
||||
) -> None:
|
||||
"""Without a usable site timezone, last_reset falls back to Home Assistant's."""
|
||||
site_info = deepcopy(SITE_INFO)
|
||||
site_info["response"]["installation_time_zone"] = installation_time_zone
|
||||
mock_site_info.side_effect = lambda: deepcopy(site_info)
|
||||
|
||||
await setup_platform(hass, [Platform.SENSOR])
|
||||
|
||||
mock_energy_totals_stream.send()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (state := hass.states.get(ENERGY_HISTORY_ENTITY))
|
||||
# Home Assistant runs on US/Pacific in tests, which was in PDT on this date.
|
||||
assert state.attributes["last_reset"] == "2024-09-18T00:00:00-07:00"
|
||||
|
||||
|
||||
async def test_energy_history_update_entity_service_is_a_noop(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
mock_energy_totals_stream: MagicMock,
|
||||
) -> None:
|
||||
"""The generic update service keeps the streamed totals instead of failing.
|
||||
|
||||
The coordinator has nothing to fetch, so the service must not leave the
|
||||
sensors unavailable on a stream that is perfectly healthy.
|
||||
"""
|
||||
await setup_platform(hass, [Platform.SENSOR])
|
||||
await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {})
|
||||
|
||||
mock_energy_totals_stream.send()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await hass.services.async_call(
|
||||
HOMEASSISTANT_DOMAIN,
|
||||
SERVICE_UPDATE_ENTITY,
|
||||
{ATTR_ENTITY_ID: ENERGY_HISTORY_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
# The coordinator debounces refresh requests, so let the deferred one land.
|
||||
freezer.tick(timedelta(seconds=30))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
assert "NotImplementedError" not in caplog.text
|
||||
assert (state := hass.states.get(ENERGY_HISTORY_ENTITY))
|
||||
assert state.state == "0.036"
|
||||
assert state.attributes["last_reset"] == SITE_MIDNIGHT
|
||||
|
||||
|
||||
async def test_energy_history_unavailable_while_stream_disconnected(
|
||||
hass: HomeAssistant,
|
||||
mock_add_connection_listener: MagicMock,
|
||||
mock_energy_totals_stream: MagicMock,
|
||||
) -> None:
|
||||
"""A dropped stream makes the history sensors unavailable until a snapshot returns."""
|
||||
await setup_platform(hass, [Platform.SENSOR])
|
||||
|
||||
mock_energy_totals_stream.send()
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(ENERGY_HISTORY_ENTITY).state == "0.036"
|
||||
|
||||
mock_add_connection_listener.send(False)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(ENERGY_HISTORY_ENTITY).state == STATE_UNAVAILABLE
|
||||
|
||||
mock_energy_totals_stream.send(is_cache=True)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(ENERGY_HISTORY_ENTITY).state == "0.036"
|
||||
|
||||
Reference in New Issue
Block a user