mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Add scheduled envoy firmware checks to enphase_envoy coordinator (#136102)
* Add scheduled envoy firmware checks to enphase_envoy coordinator * Set firmware scantime to 4 hours and split test in 2
This commit is contained in:
parent
11d44e608b
commit
ba2c8646e9
@ -79,6 +79,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: EnphaseConfigEntry) ->
|
|||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
coordinator.async_cancel_token_refresh()
|
coordinator.async_cancel_token_refresh()
|
||||||
|
coordinator.async_cancel_firmware_refresh()
|
||||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ SCAN_INTERVAL = timedelta(seconds=60)
|
|||||||
TOKEN_REFRESH_CHECK_INTERVAL = timedelta(days=1)
|
TOKEN_REFRESH_CHECK_INTERVAL = timedelta(days=1)
|
||||||
STALE_TOKEN_THRESHOLD = timedelta(days=30).total_seconds()
|
STALE_TOKEN_THRESHOLD = timedelta(days=30).total_seconds()
|
||||||
NOTIFICATION_ID = "enphase_envoy_notification"
|
NOTIFICATION_ID = "enphase_envoy_notification"
|
||||||
|
FIRMWARE_REFRESH_INTERVAL = timedelta(hours=4)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -50,6 +51,7 @@ class EnphaseUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||||||
self._setup_complete = False
|
self._setup_complete = False
|
||||||
self.envoy_firmware = ""
|
self.envoy_firmware = ""
|
||||||
self._cancel_token_refresh: CALLBACK_TYPE | None = None
|
self._cancel_token_refresh: CALLBACK_TYPE | None = None
|
||||||
|
self._cancel_firmware_refresh: CALLBACK_TYPE | None = None
|
||||||
super().__init__(
|
super().__init__(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
@ -87,10 +89,48 @@ class EnphaseUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||||||
return
|
return
|
||||||
self._async_update_saved_token()
|
self._async_update_saved_token()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_refresh_firmware(self, now: datetime.datetime) -> None:
|
||||||
|
"""Proactively check for firmware changes in Envoy."""
|
||||||
|
self.hass.async_create_background_task(
|
||||||
|
self._async_try_refresh_firmware(), "{name} firmware refresh"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _async_try_refresh_firmware(self) -> None:
|
||||||
|
"""Check firmware in Envoy and reload config entry if changed."""
|
||||||
|
# envoy.setup just reads firmware, serial and partnumber from /info
|
||||||
|
try:
|
||||||
|
await self.envoy.setup()
|
||||||
|
except EnvoyError as err:
|
||||||
|
# just try again next time
|
||||||
|
_LOGGER.debug("%s: Error reading firmware: %s", err, self.name)
|
||||||
|
return
|
||||||
|
if (current_firmware := self.envoy_firmware) and current_firmware != (
|
||||||
|
new_firmware := self.envoy.firmware
|
||||||
|
):
|
||||||
|
self.envoy_firmware = new_firmware
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Envoy firmware changed from: %s to: %s, reloading config entry %s",
|
||||||
|
current_firmware,
|
||||||
|
new_firmware,
|
||||||
|
self.name,
|
||||||
|
)
|
||||||
|
# reload the integration to get all established again
|
||||||
|
self.hass.async_create_task(
|
||||||
|
self.hass.config_entries.async_reload(self.config_entry.entry_id)
|
||||||
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_mark_setup_complete(self) -> None:
|
def _async_mark_setup_complete(self) -> None:
|
||||||
"""Mark setup as complete and setup token refresh if needed."""
|
"""Mark setup as complete and setup firmware checks and token refresh if needed."""
|
||||||
self._setup_complete = True
|
self._setup_complete = True
|
||||||
|
self.async_cancel_firmware_refresh()
|
||||||
|
self._cancel_firmware_refresh = async_track_time_interval(
|
||||||
|
self.hass,
|
||||||
|
self._async_refresh_firmware,
|
||||||
|
FIRMWARE_REFRESH_INTERVAL,
|
||||||
|
cancel_on_shutdown=True,
|
||||||
|
)
|
||||||
self.async_cancel_token_refresh()
|
self.async_cancel_token_refresh()
|
||||||
if not isinstance(self.envoy.auth, EnvoyTokenAuth):
|
if not isinstance(self.envoy.auth, EnvoyTokenAuth):
|
||||||
return
|
return
|
||||||
@ -204,3 +244,10 @@ class EnphaseUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||||||
if self._cancel_token_refresh:
|
if self._cancel_token_refresh:
|
||||||
self._cancel_token_refresh()
|
self._cancel_token_refresh()
|
||||||
self._cancel_token_refresh = None
|
self._cancel_token_refresh = None
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_cancel_firmware_refresh(self) -> None:
|
||||||
|
"""Cancel firmware refresh."""
|
||||||
|
if self._cancel_firmware_refresh:
|
||||||
|
self._cancel_firmware_refresh()
|
||||||
|
self._cancel_firmware_refresh = None
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
"""Test Enphase Envoy runtime."""
|
"""Test Enphase Envoy runtime."""
|
||||||
|
|
||||||
from unittest.mock import AsyncMock, patch
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
from jwt import encode
|
from jwt import encode
|
||||||
@ -15,7 +17,10 @@ from homeassistant.components.enphase_envoy.const import (
|
|||||||
OPTION_DISABLE_KEEP_ALIVE,
|
OPTION_DISABLE_KEEP_ALIVE,
|
||||||
Platform,
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.components.enphase_envoy.coordinator import SCAN_INTERVAL
|
from homeassistant.components.enphase_envoy.coordinator import (
|
||||||
|
FIRMWARE_REFRESH_INTERVAL,
|
||||||
|
SCAN_INTERVAL,
|
||||||
|
)
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
@ -377,3 +382,82 @@ async def test_option_change_reload(
|
|||||||
OPTION_DIAGNOSTICS_INCLUDE_FIXTURES: True,
|
OPTION_DIAGNOSTICS_INCLUDE_FIXTURES: True,
|
||||||
OPTION_DISABLE_KEEP_ALIVE: False,
|
OPTION_DISABLE_KEEP_ALIVE: False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def mock_envoy_setup(mock_envoy: AsyncMock):
|
||||||
|
"""Mock envoy.setup."""
|
||||||
|
mock_envoy.firmware = "9.9.9999"
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
"homeassistant.components.enphase_envoy.coordinator.SCAN_INTERVAL",
|
||||||
|
timedelta(days=1),
|
||||||
|
)
|
||||||
|
@respx.mock
|
||||||
|
async def test_coordinator_firmware_refresh(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
mock_envoy: AsyncMock,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test coordinator scheduled firmware check."""
|
||||||
|
await setup_integration(hass, config_entry)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
# Move time to next firmware check moment
|
||||||
|
# SCAN_INTERVAL is patched to 1 day to disable it's firmware detection
|
||||||
|
mock_envoy.setup.reset_mock()
|
||||||
|
freezer.tick(FIRMWARE_REFRESH_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
|
mock_envoy.setup.assert_called_once_with()
|
||||||
|
mock_envoy.setup.reset_mock()
|
||||||
|
|
||||||
|
envoy = config_entry.runtime_data.envoy
|
||||||
|
assert envoy.firmware == "7.6.175"
|
||||||
|
|
||||||
|
caplog.set_level(logging.WARNING)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.enphase_envoy.Envoy.setup",
|
||||||
|
MagicMock(return_value=mock_envoy_setup(mock_envoy)),
|
||||||
|
):
|
||||||
|
freezer.tick(FIRMWARE_REFRESH_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"Envoy firmware changed from: 7.6.175 to: 9.9.9999, reloading config entry Envoy 1234"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
envoy = config_entry.runtime_data.envoy
|
||||||
|
assert envoy.firmware == "9.9.9999"
|
||||||
|
|
||||||
|
|
||||||
|
@respx.mock
|
||||||
|
async def test_coordinator_firmware_refresh_with_envoy_error(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
mock_envoy: AsyncMock,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test coordinator scheduled firmware check."""
|
||||||
|
await setup_integration(hass, config_entry)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
caplog.set_level(logging.DEBUG)
|
||||||
|
logging.getLogger("homeassistant.components.enphase_envoy.coordinator").setLevel(
|
||||||
|
logging.DEBUG
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_envoy.setup.side_effect = EnvoyError
|
||||||
|
freezer.tick(FIRMWARE_REFRESH_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
|
assert "Error reading firmware:" in caplog.text
|
||||||
|
Loading…
x
Reference in New Issue
Block a user