From d81017f62ef39fd9d87a08e18fde2490e3d78ead Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Jan 2021 02:56:51 -0600 Subject: [PATCH] Use DataUpdateCoordinator for solaredge (#45734) Co-authored-by: Martin Hjelmare --- homeassistant/components/solaredge/sensor.py | 266 ++++++++++--------- 1 file changed, 146 insertions(+), 120 deletions(-) diff --git a/homeassistant/components/solaredge/sensor.py b/homeassistant/components/solaredge/sensor.py index e3e59676bf5..8609e578e5e 100644 --- a/homeassistant/components/solaredge/sensor.py +++ b/homeassistant/components/solaredge/sensor.py @@ -1,4 +1,5 @@ """Support for SolarEdge Monitoring API.""" +from abc import abstractmethod from datetime import date, datetime import logging @@ -7,8 +8,14 @@ import solaredge from stringcase import snakecase from homeassistant.const import CONF_API_KEY, DEVICE_CLASS_BATTERY, DEVICE_CLASS_POWER +from homeassistant.core import callback +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) from .const import ( CONF_SITE_ID, @@ -37,14 +44,20 @@ async def async_setup_entry(hass, entry, async_add_entities): _LOGGER.error("SolarEdge site is not active") return _LOGGER.debug("Credentials correct and site is active") - except KeyError: + except KeyError as ex: _LOGGER.error("Missing details data in SolarEdge response") - return - except (ConnectTimeout, HTTPError): + raise ConfigEntryNotReady from ex + except (ConnectTimeout, HTTPError) as ex: _LOGGER.error("Could not retrieve details from SolarEdge API") - return + raise ConfigEntryNotReady from ex + + sensor_factory = SolarEdgeSensorFactory( + hass, entry.title, entry.data[CONF_SITE_ID], api + ) + for service in sensor_factory.all_services: + service.async_setup() + await service.coordinator.async_refresh() - sensor_factory = SolarEdgeSensorFactory(entry.title, entry.data[CONF_SITE_ID], api) entities = [] for sensor_key in SENSOR_TYPES: sensor = sensor_factory.create_sensor(sensor_key) @@ -56,15 +69,17 @@ async def async_setup_entry(hass, entry, async_add_entities): class SolarEdgeSensorFactory: """Factory which creates sensors based on the sensor_key.""" - def __init__(self, platform_name, site_id, api): + def __init__(self, hass, platform_name, site_id, api): """Initialize the factory.""" self.platform_name = platform_name - details = SolarEdgeDetailsDataService(api, site_id) - overview = SolarEdgeOverviewDataService(api, site_id) - inventory = SolarEdgeInventoryDataService(api, site_id) - flow = SolarEdgePowerFlowDataService(api, site_id) - energy = SolarEdgeEnergyDetailsService(api, site_id) + details = SolarEdgeDetailsDataService(hass, api, site_id) + overview = SolarEdgeOverviewDataService(hass, api, site_id) + inventory = SolarEdgeInventoryDataService(hass, api, site_id) + flow = SolarEdgePowerFlowDataService(hass, api, site_id) + energy = SolarEdgeEnergyDetailsService(hass, api, site_id) + + self.all_services = (details, overview, inventory, flow, energy) self.services = {"site_details": (SolarEdgeDetailsSensor, details)} @@ -102,39 +117,30 @@ class SolarEdgeSensorFactory: return sensor_class(self.platform_name, sensor_key, service) -class SolarEdgeSensor(Entity): +class SolarEdgeSensor(CoordinatorEntity, Entity): """Abstract class for a solaredge sensor.""" def __init__(self, platform_name, sensor_key, data_service): """Initialize the sensor.""" + super().__init__(data_service.coordinator) self.platform_name = platform_name self.sensor_key = sensor_key self.data_service = data_service - self._state = None - - self._unit_of_measurement = SENSOR_TYPES[self.sensor_key][2] - self._icon = SENSOR_TYPES[self.sensor_key][3] + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return SENSOR_TYPES[self.sensor_key][2] @property def name(self): """Return the name.""" return "{} ({})".format(self.platform_name, SENSOR_TYPES[self.sensor_key][1]) - @property - def unit_of_measurement(self): - """Return the unit of measurement.""" - return self._unit_of_measurement - @property def icon(self): """Return the sensor icon.""" - return self._icon - - @property - def state(self): - """Return the state of the sensor.""" - return self._state + return SENSOR_TYPES[self.sensor_key][3] class SolarEdgeOverviewSensor(SolarEdgeSensor): @@ -146,31 +152,24 @@ class SolarEdgeOverviewSensor(SolarEdgeSensor): self._json_key = SENSOR_TYPES[self.sensor_key][0] - def update(self): - """Get the latest data from the sensor and update the state.""" - self.data_service.update() - self._state = self.data_service.data.get(self._json_key) + @property + def state(self): + """Return the state of the sensor.""" + return self.data_service.data.get(self._json_key) class SolarEdgeDetailsSensor(SolarEdgeSensor): """Representation of an SolarEdge Monitoring API details sensor.""" - def __init__(self, platform_name, sensor_key, data_service): - """Initialize the details sensor.""" - super().__init__(platform_name, sensor_key, data_service) - - self._attributes = {} - @property def device_state_attributes(self): """Return the state attributes.""" - return self._attributes + return self.data_service.attributes - def update(self): - """Get the latest details and update state and attributes.""" - self.data_service.update() - self._state = self.data_service.data - self._attributes = self.data_service.attributes + @property + def state(self): + """Return the state of the sensor.""" + return self.data_service.data class SolarEdgeInventorySensor(SolarEdgeSensor): @@ -182,18 +181,15 @@ class SolarEdgeInventorySensor(SolarEdgeSensor): self._json_key = SENSOR_TYPES[self.sensor_key][0] - self._attributes = {} - @property def device_state_attributes(self): """Return the state attributes.""" - return self._attributes + return self.data_service.attributes.get(self._json_key) - def update(self): - """Get the latest inventory data and update state and attributes.""" - self.data_service.update() - self._state = self.data_service.data.get(self._json_key) - self._attributes = self.data_service.attributes.get(self._json_key) + @property + def state(self): + """Return the state of the sensor.""" + return self.data_service.data.get(self._json_key) class SolarEdgeEnergyDetailsSensor(SolarEdgeSensor): @@ -205,19 +201,20 @@ class SolarEdgeEnergyDetailsSensor(SolarEdgeSensor): self._json_key = SENSOR_TYPES[self.sensor_key][0] - self._attributes = {} - @property def device_state_attributes(self): """Return the state attributes.""" - return self._attributes + return self.data_service.attributes.get(self._json_key) - def update(self): - """Get the latest inventory data and update state and attributes.""" - self.data_service.update() - self._state = self.data_service.data.get(self._json_key) - self._attributes = self.data_service.attributes.get(self._json_key) - self._unit_of_measurement = self.data_service.unit + @property + def state(self): + """Return the state of the sensor.""" + return self.data_service.data.get(self._json_key) + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self.data_service.unit class SolarEdgePowerFlowSensor(SolarEdgeSensor): @@ -229,24 +226,25 @@ class SolarEdgePowerFlowSensor(SolarEdgeSensor): self._json_key = SENSOR_TYPES[self.sensor_key][0] - self._attributes = {} - - @property - def device_state_attributes(self): - """Return the state attributes.""" - return self._attributes - @property def device_class(self): """Device Class.""" return DEVICE_CLASS_POWER - def update(self): - """Get the latest inventory data and update state and attributes.""" - self.data_service.update() - self._state = self.data_service.data.get(self._json_key) - self._attributes = self.data_service.attributes.get(self._json_key) - self._unit_of_measurement = self.data_service.unit + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self.data_service.attributes.get(self._json_key) + + @property + def state(self): + """Return the state of the sensor.""" + return self.data_service.data.get(self._json_key) + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self.data_service.unit class SolarEdgeStorageLevelSensor(SolarEdgeSensor): @@ -263,18 +261,19 @@ class SolarEdgeStorageLevelSensor(SolarEdgeSensor): """Return the device_class of the device.""" return DEVICE_CLASS_BATTERY - def update(self): - """Get the latest inventory data and update state and attributes.""" - self.data_service.update() + @property + def state(self): + """Return the state of the sensor.""" attr = self.data_service.attributes.get(self._json_key) if attr and "soc" in attr: - self._state = attr["soc"] + return attr["soc"] + return None class SolarEdgeDataService: """Get and update the latest data.""" - def __init__(self, api, site_id): + def __init__(self, hass, api, site_id): """Initialize the data object.""" self.api = api self.site_id = site_id @@ -282,22 +281,49 @@ class SolarEdgeDataService: self.data = {} self.attributes = {} + self.hass = hass + self.coordinator = None + + @callback + def async_setup(self): + """Coordinator creation.""" + self.coordinator = DataUpdateCoordinator( + self.hass, + _LOGGER, + name=str(self), + update_method=self.async_update_data, + update_interval=self.update_interval, + ) + + @property + @abstractmethod + def update_interval(self): + """Update interval.""" + + @abstractmethod + def update(self): + """Update data in executor.""" + + async def async_update_data(self): + """Update data.""" + await self.hass.async_add_executor_job(self.update) + class SolarEdgeOverviewDataService(SolarEdgeDataService): """Get and update the latest overview data.""" - @Throttle(OVERVIEW_UPDATE_DELAY) + @property + def update_interval(self): + """Update interval.""" + return OVERVIEW_UPDATE_DELAY + def update(self): """Update the data from the SolarEdge Monitoring API.""" try: data = self.api.get_overview(self.site_id) overview = data["overview"] - except KeyError: - _LOGGER.error("Missing overview data, skipping update") - return - except (ConnectTimeout, HTTPError): - _LOGGER.error("Could not retrieve data, skipping update") - return + except KeyError as ex: + raise UpdateFailed("Missing overview data, skipping update") from ex self.data = {} @@ -316,25 +342,25 @@ class SolarEdgeOverviewDataService(SolarEdgeDataService): class SolarEdgeDetailsDataService(SolarEdgeDataService): """Get and update the latest details data.""" - def __init__(self, api, site_id): + def __init__(self, hass, api, site_id): """Initialize the details data service.""" - super().__init__(api, site_id) + super().__init__(hass, api, site_id) self.data = None - @Throttle(DETAILS_UPDATE_DELAY) + @property + def update_interval(self): + """Update interval.""" + return DETAILS_UPDATE_DELAY + def update(self): """Update the data from the SolarEdge Monitoring API.""" try: data = self.api.get_details(self.site_id) details = data["details"] - except KeyError: - _LOGGER.error("Missing details data, skipping update") - return - except (ConnectTimeout, HTTPError): - _LOGGER.error("Could not retrieve data, skipping update") - return + except KeyError as ex: + raise UpdateFailed("Missing details data, skipping update") from ex self.data = None self.attributes = {} @@ -362,18 +388,18 @@ class SolarEdgeDetailsDataService(SolarEdgeDataService): class SolarEdgeInventoryDataService(SolarEdgeDataService): """Get and update the latest inventory data.""" - @Throttle(INVENTORY_UPDATE_DELAY) + @property + def update_interval(self): + """Update interval.""" + return INVENTORY_UPDATE_DELAY + def update(self): """Update the data from the SolarEdge Monitoring API.""" try: data = self.api.get_inventory(self.site_id) inventory = data["Inventory"] - except KeyError: - _LOGGER.error("Missing inventory data, skipping update") - return - except (ConnectTimeout, HTTPError): - _LOGGER.error("Could not retrieve data, skipping update") - return + except KeyError as ex: + raise UpdateFailed("Missing inventory data, skipping update") from ex self.data = {} self.attributes = {} @@ -388,13 +414,17 @@ class SolarEdgeInventoryDataService(SolarEdgeDataService): class SolarEdgeEnergyDetailsService(SolarEdgeDataService): """Get and update the latest power flow data.""" - def __init__(self, api, site_id): + def __init__(self, hass, api, site_id): """Initialize the power flow data service.""" - super().__init__(api, site_id) + super().__init__(hass, api, site_id) self.unit = None - @Throttle(ENERGY_DETAILS_DELAY) + @property + def update_interval(self): + """Update interval.""" + return ENERGY_DETAILS_DELAY + def update(self): """Update the data from the SolarEdge Monitoring API.""" try: @@ -409,12 +439,8 @@ class SolarEdgeEnergyDetailsService(SolarEdgeDataService): time_unit="DAY", ) energy_details = data["energyDetails"] - except KeyError: - _LOGGER.error("Missing power flow data, skipping update") - return - except (ConnectTimeout, HTTPError): - _LOGGER.error("Could not retrieve data, skipping update") - return + except KeyError as ex: + raise UpdateFailed("Missing power flow data, skipping update") from ex if "meters" not in energy_details: _LOGGER.debug( @@ -449,24 +475,24 @@ class SolarEdgeEnergyDetailsService(SolarEdgeDataService): class SolarEdgePowerFlowDataService(SolarEdgeDataService): """Get and update the latest power flow data.""" - def __init__(self, api, site_id): + def __init__(self, hass, api, site_id): """Initialize the power flow data service.""" - super().__init__(api, site_id) + super().__init__(hass, api, site_id) self.unit = None - @Throttle(POWER_FLOW_UPDATE_DELAY) + @property + def update_interval(self): + """Update interval.""" + return POWER_FLOW_UPDATE_DELAY + def update(self): """Update the data from the SolarEdge Monitoring API.""" try: data = self.api.get_current_power_flow(self.site_id) power_flow = data["siteCurrentPowerFlow"] - except KeyError: - _LOGGER.error("Missing power flow data, skipping update") - return - except (ConnectTimeout, HTTPError): - _LOGGER.error("Could not retrieve data, skipping update") - return + except KeyError as ex: + raise UpdateFailed("Missing power flow data, skipping update") from ex power_from = [] power_to = []