mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Add individual battery banks as devices (#108339)
This commit is contained in:
parent
5fd6028d97
commit
b629ad9c3d
@ -229,6 +229,7 @@ async def _call_base_info(power_wall: Powerwall, host: str) -> PowerwallBaseInfo
|
|||||||
status = tg.create_task(power_wall.get_status())
|
status = tg.create_task(power_wall.get_status())
|
||||||
device_type = tg.create_task(power_wall.get_device_type())
|
device_type = tg.create_task(power_wall.get_device_type())
|
||||||
serial_numbers = tg.create_task(power_wall.get_serial_numbers())
|
serial_numbers = tg.create_task(power_wall.get_serial_numbers())
|
||||||
|
batteries = tg.create_task(power_wall.get_batteries())
|
||||||
|
|
||||||
# Mimic the behavior of asyncio.gather by reraising the first caught exception since
|
# Mimic the behavior of asyncio.gather by reraising the first caught exception since
|
||||||
# this is what is expected by the caller of this method
|
# this is what is expected by the caller of this method
|
||||||
@ -248,6 +249,7 @@ async def _call_base_info(power_wall: Powerwall, host: str) -> PowerwallBaseInfo
|
|||||||
device_type=device_type.result(),
|
device_type=device_type.result(),
|
||||||
serial_numbers=sorted(serial_numbers.result()),
|
serial_numbers=sorted(serial_numbers.result()),
|
||||||
url=f"https://{host}",
|
url=f"https://{host}",
|
||||||
|
batteries={battery.serial_number: battery for battery in batteries.result()},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -270,6 +272,7 @@ async def _fetch_powerwall_data(power_wall: Powerwall) -> PowerwallData:
|
|||||||
meters = tg.create_task(power_wall.get_meters())
|
meters = tg.create_task(power_wall.get_meters())
|
||||||
grid_services_active = tg.create_task(power_wall.is_grid_services_active())
|
grid_services_active = tg.create_task(power_wall.is_grid_services_active())
|
||||||
grid_status = tg.create_task(power_wall.get_grid_status())
|
grid_status = tg.create_task(power_wall.get_grid_status())
|
||||||
|
batteries = tg.create_task(power_wall.get_batteries())
|
||||||
|
|
||||||
# Mimic the behavior of asyncio.gather by reraising the first caught exception since
|
# Mimic the behavior of asyncio.gather by reraising the first caught exception since
|
||||||
# this is what is expected by the caller of this method
|
# this is what is expected by the caller of this method
|
||||||
@ -287,6 +290,7 @@ async def _fetch_powerwall_data(power_wall: Powerwall) -> PowerwallData:
|
|||||||
grid_services_active=grid_services_active.result(),
|
grid_services_active=grid_services_active.result(),
|
||||||
grid_status=grid_status.result(),
|
grid_status=grid_status.result(),
|
||||||
backup_reserve=backup_reserve.result(),
|
backup_reserve=backup_reserve.result(),
|
||||||
|
batteries={battery.serial_number: battery for battery in batteries.result()},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ from .const import (
|
|||||||
POWERWALL_BASE_INFO,
|
POWERWALL_BASE_INFO,
|
||||||
POWERWALL_COORDINATOR,
|
POWERWALL_COORDINATOR,
|
||||||
)
|
)
|
||||||
from .models import PowerwallData, PowerwallRuntimeData
|
from .models import BatteryResponse, PowerwallData, PowerwallRuntimeData
|
||||||
|
|
||||||
|
|
||||||
class PowerWallEntity(CoordinatorEntity[DataUpdateCoordinator[PowerwallData]]):
|
class PowerWallEntity(CoordinatorEntity[DataUpdateCoordinator[PowerwallData]]):
|
||||||
@ -43,3 +43,36 @@ class PowerWallEntity(CoordinatorEntity[DataUpdateCoordinator[PowerwallData]]):
|
|||||||
def data(self) -> PowerwallData:
|
def data(self) -> PowerwallData:
|
||||||
"""Return the coordinator data."""
|
"""Return the coordinator data."""
|
||||||
return self.coordinator.data
|
return self.coordinator.data
|
||||||
|
|
||||||
|
|
||||||
|
class BatteryEntity(CoordinatorEntity[DataUpdateCoordinator[PowerwallData]]):
|
||||||
|
"""Base class for battery entities."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, powerwall_data: PowerwallRuntimeData, battery: BatteryResponse
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the entity."""
|
||||||
|
base_info = powerwall_data[POWERWALL_BASE_INFO]
|
||||||
|
coordinator = powerwall_data[POWERWALL_COORDINATOR]
|
||||||
|
assert coordinator is not None
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self.serial_number = battery.serial_number
|
||||||
|
self.power_wall = powerwall_data[POWERWALL_API]
|
||||||
|
self.base_unique_id = f"{base_info.gateway_din}_{battery.serial_number}"
|
||||||
|
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, self.base_unique_id)},
|
||||||
|
manufacturer=MANUFACTURER,
|
||||||
|
model=f"{MODEL} ({battery.part_number})",
|
||||||
|
name=f"{base_info.site_info.site_name} {battery.serial_number}",
|
||||||
|
sw_version=base_info.status.version,
|
||||||
|
configuration_url=base_info.url,
|
||||||
|
via_device=(DOMAIN, base_info.gateway_din),
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def battery_data(self) -> BatteryResponse:
|
||||||
|
"""Return the coordinator data."""
|
||||||
|
return self.coordinator.data.batteries[self.serial_number]
|
||||||
|
@ -5,6 +5,7 @@ from dataclasses import dataclass
|
|||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
from tesla_powerwall import (
|
from tesla_powerwall import (
|
||||||
|
BatteryResponse,
|
||||||
DeviceType,
|
DeviceType,
|
||||||
GridStatus,
|
GridStatus,
|
||||||
MetersAggregatesResponse,
|
MetersAggregatesResponse,
|
||||||
@ -27,6 +28,7 @@ class PowerwallBaseInfo:
|
|||||||
device_type: DeviceType
|
device_type: DeviceType
|
||||||
serial_numbers: list[str]
|
serial_numbers: list[str]
|
||||||
url: str
|
url: str
|
||||||
|
batteries: dict[str, BatteryResponse]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -39,6 +41,7 @@ class PowerwallData:
|
|||||||
grid_services_active: bool
|
grid_services_active: bool
|
||||||
grid_status: GridStatus
|
grid_status: GridStatus
|
||||||
backup_reserve: float | None
|
backup_reserve: float | None
|
||||||
|
batteries: dict[str, BatteryResponse]
|
||||||
|
|
||||||
|
|
||||||
class PowerwallRuntimeData(TypedDict):
|
class PowerwallRuntimeData(TypedDict):
|
||||||
|
@ -5,7 +5,7 @@ from collections.abc import Callable
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, Generic, TypeVar
|
from typing import TYPE_CHECKING, Generic, TypeVar
|
||||||
|
|
||||||
from tesla_powerwall import MeterResponse, MeterType
|
from tesla_powerwall import GridState, MeterResponse, MeterType
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
@ -16,6 +16,7 @@ from homeassistant.components.sensor import (
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
|
EntityCategory,
|
||||||
UnitOfElectricCurrent,
|
UnitOfElectricCurrent,
|
||||||
UnitOfElectricPotential,
|
UnitOfElectricPotential,
|
||||||
UnitOfEnergy,
|
UnitOfEnergy,
|
||||||
@ -27,14 +28,14 @@ from homeassistant.helpers.entity import Entity
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import DOMAIN, POWERWALL_COORDINATOR
|
from .const import DOMAIN, POWERWALL_COORDINATOR
|
||||||
from .entity import PowerWallEntity
|
from .entity import BatteryEntity, PowerWallEntity
|
||||||
from .models import PowerwallRuntimeData
|
from .models import BatteryResponse, PowerwallRuntimeData
|
||||||
|
|
||||||
_METER_DIRECTION_EXPORT = "export"
|
_METER_DIRECTION_EXPORT = "export"
|
||||||
_METER_DIRECTION_IMPORT = "import"
|
_METER_DIRECTION_IMPORT = "import"
|
||||||
|
|
||||||
_ValueParamT = TypeVar("_ValueParamT")
|
_ValueParamT = TypeVar("_ValueParamT")
|
||||||
_ValueT = TypeVar("_ValueT", bound=float)
|
_ValueT = TypeVar("_ValueT", bound=float | int | str)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@ -112,6 +113,116 @@ POWERWALL_INSTANT_SENSORS = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_battery_charge(battery_data: BatteryResponse) -> float:
|
||||||
|
"""Get the current value in %."""
|
||||||
|
ratio = float(battery_data.energy_remaining) / float(battery_data.capacity)
|
||||||
|
return round(100 * ratio, 1)
|
||||||
|
|
||||||
|
|
||||||
|
BATTERY_INSTANT_SENSORS: list[PowerwallSensorEntityDescription] = [
|
||||||
|
PowerwallSensorEntityDescription[BatteryResponse, int](
|
||||||
|
key="battery_capacity",
|
||||||
|
translation_key="battery_capacity",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.ENERGY_STORAGE,
|
||||||
|
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||||
|
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
value_fn=lambda battery_data: battery_data.capacity,
|
||||||
|
),
|
||||||
|
PowerwallSensorEntityDescription[BatteryResponse, float](
|
||||||
|
key="battery_instant_voltage",
|
||||||
|
translation_key="battery_instant_voltage",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.VOLTAGE,
|
||||||
|
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||||
|
value_fn=lambda battery_data: round(battery_data.v_out, 1),
|
||||||
|
),
|
||||||
|
PowerwallSensorEntityDescription[BatteryResponse, float](
|
||||||
|
key="instant_frequency",
|
||||||
|
translation_key="instant_frequency",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.FREQUENCY,
|
||||||
|
native_unit_of_measurement=UnitOfFrequency.HERTZ,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
value_fn=lambda battery_data: round(battery_data.f_out, 1),
|
||||||
|
),
|
||||||
|
PowerwallSensorEntityDescription[BatteryResponse, float](
|
||||||
|
key="instant_current",
|
||||||
|
translation_key="instant_current",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.CURRENT,
|
||||||
|
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
value_fn=lambda battery_data: round(battery_data.i_out, 1),
|
||||||
|
),
|
||||||
|
PowerwallSensorEntityDescription[BatteryResponse, int](
|
||||||
|
key="instant_power",
|
||||||
|
translation_key="instant_power",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
|
value_fn=lambda battery_data: battery_data.p_out,
|
||||||
|
),
|
||||||
|
PowerwallSensorEntityDescription[BatteryResponse, float](
|
||||||
|
key="battery_export",
|
||||||
|
translation_key="battery_export",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
|
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||||
|
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
|
suggested_display_precision=0,
|
||||||
|
value_fn=lambda battery_data: battery_data.energy_discharged,
|
||||||
|
),
|
||||||
|
PowerwallSensorEntityDescription[BatteryResponse, float](
|
||||||
|
key="battery_import",
|
||||||
|
translation_key="battery_import",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
|
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||||
|
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
|
suggested_display_precision=0,
|
||||||
|
value_fn=lambda battery_data: battery_data.energy_charged,
|
||||||
|
),
|
||||||
|
PowerwallSensorEntityDescription[BatteryResponse, int](
|
||||||
|
key="battery_remaining",
|
||||||
|
translation_key="battery_remaining",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.ENERGY_STORAGE,
|
||||||
|
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||||
|
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
value_fn=lambda battery_data: battery_data.energy_remaining,
|
||||||
|
),
|
||||||
|
PowerwallSensorEntityDescription[BatteryResponse, float](
|
||||||
|
key="charge",
|
||||||
|
translation_key="charge",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.BATTERY,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
suggested_display_precision=0,
|
||||||
|
value_fn=_get_battery_charge,
|
||||||
|
),
|
||||||
|
PowerwallSensorEntityDescription[BatteryResponse, str](
|
||||||
|
key="grid_state",
|
||||||
|
translation_key="grid_state",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
options=[state.value.lower() for state in GridState],
|
||||||
|
value_fn=lambda battery_data: battery_data.grid_state.value.lower(),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
@ -137,6 +248,12 @@ async def async_setup_entry(
|
|||||||
for description in POWERWALL_INSTANT_SENSORS
|
for description in POWERWALL_INSTANT_SENSORS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for battery in data.batteries.values():
|
||||||
|
entities.extend(
|
||||||
|
PowerWallBatterySensor(powerwall_data, battery, description)
|
||||||
|
for description in BATTERY_INSTANT_SENSORS
|
||||||
|
)
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
@ -281,3 +398,26 @@ class PowerWallImportSensor(PowerWallEnergyDirectionSensor):
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
assert meter is not None
|
assert meter is not None
|
||||||
return meter.get_energy_imported()
|
return meter.get_energy_imported()
|
||||||
|
|
||||||
|
|
||||||
|
class PowerWallBatterySensor(BatteryEntity, SensorEntity, Generic[_ValueT]):
|
||||||
|
"""Representation of an Powerwall Battery sensor."""
|
||||||
|
|
||||||
|
entity_description: PowerwallSensorEntityDescription[BatteryResponse, _ValueT]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
powerwall_data: PowerwallRuntimeData,
|
||||||
|
battery: BatteryResponse,
|
||||||
|
description: PowerwallSensorEntityDescription[BatteryResponse, _ValueT],
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
self.entity_description = description
|
||||||
|
super().__init__(powerwall_data, battery)
|
||||||
|
self._attr_translation_key = description.translation_key
|
||||||
|
self._attr_unique_id = f"{self.base_unique_id}_{description.key}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self) -> float | int | str:
|
||||||
|
"""Get the current value."""
|
||||||
|
return self.entity_description.value_fn(self.battery_data)
|
||||||
|
@ -146,6 +146,20 @@
|
|||||||
"battery_export": {
|
"battery_export": {
|
||||||
"name": "Battery export"
|
"name": "Battery export"
|
||||||
},
|
},
|
||||||
|
"battery_capacity": {
|
||||||
|
"name": "Battery capacity"
|
||||||
|
},
|
||||||
|
"battery_remaining": {
|
||||||
|
"name": "Battery remaining"
|
||||||
|
},
|
||||||
|
"grid_state": {
|
||||||
|
"name": "Grid state",
|
||||||
|
"state": {
|
||||||
|
"grid_compliant": "Compliant",
|
||||||
|
"grid_qualifying": "Qualifying",
|
||||||
|
"grid_uncompliant": "Uncompliant"
|
||||||
|
}
|
||||||
|
},
|
||||||
"load_import": {
|
"load_import": {
|
||||||
"name": "Load import"
|
"name": "Load import"
|
||||||
},
|
},
|
||||||
|
32
tests/components/powerwall/fixtures/batteries.json
Normal file
32
tests/components/powerwall/fixtures/batteries.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"PackagePartNumber": "3012170-05-C",
|
||||||
|
"PackageSerialNumber": "TG0123456789AB",
|
||||||
|
"energy_charged": 2693355,
|
||||||
|
"energy_discharged": 2358235,
|
||||||
|
"nominal_energy_remaining": 14715,
|
||||||
|
"nominal_full_pack_energy": 14715,
|
||||||
|
"wobble_detected": false,
|
||||||
|
"p_out": -100,
|
||||||
|
"q_out": -1080,
|
||||||
|
"v_out": 245.70000000000002,
|
||||||
|
"f_out": 50.037,
|
||||||
|
"i_out": 0.30000000000000004,
|
||||||
|
"pinv_grid_state": "Grid_Compliant"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PackagePartNumber": "3012170-05-C",
|
||||||
|
"PackageSerialNumber": "TG9876543210BA",
|
||||||
|
"energy_charged": 610483,
|
||||||
|
"energy_discharged": 509907,
|
||||||
|
"nominal_energy_remaining": 15137,
|
||||||
|
"nominal_full_pack_energy": 15137,
|
||||||
|
"wobble_detected": false,
|
||||||
|
"p_out": -100,
|
||||||
|
"q_out": -1090,
|
||||||
|
"v_out": 245.60000000000002,
|
||||||
|
"f_out": 50.037,
|
||||||
|
"i_out": 0.1,
|
||||||
|
"pinv_grid_state": "Grid_Compliant"
|
||||||
|
}
|
||||||
|
]
|
@ -6,6 +6,7 @@ import os
|
|||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from tesla_powerwall import (
|
from tesla_powerwall import (
|
||||||
|
BatteryResponse,
|
||||||
DeviceType,
|
DeviceType,
|
||||||
GridStatus,
|
GridStatus,
|
||||||
MetersAggregatesResponse,
|
MetersAggregatesResponse,
|
||||||
@ -29,6 +30,7 @@ async def _mock_powerwall_with_fixtures(hass, empty_meters: bool = False) -> Mag
|
|||||||
site_info = tg.create_task(_async_load_json_fixture(hass, "site_info.json"))
|
site_info = tg.create_task(_async_load_json_fixture(hass, "site_info.json"))
|
||||||
status = tg.create_task(_async_load_json_fixture(hass, "status.json"))
|
status = tg.create_task(_async_load_json_fixture(hass, "status.json"))
|
||||||
device_type = tg.create_task(_async_load_json_fixture(hass, "device_type.json"))
|
device_type = tg.create_task(_async_load_json_fixture(hass, "device_type.json"))
|
||||||
|
batteries = tg.create_task(_async_load_json_fixture(hass, "batteries.json"))
|
||||||
|
|
||||||
return await _mock_powerwall_return_value(
|
return await _mock_powerwall_return_value(
|
||||||
site_info=SiteInfoResponse.from_dict(site_info.result()),
|
site_info=SiteInfoResponse.from_dict(site_info.result()),
|
||||||
@ -41,6 +43,9 @@ async def _mock_powerwall_with_fixtures(hass, empty_meters: bool = False) -> Mag
|
|||||||
device_type=DeviceType(device_type.result()["device_type"]),
|
device_type=DeviceType(device_type.result()["device_type"]),
|
||||||
serial_numbers=["TG0123456789AB", "TG9876543210BA"],
|
serial_numbers=["TG0123456789AB", "TG9876543210BA"],
|
||||||
backup_reserve_percentage=15.0,
|
backup_reserve_percentage=15.0,
|
||||||
|
batteries=[
|
||||||
|
BatteryResponse.from_dict(battery) for battery in batteries.result()
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -55,6 +60,7 @@ async def _mock_powerwall_return_value(
|
|||||||
device_type=None,
|
device_type=None,
|
||||||
serial_numbers=None,
|
serial_numbers=None,
|
||||||
backup_reserve_percentage=None,
|
backup_reserve_percentage=None,
|
||||||
|
batteries=None,
|
||||||
):
|
):
|
||||||
powerwall_mock = MagicMock(Powerwall)
|
powerwall_mock = MagicMock(Powerwall)
|
||||||
powerwall_mock.__aenter__.return_value = powerwall_mock
|
powerwall_mock.__aenter__.return_value = powerwall_mock
|
||||||
@ -72,6 +78,7 @@ async def _mock_powerwall_return_value(
|
|||||||
)
|
)
|
||||||
powerwall_mock.is_grid_services_active.return_value = grid_services_active
|
powerwall_mock.is_grid_services_active.return_value = grid_services_active
|
||||||
powerwall_mock.get_gateway_din.return_value = MOCK_GATEWAY_DIN
|
powerwall_mock.get_gateway_din.return_value = MOCK_GATEWAY_DIN
|
||||||
|
powerwall_mock.get_batteries.return_value = batteries
|
||||||
|
|
||||||
return powerwall_mock
|
return powerwall_mock
|
||||||
|
|
||||||
|
@ -132,6 +132,67 @@ async def test_sensors(
|
|||||||
assert hass.states.get("sensor.mysite_load_frequency").state == STATE_UNKNOWN
|
assert hass.states.get("sensor.mysite_load_frequency").state == STATE_UNKNOWN
|
||||||
assert hass.states.get("sensor.mysite_backup_reserve").state == STATE_UNKNOWN
|
assert hass.states.get("sensor.mysite_backup_reserve").state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
assert (
|
||||||
|
float(hass.states.get("sensor.mysite_tg0123456789ab_battery_capacity").state)
|
||||||
|
== 14.715
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
float(hass.states.get("sensor.mysite_tg0123456789ab_battery_voltage").state)
|
||||||
|
== 245.7
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
float(hass.states.get("sensor.mysite_tg0123456789ab_frequency").state) == 50.0
|
||||||
|
)
|
||||||
|
assert float(hass.states.get("sensor.mysite_tg0123456789ab_current").state) == 0.3
|
||||||
|
assert int(hass.states.get("sensor.mysite_tg0123456789ab_power").state) == -100
|
||||||
|
assert (
|
||||||
|
float(hass.states.get("sensor.mysite_tg0123456789ab_battery_export").state)
|
||||||
|
== 2358.235
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
float(hass.states.get("sensor.mysite_tg0123456789ab_battery_import").state)
|
||||||
|
== 2693.355
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
float(hass.states.get("sensor.mysite_tg0123456789ab_battery_remaining").state)
|
||||||
|
== 14.715
|
||||||
|
)
|
||||||
|
assert float(hass.states.get("sensor.mysite_tg0123456789ab_charge").state) == 100.0
|
||||||
|
assert (
|
||||||
|
str(hass.states.get("sensor.mysite_tg0123456789ab_grid_state").state)
|
||||||
|
== "grid_compliant"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
float(hass.states.get("sensor.mysite_tg9876543210ba_battery_capacity").state)
|
||||||
|
== 15.137
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
float(hass.states.get("sensor.mysite_tg9876543210ba_battery_voltage").state)
|
||||||
|
== 245.6
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
float(hass.states.get("sensor.mysite_tg9876543210ba_frequency").state) == 50.0
|
||||||
|
)
|
||||||
|
assert float(hass.states.get("sensor.mysite_tg9876543210ba_current").state) == 0.1
|
||||||
|
assert int(hass.states.get("sensor.mysite_tg9876543210ba_power").state) == -100
|
||||||
|
assert (
|
||||||
|
float(hass.states.get("sensor.mysite_tg9876543210ba_battery_export").state)
|
||||||
|
== 509.907
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
float(hass.states.get("sensor.mysite_tg9876543210ba_battery_import").state)
|
||||||
|
== 610.483
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
float(hass.states.get("sensor.mysite_tg9876543210ba_battery_remaining").state)
|
||||||
|
== 15.137
|
||||||
|
)
|
||||||
|
assert float(hass.states.get("sensor.mysite_tg9876543210ba_charge").state) == 100.0
|
||||||
|
assert (
|
||||||
|
str(hass.states.get("sensor.mysite_tg9876543210ba_grid_state").state)
|
||||||
|
== "grid_compliant"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_sensor_backup_reserve_unavailable(hass: HomeAssistant) -> None:
|
async def test_sensor_backup_reserve_unavailable(hass: HomeAssistant) -> None:
|
||||||
"""Confirm that backup reserve sensor is not added if data is unavailable from the device."""
|
"""Confirm that backup reserve sensor is not added if data is unavailable from the device."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user