diff --git a/homeassistant/components/enphase_envoy/coordinator.py b/homeassistant/components/enphase_envoy/coordinator.py index e91e245658c..00bc7666f78 100644 --- a/homeassistant/components/enphase_envoy/coordinator.py +++ b/homeassistant/components/enphase_envoy/coordinator.py @@ -24,6 +24,7 @@ SCAN_INTERVAL = timedelta(seconds=60) TOKEN_REFRESH_CHECK_INTERVAL = timedelta(days=1) STALE_TOKEN_THRESHOLD = timedelta(days=30).total_seconds() +NOTIFICATION_ID = "enphase_envoy_notification" _LOGGER = logging.getLogger(__name__) @@ -35,6 +36,7 @@ class EnphaseUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): """DataUpdateCoordinator to gather data from any envoy.""" envoy_serial_number: str + envoy_firmware: str def __init__( self, hass: HomeAssistant, envoy: Envoy, entry: EnphaseConfigEntry @@ -46,6 +48,7 @@ class EnphaseUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): self.username = entry_data[CONF_USERNAME] self.password = entry_data[CONF_PASSWORD] self._setup_complete = False + self.envoy_firmware = "" self._cancel_token_refresh: CALLBACK_TYPE | None = None super().__init__( hass, @@ -158,6 +161,24 @@ class EnphaseUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): raise ConfigEntryAuthFailed from err except EnvoyError as err: raise UpdateFailed(f"Error communicating with API: {err}") from err + + # if we have a firmware version from previous setup, compare to current one + # when envoy gets new firmware there will be an authentication failure + # which results in getting fw version again, if so reload the integration. + if (current_firmware := self.envoy_firmware) and current_firmware != ( + new_firmware := envoy.firmware + ): + _LOGGER.warning( + "Envoy firmware changed from: %s to: %s, reloading enphase envoy integration", + current_firmware, + new_firmware, + ) + # reload the integration to get all established again + self.hass.async_create_task( + self.hass.config_entries.async_reload(self.entry.entry_id) + ) + # remember firmware version for next time + self.envoy_firmware = envoy.firmware _LOGGER.debug("Envoy data: %s", envoy_data) return envoy_data.raw diff --git a/tests/components/enphase_envoy/test_sensor.py b/tests/components/enphase_envoy/test_sensor.py index 3156f154729..784dfe54073 100644 --- a/tests/components/enphase_envoy/test_sensor.py +++ b/tests/components/enphase_envoy/test_sensor.py @@ -1,6 +1,7 @@ """Test Enphase Envoy sensors.""" from itertools import chain +import logging from unittest.mock import AsyncMock, patch from freezegun.api import FrozenDateTimeFactory @@ -1002,3 +1003,36 @@ async def test_sensor_missing_data( # test the original inverter is now unknown assert (entity_state := hass.states.get("sensor.inverter_1")) assert entity_state.state == STATE_UNKNOWN + + +@pytest.mark.parametrize( + ("mock_envoy"), + [ + "envoy_metered_batt_relay", + ], + indirect=["mock_envoy"], +) +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_fw_update( + hass: HomeAssistant, + config_entry: MockConfigEntry, + mock_envoy: AsyncMock, + entity_registry: er.EntityRegistry, + freezer: FrozenDateTimeFactory, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test enphase_envoy sensor update over fw update.""" + logging.getLogger("homeassistant.components.enphase_envoy").setLevel(logging.DEBUG) + with patch("homeassistant.components.enphase_envoy.PLATFORMS", [Platform.SENSOR]): + await setup_integration(hass, config_entry) + + # force HA to detect changed data by changing raw + mock_envoy.firmware = "0.0.0" + + # Move time to next update + freezer.tick(SCAN_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + + assert "firmware changed from: " in caplog.text + assert "to: 0.0.0, reloading enphase envoy integration" in caplog.text