Periodically re-scan for Fronius inverters that were offline while setup (#96538)
parent
65ebb6a74f
commit
e29b6408f6
|
@ -3,20 +3,28 @@ from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import Final, TypeVar
|
from typing import Final, TypeVar
|
||||||
|
|
||||||
from pyfronius import Fronius, FroniusError
|
from pyfronius import Fronius, FroniusError
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||||
from homeassistant.const import ATTR_MODEL, ATTR_SW_VERSION, CONF_HOST, Platform
|
from homeassistant.const import ATTR_MODEL, ATTR_SW_VERSION, CONF_HOST, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.event import async_track_time_interval
|
||||||
|
|
||||||
from .const import DOMAIN, SOLAR_NET_ID_SYSTEM, FroniusDeviceInfo
|
from .const import (
|
||||||
|
DOMAIN,
|
||||||
|
SOLAR_NET_ID_SYSTEM,
|
||||||
|
SOLAR_NET_RESCAN_TIMER,
|
||||||
|
FroniusDeviceInfo,
|
||||||
|
)
|
||||||
from .coordinator import (
|
from .coordinator import (
|
||||||
FroniusCoordinatorBase,
|
FroniusCoordinatorBase,
|
||||||
FroniusInverterUpdateCoordinator,
|
FroniusInverterUpdateCoordinator,
|
||||||
|
@ -26,6 +34,7 @@ from .coordinator import (
|
||||||
FroniusPowerFlowUpdateCoordinator,
|
FroniusPowerFlowUpdateCoordinator,
|
||||||
FroniusStorageUpdateCoordinator,
|
FroniusStorageUpdateCoordinator,
|
||||||
)
|
)
|
||||||
|
from .sensor import InverterSensor
|
||||||
|
|
||||||
_LOGGER: Final = logging.getLogger(__name__)
|
_LOGGER: Final = logging.getLogger(__name__)
|
||||||
PLATFORMS: Final = [Platform.SENSOR]
|
PLATFORMS: Final = [Platform.SENSOR]
|
||||||
|
@ -67,6 +76,7 @@ class FroniusSolarNet:
|
||||||
self.cleanup_callbacks: list[Callable[[], None]] = []
|
self.cleanup_callbacks: list[Callable[[], None]] = []
|
||||||
self.config_entry = entry
|
self.config_entry = entry
|
||||||
self.coordinator_lock = asyncio.Lock()
|
self.coordinator_lock = asyncio.Lock()
|
||||||
|
self.sensor_async_add_entities: AddEntitiesCallback | None = None
|
||||||
self.fronius = fronius
|
self.fronius = fronius
|
||||||
self.host: str = entry.data[CONF_HOST]
|
self.host: str = entry.data[CONF_HOST]
|
||||||
# entry.unique_id is either logger uid or first inverter uid if no logger available
|
# entry.unique_id is either logger uid or first inverter uid if no logger available
|
||||||
|
@ -95,17 +105,7 @@ class FroniusSolarNet:
|
||||||
# _create_solar_net_device uses data from self.logger_coordinator when available
|
# _create_solar_net_device uses data from self.logger_coordinator when available
|
||||||
self.system_device_info = await self._create_solar_net_device()
|
self.system_device_info = await self._create_solar_net_device()
|
||||||
|
|
||||||
_inverter_infos = await self._get_inverter_infos()
|
await self._init_devices_inverter()
|
||||||
for inverter_info in _inverter_infos:
|
|
||||||
coordinator = FroniusInverterUpdateCoordinator(
|
|
||||||
hass=self.hass,
|
|
||||||
solar_net=self,
|
|
||||||
logger=_LOGGER,
|
|
||||||
name=f"{DOMAIN}_inverter_{inverter_info.solar_net_id}_{self.host}",
|
|
||||||
inverter_info=inverter_info,
|
|
||||||
)
|
|
||||||
await coordinator.async_config_entry_first_refresh()
|
|
||||||
self.inverter_coordinators.append(coordinator)
|
|
||||||
|
|
||||||
self.meter_coordinator = await self._init_optional_coordinator(
|
self.meter_coordinator = await self._init_optional_coordinator(
|
||||||
FroniusMeterUpdateCoordinator(
|
FroniusMeterUpdateCoordinator(
|
||||||
|
@ -143,6 +143,15 @@ class FroniusSolarNet:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Setup periodic re-scan
|
||||||
|
self.cleanup_callbacks.append(
|
||||||
|
async_track_time_interval(
|
||||||
|
self.hass,
|
||||||
|
self._init_devices_inverter,
|
||||||
|
timedelta(minutes=SOLAR_NET_RESCAN_TIMER),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
async def _create_solar_net_device(self) -> DeviceInfo:
|
async def _create_solar_net_device(self) -> DeviceInfo:
|
||||||
"""Create a device for the Fronius SolarNet system."""
|
"""Create a device for the Fronius SolarNet system."""
|
||||||
solar_net_device: DeviceInfo = DeviceInfo(
|
solar_net_device: DeviceInfo = DeviceInfo(
|
||||||
|
@ -168,14 +177,57 @@ class FroniusSolarNet:
|
||||||
)
|
)
|
||||||
return solar_net_device
|
return solar_net_device
|
||||||
|
|
||||||
|
async def _init_devices_inverter(self, _now: datetime | None = None) -> None:
|
||||||
|
"""Get available inverters and set up coordinators for new found devices."""
|
||||||
|
_inverter_infos = await self._get_inverter_infos()
|
||||||
|
|
||||||
|
_LOGGER.debug("Processing inverters for: %s", _inverter_infos)
|
||||||
|
for _inverter_info in _inverter_infos:
|
||||||
|
_inverter_name = (
|
||||||
|
f"{DOMAIN}_inverter_{_inverter_info.solar_net_id}_{self.host}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add found inverter only not already existing
|
||||||
|
if _inverter_info.solar_net_id in [
|
||||||
|
inv.inverter_info.solar_net_id for inv in self.inverter_coordinators
|
||||||
|
]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
_coordinator = FroniusInverterUpdateCoordinator(
|
||||||
|
hass=self.hass,
|
||||||
|
solar_net=self,
|
||||||
|
logger=_LOGGER,
|
||||||
|
name=_inverter_name,
|
||||||
|
inverter_info=_inverter_info,
|
||||||
|
)
|
||||||
|
await _coordinator.async_config_entry_first_refresh()
|
||||||
|
self.inverter_coordinators.append(_coordinator)
|
||||||
|
|
||||||
|
# Only for re-scans. Initial setup adds entities through sensor.async_setup_entry
|
||||||
|
if self.sensor_async_add_entities is not None:
|
||||||
|
_coordinator.add_entities_for_seen_keys(
|
||||||
|
self.sensor_async_add_entities, InverterSensor
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"New inverter added (UID: %s)",
|
||||||
|
_inverter_info.unique_id,
|
||||||
|
)
|
||||||
|
|
||||||
async def _get_inverter_infos(self) -> list[FroniusDeviceInfo]:
|
async def _get_inverter_infos(self) -> list[FroniusDeviceInfo]:
|
||||||
"""Get information about the inverters in the SolarNet system."""
|
"""Get information about the inverters in the SolarNet system."""
|
||||||
|
inverter_infos: list[FroniusDeviceInfo] = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_inverter_info = await self.fronius.inverter_info()
|
_inverter_info = await self.fronius.inverter_info()
|
||||||
except FroniusError as err:
|
except FroniusError as err:
|
||||||
|
if self.config_entry.state == ConfigEntryState.LOADED:
|
||||||
|
# During a re-scan we will attempt again as per schedule.
|
||||||
|
_LOGGER.debug("Re-scan failed for %s", self.host)
|
||||||
|
return inverter_infos
|
||||||
|
|
||||||
raise ConfigEntryNotReady from err
|
raise ConfigEntryNotReady from err
|
||||||
|
|
||||||
inverter_infos: list[FroniusDeviceInfo] = []
|
|
||||||
for inverter in _inverter_info["inverters"]:
|
for inverter in _inverter_info["inverters"]:
|
||||||
solar_net_id = inverter["device_id"]["value"]
|
solar_net_id = inverter["device_id"]["value"]
|
||||||
unique_id = inverter["unique_id"]["value"]
|
unique_id = inverter["unique_id"]["value"]
|
||||||
|
@ -195,6 +247,12 @@ class FroniusSolarNet:
|
||||||
unique_id=unique_id,
|
unique_id=unique_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Inverter found at %s (Device ID: %s, UID: %s)",
|
||||||
|
self.host,
|
||||||
|
solar_net_id,
|
||||||
|
unique_id,
|
||||||
|
)
|
||||||
return inverter_infos
|
return inverter_infos
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -8,6 +8,7 @@ DOMAIN: Final = "fronius"
|
||||||
SolarNetId = str
|
SolarNetId = str
|
||||||
SOLAR_NET_ID_POWER_FLOW: SolarNetId = "power_flow"
|
SOLAR_NET_ID_POWER_FLOW: SolarNetId = "power_flow"
|
||||||
SOLAR_NET_ID_SYSTEM: SolarNetId = "system"
|
SOLAR_NET_ID_SYSTEM: SolarNetId = "system"
|
||||||
|
SOLAR_NET_RESCAN_TIMER: Final = 60
|
||||||
|
|
||||||
|
|
||||||
class FroniusConfigEntryData(TypedDict):
|
class FroniusConfigEntryData(TypedDict):
|
||||||
|
|
|
@ -53,6 +53,8 @@ async def async_setup_entry(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Fronius sensor entities based on a config entry."""
|
"""Set up Fronius sensor entities based on a config entry."""
|
||||||
solar_net: FroniusSolarNet = hass.data[DOMAIN][config_entry.entry_id]
|
solar_net: FroniusSolarNet = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
solar_net.sensor_async_add_entities = async_add_entities
|
||||||
|
|
||||||
for inverter_coordinator in solar_net.inverter_coordinators:
|
for inverter_coordinator in solar_net.inverter_coordinators:
|
||||||
inverter_coordinator.add_entities_for_seen_keys(
|
inverter_coordinator.add_entities_for_seen_keys(
|
||||||
async_add_entities, InverterSensor
|
async_add_entities, InverterSensor
|
||||||
|
|
|
@ -59,7 +59,7 @@ def mock_responses(
|
||||||
)
|
)
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
f"{host}/solar_api/v1/GetInverterInfo.cgi",
|
f"{host}/solar_api/v1/GetInverterInfo.cgi",
|
||||||
text=load_fixture(f"{fixture_set}/GetInverterInfo.json", "fronius"),
|
text=load_fixture(f"{fixture_set}/GetInverterInfo{_night}.json", "fronius"),
|
||||||
)
|
)
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
f"{host}/solar_api/v1/GetLoggerInfo.cgi",
|
f"{host}/solar_api/v1/GetLoggerInfo.cgi",
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"APIVersion": 1,
|
||||||
|
"BaseURL": "/solar_api/v1/",
|
||||||
|
"CompatibilityRange": "1.5-18"
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"Body": {
|
||||||
|
"Data": {
|
||||||
|
"1": {
|
||||||
|
"CustomName": "IG Plus 70 V-2",
|
||||||
|
"DT": 174,
|
||||||
|
"ErrorCode": 0,
|
||||||
|
"PVPower": 6500,
|
||||||
|
"Show": 1,
|
||||||
|
"StatusCode": 7,
|
||||||
|
"UniqueID": "203200"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Head": {
|
||||||
|
"RequestArguments": {},
|
||||||
|
"Status": {
|
||||||
|
"Code": 0,
|
||||||
|
"Reason": "",
|
||||||
|
"UserMessage": ""
|
||||||
|
},
|
||||||
|
"Timestamp": "2023-07-14T17:19:20+02:00"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"Body": {
|
||||||
|
"Data": {}
|
||||||
|
},
|
||||||
|
"Head": {
|
||||||
|
"RequestArguments": {},
|
||||||
|
"Status": {
|
||||||
|
"Code": 0,
|
||||||
|
"Reason": "",
|
||||||
|
"UserMessage": ""
|
||||||
|
},
|
||||||
|
"Timestamp": "2023-06-27T21:48:52+02:00"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"Body": {
|
||||||
|
"Data": {
|
||||||
|
"DAY_ENERGY": {
|
||||||
|
"Unit": "Wh",
|
||||||
|
"Value": 42000
|
||||||
|
},
|
||||||
|
"DeviceStatus": {
|
||||||
|
"ErrorCode": 0,
|
||||||
|
"LEDColor": 2,
|
||||||
|
"LEDState": 0,
|
||||||
|
"MgmtTimerRemainingTime": -1,
|
||||||
|
"StateToReset": false,
|
||||||
|
"StatusCode": 7
|
||||||
|
},
|
||||||
|
"FAC": {
|
||||||
|
"Unit": "Hz",
|
||||||
|
"Value": 49.960000000000001
|
||||||
|
},
|
||||||
|
"IAC": {
|
||||||
|
"Unit": "A",
|
||||||
|
"Value": 9.0299999999999994
|
||||||
|
},
|
||||||
|
"IDC": {
|
||||||
|
"Unit": "A",
|
||||||
|
"Value": 6.46
|
||||||
|
},
|
||||||
|
"PAC": {
|
||||||
|
"Unit": "W",
|
||||||
|
"Value": 2096
|
||||||
|
},
|
||||||
|
"TOTAL_ENERGY": {
|
||||||
|
"Unit": "Wh",
|
||||||
|
"Value": 81809000
|
||||||
|
},
|
||||||
|
"UAC": {
|
||||||
|
"Unit": "V",
|
||||||
|
"Value": 232
|
||||||
|
},
|
||||||
|
"UDC": {
|
||||||
|
"Unit": "V",
|
||||||
|
"Value": 345
|
||||||
|
},
|
||||||
|
"YEAR_ENERGY": {
|
||||||
|
"Unit": "Wh",
|
||||||
|
"Value": 4927000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Head": {
|
||||||
|
"RequestArguments": {
|
||||||
|
"DataCollection": "CommonInverterData",
|
||||||
|
"DeviceClass": "Inverter",
|
||||||
|
"DeviceId": "1",
|
||||||
|
"Scope": "Device"
|
||||||
|
},
|
||||||
|
"Status": {
|
||||||
|
"Code": 0,
|
||||||
|
"Reason": "",
|
||||||
|
"UserMessage": ""
|
||||||
|
},
|
||||||
|
"Timestamp": "2023-07-14T17:21:42+02:00"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"Body": {
|
||||||
|
"Data": {}
|
||||||
|
},
|
||||||
|
"Head": {
|
||||||
|
"RequestArguments": {},
|
||||||
|
"Status": {
|
||||||
|
"Code": 0,
|
||||||
|
"Reason": "",
|
||||||
|
"UserMessage": ""
|
||||||
|
},
|
||||||
|
"Timestamp": "2023-06-27T21:48:52+02:00"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"Body": {
|
||||||
|
"LoggerInfo": {
|
||||||
|
"CO2Factor": 0.52999997138977051,
|
||||||
|
"CO2Unit": "kg",
|
||||||
|
"CashCurrency": "EUR",
|
||||||
|
"CashFactor": 0.07700000643730164,
|
||||||
|
"DefaultLanguage": "en",
|
||||||
|
"DeliveryFactor": 0.25,
|
||||||
|
"HWVersion": "2.4D",
|
||||||
|
"PlatformID": "wilma",
|
||||||
|
"ProductID": "fronius-datamanager-card",
|
||||||
|
"SWVersion": "3.26.1-3",
|
||||||
|
"TimezoneLocation": "Berlin",
|
||||||
|
"TimezoneName": "CEST",
|
||||||
|
"UTCOffset": 7200,
|
||||||
|
"UniqueID": "123.4567890"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Head": {
|
||||||
|
"RequestArguments": {},
|
||||||
|
"Status": {
|
||||||
|
"Code": 0,
|
||||||
|
"Reason": "",
|
||||||
|
"UserMessage": ""
|
||||||
|
},
|
||||||
|
"Timestamp": "2023-07-14T17:23:22+02:00"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"Body": {
|
||||||
|
"Data": {}
|
||||||
|
},
|
||||||
|
"Head": {
|
||||||
|
"RequestArguments": {
|
||||||
|
"DeviceClass": "Meter",
|
||||||
|
"Scope": "System"
|
||||||
|
},
|
||||||
|
"Status": {
|
||||||
|
"Code": 0,
|
||||||
|
"Reason": "",
|
||||||
|
"UserMessage": ""
|
||||||
|
},
|
||||||
|
"Timestamp": "2023-07-14T17:28:05+02:00"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"Body": {
|
||||||
|
"Data": {}
|
||||||
|
},
|
||||||
|
"Head": {
|
||||||
|
"RequestArguments": {
|
||||||
|
"DeviceClass": "OhmPilot",
|
||||||
|
"Scope": "System"
|
||||||
|
},
|
||||||
|
"Status": {
|
||||||
|
"Code": 0,
|
||||||
|
"Reason": "",
|
||||||
|
"UserMessage": ""
|
||||||
|
},
|
||||||
|
"Timestamp": "2023-07-14T17:29:16+02:00"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"Body": {
|
||||||
|
"Data": {
|
||||||
|
"Inverters": {
|
||||||
|
"1": {
|
||||||
|
"DT": 174,
|
||||||
|
"E_Day": 43000,
|
||||||
|
"E_Total": 1230000,
|
||||||
|
"E_Year": 12345,
|
||||||
|
"P": 2241
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Site": {
|
||||||
|
"E_Day": 43000,
|
||||||
|
"E_Total": 1230000,
|
||||||
|
"E_Year": 12345,
|
||||||
|
"Meter_Location": "unknown",
|
||||||
|
"Mode": "produce-only",
|
||||||
|
"P_Akku": null,
|
||||||
|
"P_Grid": null,
|
||||||
|
"P_Load": null,
|
||||||
|
"P_PV": 2241,
|
||||||
|
"rel_Autonomy": null,
|
||||||
|
"rel_SelfConsumption": null
|
||||||
|
},
|
||||||
|
"Version": "12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Head": {
|
||||||
|
"RequestArguments": {},
|
||||||
|
"Status": {
|
||||||
|
"Code": 0,
|
||||||
|
"Reason": "",
|
||||||
|
"UserMessage": ""
|
||||||
|
},
|
||||||
|
"Timestamp": "2023-07-14T17:29:55+02:00"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"Body": {
|
||||||
|
"Data": {
|
||||||
|
"Inverters": {},
|
||||||
|
"Site": {
|
||||||
|
"E_Day": null,
|
||||||
|
"E_Total": null,
|
||||||
|
"E_Year": null,
|
||||||
|
"Meter_Location": "unknown",
|
||||||
|
"Mode": "produce-only",
|
||||||
|
"P_Akku": null,
|
||||||
|
"P_Grid": null,
|
||||||
|
"P_Load": null,
|
||||||
|
"P_PV": null,
|
||||||
|
"rel_Autonomy": null,
|
||||||
|
"rel_SelfConsumption": null
|
||||||
|
},
|
||||||
|
"Version": "12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Head": {
|
||||||
|
"RequestArguments": {
|
||||||
|
"humanreadable": "false"
|
||||||
|
},
|
||||||
|
"Status": {
|
||||||
|
"Code": 0,
|
||||||
|
"Reason": "",
|
||||||
|
"UserMessage": ""
|
||||||
|
},
|
||||||
|
"Timestamp": "2023-07-13T22:04:44+02:00"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"Body": {
|
||||||
|
"Data": {
|
||||||
|
"1": {
|
||||||
|
"CustomName": "Symo 20",
|
||||||
|
"DT": 121,
|
||||||
|
"ErrorCode": 0,
|
||||||
|
"PVPower": 23100,
|
||||||
|
"Show": 1,
|
||||||
|
"StatusCode": 7,
|
||||||
|
"UniqueID": "1234567"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Head": {
|
||||||
|
"RequestArguments": {},
|
||||||
|
"Status": {
|
||||||
|
"Code": 0,
|
||||||
|
"Reason": "",
|
||||||
|
"UserMessage": ""
|
||||||
|
},
|
||||||
|
"Timestamp": "2021-10-07T13:41:00+02:00"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,18 @@
|
||||||
"""Test the Fronius integration."""
|
"""Test the Fronius integration."""
|
||||||
|
from datetime import timedelta
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from pyfronius import FroniusError
|
from pyfronius import FroniusError
|
||||||
|
|
||||||
from homeassistant.components.fronius.const import DOMAIN
|
from homeassistant.components.fronius.const import DOMAIN, SOLAR_NET_RESCAN_TIMER
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import mock_responses, setup_fronius_integration
|
from . import mock_responses, setup_fronius_integration
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,3 +57,82 @@ async def test_inverter_error(
|
||||||
):
|
):
|
||||||
config_entry = await setup_fronius_integration(hass)
|
config_entry = await setup_fronius_integration(hass)
|
||||||
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_inverter_night_rescan(
|
||||||
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||||
|
) -> None:
|
||||||
|
"""Test dynamic adding of an inverter discovered automatically after a Home Assistant reboot during the night."""
|
||||||
|
mock_responses(aioclient_mock, fixture_set="igplus_v2", night=True)
|
||||||
|
config_entry = await setup_fronius_integration(hass, is_logger=True)
|
||||||
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
# Only expect logger during the night
|
||||||
|
fronius_entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
|
assert len(fronius_entries) == 1
|
||||||
|
|
||||||
|
# Switch to daytime
|
||||||
|
mock_responses(aioclient_mock, fixture_set="igplus_v2", night=False)
|
||||||
|
async_fire_time_changed(
|
||||||
|
hass, dt_util.utcnow() + timedelta(minutes=SOLAR_NET_RESCAN_TIMER)
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# We expect our inverter to be present now
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
inverter_1 = device_registry.async_get_device(identifiers={(DOMAIN, "203200")})
|
||||||
|
assert inverter_1.manufacturer == "Fronius"
|
||||||
|
|
||||||
|
# After another re-scan we still only expect this inverter
|
||||||
|
async_fire_time_changed(
|
||||||
|
hass, dt_util.utcnow() + timedelta(minutes=SOLAR_NET_RESCAN_TIMER * 2)
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
inverter_1 = device_registry.async_get_device(identifiers={(DOMAIN, "203200")})
|
||||||
|
assert inverter_1.manufacturer == "Fronius"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_inverter_rescan_interruption(
|
||||||
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||||
|
) -> None:
|
||||||
|
"""Test interruption of re-scan during runtime to process further."""
|
||||||
|
mock_responses(aioclient_mock, fixture_set="igplus_v2", night=True)
|
||||||
|
config_entry = await setup_fronius_integration(hass, is_logger=True)
|
||||||
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
# Expect 1 devices during the night, logger
|
||||||
|
assert (
|
||||||
|
len(dr.async_entries_for_config_entry(device_registry, config_entry.entry_id))
|
||||||
|
== 1
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"pyfronius.Fronius.inverter_info",
|
||||||
|
side_effect=FroniusError,
|
||||||
|
):
|
||||||
|
async_fire_time_changed(
|
||||||
|
hass, dt_util.utcnow() + timedelta(minutes=SOLAR_NET_RESCAN_TIMER)
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# No increase of devices expected because of a FroniusError
|
||||||
|
assert (
|
||||||
|
len(
|
||||||
|
dr.async_entries_for_config_entry(
|
||||||
|
device_registry, config_entry.entry_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== 1
|
||||||
|
)
|
||||||
|
|
||||||
|
# Next re-scan will pick up the new inverter. Expect 2 devices now.
|
||||||
|
mock_responses(aioclient_mock, fixture_set="igplus_v2", night=False)
|
||||||
|
async_fire_time_changed(
|
||||||
|
hass, dt_util.utcnow() + timedelta(minutes=SOLAR_NET_RESCAN_TIMER * 2)
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert (
|
||||||
|
len(dr.async_entries_for_config_entry(device_registry, config_entry.entry_id))
|
||||||
|
== 2
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue