mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Add textual representation entities for Fronius status codes (#94155)
* optionally decouple `EntityDescription.key` from API response key this makes it possible to have multiple entities for a single API response field * Add optional `value_fn` to EntityDescriptions eg. to be able to map a API response value to a different value (status_code -> message) * Add inverter `status_message` entity * Add meter `meter_location_description` entity * add external battery state * Make Ohmpilot entity state translateable * use built-in StrEnum * test coverage * remove unnecessary checks None is handled before
This commit is contained in:
parent
ba8e2ed7d6
commit
5550dcbec8
@ -1,7 +1,9 @@
|
|||||||
"""Constants for the Fronius integration."""
|
"""Constants for the Fronius integration."""
|
||||||
|
from enum import StrEnum
|
||||||
from typing import Final, NamedTuple, TypedDict
|
from typing import Final, NamedTuple, TypedDict
|
||||||
|
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
|
from homeassistant.helpers.typing import StateType
|
||||||
|
|
||||||
DOMAIN: Final = "fronius"
|
DOMAIN: Final = "fronius"
|
||||||
|
|
||||||
@ -25,3 +27,97 @@ class FroniusDeviceInfo(NamedTuple):
|
|||||||
device_info: DeviceInfo
|
device_info: DeviceInfo
|
||||||
solar_net_id: SolarNetId
|
solar_net_id: SolarNetId
|
||||||
unique_id: str
|
unique_id: str
|
||||||
|
|
||||||
|
|
||||||
|
class InverterStatusCodeOption(StrEnum):
|
||||||
|
"""Status codes for Fronius inverters."""
|
||||||
|
|
||||||
|
# these are keys for state translations - so snake_case is used
|
||||||
|
STARTUP = "startup"
|
||||||
|
RUNNING = "running"
|
||||||
|
STANDBY = "standby"
|
||||||
|
BOOTLOADING = "bootloading"
|
||||||
|
ERROR = "error"
|
||||||
|
IDLE = "idle"
|
||||||
|
READY = "ready"
|
||||||
|
SLEEPING = "sleeping"
|
||||||
|
UNKNOWN = "unknown"
|
||||||
|
INVALID = "invalid"
|
||||||
|
|
||||||
|
|
||||||
|
_INVERTER_STATUS_CODES: Final[dict[int, InverterStatusCodeOption]] = {
|
||||||
|
0: InverterStatusCodeOption.STARTUP,
|
||||||
|
1: InverterStatusCodeOption.STARTUP,
|
||||||
|
2: InverterStatusCodeOption.STARTUP,
|
||||||
|
3: InverterStatusCodeOption.STARTUP,
|
||||||
|
4: InverterStatusCodeOption.STARTUP,
|
||||||
|
5: InverterStatusCodeOption.STARTUP,
|
||||||
|
6: InverterStatusCodeOption.STARTUP,
|
||||||
|
7: InverterStatusCodeOption.RUNNING,
|
||||||
|
8: InverterStatusCodeOption.STANDBY,
|
||||||
|
9: InverterStatusCodeOption.BOOTLOADING,
|
||||||
|
10: InverterStatusCodeOption.ERROR,
|
||||||
|
11: InverterStatusCodeOption.IDLE,
|
||||||
|
12: InverterStatusCodeOption.READY,
|
||||||
|
13: InverterStatusCodeOption.SLEEPING,
|
||||||
|
255: InverterStatusCodeOption.UNKNOWN,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_inverter_status_message(code: StateType) -> InverterStatusCodeOption:
|
||||||
|
"""Return a status message for a given status code."""
|
||||||
|
return _INVERTER_STATUS_CODES.get(code, InverterStatusCodeOption.INVALID) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
|
class MeterLocationCodeOption(StrEnum):
|
||||||
|
"""Meter location codes for Fronius meters."""
|
||||||
|
|
||||||
|
# these are keys for state translations - so snake_case is used
|
||||||
|
FEED_IN = "feed_in"
|
||||||
|
CONSUMPTION_PATH = "consumption_path"
|
||||||
|
GENERATOR = "external_generator"
|
||||||
|
EXT_BATTERY = "external_battery"
|
||||||
|
SUBLOAD = "subload"
|
||||||
|
|
||||||
|
|
||||||
|
def get_meter_location_description(code: StateType) -> MeterLocationCodeOption | None:
|
||||||
|
"""Return a location_description for a given location code."""
|
||||||
|
match int(code): # type: ignore[arg-type]
|
||||||
|
case 0:
|
||||||
|
return MeterLocationCodeOption.FEED_IN
|
||||||
|
case 1:
|
||||||
|
return MeterLocationCodeOption.CONSUMPTION_PATH
|
||||||
|
case 3:
|
||||||
|
return MeterLocationCodeOption.GENERATOR
|
||||||
|
case 4:
|
||||||
|
return MeterLocationCodeOption.EXT_BATTERY
|
||||||
|
case _ as _code if 256 <= _code <= 511:
|
||||||
|
return MeterLocationCodeOption.SUBLOAD
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class OhmPilotStateCodeOption(StrEnum):
|
||||||
|
"""OhmPilot state codes for Fronius inverters."""
|
||||||
|
|
||||||
|
# these are keys for state translations - so snake_case is used
|
||||||
|
UP_AND_RUNNING = "up_and_running"
|
||||||
|
KEEP_MINIMUM_TEMPERATURE = "keep_minimum_temperature"
|
||||||
|
LEGIONELLA_PROTECTION = "legionella_protection"
|
||||||
|
CRITICAL_FAULT = "critical_fault"
|
||||||
|
FAULT = "fault"
|
||||||
|
BOOST_MODE = "boost_mode"
|
||||||
|
|
||||||
|
|
||||||
|
_OHMPILOT_STATE_CODES: Final[dict[int, OhmPilotStateCodeOption]] = {
|
||||||
|
0: OhmPilotStateCodeOption.UP_AND_RUNNING,
|
||||||
|
1: OhmPilotStateCodeOption.KEEP_MINIMUM_TEMPERATURE,
|
||||||
|
2: OhmPilotStateCodeOption.LEGIONELLA_PROTECTION,
|
||||||
|
3: OhmPilotStateCodeOption.CRITICAL_FAULT,
|
||||||
|
4: OhmPilotStateCodeOption.FAULT,
|
||||||
|
5: OhmPilotStateCodeOption.BOOST_MODE,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_ohmpilot_state_message(code: StateType) -> OhmPilotStateCodeOption | None:
|
||||||
|
"""Return a status message for a given status code."""
|
||||||
|
return _OHMPILOT_STATE_CODES.get(code) # type: ignore[arg-type]
|
||||||
|
@ -49,8 +49,10 @@ class FroniusCoordinatorBase(
|
|||||||
"""Set up the FroniusCoordinatorBase class."""
|
"""Set up the FroniusCoordinatorBase class."""
|
||||||
self._failed_update_count = 0
|
self._failed_update_count = 0
|
||||||
self.solar_net = solar_net
|
self.solar_net = solar_net
|
||||||
# unregistered_keys are used to create entities in platform module
|
# unregistered_descriptors are used to create entities in platform module
|
||||||
self.unregistered_keys: dict[SolarNetId, set[str]] = {}
|
self.unregistered_descriptors: dict[
|
||||||
|
SolarNetId, list[FroniusSensorEntityDescription]
|
||||||
|
] = {}
|
||||||
super().__init__(*args, update_interval=self.default_interval, **kwargs)
|
super().__init__(*args, update_interval=self.default_interval, **kwargs)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -73,11 +75,11 @@ class FroniusCoordinatorBase(
|
|||||||
self.update_interval = self.default_interval
|
self.update_interval = self.default_interval
|
||||||
|
|
||||||
for solar_net_id in data:
|
for solar_net_id in data:
|
||||||
if solar_net_id not in self.unregistered_keys:
|
if solar_net_id not in self.unregistered_descriptors:
|
||||||
# id seen for the first time
|
# id seen for the first time
|
||||||
self.unregistered_keys[solar_net_id] = {
|
self.unregistered_descriptors[
|
||||||
desc.key for desc in self.valid_descriptions
|
solar_net_id
|
||||||
}
|
] = self.valid_descriptions.copy()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -92,22 +94,34 @@ class FroniusCoordinatorBase(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _add_entities_for_unregistered_keys() -> None:
|
def _add_entities_for_unregistered_descriptors() -> None:
|
||||||
"""Add entities for keys seen for the first time."""
|
"""Add entities for keys seen for the first time."""
|
||||||
new_entities: list = []
|
new_entities: list[_FroniusEntityT] = []
|
||||||
for solar_net_id, device_data in self.data.items():
|
for solar_net_id, device_data in self.data.items():
|
||||||
for key in self.unregistered_keys[solar_net_id].intersection(
|
remaining_unregistered_descriptors = []
|
||||||
device_data
|
for description in self.unregistered_descriptors[solar_net_id]:
|
||||||
):
|
key = description.response_key or description.key
|
||||||
if device_data[key]["value"] is None:
|
if key not in device_data:
|
||||||
|
remaining_unregistered_descriptors.append(description)
|
||||||
continue
|
continue
|
||||||
new_entities.append(entity_constructor(self, key, solar_net_id))
|
if device_data[key]["value"] is None:
|
||||||
self.unregistered_keys[solar_net_id].remove(key)
|
remaining_unregistered_descriptors.append(description)
|
||||||
|
continue
|
||||||
|
new_entities.append(
|
||||||
|
entity_constructor(
|
||||||
|
coordinator=self,
|
||||||
|
description=description,
|
||||||
|
solar_net_id=solar_net_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.unregistered_descriptors[
|
||||||
|
solar_net_id
|
||||||
|
] = remaining_unregistered_descriptors
|
||||||
async_add_entities(new_entities)
|
async_add_entities(new_entities)
|
||||||
|
|
||||||
_add_entities_for_unregistered_keys()
|
_add_entities_for_unregistered_descriptors()
|
||||||
self.solar_net.cleanup_callbacks.append(
|
self.solar_net.cleanup_callbacks.append(
|
||||||
self.async_add_listener(_add_entities_for_unregistered_keys)
|
self.async_add_listener(_add_entities_for_unregistered_descriptors)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Support for Fronius devices."""
|
"""Support for Fronius devices."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, Any, Final
|
from typing import TYPE_CHECKING, Any, Final
|
||||||
|
|
||||||
@ -30,7 +31,16 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import DOMAIN, SOLAR_NET_DISCOVERY_NEW
|
from .const import (
|
||||||
|
DOMAIN,
|
||||||
|
SOLAR_NET_DISCOVERY_NEW,
|
||||||
|
InverterStatusCodeOption,
|
||||||
|
MeterLocationCodeOption,
|
||||||
|
OhmPilotStateCodeOption,
|
||||||
|
get_inverter_status_message,
|
||||||
|
get_meter_location_description,
|
||||||
|
get_ohmpilot_state_message,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from . import FroniusSolarNet
|
from . import FroniusSolarNet
|
||||||
@ -102,6 +112,8 @@ class FroniusSensorEntityDescription(SensorEntityDescription):
|
|||||||
# Gen24 devices may report 0 for total energy while doing firmware updates.
|
# Gen24 devices may report 0 for total energy while doing firmware updates.
|
||||||
# Handling such values shall mitigate spikes in delta calculations.
|
# Handling such values shall mitigate spikes in delta calculations.
|
||||||
invalid_when_falsy: bool = False
|
invalid_when_falsy: bool = False
|
||||||
|
response_key: str | None = None
|
||||||
|
value_fn: Callable[[StateType], StateType] | None = None
|
||||||
|
|
||||||
|
|
||||||
INVERTER_ENTITY_DESCRIPTIONS: list[FroniusSensorEntityDescription] = [
|
INVERTER_ENTITY_DESCRIPTIONS: list[FroniusSensorEntityDescription] = [
|
||||||
@ -198,6 +210,15 @@ INVERTER_ENTITY_DESCRIPTIONS: list[FroniusSensorEntityDescription] = [
|
|||||||
FroniusSensorEntityDescription(
|
FroniusSensorEntityDescription(
|
||||||
key="status_code",
|
key="status_code",
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
FroniusSensorEntityDescription(
|
||||||
|
key="status_message",
|
||||||
|
response_key="status_code",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
options=[opt.value for opt in InverterStatusCodeOption],
|
||||||
|
value_fn=get_inverter_status_message,
|
||||||
),
|
),
|
||||||
FroniusSensorEntityDescription(
|
FroniusSensorEntityDescription(
|
||||||
key="led_state",
|
key="led_state",
|
||||||
@ -306,6 +327,15 @@ METER_ENTITY_DESCRIPTIONS: list[FroniusSensorEntityDescription] = [
|
|||||||
FroniusSensorEntityDescription(
|
FroniusSensorEntityDescription(
|
||||||
key="meter_location",
|
key="meter_location",
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
value_fn=int, # type: ignore[arg-type]
|
||||||
|
),
|
||||||
|
FroniusSensorEntityDescription(
|
||||||
|
key="meter_location_description",
|
||||||
|
response_key="meter_location",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
options=[opt.value for opt in MeterLocationCodeOption],
|
||||||
|
value_fn=get_meter_location_description,
|
||||||
),
|
),
|
||||||
FroniusSensorEntityDescription(
|
FroniusSensorEntityDescription(
|
||||||
key="power_apparent_phase_1",
|
key="power_apparent_phase_1",
|
||||||
@ -495,7 +525,11 @@ OHMPILOT_ENTITY_DESCRIPTIONS: list[FroniusSensorEntityDescription] = [
|
|||||||
),
|
),
|
||||||
FroniusSensorEntityDescription(
|
FroniusSensorEntityDescription(
|
||||||
key="state_message",
|
key="state_message",
|
||||||
|
response_key="state_code",
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
options=[opt.value for opt in OhmPilotStateCodeOption],
|
||||||
|
value_fn=get_ohmpilot_state_message,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -630,24 +664,22 @@ class _FroniusSensorEntity(CoordinatorEntity["FroniusCoordinatorBase"], SensorEn
|
|||||||
"""Defines a Fronius coordinator entity."""
|
"""Defines a Fronius coordinator entity."""
|
||||||
|
|
||||||
entity_description: FroniusSensorEntityDescription
|
entity_description: FroniusSensorEntityDescription
|
||||||
entity_descriptions: list[FroniusSensorEntityDescription]
|
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: FroniusCoordinatorBase,
|
coordinator: FroniusCoordinatorBase,
|
||||||
key: str,
|
description: FroniusSensorEntityDescription,
|
||||||
solar_net_id: str,
|
solar_net_id: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up an individual Fronius meter sensor."""
|
"""Set up an individual Fronius meter sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self.entity_description = next(
|
self.entity_description = description
|
||||||
desc for desc in self.entity_descriptions if desc.key == key
|
self.response_key = description.response_key or description.key
|
||||||
)
|
|
||||||
self.solar_net_id = solar_net_id
|
self.solar_net_id = solar_net_id
|
||||||
self._attr_native_value = self._get_entity_value()
|
self._attr_native_value = self._get_entity_value()
|
||||||
self._attr_translation_key = self.entity_description.key
|
self._attr_translation_key = description.key
|
||||||
|
|
||||||
def _device_data(self) -> dict[str, Any]:
|
def _device_data(self) -> dict[str, Any]:
|
||||||
"""Extract information for SolarNet device from coordinator data."""
|
"""Extract information for SolarNet device from coordinator data."""
|
||||||
@ -655,13 +687,13 @@ class _FroniusSensorEntity(CoordinatorEntity["FroniusCoordinatorBase"], SensorEn
|
|||||||
|
|
||||||
def _get_entity_value(self) -> Any:
|
def _get_entity_value(self) -> Any:
|
||||||
"""Extract entity value from coordinator. Raises KeyError if not included in latest update."""
|
"""Extract entity value from coordinator. Raises KeyError if not included in latest update."""
|
||||||
new_value = self.coordinator.data[self.solar_net_id][
|
new_value = self.coordinator.data[self.solar_net_id][self.response_key]["value"]
|
||||||
self.entity_description.key
|
|
||||||
]["value"]
|
|
||||||
if new_value is None:
|
if new_value is None:
|
||||||
return self.entity_description.default_value
|
return self.entity_description.default_value
|
||||||
if self.entity_description.invalid_when_falsy and not new_value:
|
if self.entity_description.invalid_when_falsy and not new_value:
|
||||||
return None
|
return None
|
||||||
|
if self.entity_description.value_fn is not None:
|
||||||
|
return self.entity_description.value_fn(new_value)
|
||||||
if isinstance(new_value, float):
|
if isinstance(new_value, float):
|
||||||
return round(new_value, 4)
|
return round(new_value, 4)
|
||||||
return new_value
|
return new_value
|
||||||
@ -681,54 +713,54 @@ class _FroniusSensorEntity(CoordinatorEntity["FroniusCoordinatorBase"], SensorEn
|
|||||||
class InverterSensor(_FroniusSensorEntity):
|
class InverterSensor(_FroniusSensorEntity):
|
||||||
"""Defines a Fronius inverter device sensor entity."""
|
"""Defines a Fronius inverter device sensor entity."""
|
||||||
|
|
||||||
entity_descriptions = INVERTER_ENTITY_DESCRIPTIONS
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: FroniusInverterUpdateCoordinator,
|
coordinator: FroniusInverterUpdateCoordinator,
|
||||||
key: str,
|
description: FroniusSensorEntityDescription,
|
||||||
solar_net_id: str,
|
solar_net_id: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up an individual Fronius inverter sensor."""
|
"""Set up an individual Fronius inverter sensor."""
|
||||||
super().__init__(coordinator, key, solar_net_id)
|
super().__init__(coordinator, description, solar_net_id)
|
||||||
# device_info created in __init__ from a `GetInverterInfo` request
|
# device_info created in __init__ from a `GetInverterInfo` request
|
||||||
self._attr_device_info = coordinator.inverter_info.device_info
|
self._attr_device_info = coordinator.inverter_info.device_info
|
||||||
self._attr_unique_id = f"{coordinator.inverter_info.unique_id}-{key}"
|
self._attr_unique_id = (
|
||||||
|
f"{coordinator.inverter_info.unique_id}-{description.key}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LoggerSensor(_FroniusSensorEntity):
|
class LoggerSensor(_FroniusSensorEntity):
|
||||||
"""Defines a Fronius logger device sensor entity."""
|
"""Defines a Fronius logger device sensor entity."""
|
||||||
|
|
||||||
entity_descriptions = LOGGER_ENTITY_DESCRIPTIONS
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: FroniusLoggerUpdateCoordinator,
|
coordinator: FroniusLoggerUpdateCoordinator,
|
||||||
key: str,
|
description: FroniusSensorEntityDescription,
|
||||||
solar_net_id: str,
|
solar_net_id: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up an individual Fronius meter sensor."""
|
"""Set up an individual Fronius meter sensor."""
|
||||||
super().__init__(coordinator, key, solar_net_id)
|
super().__init__(coordinator, description, solar_net_id)
|
||||||
logger_data = self._device_data()
|
logger_data = self._device_data()
|
||||||
# Logger device is already created in FroniusSolarNet._create_solar_net_device
|
# Logger device is already created in FroniusSolarNet._create_solar_net_device
|
||||||
self._attr_device_info = coordinator.solar_net.system_device_info
|
self._attr_device_info = coordinator.solar_net.system_device_info
|
||||||
self._attr_native_unit_of_measurement = logger_data[key].get("unit")
|
self._attr_native_unit_of_measurement = logger_data[self.response_key].get(
|
||||||
self._attr_unique_id = f'{logger_data["unique_identifier"]["value"]}-{key}'
|
"unit"
|
||||||
|
)
|
||||||
|
self._attr_unique_id = (
|
||||||
|
f'{logger_data["unique_identifier"]["value"]}-{description.key}'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MeterSensor(_FroniusSensorEntity):
|
class MeterSensor(_FroniusSensorEntity):
|
||||||
"""Defines a Fronius meter device sensor entity."""
|
"""Defines a Fronius meter device sensor entity."""
|
||||||
|
|
||||||
entity_descriptions = METER_ENTITY_DESCRIPTIONS
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: FroniusMeterUpdateCoordinator,
|
coordinator: FroniusMeterUpdateCoordinator,
|
||||||
key: str,
|
description: FroniusSensorEntityDescription,
|
||||||
solar_net_id: str,
|
solar_net_id: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up an individual Fronius meter sensor."""
|
"""Set up an individual Fronius meter sensor."""
|
||||||
super().__init__(coordinator, key, solar_net_id)
|
super().__init__(coordinator, description, solar_net_id)
|
||||||
meter_data = self._device_data()
|
meter_data = self._device_data()
|
||||||
# S0 meters connected directly to inverters respond "n.a." as serial number
|
# S0 meters connected directly to inverters respond "n.a." as serial number
|
||||||
# `model` contains the inverter id: "S0 Meter at inverter 1"
|
# `model` contains the inverter id: "S0 Meter at inverter 1"
|
||||||
@ -745,22 +777,20 @@ class MeterSensor(_FroniusSensorEntity):
|
|||||||
name=meter_data["model"]["value"],
|
name=meter_data["model"]["value"],
|
||||||
via_device=(DOMAIN, coordinator.solar_net.solar_net_device_id),
|
via_device=(DOMAIN, coordinator.solar_net.solar_net_device_id),
|
||||||
)
|
)
|
||||||
self._attr_unique_id = f"{meter_uid}-{key}"
|
self._attr_unique_id = f"{meter_uid}-{description.key}"
|
||||||
|
|
||||||
|
|
||||||
class OhmpilotSensor(_FroniusSensorEntity):
|
class OhmpilotSensor(_FroniusSensorEntity):
|
||||||
"""Defines a Fronius Ohmpilot sensor entity."""
|
"""Defines a Fronius Ohmpilot sensor entity."""
|
||||||
|
|
||||||
entity_descriptions = OHMPILOT_ENTITY_DESCRIPTIONS
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: FroniusOhmpilotUpdateCoordinator,
|
coordinator: FroniusOhmpilotUpdateCoordinator,
|
||||||
key: str,
|
description: FroniusSensorEntityDescription,
|
||||||
solar_net_id: str,
|
solar_net_id: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up an individual Fronius meter sensor."""
|
"""Set up an individual Fronius meter sensor."""
|
||||||
super().__init__(coordinator, key, solar_net_id)
|
super().__init__(coordinator, description, solar_net_id)
|
||||||
device_data = self._device_data()
|
device_data = self._device_data()
|
||||||
|
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
@ -771,45 +801,41 @@ class OhmpilotSensor(_FroniusSensorEntity):
|
|||||||
sw_version=device_data["software"]["value"],
|
sw_version=device_data["software"]["value"],
|
||||||
via_device=(DOMAIN, coordinator.solar_net.solar_net_device_id),
|
via_device=(DOMAIN, coordinator.solar_net.solar_net_device_id),
|
||||||
)
|
)
|
||||||
self._attr_unique_id = f'{device_data["serial"]["value"]}-{key}'
|
self._attr_unique_id = f'{device_data["serial"]["value"]}-{description.key}'
|
||||||
|
|
||||||
|
|
||||||
class PowerFlowSensor(_FroniusSensorEntity):
|
class PowerFlowSensor(_FroniusSensorEntity):
|
||||||
"""Defines a Fronius power flow sensor entity."""
|
"""Defines a Fronius power flow sensor entity."""
|
||||||
|
|
||||||
entity_descriptions = POWER_FLOW_ENTITY_DESCRIPTIONS
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: FroniusPowerFlowUpdateCoordinator,
|
coordinator: FroniusPowerFlowUpdateCoordinator,
|
||||||
key: str,
|
description: FroniusSensorEntityDescription,
|
||||||
solar_net_id: str,
|
solar_net_id: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up an individual Fronius power flow sensor."""
|
"""Set up an individual Fronius power flow sensor."""
|
||||||
super().__init__(coordinator, key, solar_net_id)
|
super().__init__(coordinator, description, solar_net_id)
|
||||||
# SolarNet device is already created in FroniusSolarNet._create_solar_net_device
|
# SolarNet device is already created in FroniusSolarNet._create_solar_net_device
|
||||||
self._attr_device_info = coordinator.solar_net.system_device_info
|
self._attr_device_info = coordinator.solar_net.system_device_info
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = (
|
||||||
f"{coordinator.solar_net.solar_net_device_id}-power_flow-{key}"
|
f"{coordinator.solar_net.solar_net_device_id}-power_flow-{description.key}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class StorageSensor(_FroniusSensorEntity):
|
class StorageSensor(_FroniusSensorEntity):
|
||||||
"""Defines a Fronius storage device sensor entity."""
|
"""Defines a Fronius storage device sensor entity."""
|
||||||
|
|
||||||
entity_descriptions = STORAGE_ENTITY_DESCRIPTIONS
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: FroniusStorageUpdateCoordinator,
|
coordinator: FroniusStorageUpdateCoordinator,
|
||||||
key: str,
|
description: FroniusSensorEntityDescription,
|
||||||
solar_net_id: str,
|
solar_net_id: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up an individual Fronius storage sensor."""
|
"""Set up an individual Fronius storage sensor."""
|
||||||
super().__init__(coordinator, key, solar_net_id)
|
super().__init__(coordinator, description, solar_net_id)
|
||||||
storage_data = self._device_data()
|
storage_data = self._device_data()
|
||||||
|
|
||||||
self._attr_unique_id = f'{storage_data["serial"]["value"]}-{key}'
|
self._attr_unique_id = f'{storage_data["serial"]["value"]}-{description.key}'
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, storage_data["serial"]["value"])},
|
identifiers={(DOMAIN, storage_data["serial"]["value"])},
|
||||||
manufacturer=storage_data["manufacturer"]["value"],
|
manufacturer=storage_data["manufacturer"]["value"],
|
||||||
|
@ -66,6 +66,21 @@
|
|||||||
"status_code": {
|
"status_code": {
|
||||||
"name": "Status code"
|
"name": "Status code"
|
||||||
},
|
},
|
||||||
|
"status_message": {
|
||||||
|
"name": "Status message",
|
||||||
|
"state": {
|
||||||
|
"startup": "Startup",
|
||||||
|
"running": "Running",
|
||||||
|
"standby": "Standby",
|
||||||
|
"bootloading": "Bootloading",
|
||||||
|
"error": "Error",
|
||||||
|
"idle": "Idle",
|
||||||
|
"ready": "Ready",
|
||||||
|
"sleeping": "Sleeping",
|
||||||
|
"unknown": "Unknown",
|
||||||
|
"invalid": "Invalid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"led_state": {
|
"led_state": {
|
||||||
"name": "LED state"
|
"name": "LED state"
|
||||||
},
|
},
|
||||||
@ -114,6 +129,16 @@
|
|||||||
"meter_location": {
|
"meter_location": {
|
||||||
"name": "Meter location"
|
"name": "Meter location"
|
||||||
},
|
},
|
||||||
|
"meter_location_description": {
|
||||||
|
"name": "Meter location description",
|
||||||
|
"state": {
|
||||||
|
"feed_in": "Grid interconnection point",
|
||||||
|
"consumption_path": "Consumption path",
|
||||||
|
"external_generator": "External generator",
|
||||||
|
"external_battery": "External battery",
|
||||||
|
"subload": "Subload"
|
||||||
|
}
|
||||||
|
},
|
||||||
"power_apparent_phase_1": {
|
"power_apparent_phase_1": {
|
||||||
"name": "Apparent power phase 1"
|
"name": "Apparent power phase 1"
|
||||||
},
|
},
|
||||||
@ -193,7 +218,15 @@
|
|||||||
"name": "State code"
|
"name": "State code"
|
||||||
},
|
},
|
||||||
"state_message": {
|
"state_message": {
|
||||||
"name": "State message"
|
"name": "State message",
|
||||||
|
"state": {
|
||||||
|
"up_and_running": "Up and running",
|
||||||
|
"keep_minimum_temperature": "Keep minimum temperature",
|
||||||
|
"legionella_protection": "Legionella protection",
|
||||||
|
"critical_fault": "Critical fault",
|
||||||
|
"fault": "Fault",
|
||||||
|
"boost_mode": "Boost mode"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"meter_mode": {
|
"meter_mode": {
|
||||||
"name": "Meter mode"
|
"name": "Meter mode"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Tests for the Fronius sensor platform."""
|
"""Tests for the Fronius sensor platform."""
|
||||||
|
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.fronius.const import DOMAIN
|
from homeassistant.components.fronius.const import DOMAIN
|
||||||
from homeassistant.components.fronius.coordinator import (
|
from homeassistant.components.fronius.coordinator import (
|
||||||
@ -33,33 +33,34 @@ async def test_symo_inverter(
|
|||||||
mock_responses(aioclient_mock, night=True)
|
mock_responses(aioclient_mock, night=True)
|
||||||
config_entry = await setup_fronius_integration(hass)
|
config_entry = await setup_fronius_integration(hass)
|
||||||
|
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 20
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 21
|
||||||
await enable_all_entities(
|
await enable_all_entities(
|
||||||
hass,
|
hass,
|
||||||
freezer,
|
freezer,
|
||||||
config_entry.entry_id,
|
config_entry.entry_id,
|
||||||
FroniusInverterUpdateCoordinator.default_interval,
|
FroniusInverterUpdateCoordinator.default_interval,
|
||||||
)
|
)
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 52
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 54
|
||||||
assert_state("sensor.symo_20_dc_current", 0)
|
assert_state("sensor.symo_20_dc_current", 0)
|
||||||
assert_state("sensor.symo_20_energy_day", 10828)
|
assert_state("sensor.symo_20_energy_day", 10828)
|
||||||
assert_state("sensor.symo_20_total_energy", 44186900)
|
assert_state("sensor.symo_20_total_energy", 44186900)
|
||||||
assert_state("sensor.symo_20_energy_year", 25507686)
|
assert_state("sensor.symo_20_energy_year", 25507686)
|
||||||
assert_state("sensor.symo_20_dc_voltage", 16)
|
assert_state("sensor.symo_20_dc_voltage", 16)
|
||||||
|
assert_state("sensor.symo_20_status_message", "startup")
|
||||||
|
|
||||||
# Second test at daytime when inverter is producing
|
# Second test at daytime when inverter is producing
|
||||||
mock_responses(aioclient_mock, night=False)
|
mock_responses(aioclient_mock, night=False)
|
||||||
freezer.tick(FroniusInverterUpdateCoordinator.default_interval)
|
freezer.tick(FroniusInverterUpdateCoordinator.default_interval)
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 56
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 58
|
||||||
await enable_all_entities(
|
await enable_all_entities(
|
||||||
hass,
|
hass,
|
||||||
freezer,
|
freezer,
|
||||||
config_entry.entry_id,
|
config_entry.entry_id,
|
||||||
FroniusInverterUpdateCoordinator.default_interval,
|
FroniusInverterUpdateCoordinator.default_interval,
|
||||||
)
|
)
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 58
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 60
|
||||||
# 4 additional AC entities
|
# 4 additional AC entities
|
||||||
assert_state("sensor.symo_20_dc_current", 2.19)
|
assert_state("sensor.symo_20_dc_current", 2.19)
|
||||||
assert_state("sensor.symo_20_energy_day", 1113)
|
assert_state("sensor.symo_20_energy_day", 1113)
|
||||||
@ -70,6 +71,7 @@ async def test_symo_inverter(
|
|||||||
assert_state("sensor.symo_20_frequency", 49.94)
|
assert_state("sensor.symo_20_frequency", 49.94)
|
||||||
assert_state("sensor.symo_20_ac_power", 1190)
|
assert_state("sensor.symo_20_ac_power", 1190)
|
||||||
assert_state("sensor.symo_20_ac_voltage", 227.90)
|
assert_state("sensor.symo_20_ac_voltage", 227.90)
|
||||||
|
assert_state("sensor.symo_20_status_message", "running")
|
||||||
|
|
||||||
# Third test at nighttime - additional AC entities default to 0
|
# Third test at nighttime - additional AC entities default to 0
|
||||||
mock_responses(aioclient_mock, night=True)
|
mock_responses(aioclient_mock, night=True)
|
||||||
@ -94,7 +96,7 @@ async def test_symo_logger(
|
|||||||
|
|
||||||
mock_responses(aioclient_mock)
|
mock_responses(aioclient_mock)
|
||||||
await setup_fronius_integration(hass)
|
await setup_fronius_integration(hass)
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 24
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 25
|
||||||
# states are rounded to 4 decimals
|
# states are rounded to 4 decimals
|
||||||
assert_state("sensor.solarnet_grid_export_tariff", 0.078)
|
assert_state("sensor.solarnet_grid_export_tariff", 0.078)
|
||||||
assert_state("sensor.solarnet_co2_factor", 0.53)
|
assert_state("sensor.solarnet_co2_factor", 0.53)
|
||||||
@ -116,14 +118,14 @@ async def test_symo_meter(
|
|||||||
mock_responses(aioclient_mock)
|
mock_responses(aioclient_mock)
|
||||||
config_entry = await setup_fronius_integration(hass)
|
config_entry = await setup_fronius_integration(hass)
|
||||||
|
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 24
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 25
|
||||||
await enable_all_entities(
|
await enable_all_entities(
|
||||||
hass,
|
hass,
|
||||||
freezer,
|
freezer,
|
||||||
config_entry.entry_id,
|
config_entry.entry_id,
|
||||||
FroniusMeterUpdateCoordinator.default_interval,
|
FroniusMeterUpdateCoordinator.default_interval,
|
||||||
)
|
)
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 58
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 60
|
||||||
# states are rounded to 4 decimals
|
# states are rounded to 4 decimals
|
||||||
assert_state("sensor.smart_meter_63a_current_phase_1", 7.755)
|
assert_state("sensor.smart_meter_63a_current_phase_1", 7.755)
|
||||||
assert_state("sensor.smart_meter_63a_current_phase_2", 6.68)
|
assert_state("sensor.smart_meter_63a_current_phase_2", 6.68)
|
||||||
@ -157,6 +159,50 @@ async def test_symo_meter(
|
|||||||
assert_state("sensor.smart_meter_63a_voltage_phase_1_2", 395.9)
|
assert_state("sensor.smart_meter_63a_voltage_phase_1_2", 395.9)
|
||||||
assert_state("sensor.smart_meter_63a_voltage_phase_2_3", 398)
|
assert_state("sensor.smart_meter_63a_voltage_phase_2_3", 398)
|
||||||
assert_state("sensor.smart_meter_63a_voltage_phase_3_1", 398)
|
assert_state("sensor.smart_meter_63a_voltage_phase_3_1", 398)
|
||||||
|
assert_state("sensor.smart_meter_63a_meter_location", 0)
|
||||||
|
assert_state("sensor.smart_meter_63a_meter_location_description", "feed_in")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("location_code", "expected_code", "expected_description"),
|
||||||
|
[
|
||||||
|
(-1, -1, "unknown"),
|
||||||
|
(3, 3, "external_generator"),
|
||||||
|
(4, 4, "external_battery"),
|
||||||
|
(7, 7, "unknown"),
|
||||||
|
(256, 256, "subload"),
|
||||||
|
(511, 511, "subload"),
|
||||||
|
(512, 512, "unknown"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_symo_meter_forged(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
location_code: int | None,
|
||||||
|
expected_code: int | str,
|
||||||
|
expected_description: str,
|
||||||
|
) -> None:
|
||||||
|
"""Tests for meter location codes we have no fixture for."""
|
||||||
|
|
||||||
|
def assert_state(entity_id, expected_state):
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
assert state.state == str(expected_state)
|
||||||
|
|
||||||
|
mock_responses(
|
||||||
|
aioclient_mock,
|
||||||
|
fixture_set="symo",
|
||||||
|
override_data={
|
||||||
|
"symo/GetMeterRealtimeData.json": [
|
||||||
|
(["Body", "Data", "0", "Meter_Location_Current"], location_code),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await setup_fronius_integration(hass)
|
||||||
|
assert_state("sensor.smart_meter_63a_meter_location", expected_code)
|
||||||
|
assert_state(
|
||||||
|
"sensor.smart_meter_63a_meter_location_description", expected_description
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_symo_power_flow(
|
async def test_symo_power_flow(
|
||||||
@ -175,14 +221,14 @@ async def test_symo_power_flow(
|
|||||||
mock_responses(aioclient_mock, night=True)
|
mock_responses(aioclient_mock, night=True)
|
||||||
config_entry = await setup_fronius_integration(hass)
|
config_entry = await setup_fronius_integration(hass)
|
||||||
|
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 20
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 21
|
||||||
await enable_all_entities(
|
await enable_all_entities(
|
||||||
hass,
|
hass,
|
||||||
freezer,
|
freezer,
|
||||||
config_entry.entry_id,
|
config_entry.entry_id,
|
||||||
FroniusInverterUpdateCoordinator.default_interval,
|
FroniusInverterUpdateCoordinator.default_interval,
|
||||||
)
|
)
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 52
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 54
|
||||||
# states are rounded to 4 decimals
|
# states are rounded to 4 decimals
|
||||||
assert_state("sensor.solarnet_energy_day", 10828)
|
assert_state("sensor.solarnet_energy_day", 10828)
|
||||||
assert_state("sensor.solarnet_total_energy", 44186900)
|
assert_state("sensor.solarnet_total_energy", 44186900)
|
||||||
@ -197,7 +243,7 @@ async def test_symo_power_flow(
|
|||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
# 54 because power_flow `rel_SelfConsumption` and `P_PV` is not `null` anymore
|
# 54 because power_flow `rel_SelfConsumption` and `P_PV` is not `null` anymore
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 54
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 56
|
||||||
assert_state("sensor.solarnet_energy_day", 1101.7001)
|
assert_state("sensor.solarnet_energy_day", 1101.7001)
|
||||||
assert_state("sensor.solarnet_total_energy", 44188000)
|
assert_state("sensor.solarnet_total_energy", 44188000)
|
||||||
assert_state("sensor.solarnet_energy_year", 25508788)
|
assert_state("sensor.solarnet_energy_year", 25508788)
|
||||||
@ -212,7 +258,7 @@ async def test_symo_power_flow(
|
|||||||
freezer.tick(FroniusPowerFlowUpdateCoordinator.default_interval)
|
freezer.tick(FroniusPowerFlowUpdateCoordinator.default_interval)
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 54
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 56
|
||||||
assert_state("sensor.solarnet_energy_day", 10828)
|
assert_state("sensor.solarnet_energy_day", 10828)
|
||||||
assert_state("sensor.solarnet_total_energy", 44186900)
|
assert_state("sensor.solarnet_total_energy", 44186900)
|
||||||
assert_state("sensor.solarnet_energy_year", 25507686)
|
assert_state("sensor.solarnet_energy_year", 25507686)
|
||||||
@ -238,18 +284,19 @@ async def test_gen24(
|
|||||||
mock_responses(aioclient_mock, fixture_set="gen24")
|
mock_responses(aioclient_mock, fixture_set="gen24")
|
||||||
config_entry = await setup_fronius_integration(hass, is_logger=False)
|
config_entry = await setup_fronius_integration(hass, is_logger=False)
|
||||||
|
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 22
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 23
|
||||||
await enable_all_entities(
|
await enable_all_entities(
|
||||||
hass,
|
hass,
|
||||||
freezer,
|
freezer,
|
||||||
config_entry.entry_id,
|
config_entry.entry_id,
|
||||||
FroniusMeterUpdateCoordinator.default_interval,
|
FroniusMeterUpdateCoordinator.default_interval,
|
||||||
)
|
)
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 52
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 54
|
||||||
# inverter 1
|
# inverter 1
|
||||||
assert_state("sensor.inverter_name_ac_current", 0.1589)
|
assert_state("sensor.inverter_name_ac_current", 0.1589)
|
||||||
assert_state("sensor.inverter_name_dc_current_2", 0.0754)
|
assert_state("sensor.inverter_name_dc_current_2", 0.0754)
|
||||||
assert_state("sensor.inverter_name_status_code", 7)
|
assert_state("sensor.inverter_name_status_code", 7)
|
||||||
|
assert_state("sensor.inverter_name_status_message", "running")
|
||||||
assert_state("sensor.inverter_name_dc_current", 0.0783)
|
assert_state("sensor.inverter_name_dc_current", 0.0783)
|
||||||
assert_state("sensor.inverter_name_dc_voltage_2", 403.4312)
|
assert_state("sensor.inverter_name_dc_voltage_2", 403.4312)
|
||||||
assert_state("sensor.inverter_name_ac_power", 37.3204)
|
assert_state("sensor.inverter_name_ac_power", 37.3204)
|
||||||
@ -264,7 +311,8 @@ async def test_gen24(
|
|||||||
assert_state("sensor.smart_meter_ts_65a_3_real_energy_consumed", 2013105.0)
|
assert_state("sensor.smart_meter_ts_65a_3_real_energy_consumed", 2013105.0)
|
||||||
assert_state("sensor.smart_meter_ts_65a_3_real_power", 653.1)
|
assert_state("sensor.smart_meter_ts_65a_3_real_power", 653.1)
|
||||||
assert_state("sensor.smart_meter_ts_65a_3_frequency_phase_average", 49.9)
|
assert_state("sensor.smart_meter_ts_65a_3_frequency_phase_average", 49.9)
|
||||||
assert_state("sensor.smart_meter_ts_65a_3_meter_location", 0.0)
|
assert_state("sensor.smart_meter_ts_65a_3_meter_location", 0)
|
||||||
|
assert_state("sensor.smart_meter_ts_65a_3_meter_location_description", "feed_in")
|
||||||
assert_state("sensor.smart_meter_ts_65a_3_power_factor", 0.828)
|
assert_state("sensor.smart_meter_ts_65a_3_power_factor", 0.828)
|
||||||
assert_state("sensor.smart_meter_ts_65a_3_reactive_energy_consumed", 88221.0)
|
assert_state("sensor.smart_meter_ts_65a_3_reactive_energy_consumed", 88221.0)
|
||||||
assert_state("sensor.smart_meter_ts_65a_3_real_energy_minus", 3863340.0)
|
assert_state("sensor.smart_meter_ts_65a_3_real_energy_minus", 3863340.0)
|
||||||
@ -336,14 +384,14 @@ async def test_gen24_storage(
|
|||||||
hass, is_logger=False, unique_id="12345678"
|
hass, is_logger=False, unique_id="12345678"
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 34
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 35
|
||||||
await enable_all_entities(
|
await enable_all_entities(
|
||||||
hass,
|
hass,
|
||||||
freezer,
|
freezer,
|
||||||
config_entry.entry_id,
|
config_entry.entry_id,
|
||||||
FroniusMeterUpdateCoordinator.default_interval,
|
FroniusMeterUpdateCoordinator.default_interval,
|
||||||
)
|
)
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 64
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 66
|
||||||
# inverter 1
|
# inverter 1
|
||||||
assert_state("sensor.gen24_storage_dc_current", 0.3952)
|
assert_state("sensor.gen24_storage_dc_current", 0.3952)
|
||||||
assert_state("sensor.gen24_storage_dc_voltage_2", 318.8103)
|
assert_state("sensor.gen24_storage_dc_voltage_2", 318.8103)
|
||||||
@ -352,6 +400,7 @@ async def test_gen24_storage(
|
|||||||
assert_state("sensor.gen24_storage_ac_power", 250.9093)
|
assert_state("sensor.gen24_storage_ac_power", 250.9093)
|
||||||
assert_state("sensor.gen24_storage_error_code", 0)
|
assert_state("sensor.gen24_storage_error_code", 0)
|
||||||
assert_state("sensor.gen24_storage_status_code", 7)
|
assert_state("sensor.gen24_storage_status_code", 7)
|
||||||
|
assert_state("sensor.gen24_storage_status_message", "running")
|
||||||
assert_state("sensor.gen24_storage_total_energy", 7512794.0117)
|
assert_state("sensor.gen24_storage_total_energy", 7512794.0117)
|
||||||
assert_state("sensor.gen24_storage_inverter_state", "Running")
|
assert_state("sensor.gen24_storage_inverter_state", "Running")
|
||||||
assert_state("sensor.gen24_storage_dc_voltage", 419.1009)
|
assert_state("sensor.gen24_storage_dc_voltage", 419.1009)
|
||||||
@ -363,7 +412,8 @@ async def test_gen24_storage(
|
|||||||
assert_state("sensor.smart_meter_ts_65a_3_power_factor", 0.698)
|
assert_state("sensor.smart_meter_ts_65a_3_power_factor", 0.698)
|
||||||
assert_state("sensor.smart_meter_ts_65a_3_real_energy_consumed", 1247204.0)
|
assert_state("sensor.smart_meter_ts_65a_3_real_energy_consumed", 1247204.0)
|
||||||
assert_state("sensor.smart_meter_ts_65a_3_frequency_phase_average", 49.9)
|
assert_state("sensor.smart_meter_ts_65a_3_frequency_phase_average", 49.9)
|
||||||
assert_state("sensor.smart_meter_ts_65a_3_meter_location", 0.0)
|
assert_state("sensor.smart_meter_ts_65a_3_meter_location", 0)
|
||||||
|
assert_state("sensor.smart_meter_ts_65a_3_meter_location_description", "feed_in")
|
||||||
assert_state("sensor.smart_meter_ts_65a_3_reactive_power", -501.5)
|
assert_state("sensor.smart_meter_ts_65a_3_reactive_power", -501.5)
|
||||||
assert_state("sensor.smart_meter_ts_65a_3_reactive_energy_produced", 3266105.0)
|
assert_state("sensor.smart_meter_ts_65a_3_reactive_energy_produced", 3266105.0)
|
||||||
assert_state("sensor.smart_meter_ts_65a_3_real_power_phase_3", 19.6)
|
assert_state("sensor.smart_meter_ts_65a_3_real_power_phase_3", 19.6)
|
||||||
@ -396,7 +446,7 @@ async def test_gen24_storage(
|
|||||||
assert_state("sensor.ohmpilot_power", 0.0)
|
assert_state("sensor.ohmpilot_power", 0.0)
|
||||||
assert_state("sensor.ohmpilot_temperature", 38.9)
|
assert_state("sensor.ohmpilot_temperature", 38.9)
|
||||||
assert_state("sensor.ohmpilot_state_code", 0.0)
|
assert_state("sensor.ohmpilot_state_code", 0.0)
|
||||||
assert_state("sensor.ohmpilot_state_message", "Up and running")
|
assert_state("sensor.ohmpilot_state_message", "up_and_running")
|
||||||
# power_flow
|
# power_flow
|
||||||
assert_state("sensor.solarnet_power_grid", 2274.9)
|
assert_state("sensor.solarnet_power_grid", 2274.9)
|
||||||
assert_state("sensor.solarnet_power_battery", 0.1591)
|
assert_state("sensor.solarnet_power_battery", 0.1591)
|
||||||
@ -463,14 +513,14 @@ async def test_primo_s0(
|
|||||||
mock_responses(aioclient_mock, fixture_set="primo_s0", inverter_ids=[1, 2])
|
mock_responses(aioclient_mock, fixture_set="primo_s0", inverter_ids=[1, 2])
|
||||||
config_entry = await setup_fronius_integration(hass, is_logger=True)
|
config_entry = await setup_fronius_integration(hass, is_logger=True)
|
||||||
|
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 29
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 30
|
||||||
await enable_all_entities(
|
await enable_all_entities(
|
||||||
hass,
|
hass,
|
||||||
freezer,
|
freezer,
|
||||||
config_entry.entry_id,
|
config_entry.entry_id,
|
||||||
FroniusMeterUpdateCoordinator.default_interval,
|
FroniusMeterUpdateCoordinator.default_interval,
|
||||||
)
|
)
|
||||||
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 40
|
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 43
|
||||||
# logger
|
# logger
|
||||||
assert_state("sensor.solarnet_grid_export_tariff", 1)
|
assert_state("sensor.solarnet_grid_export_tariff", 1)
|
||||||
assert_state("sensor.solarnet_co2_factor", 0.53)
|
assert_state("sensor.solarnet_co2_factor", 0.53)
|
||||||
@ -483,6 +533,7 @@ async def test_primo_s0(
|
|||||||
assert_state("sensor.primo_5_0_1_error_code", 0)
|
assert_state("sensor.primo_5_0_1_error_code", 0)
|
||||||
assert_state("sensor.primo_5_0_1_dc_current", 4.23)
|
assert_state("sensor.primo_5_0_1_dc_current", 4.23)
|
||||||
assert_state("sensor.primo_5_0_1_status_code", 7)
|
assert_state("sensor.primo_5_0_1_status_code", 7)
|
||||||
|
assert_state("sensor.primo_5_0_1_status_message", "running")
|
||||||
assert_state("sensor.primo_5_0_1_energy_year", 7532755.5)
|
assert_state("sensor.primo_5_0_1_energy_year", 7532755.5)
|
||||||
assert_state("sensor.primo_5_0_1_ac_current", 3.85)
|
assert_state("sensor.primo_5_0_1_ac_current", 3.85)
|
||||||
assert_state("sensor.primo_5_0_1_ac_voltage", 223.9)
|
assert_state("sensor.primo_5_0_1_ac_voltage", 223.9)
|
||||||
@ -497,6 +548,7 @@ async def test_primo_s0(
|
|||||||
assert_state("sensor.primo_3_0_1_error_code", 0)
|
assert_state("sensor.primo_3_0_1_error_code", 0)
|
||||||
assert_state("sensor.primo_3_0_1_dc_current", 0.97)
|
assert_state("sensor.primo_3_0_1_dc_current", 0.97)
|
||||||
assert_state("sensor.primo_3_0_1_status_code", 7)
|
assert_state("sensor.primo_3_0_1_status_code", 7)
|
||||||
|
assert_state("sensor.primo_3_0_1_status_message", "running")
|
||||||
assert_state("sensor.primo_3_0_1_energy_year", 3596193.25)
|
assert_state("sensor.primo_3_0_1_energy_year", 3596193.25)
|
||||||
assert_state("sensor.primo_3_0_1_ac_current", 1.32)
|
assert_state("sensor.primo_3_0_1_ac_current", 1.32)
|
||||||
assert_state("sensor.primo_3_0_1_ac_voltage", 223.6)
|
assert_state("sensor.primo_3_0_1_ac_voltage", 223.6)
|
||||||
@ -505,6 +557,9 @@ async def test_primo_s0(
|
|||||||
assert_state("sensor.primo_3_0_1_led_state", 0)
|
assert_state("sensor.primo_3_0_1_led_state", 0)
|
||||||
# meter
|
# meter
|
||||||
assert_state("sensor.s0_meter_at_inverter_1_meter_location", 1)
|
assert_state("sensor.s0_meter_at_inverter_1_meter_location", 1)
|
||||||
|
assert_state(
|
||||||
|
"sensor.s0_meter_at_inverter_1_meter_location_description", "consumption_path"
|
||||||
|
)
|
||||||
assert_state("sensor.s0_meter_at_inverter_1_real_power", -2216.7487)
|
assert_state("sensor.s0_meter_at_inverter_1_real_power", -2216.7487)
|
||||||
# power_flow
|
# power_flow
|
||||||
assert_state("sensor.solarnet_power_load", -2218.9349)
|
assert_state("sensor.solarnet_power_load", -2218.9349)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user