Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for SolarEdge-local Monitoring API."""
2 
3 from __future__ import annotations
4 
5 from contextlib import suppress
6 import dataclasses
7 from datetime import timedelta
8 import logging
9 import statistics
10 
11 from requests.exceptions import ConnectTimeout, HTTPError
12 from solaredge_local import SolarEdge
13 import voluptuous as vol
14 
16  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
17  SensorDeviceClass,
18  SensorEntity,
19  SensorEntityDescription,
20 )
21 from homeassistant.const import (
22  CONF_IP_ADDRESS,
23  CONF_NAME,
24  UnitOfElectricCurrent,
25  UnitOfElectricPotential,
26  UnitOfEnergy,
27  UnitOfFrequency,
28  UnitOfPower,
29  UnitOfTemperature,
30 )
31 from homeassistant.core import HomeAssistant
33 from homeassistant.helpers.entity_platform import AddEntitiesCallback
34 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
35 from homeassistant.util import Throttle
36 
37 DOMAIN = "solaredge_local"
38 UPDATE_DELAY = timedelta(seconds=10)
39 
40 INVERTER_MODES = (
41  "SHUTTING_DOWN",
42  "ERROR",
43  "STANDBY",
44  "PAIRING",
45  "POWER_PRODUCTION",
46  "AC_CHARGING",
47  "NOT_PAIRED",
48  "NIGHT_MODE",
49  "GRID_MONITORING",
50  "IDLE",
51 )
52 
53 
54 @dataclasses.dataclass(frozen=True)
56  """Describes SolarEdge-local sensor entity."""
57 
58  extra_attribute: str | None = None
59 
60 
61 SENSOR_TYPES: tuple[SolarEdgeLocalSensorEntityDescription, ...] = (
63  key="gridvoltage",
64  name="Grid Voltage",
65  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
66  device_class=SensorDeviceClass.VOLTAGE,
67  icon="mdi:current-ac",
68  ),
70  key="dcvoltage",
71  name="DC Voltage",
72  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
73  device_class=SensorDeviceClass.VOLTAGE,
74  icon="mdi:current-dc",
75  ),
77  key="gridfrequency",
78  name="Grid Frequency",
79  native_unit_of_measurement=UnitOfFrequency.HERTZ,
80  device_class=SensorDeviceClass.FREQUENCY,
81  ),
83  key="currentPower",
84  name="Current Power",
85  native_unit_of_measurement=UnitOfPower.WATT,
86  device_class=SensorDeviceClass.POWER,
87  icon="mdi:solar-power",
88  ),
90  key="energyThisMonth",
91  name="Energy This Month",
92  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
93  device_class=SensorDeviceClass.ENERGY,
94  icon="mdi:solar-power",
95  ),
97  key="energyThisYear",
98  name="Energy This Year",
99  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
100  device_class=SensorDeviceClass.ENERGY,
101  icon="mdi:solar-power",
102  ),
104  key="energyToday",
105  name="Energy Today",
106  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
107  device_class=SensorDeviceClass.ENERGY,
108  icon="mdi:solar-power",
109  ),
111  key="energyTotal",
112  name="Lifetime Energy",
113  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
114  device_class=SensorDeviceClass.ENERGY,
115  icon="mdi:solar-power",
116  ),
118  key="optimizers",
119  name="Optimizers Online",
120  native_unit_of_measurement="optimizers",
121  icon="mdi:solar-panel",
122  extra_attribute="optimizers_connected",
123  ),
125  key="optimizercurrent",
126  name="Average Optimizer Current",
127  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
128  device_class=SensorDeviceClass.CURRENT,
129  icon="mdi:solar-panel",
130  ),
132  key="optimizerpower",
133  name="Average Optimizer Power",
134  native_unit_of_measurement=UnitOfPower.WATT,
135  device_class=SensorDeviceClass.POWER,
136  icon="mdi:solar-panel",
137  ),
139  key="optimizertemperature",
140  name="Average Optimizer Temperature",
141  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
142  icon="mdi:solar-panel",
143  device_class=SensorDeviceClass.TEMPERATURE,
144  ),
146  key="optimizervoltage",
147  name="Average Optimizer Voltage",
148  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
149  device_class=SensorDeviceClass.VOLTAGE,
150  icon="mdi:solar-panel",
151  ),
152 )
153 
154 SENSOR_TYPE_INVERTER_TEMPERATURE = SolarEdgeLocalSensorEntityDescription(
155  key="invertertemperature",
156  name="Inverter Temperature",
157  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
158  extra_attribute="operating_mode",
159  device_class=SensorDeviceClass.TEMPERATURE,
160 )
161 
162 SENSOR_TYPES_ENERGY_IMPORT: tuple[SolarEdgeLocalSensorEntityDescription, ...] = (
164  key="currentPowerimport",
165  name="current import Power",
166  native_unit_of_measurement=UnitOfPower.WATT,
167  device_class=SensorDeviceClass.POWER,
168  icon="mdi:arrow-collapse-down",
169  ),
171  key="totalEnergyimport",
172  name="total import Energy",
173  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
174  device_class=SensorDeviceClass.ENERGY,
175  icon="mdi:counter",
176  ),
177 )
178 
179 SENSOR_TYPES_ENERGY_EXPORT: tuple[SolarEdgeLocalSensorEntityDescription, ...] = (
181  key="currentPowerexport",
182  name="current export Power",
183  native_unit_of_measurement=UnitOfPower.WATT,
184  device_class=SensorDeviceClass.POWER,
185  icon="mdi:arrow-expand-up",
186  ),
188  key="totalEnergyexport",
189  name="total export Energy",
190  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
191  device_class=SensorDeviceClass.ENERGY,
192  icon="mdi:counter",
193  ),
194 )
195 
196 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
197  {
198  vol.Required(CONF_IP_ADDRESS): cv.string,
199  vol.Optional(CONF_NAME, default="SolarEdge"): cv.string,
200  }
201 )
202 
203 _LOGGER = logging.getLogger(__name__)
204 
205 
207  hass: HomeAssistant,
208  config: ConfigType,
209  add_entities: AddEntitiesCallback,
210  discovery_info: DiscoveryInfoType | None = None,
211 ) -> None:
212  """Create the SolarEdge Monitoring API sensor."""
213  ip_address = config[CONF_IP_ADDRESS]
214  platform_name = config[CONF_NAME]
215 
216  # Create new SolarEdge object to retrieve data.
217  api = SolarEdge(f"http://{ip_address}/")
218 
219  # Check if api can be reached and site is active.
220  try:
221  status = api.get_status()
222  _LOGGER.debug("Credentials correct and site is active")
223  except AttributeError:
224  _LOGGER.error("Missing details data in solaredge status")
225  return
226  except (ConnectTimeout, HTTPError):
227  _LOGGER.error("Could not retrieve details from SolarEdge API")
228  return
229 
230  # Create solaredge data service which will retrieve and update the data.
231  data = SolarEdgeData(hass, api)
232 
233  # Changing inverter temperature unit.
234  inverter_temp_description = SENSOR_TYPE_INVERTER_TEMPERATURE
235  if (
236  status.inverters.primary.temperature.units.farenheit # codespell:ignore farenheit
237  ):
238  inverter_temp_description = dataclasses.replace(
239  inverter_temp_description,
240  native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
241  )
242 
243  # Create entities
244  entities = [
245  SolarEdgeSensor(platform_name, data, description)
246  for description in (*SENSOR_TYPES, inverter_temp_description)
247  ]
248 
249  try:
250  if status.metersList[0]:
251  entities.extend(
252  [
253  SolarEdgeSensor(platform_name, data, description)
254  for description in SENSOR_TYPES_ENERGY_IMPORT
255  ]
256  )
257  except IndexError:
258  _LOGGER.debug("Import meter sensors are not created")
259 
260  try:
261  if status.metersList[1]:
262  entities.extend(
263  [
264  SolarEdgeSensor(platform_name, data, description)
265  for description in SENSOR_TYPES_ENERGY_EXPORT
266  ]
267  )
268  except IndexError:
269  _LOGGER.debug("Export meter sensors are not created")
270 
271  add_entities(entities, True)
272 
273 
275  """Representation of an SolarEdge Monitoring API sensor."""
276 
277  entity_description: SolarEdgeLocalSensorEntityDescription
278 
279  def __init__(
280  self,
281  platform_name,
282  data,
283  description: SolarEdgeLocalSensorEntityDescription,
284  ) -> None:
285  """Initialize the sensor."""
286  self.entity_descriptionentity_description = description
287  self._platform_name_platform_name = platform_name
288  self._data_data = data
289  self._attr_name_attr_name = f"{platform_name} ({description.name})"
290 
291  @property
293  """Return the state attributes."""
294  if extra_attr := self.entity_descriptionentity_description.extra_attribute:
295  try:
296  return {extra_attr: self._data_data.info.get(self.entity_descriptionentity_description.key)}
297  except KeyError:
298  pass
299  return None
300 
301  def update(self) -> None:
302  """Get the latest data from the sensor and update the state."""
303  self._data_data.update()
304  self._attr_native_value_attr_native_value = self._data_data.data.get(self.entity_descriptionentity_description.key)
305 
306 
308  """Get and update the latest data."""
309 
310  def __init__(self, hass, api):
311  """Initialize the data object."""
312  self.hasshass = hass
313  self.apiapi = api
314  self.datadata = {}
315  self.infoinfo = {}
316 
317  @Throttle(UPDATE_DELAY)
318  def update(self):
319  """Update the data from the SolarEdge Monitoring API."""
320  try:
321  status = self.apiapi.get_status()
322  _LOGGER.debug("Status from SolarEdge: %s", status)
323  except ConnectTimeout:
324  _LOGGER.error("Connection timeout, skipping update")
325  return
326  except HTTPError:
327  _LOGGER.error("Could not retrieve status, skipping update")
328  return
329 
330  try:
331  maintenance = self.apiapi.get_maintenance()
332  _LOGGER.debug("Maintenance from SolarEdge: %s", maintenance)
333  except ConnectTimeout:
334  _LOGGER.error("Connection timeout, skipping update")
335  return
336  except HTTPError:
337  _LOGGER.error("Could not retrieve maintenance, skipping update")
338  return
339 
340  temperature = []
341  voltage = []
342  current = []
343  power = 0
344 
345  for optimizer in maintenance.diagnostics.inverters.primary.optimizer:
346  if not optimizer.online:
347  continue
348  temperature.append(optimizer.temperature.value)
349  voltage.append(optimizer.inputV)
350  current.append(optimizer.inputC)
351 
352  if not voltage:
353  temperature.append(0)
354  voltage.append(0)
355  current.append(0)
356  else:
357  power = statistics.mean(voltage) * statistics.mean(current)
358 
359  if status.sn:
360  self.datadata["energyTotal"] = round(status.energy.total, 2)
361  self.datadata["energyThisYear"] = round(status.energy.thisYear, 2)
362  self.datadata["energyThisMonth"] = round(status.energy.thisMonth, 2)
363  self.datadata["energyToday"] = round(status.energy.today, 2)
364  self.datadata["currentPower"] = round(status.powerWatt, 2)
365  self.datadata["invertertemperature"] = round(
366  status.inverters.primary.temperature.value, 2
367  )
368  self.datadata["dcvoltage"] = round(status.inverters.primary.voltage, 2)
369  self.datadata["gridfrequency"] = round(status.frequencyHz, 2)
370  self.datadata["gridvoltage"] = round(status.voltage, 2)
371  self.datadata["optimizers"] = status.optimizersStatus.online
372 
373  self.infoinfo["optimizers"] = status.optimizersStatus.total
374  self.infoinfo["invertertemperature"] = INVERTER_MODES[status.status]
375 
376  with suppress(IndexError):
377  if status.metersList[1]:
378  self.datadata["currentPowerimport"] = status.metersList[1].currentPower
379  self.datadata["totalEnergyimport"] = status.metersList[1].totalEnergy
380 
381  with suppress(IndexError):
382  if status.metersList[0]:
383  self.datadata["currentPowerexport"] = status.metersList[0].currentPower
384  self.datadata["totalEnergyexport"] = status.metersList[0].totalEnergy
385 
386  if maintenance.system.name:
387  self.datadata["optimizertemperature"] = round(statistics.mean(temperature), 2)
388  self.datadata["optimizervoltage"] = round(statistics.mean(voltage), 2)
389  self.datadata["optimizercurrent"] = round(statistics.mean(current), 2)
390  self.datadata["optimizerpower"] = round(power, 2)
None __init__(self, platform_name, data, SolarEdgeLocalSensorEntityDescription description)
Definition: sensor.py:284
def add_entities(account, async_add_entities, tracked)
Definition: sensor.py:40
def get_status(hass, host, port)
Definition: panel.py:387
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:211