Optimize api calls between envoy_reader and Home Assistant (#42857)

* Updating sensor to use single API call

* Updated comment

Updated comment to reflect that polling is needed.

* Reduced calls to single API call

* Added except handling and increased async timeout

* Cleaned up some comments

* Added error handling

* Added last_reported date for inverters

* Added message during failed update

* Added retries to update function

* Updated update function

* Reformatted sensor.py with black

* Increased default scan period

* fixed timedelta typo

* importing CoordinatorEntity

* Check during setup else raise PlatformNotReady

* Removed async_update and override state

* using SCAN_INTERVAL constant

* fixed typo

* removed unused constant

* Removed retry logic

* Changed to catching exceptions rather than strings

* shortened string split line

* Replace requests_async with httpx

* Bump envoy_reader version to 0.17.2

* Resolving comments from PR requested changes

* Fixed typo in scan_interval

* Removed period from logging messages

* Bumping envoy_reader to 0.18.0

* Incorporating suggested changes

* Removing no longer used try/except

* Fail setup if authentication fails

* Bump envoy_reader to 0.18.2
This commit is contained in:
Greg 2020-12-28 14:58:09 -08:00 committed by GitHub
parent 0d8ed9061c
commit a5cd4efd83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 67 deletions

View File

@ -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"
]

View File

@ -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

View File

@ -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