1 """The enphase_envoy component."""
3 from __future__
import annotations
7 from datetime
import timedelta
11 from pyenphase
import Envoy, EnvoyError, EnvoyTokenAuth
21 from .const
import INVALID_AUTH_ERRORS
26 STALE_TOKEN_THRESHOLD =
timedelta(days=30).total_seconds()
27 NOTIFICATION_ID =
"enphase_envoy_notification"
29 _LOGGER = logging.getLogger(__name__)
32 type EnphaseConfigEntry = ConfigEntry[EnphaseUpdateCoordinator]
36 """DataUpdateCoordinator to gather data from any envoy."""
38 envoy_serial_number: str
42 self, hass: HomeAssistant, envoy: Envoy, entry: EnphaseConfigEntry
44 """Initialize DataUpdateCoordinator for the envoy."""
46 entry_data = entry.data
48 self.
usernameusername = entry_data[CONF_USERNAME]
49 self.
passwordpassword = entry_data[CONF_PASSWORD]
56 name=entry_data[CONF_NAME],
57 update_interval=SCAN_INTERVAL,
63 """Proactively refresh token if its stale in case cloud services goes down."""
64 assert isinstance(self.
envoyenvoy.auth, EnvoyTokenAuth)
65 expire_time = self.
envoyenvoy.auth.expire_timestamp
66 remain = expire_time - now.timestamp()
67 fresh = remain > STALE_TOKEN_THRESHOLD
69 _LOGGER.debug(
"%s: %s seconds remaining on token fresh=%s", name, remain, fresh)
71 self.
hasshass.async_create_background_task(
76 """Try to refresh token."""
77 assert isinstance(self.
envoyenvoy.auth, EnvoyTokenAuth)
78 _LOGGER.debug(
"%s: Trying to refresh token", self.
namename)
80 await self.
envoyenvoy.auth.refresh()
81 except EnvoyError
as err:
86 _LOGGER.debug(
"%s: Error refreshing token: %s", err, self.
namename)
92 """Mark setup as complete and setup token refresh if needed."""
95 if not isinstance(self.
envoyenvoy.auth, EnvoyTokenAuth):
100 TOKEN_REFRESH_CHECK_INTERVAL,
101 cancel_on_shutdown=
True,
105 """Set up and authenticate with the envoy."""
106 envoy = self.
envoyenvoy
108 assert envoy.serial_number
is not None
110 if token := self.
entryentry.data.get(CONF_TOKEN):
111 with contextlib.suppress(*INVALID_AUTH_ERRORS):
114 await envoy.authenticate(
130 """Update saved token in config entry."""
131 envoy = self.
envoyenvoy
132 if not isinstance(envoy.auth, EnvoyTokenAuth):
137 _LOGGER.debug(
"%s: Updating token in config entry from auth", self.
namename)
138 self.
hasshass.config_entries.async_update_entry(
141 **self.
entryentry.data,
142 CONF_TOKEN: envoy.auth.token,
147 """Fetch all device and sensor data from api."""
148 envoy = self.
envoyenvoy
149 for tries
in range(2):
155 envoy_data = await envoy.update()
156 except INVALID_AUTH_ERRORS
as err:
161 raise ConfigEntryAuthFailed
from err
162 except EnvoyError
as err:
163 raise UpdateFailed(f
"Error communicating with API: {err}")
from err
168 if (current_firmware := self.
envoy_firmwareenvoy_firmware)
and current_firmware != (
169 new_firmware := envoy.firmware
172 "Envoy firmware changed from: %s to: %s, reloading enphase envoy integration",
177 self.
hasshass.async_create_task(
178 self.
hasshass.config_entries.async_reload(self.
entryentry.entry_id)
182 _LOGGER.debug(
"Envoy data: %s", envoy_data)
183 return envoy_data.raw
185 raise RuntimeError(
"Unreachable code in _async_update_data")
189 """Cancel token refresh."""
None _async_setup_and_authenticate(self)
None async_cancel_token_refresh(self)
None _async_update_saved_token(self)
None _async_refresh_token_if_needed(self, datetime.datetime now)
None __init__(self, HomeAssistant hass, Envoy envoy, EnphaseConfigEntry entry)
None _async_try_refresh_token(self)
None _async_mark_setup_complete(self)
dict[str, Any] _async_update_data(self)
def authenticate(HomeAssistant hass, host, port, servers)
CALLBACK_TYPE async_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)