diff --git a/homeassistant/components/aurora_abb_powerone/__init__.py b/homeassistant/components/aurora_abb_powerone/__init__.py index c7400f31727..05e5819e36e 100644 --- a/homeassistant/components/aurora_abb_powerone/__init__.py +++ b/homeassistant/components/aurora_abb_powerone/__init__.py @@ -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 diff --git a/tests/components/aurora_abb_powerone/test_sensor.py b/tests/components/aurora_abb_powerone/test_sensor.py index d1e8bbb015a..32e96df7fac 100644 --- a/tests/components/aurora_abb_powerone/test_sensor.py +++ b/tests/components/aurora_abb_powerone/test_sensor.py @@ -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"