Allow retries on communication exceptions for Aurora ABB Powerone solar inverter (#104492)
* Allow retries on SerialException, AuroraError * Add test to verify that retry is occuring * Fix tests and indents * Only log to info level for normal on/offline * Review comment: don't log warning, debug and raise UpdateFailed * Fix tests
This commit is contained in:
parent
5230a8a210
commit
318b6e3a8b
2 changed files with 71 additions and 36 deletions
|
@ -11,13 +11,15 @@
|
|||
# sudo chmod 777 /dev/ttyUSB0
|
||||
|
||||
import logging
|
||||
from time import sleep
|
||||
|
||||
from aurorapy.client import AuroraError, AuroraSerialClient, AuroraTimeoutError
|
||||
from serial import SerialException
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ADDRESS, CONF_PORT, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN, SCAN_INTERVAL
|
||||
|
||||
|
@ -69,38 +71,49 @@ class AuroraAbbDataUpdateCoordinator(DataUpdateCoordinator[dict[str, float]]):
|
|||
"""
|
||||
data: dict[str, float] = {}
|
||||
self.available_prev = self.available
|
||||
try:
|
||||
self.client.connect()
|
||||
retries: int = 3
|
||||
while retries > 0:
|
||||
try:
|
||||
self.client.connect()
|
||||
|
||||
# read ADC channel 3 (grid power output)
|
||||
power_watts = self.client.measure(3, True)
|
||||
temperature_c = self.client.measure(21)
|
||||
energy_wh = self.client.cumulated_energy(5)
|
||||
[alarm, *_] = self.client.alarms()
|
||||
except AuroraTimeoutError:
|
||||
self.available = False
|
||||
_LOGGER.debug("No response from inverter (could be dark)")
|
||||
except AuroraError as error:
|
||||
self.available = False
|
||||
raise error
|
||||
else:
|
||||
data["instantaneouspower"] = round(power_watts, 1)
|
||||
data["temp"] = round(temperature_c, 1)
|
||||
data["totalenergy"] = round(energy_wh / 1000, 2)
|
||||
data["alarm"] = alarm
|
||||
self.available = True
|
||||
|
||||
finally:
|
||||
if self.available != self.available_prev:
|
||||
if self.available:
|
||||
_LOGGER.info("Communication with %s back online", self.name)
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"Communication with %s lost",
|
||||
self.name,
|
||||
)
|
||||
if self.client.serline.isOpen():
|
||||
self.client.close()
|
||||
# read ADC channel 3 (grid power output)
|
||||
power_watts = self.client.measure(3, True)
|
||||
temperature_c = self.client.measure(21)
|
||||
energy_wh = self.client.cumulated_energy(5)
|
||||
[alarm, *_] = self.client.alarms()
|
||||
except AuroraTimeoutError:
|
||||
self.available = False
|
||||
_LOGGER.debug("No response from inverter (could be dark)")
|
||||
retries = 0
|
||||
except (SerialException, AuroraError) as error:
|
||||
self.available = False
|
||||
retries -= 1
|
||||
if retries <= 0:
|
||||
raise UpdateFailed(error) from error
|
||||
_LOGGER.debug(
|
||||
"Exception: %s occurred, %d retries remaining",
|
||||
repr(error),
|
||||
retries,
|
||||
)
|
||||
sleep(1)
|
||||
else:
|
||||
data["instantaneouspower"] = round(power_watts, 1)
|
||||
data["temp"] = round(temperature_c, 1)
|
||||
data["totalenergy"] = round(energy_wh / 1000, 2)
|
||||
data["alarm"] = alarm
|
||||
self.available = True
|
||||
retries = 0
|
||||
finally:
|
||||
if self.available != self.available_prev:
|
||||
if self.available:
|
||||
_LOGGER.info("Communication with %s back online", self.name)
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"Communication with %s lost",
|
||||
self.name,
|
||||
)
|
||||
if self.client.serline.isOpen():
|
||||
self.client.close()
|
||||
|
||||
return data
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ from unittest.mock import patch
|
|||
|
||||
from aurorapy.client import AuroraError, AuroraTimeoutError
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.aurora_abb_powerone.const import (
|
||||
ATTR_DEVICE_NAME,
|
||||
|
@ -171,18 +172,39 @@ async def test_sensor_dark(hass: HomeAssistant, freezer: FrozenDateTimeFactory)
|
|||
assert power.state == "unknown" # should this be 'available'?
|
||||
|
||||
|
||||
async def test_sensor_unknown_error(hass: HomeAssistant) -> None:
|
||||
async def test_sensor_unknown_error(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test other comms error is handled correctly."""
|
||||
mock_entry = _mock_config_entry()
|
||||
|
||||
# sun is up
|
||||
with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch(
|
||||
"aurorapy.client.AuroraSerialClient.measure", side_effect=_simulated_returns
|
||||
), patch(
|
||||
"aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"]
|
||||
), patch(
|
||||
"aurorapy.client.AuroraSerialClient.cumulated_energy",
|
||||
side_effect=_simulated_returns,
|
||||
):
|
||||
mock_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch(
|
||||
"aurorapy.client.AuroraSerialClient.measure",
|
||||
side_effect=AuroraError("another error"),
|
||||
), patch(
|
||||
"aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"]
|
||||
), patch("serial.Serial.isOpen", return_value=True):
|
||||
mock_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
freezer.tick(SCAN_INTERVAL * 2)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert (
|
||||
"Exception: AuroraError('another error') occurred, 2 retries remaining"
|
||||
in caplog.text
|
||||
)
|
||||
power = hass.states.get("sensor.mydevicename_power_output")
|
||||
assert power is None
|
||||
assert power.state == "unavailable"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue