Silently retry Fronius inverter endpoint 2 times (#61826)

This commit is contained in:
Matthias Alphart 2021-12-19 11:37:14 +01:00 committed by GitHub
parent 38cb477e7b
commit 37bed64607
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 47 additions and 15 deletions

View File

@ -5,7 +5,7 @@ from abc import ABC, abstractmethod
from datetime import timedelta
from typing import TYPE_CHECKING, Any, Dict, TypeVar
from pyfronius import FroniusError
from pyfronius import BadStatusError, FroniusError
from homeassistant.components.sensor import SensorEntityDescription
from homeassistant.core import callback
@ -43,6 +43,8 @@ class FroniusCoordinatorBase(
error_interval: timedelta
valid_descriptions: list[SensorEntityDescription]
MAX_FAILED_UPDATES = 3
def __init__(self, *args: Any, solar_net: FroniusSolarNet, **kwargs: Any) -> None:
"""Set up the FroniusCoordinatorBase class."""
self._failed_update_count = 0
@ -62,7 +64,7 @@ class FroniusCoordinatorBase(
data = await self._update_method()
except FroniusError as err:
self._failed_update_count += 1
if self._failed_update_count == 3:
if self._failed_update_count == self.MAX_FAILED_UPDATES:
self.update_interval = self.error_interval
raise UpdateFailed(err) from err
@ -116,6 +118,8 @@ class FroniusInverterUpdateCoordinator(FroniusCoordinatorBase):
error_interval = timedelta(minutes=10)
valid_descriptions = INVERTER_ENTITY_DESCRIPTIONS
SILENT_RETRIES = 3
def __init__(
self, *args: Any, inverter_info: FroniusDeviceInfo, **kwargs: Any
) -> None:
@ -125,9 +129,19 @@ class FroniusInverterUpdateCoordinator(FroniusCoordinatorBase):
async def _update_method(self) -> dict[SolarNetId, Any]:
"""Return data per solar net id from pyfronius."""
data = await self.solar_net.fronius.current_inverter_data(
self.inverter_info.solar_net_id
)
# almost 1% of `current_inverter_data` requests on Symo devices result in
# `BadStatusError Code: 8 - LNRequestTimeout` due to flaky internal
# communication between the logger and the inverter.
for silent_retry in range(self.SILENT_RETRIES):
try:
data = await self.solar_net.fronius.current_inverter_data(
self.inverter_info.solar_net_id
)
except BadStatusError as err:
if silent_retry == (self.SILENT_RETRIES - 1):
raise err
continue
break
# wrap a single devices data in a dict with solar_net_id key for
# FroniusCoordinatorBase _async_update_data and add_entities_for_seen_keys
return {self.inverter_info.solar_net_id: data}

View File

@ -1,7 +1,7 @@
"""Test the Fronius update coordinators."""
from unittest.mock import patch
from pyfronius import FroniusError
from pyfronius import BadStatusError, FroniusError
from homeassistant.components.fronius.coordinator import (
FroniusInverterUpdateCoordinator,
@ -18,27 +18,32 @@ async def test_adaptive_update_interval(hass, aioclient_mock):
with patch("pyfronius.Fronius.current_inverter_data") as mock_inverter_data:
mock_responses(aioclient_mock)
await setup_fronius_integration(hass)
assert mock_inverter_data.call_count == 1
mock_inverter_data.assert_called_once()
mock_inverter_data.reset_mock()
async_fire_time_changed(
hass, dt.utcnow() + FroniusInverterUpdateCoordinator.default_interval
)
await hass.async_block_till_done()
assert mock_inverter_data.call_count == 2
mock_inverter_data.assert_called_once()
mock_inverter_data.reset_mock()
mock_inverter_data.side_effect = FroniusError
# first 3 requests at default interval - 4th has different interval
for _ in range(4):
mock_inverter_data.side_effect = FroniusError()
# first 3 bad requests at default interval - 4th has different interval
for _ in range(3):
async_fire_time_changed(
hass, dt.utcnow() + FroniusInverterUpdateCoordinator.default_interval
)
await hass.async_block_till_done()
assert mock_inverter_data.call_count == 5
assert mock_inverter_data.call_count == 3
mock_inverter_data.reset_mock()
async_fire_time_changed(
hass, dt.utcnow() + FroniusInverterUpdateCoordinator.error_interval
)
await hass.async_block_till_done()
assert mock_inverter_data.call_count == 6
assert mock_inverter_data.call_count == 1
mock_inverter_data.reset_mock()
mock_inverter_data.side_effect = None
# next successful request resets to default interval
@ -46,10 +51,23 @@ async def test_adaptive_update_interval(hass, aioclient_mock):
hass, dt.utcnow() + FroniusInverterUpdateCoordinator.error_interval
)
await hass.async_block_till_done()
assert mock_inverter_data.call_count == 7
mock_inverter_data.assert_called_once()
mock_inverter_data.reset_mock()
async_fire_time_changed(
hass, dt.utcnow() + FroniusInverterUpdateCoordinator.default_interval
)
await hass.async_block_till_done()
assert mock_inverter_data.call_count == 8
mock_inverter_data.assert_called_once()
mock_inverter_data.reset_mock()
# BadStatusError on inverter endpoints have special handling
mock_inverter_data.side_effect = BadStatusError("mock_endpoint", 8)
# first 3 requests at default interval - 4th has different interval
for _ in range(3):
async_fire_time_changed(
hass, dt.utcnow() + FroniusInverterUpdateCoordinator.default_interval
)
await hass.async_block_till_done()
# BadStatusError does 3 silent retries for inverter endpoint * 3 request intervals = 9
assert mock_inverter_data.call_count == 9