diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json index b339013a69f84ae40f0fe89b8fb5eca3e352ae99..87491a72546b8c75c5c1db7dcd663688eec0dcc0 100644 --- a/homeassistant/components/enphase_envoy/manifest.json +++ b/homeassistant/components/enphase_envoy/manifest.json @@ -2,7 +2,7 @@ "domain": "enphase_envoy", "name": "Enphase Envoy", "documentation": "https://www.home-assistant.io/integrations/enphase_envoy", - "requirements": ["envoy_reader==0.17.3"], + "requirements": ["envoy_reader==0.18.2"], "codeowners": [ "@gtdiehl" ] diff --git a/homeassistant/components/enphase_envoy/sensor.py b/homeassistant/components/enphase_envoy/sensor.py index a2b50f20eb6358c3c95c0d755c4264d91426577f..64b4fdf66ad90972641da23292a880267fd2da83 100644 --- a/homeassistant/components/enphase_envoy/sensor.py +++ b/homeassistant/components/enphase_envoy/sensor.py @@ -1,8 +1,11 @@ """Support for Enphase Envoy solar energy monitor.""" + +from datetime import timedelta import logging +import async_timeout from envoy_reader.envoy_reader import EnvoyReader -import requests +import httpx import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA @@ -15,8 +18,13 @@ from homeassistant.const import ( ENERGY_WATT_HOUR, POWER_WATT, ) +from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) _LOGGER = logging.getLogger(__name__) @@ -38,10 +46,11 @@ SENSORS = { "inverters": ("Envoy Inverter", POWER_WATT), } - ICON = "mdi:flash" CONST_DEFAULT_HOST = "envoy" +SCAN_INTERVAL = timedelta(seconds=60) + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_IP_ADDRESS, default=CONST_DEFAULT_HOST): cv.string, @@ -55,7 +64,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + homeassistant, config, async_add_entities, discovery_info=None +): """Set up the Enphase Envoy sensor.""" ip_address = config[CONF_IP_ADDRESS] monitored_conditions = config[CONF_MONITORED_CONDITIONS] @@ -63,55 +74,99 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= username = config[CONF_USERNAME] password = config[CONF_PASSWORD] - envoy_reader = EnvoyReader(ip_address, username, password) + if "inverters" in monitored_conditions: + envoy_reader = EnvoyReader(ip_address, username, password, inverters=True) + else: + envoy_reader = EnvoyReader(ip_address, username, password) + + try: + await envoy_reader.getData() + except httpx.HTTPStatusError as err: + _LOGGER.error("Authentication failure during setup: %s", err) + return + except httpx.HTTPError as err: + raise PlatformNotReady from err + + async def async_update_data(): + """Fetch data from API endpoint.""" + data = {} + async with async_timeout.timeout(30): + try: + await envoy_reader.getData() + except httpx.HTTPError as err: + raise UpdateFailed(f"Error communicating with API: {err}") from err + + for condition in monitored_conditions: + if condition != "inverters": + data[condition] = await getattr(envoy_reader, condition)() + else: + data["inverters_production"] = await getattr( + envoy_reader, "inverters_production" + )() + + _LOGGER.debug("Retrieved data from API: %s", data) + + return data + + coordinator = DataUpdateCoordinator( + homeassistant, + _LOGGER, + name="sensor", + update_method=async_update_data, + update_interval=SCAN_INTERVAL, + ) + + await coordinator.async_refresh() + + if coordinator.data is None: + raise PlatformNotReady entities = [] - # Iterate through the list of sensors for condition in monitored_conditions: - if condition == "inverters": - try: - inverters = await envoy_reader.inverters_production() - except requests.exceptions.HTTPError: - _LOGGER.warning( - "Authentication for Inverter data failed during setup: %s", - ip_address, - ) - continue - - if isinstance(inverters, dict): - for inverter in inverters: - entities.append( - Envoy( - envoy_reader, - condition, - f"{name}{SENSORS[condition][0]} {inverter}", - SENSORS[condition][1], - ) + entity_name = "" + if ( + condition == "inverters" + and coordinator.data.get("inverters_production") is not None + ): + for inverter in coordinator.data["inverters_production"]: + entity_name = f"{name}{SENSORS[condition][0]} {inverter}" + split_name = entity_name.split(" ") + serial_number = split_name[-1] + entities.append( + Envoy( + condition, + entity_name, + serial_number, + SENSORS[condition][1], + coordinator, ) - - else: + ) + elif condition != "inverters": + entity_name = f"{name}{SENSORS[condition][0]}" entities.append( Envoy( - envoy_reader, condition, - f"{name}{SENSORS[condition][0]}", + entity_name, + None, SENSORS[condition][1], + coordinator, ) ) + async_add_entities(entities) -class Envoy(Entity): - """Implementation of the Enphase Envoy sensors.""" +class Envoy(CoordinatorEntity): + """Envoy entity.""" - def __init__(self, envoy_reader, sensor_type, name, unit): - """Initialize the sensor.""" - self._envoy_reader = envoy_reader + def __init__(self, sensor_type, name, serial_number, unit, coordinator): + """Initialize Envoy entity.""" self._type = sensor_type self._name = name + self._serial_number = serial_number self._unit_of_measurement = unit - self._state = None - self._last_reported = None + + super().__init__(coordinator) @property def name(self): @@ -121,7 +176,20 @@ class Envoy(Entity): @property def state(self): """Return the state of the sensor.""" - return self._state + if self._type != "inverters": + value = self.coordinator.data.get(self._type) + + elif ( + self._type == "inverters" + and self.coordinator.data.get("inverters_production") is not None + ): + value = self.coordinator.data.get("inverters_production").get( + self._serial_number + )[0] + else: + return None + + return value @property def unit_of_measurement(self): @@ -136,33 +204,13 @@ class Envoy(Entity): @property def device_state_attributes(self): """Return the state attributes.""" - if self._type == "inverters": - return {"last_reported": self._last_reported} + if ( + self._type == "inverters" + and self.coordinator.data.get("inverters_production") is not None + ): + value = self.coordinator.data.get("inverters_production").get( + self._serial_number + )[1] + return {"last_reported": value} return None - - async def async_update(self): - """Get the energy production data from the Enphase Envoy.""" - if self._type != "inverters": - _state = await getattr(self._envoy_reader, self._type)() - if isinstance(_state, int): - self._state = _state - else: - _LOGGER.error(_state) - self._state = None - - elif self._type == "inverters": - try: - inverters = await (self._envoy_reader.inverters_production()) - except requests.exceptions.HTTPError: - _LOGGER.warning( - "Authentication for Inverter data failed during update: %s", - self._envoy_reader.host, - ) - - if isinstance(inverters, dict): - serial_number = self._name.split(" ")[2] - self._state = inverters[serial_number][0] - self._last_reported = inverters[serial_number][1] - else: - self._state = None diff --git a/requirements_all.txt b/requirements_all.txt index 5bb2f37d9a0aac30fc7ba16e430175a0c6bd100e..9504cba0d5f1701370b90bad4d26f5e7afacf96b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -556,7 +556,7 @@ env_canada==0.2.4 # envirophat==0.0.6 # homeassistant.components.enphase_envoy -envoy_reader==0.17.3 +envoy_reader==0.18.2 # homeassistant.components.season ephem==3.7.7.0