Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """Provides the data update coordinators for SolarEdge."""
2 
3 from __future__ import annotations
4 
5 from abc import ABC, abstractmethod
6 from datetime import date, datetime, timedelta
7 from typing import Any
8 
9 from aiosolaredge import SolarEdge
10 from stringcase import snakecase
11 
12 from homeassistant.core import HomeAssistant, callback
13 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
14 
15 from .const import (
16  DETAILS_UPDATE_DELAY,
17  ENERGY_DETAILS_DELAY,
18  INVENTORY_UPDATE_DELAY,
19  LOGGER,
20  OVERVIEW_UPDATE_DELAY,
21  POWER_FLOW_UPDATE_DELAY,
22 )
23 
24 
26  """Get and update the latest data."""
27 
28  coordinator: DataUpdateCoordinator[None]
29 
30  def __init__(self, hass: HomeAssistant, api: SolarEdge, site_id: str) -> None:
31  """Initialize the data object."""
32  self.apiapi = api
33  self.site_idsite_id = site_id
34 
35  self.data: dict[str, Any] = {}
36  self.attributes: dict[str, Any] = {}
37 
38  self.hasshass = hass
39 
40  @callback
41  def async_setup(self) -> None:
42  """Coordinator creation."""
44  self.hasshass,
45  LOGGER,
46  name=str(self),
47  update_method=self.async_update_dataasync_update_data,
48  update_interval=self.update_intervalupdate_interval,
49  )
50 
51  @property
52  @abstractmethod
53  def update_interval(self) -> timedelta:
54  """Update interval."""
55 
56  @abstractmethod
57  async def async_update_data(self) -> None:
58  """Update data."""
59 
60 
61 class SolarEdgeOverviewDataService(SolarEdgeDataService):
62  """Get and update the latest overview data."""
63 
64  @property
65  def update_interval(self) -> timedelta:
66  """Update interval."""
67  return OVERVIEW_UPDATE_DELAY
68 
69  async def async_update_data(self) -> None:
70  """Update the data from the SolarEdge Monitoring API."""
71  try:
72  data = await self.apiapi.get_overview(self.site_idsite_id)
73  overview = data["overview"]
74  except KeyError as ex:
75  raise UpdateFailed("Missing overview data, skipping update") from ex
76 
77  self.datadata = {}
78 
79  energy_keys = ["lifeTimeData", "lastYearData", "lastMonthData", "lastDayData"]
80  for key, value in overview.items():
81  if key in energy_keys:
82  data = value["energy"]
83  elif key in ["currentPower"]:
84  data = value["power"]
85  else:
86  data = value
87  self.datadata[key] = data
88 
89  # Sanity check the energy values. SolarEdge API sometimes report "lifetimedata" of zero,
90  # while values for last Year, Month and Day energy are still OK.
91  # See https://github.com/home-assistant/core/issues/59285 .
92  if set(energy_keys).issubset(self.datadata.keys()):
93  for index, key in enumerate(energy_keys, start=1):
94  # All coming values in list should be larger than the current value.
95  if any(self.datadata[k] > self.datadata[key] for k in energy_keys[index:]):
96  LOGGER.warning(
97  "Ignoring invalid energy value %s for %s", self.datadata[key], key
98  )
99  self.datadata.pop(key)
100 
101  LOGGER.debug("Updated SolarEdge overview: %s", self.datadata)
102 
103 
105  """Get and update the latest details data."""
106 
107  @property
108  def update_interval(self) -> timedelta:
109  """Update interval."""
110  return DETAILS_UPDATE_DELAY
111 
112  async def async_update_data(self) -> None:
113  """Update the data from the SolarEdge Monitoring API."""
114 
115  try:
116  data = await self.apiapi.get_details(self.site_idsite_id)
117  details = data["details"]
118  except KeyError as ex:
119  raise UpdateFailed("Missing details data, skipping update") from ex
120 
121  self.datadata = {}
122  self.attributesattributes = {}
123 
124  for key, value in details.items():
125  key = snakecase(key)
126 
127  if key in ["primary_module"]:
128  for module_key, module_value in value.items():
129  self.attributesattributes[snakecase(module_key)] = module_value
130  elif key in [
131  "peak_power",
132  "type",
133  "name",
134  "last_update_time",
135  "installation_date",
136  ]:
137  self.attributesattributes[key] = value
138  elif key == "status":
139  self.datadata["status"] = value
140 
141  LOGGER.debug(
142  "Updated SolarEdge details: %s, %s",
143  self.datadata.get("status"),
144  self.attributesattributes,
145  )
146 
147 
149  """Get and update the latest inventory data."""
150 
151  @property
152  def update_interval(self) -> timedelta:
153  """Update interval."""
154  return INVENTORY_UPDATE_DELAY
155 
156  async def async_update_data(self) -> None:
157  """Update the data from the SolarEdge Monitoring API."""
158  try:
159  data = await self.apiapi.get_inventory(self.site_idsite_id)
160  inventory = data["Inventory"]
161  except KeyError as ex:
162  raise UpdateFailed("Missing inventory data, skipping update") from ex
163 
164  self.datadata = {}
165  self.attributesattributes = {}
166 
167  for key, value in inventory.items():
168  self.datadata[key] = len(value)
169  self.attributesattributes[key] = {key: value}
170 
171  LOGGER.debug("Updated SolarEdge inventory: %s, %s", self.datadata, self.attributesattributes)
172 
173 
175  """Get and update the latest power flow data."""
176 
177  def __init__(self, hass: HomeAssistant, api: SolarEdge, site_id: str) -> None:
178  """Initialize the power flow data service."""
179  super().__init__(hass, api, site_id)
180 
181  self.unitunit = None
182 
183  @property
184  def update_interval(self) -> timedelta:
185  """Update interval."""
186  return ENERGY_DETAILS_DELAY
187 
188  async def async_update_data(self) -> None:
189  """Update the data from the SolarEdge Monitoring API."""
190  try:
191  now = datetime.now()
192  today = date.today()
193  midnight = datetime.combine(today, datetime.min.time())
194  data = await self.apiapi.get_energy_details(
195  self.site_idsite_id,
196  midnight,
197  now,
198  time_unit="DAY",
199  )
200  energy_details = data["energyDetails"]
201  except KeyError as ex:
202  raise UpdateFailed("Missing power flow data, skipping update") from ex
203 
204  if "meters" not in energy_details:
205  LOGGER.debug(
206  "Missing meters in energy details data. Assuming site does not have any"
207  )
208  return
209 
210  self.datadata = {}
211  self.attributesattributes = {}
212  self.unitunit = energy_details["unit"]
213 
214  for meter in energy_details["meters"]:
215  if "type" not in meter or "values" not in meter:
216  continue
217  if meter["type"] not in [
218  "Production",
219  "SelfConsumption",
220  "FeedIn",
221  "Purchased",
222  "Consumption",
223  ]:
224  continue
225  if len(meter["values"][0]) == 2:
226  self.datadata[meter["type"]] = meter["values"][0]["value"]
227  self.attributesattributes[meter["type"]] = {"date": meter["values"][0]["date"]}
228 
229  LOGGER.debug(
230  "Updated SolarEdge energy details: %s, %s", self.datadata, self.attributesattributes
231  )
232 
233 
235  """Get and update the latest power flow data."""
236 
237  def __init__(self, hass: HomeAssistant, api: SolarEdge, site_id: str) -> None:
238  """Initialize the power flow data service."""
239  super().__init__(hass, api, site_id)
240 
241  self.unitunit = None
242 
243  @property
244  def update_interval(self) -> timedelta:
245  """Update interval."""
246  return POWER_FLOW_UPDATE_DELAY
247 
248  async def async_update_data(self) -> None:
249  """Update the data from the SolarEdge Monitoring API."""
250  try:
251  data = await self.apiapi.get_current_power_flow(self.site_idsite_id)
252  power_flow = data["siteCurrentPowerFlow"]
253  except KeyError as ex:
254  raise UpdateFailed("Missing power flow data, skipping update") from ex
255 
256  power_from = []
257  power_to = []
258 
259  if "connections" not in power_flow:
260  LOGGER.debug(
261  "Missing connections in power flow data. Assuming site does not"
262  " have any"
263  )
264  return
265 
266  for connection in power_flow["connections"]:
267  power_from.append(connection["from"].lower())
268  power_to.append(connection["to"].lower())
269 
270  self.datadata = {}
271  self.attributesattributes = {}
272  self.unitunit = power_flow["unit"]
273 
274  for key, value in power_flow.items():
275  if key in ["LOAD", "PV", "GRID", "STORAGE"]:
276  self.datadata[key] = value.get("currentPower")
277  self.attributesattributes[key] = {"status": value["status"]}
278 
279  if key in ["GRID"]:
280  export = key.lower() in power_to
281  if self.datadata[key]:
282  self.datadata[key] *= -1 if export else 1
283  self.attributesattributes[key]["flow"] = "export" if export else "import"
284 
285  if key in ["STORAGE"]:
286  charge = key.lower() in power_to
287  if self.datadata[key]:
288  self.datadata[key] *= -1 if charge else 1
289  self.attributesattributes[key]["flow"] = "charge" if charge else "discharge"
290  self.attributesattributes[key]["soc"] = value["chargeLevel"]
291 
292  LOGGER.debug("Updated SolarEdge power flow: %s, %s", self.datadata, self.attributesattributes)
None __init__(self, HomeAssistant hass, SolarEdge api, str site_id)
Definition: coordinator.py:30
None __init__(self, HomeAssistant hass, SolarEdge api, str site_id)
Definition: coordinator.py:177
None __init__(self, HomeAssistant hass, SolarEdge api, str site_id)
Definition: coordinator.py:237
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88