Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Enphase Envoy solar energy monitor."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass, replace
7 import datetime
8 import logging
9 from operator import attrgetter
10 from typing import TYPE_CHECKING
11 
12 from pyenphase import (
13  EnvoyEncharge,
14  EnvoyEnchargeAggregate,
15  EnvoyEnchargePower,
16  EnvoyEnpower,
17  EnvoyInverter,
18  EnvoySystemConsumption,
19  EnvoySystemProduction,
20 )
21 from pyenphase.const import PHASENAMES
22 from pyenphase.models.meters import (
23  CtMeterStatus,
24  CtState,
25  CtStatusFlags,
26  CtType,
27  EnvoyMeterData,
28 )
29 
31  SensorDeviceClass,
32  SensorEntity,
33  SensorEntityDescription,
34  SensorStateClass,
35 )
36 from homeassistant.const import (
37  PERCENTAGE,
38  UnitOfApparentPower,
39  UnitOfElectricCurrent,
40  UnitOfElectricPotential,
41  UnitOfEnergy,
42  UnitOfFrequency,
43  UnitOfPower,
44  UnitOfTemperature,
45 )
46 from homeassistant.core import HomeAssistant
47 from homeassistant.helpers.device_registry import DeviceInfo
48 from homeassistant.helpers.entity import Entity
49 from homeassistant.helpers.entity_platform import AddEntitiesCallback
50 from homeassistant.util import dt as dt_util
51 
52 from .const import DOMAIN
53 from .coordinator import EnphaseConfigEntry, EnphaseUpdateCoordinator
54 from .entity import EnvoyBaseEntity
55 
56 ICON = "mdi:flash"
57 _LOGGER = logging.getLogger(__name__)
58 
59 INVERTERS_KEY = "inverters"
60 LAST_REPORTED_KEY = "last_reported"
61 
62 
63 @dataclass(frozen=True, kw_only=True)
65  """Describes an Envoy inverter sensor entity."""
66 
67  value_fn: Callable[[EnvoyInverter], datetime.datetime | float]
68 
69 
70 INVERTER_SENSORS = (
72  key=INVERTERS_KEY,
73  name=None,
74  native_unit_of_measurement=UnitOfPower.WATT,
75  state_class=SensorStateClass.MEASUREMENT,
76  device_class=SensorDeviceClass.POWER,
77  value_fn=attrgetter("last_report_watts"),
78  ),
80  key=LAST_REPORTED_KEY,
81  translation_key=LAST_REPORTED_KEY,
82  device_class=SensorDeviceClass.TIMESTAMP,
83  entity_registry_enabled_default=False,
84  value_fn=lambda inverter: dt_util.utc_from_timestamp(inverter.last_report_date),
85  ),
86 )
87 
88 
89 @dataclass(frozen=True, kw_only=True)
91  """Describes an Envoy production sensor entity."""
92 
93  value_fn: Callable[[EnvoySystemProduction], int]
94  on_phase: str | None
95 
96 
97 PRODUCTION_SENSORS = (
99  key="production",
100  translation_key="current_power_production",
101  native_unit_of_measurement=UnitOfPower.WATT,
102  state_class=SensorStateClass.MEASUREMENT,
103  device_class=SensorDeviceClass.POWER,
104  suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
105  suggested_display_precision=3,
106  value_fn=attrgetter("watts_now"),
107  on_phase=None,
108  ),
110  key="daily_production",
111  translation_key="daily_production",
112  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
113  state_class=SensorStateClass.TOTAL_INCREASING,
114  device_class=SensorDeviceClass.ENERGY,
115  suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
116  suggested_display_precision=2,
117  value_fn=attrgetter("watt_hours_today"),
118  on_phase=None,
119  ),
121  key="seven_days_production",
122  translation_key="seven_days_production",
123  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
124  device_class=SensorDeviceClass.ENERGY,
125  suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
126  suggested_display_precision=1,
127  value_fn=attrgetter("watt_hours_last_7_days"),
128  on_phase=None,
129  ),
131  key="lifetime_production",
132  translation_key="lifetime_production",
133  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
134  state_class=SensorStateClass.TOTAL_INCREASING,
135  device_class=SensorDeviceClass.ENERGY,
136  suggested_unit_of_measurement=UnitOfEnergy.MEGA_WATT_HOUR,
137  suggested_display_precision=3,
138  value_fn=attrgetter("watt_hours_lifetime"),
139  on_phase=None,
140  ),
141 )
142 
143 
144 PRODUCTION_PHASE_SENSORS = {
145  (on_phase := PHASENAMES[phase]): [
146  replace(
147  sensor,
148  key=f"{sensor.key}_l{phase + 1}",
149  translation_key=f"{sensor.translation_key}_phase",
150  entity_registry_enabled_default=False,
151  on_phase=on_phase,
152  translation_placeholders={"phase_name": f"l{phase + 1}"},
153  )
154  for sensor in list(PRODUCTION_SENSORS)
155  ]
156  for phase in range(3)
157 }
158 
159 
160 @dataclass(frozen=True, kw_only=True)
162  """Describes an Envoy consumption sensor entity."""
163 
164  value_fn: Callable[[EnvoySystemConsumption], int]
165  on_phase: str | None
166 
167 
168 CONSUMPTION_SENSORS = (
170  key="consumption",
171  translation_key="current_power_consumption",
172  native_unit_of_measurement=UnitOfPower.WATT,
173  state_class=SensorStateClass.MEASUREMENT,
174  device_class=SensorDeviceClass.POWER,
175  suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
176  suggested_display_precision=3,
177  value_fn=attrgetter("watts_now"),
178  on_phase=None,
179  ),
181  key="daily_consumption",
182  translation_key="daily_consumption",
183  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
184  state_class=SensorStateClass.TOTAL_INCREASING,
185  device_class=SensorDeviceClass.ENERGY,
186  suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
187  suggested_display_precision=2,
188  value_fn=attrgetter("watt_hours_today"),
189  on_phase=None,
190  ),
192  key="seven_days_consumption",
193  translation_key="seven_days_consumption",
194  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
195  device_class=SensorDeviceClass.ENERGY,
196  suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
197  suggested_display_precision=1,
198  value_fn=attrgetter("watt_hours_last_7_days"),
199  on_phase=None,
200  ),
202  key="lifetime_consumption",
203  translation_key="lifetime_consumption",
204  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
205  state_class=SensorStateClass.TOTAL_INCREASING,
206  device_class=SensorDeviceClass.ENERGY,
207  suggested_unit_of_measurement=UnitOfEnergy.MEGA_WATT_HOUR,
208  suggested_display_precision=3,
209  value_fn=attrgetter("watt_hours_lifetime"),
210  on_phase=None,
211  ),
212 )
213 
214 
215 CONSUMPTION_PHASE_SENSORS = {
216  (on_phase := PHASENAMES[phase]): [
217  replace(
218  sensor,
219  key=f"{sensor.key}_l{phase + 1}",
220  translation_key=f"{sensor.translation_key}_phase",
221  entity_registry_enabled_default=False,
222  on_phase=on_phase,
223  translation_placeholders={"phase_name": f"l{phase + 1}"},
224  )
225  for sensor in list(CONSUMPTION_SENSORS)
226  ]
227  for phase in range(3)
228 }
229 
230 
231 NET_CONSUMPTION_SENSORS = (
233  key="balanced_net_consumption",
234  translation_key="balanced_net_consumption",
235  entity_registry_enabled_default=False,
236  native_unit_of_measurement=UnitOfPower.WATT,
237  state_class=SensorStateClass.MEASUREMENT,
238  device_class=SensorDeviceClass.POWER,
239  suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
240  suggested_display_precision=3,
241  value_fn=attrgetter("watts_now"),
242  on_phase=None,
243  ),
245  key="lifetime_balanced_net_consumption",
246  translation_key="lifetime_balanced_net_consumption",
247  entity_registry_enabled_default=False,
248  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
249  state_class=SensorStateClass.TOTAL,
250  device_class=SensorDeviceClass.ENERGY,
251  suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
252  suggested_display_precision=3,
253  value_fn=attrgetter("watt_hours_lifetime"),
254  on_phase=None,
255  ),
256 )
257 
258 
259 NET_CONSUMPTION_PHASE_SENSORS = {
260  (on_phase := PHASENAMES[phase]): [
261  replace(
262  sensor,
263  key=f"{sensor.key}_l{phase + 1}",
264  translation_key=f"{sensor.translation_key}_phase",
265  entity_registry_enabled_default=False,
266  on_phase=on_phase,
267  translation_placeholders={"phase_name": f"l{phase + 1}"},
268  )
269  for sensor in list(NET_CONSUMPTION_SENSORS)
270  ]
271  for phase in range(3)
272 }
273 
274 
275 @dataclass(frozen=True, kw_only=True)
277  """Describes an Envoy CT sensor entity."""
278 
279  value_fn: Callable[
280  [EnvoyMeterData],
281  int | float | str | CtType | CtMeterStatus | CtStatusFlags | CtState | None,
282  ]
283  on_phase: str | None
284 
285 
286 CT_NET_CONSUMPTION_SENSORS = (
288  key="lifetime_net_consumption",
289  translation_key="lifetime_net_consumption",
290  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
291  state_class=SensorStateClass.TOTAL_INCREASING,
292  device_class=SensorDeviceClass.ENERGY,
293  suggested_unit_of_measurement=UnitOfEnergy.MEGA_WATT_HOUR,
294  suggested_display_precision=3,
295  value_fn=attrgetter("energy_delivered"),
296  on_phase=None,
297  ),
299  key="lifetime_net_production",
300  translation_key="lifetime_net_production",
301  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
302  state_class=SensorStateClass.TOTAL_INCREASING,
303  device_class=SensorDeviceClass.ENERGY,
304  suggested_unit_of_measurement=UnitOfEnergy.MEGA_WATT_HOUR,
305  suggested_display_precision=3,
306  value_fn=attrgetter("energy_received"),
307  on_phase=None,
308  ),
310  key="net_consumption",
311  translation_key="net_consumption",
312  native_unit_of_measurement=UnitOfPower.WATT,
313  state_class=SensorStateClass.MEASUREMENT,
314  device_class=SensorDeviceClass.POWER,
315  suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
316  suggested_display_precision=3,
317  value_fn=attrgetter("active_power"),
318  on_phase=None,
319  ),
321  key="frequency",
322  translation_key="net_ct_frequency",
323  native_unit_of_measurement=UnitOfFrequency.HERTZ,
324  state_class=SensorStateClass.MEASUREMENT,
325  device_class=SensorDeviceClass.FREQUENCY,
326  suggested_display_precision=1,
327  entity_registry_enabled_default=False,
328  value_fn=attrgetter("frequency"),
329  on_phase=None,
330  ),
332  key="voltage",
333  translation_key="net_ct_voltage",
334  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
335  state_class=SensorStateClass.MEASUREMENT,
336  device_class=SensorDeviceClass.VOLTAGE,
337  suggested_unit_of_measurement=UnitOfElectricPotential.VOLT,
338  suggested_display_precision=1,
339  entity_registry_enabled_default=False,
340  value_fn=attrgetter("voltage"),
341  on_phase=None,
342  ),
344  key="net_ct_current",
345  translation_key="net_ct_current",
346  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
347  state_class=SensorStateClass.MEASUREMENT,
348  device_class=SensorDeviceClass.CURRENT,
349  suggested_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
350  suggested_display_precision=3,
351  entity_registry_enabled_default=False,
352  value_fn=attrgetter("current"),
353  on_phase=None,
354  ),
356  key="net_ct_powerfactor",
357  translation_key="net_ct_powerfactor",
358  device_class=SensorDeviceClass.POWER_FACTOR,
359  state_class=SensorStateClass.MEASUREMENT,
360  suggested_display_precision=2,
361  entity_registry_enabled_default=False,
362  value_fn=attrgetter("power_factor"),
363  on_phase=None,
364  ),
366  key="net_consumption_ct_metering_status",
367  translation_key="net_ct_metering_status",
368  device_class=SensorDeviceClass.ENUM,
369  options=list(CtMeterStatus),
370  entity_registry_enabled_default=False,
371  value_fn=attrgetter("metering_status"),
372  on_phase=None,
373  ),
375  key="net_consumption_ct_status_flags",
376  translation_key="net_ct_status_flags",
377  state_class=None,
378  entity_registry_enabled_default=False,
379  value_fn=lambda ct: 0 if ct.status_flags is None else len(ct.status_flags),
380  on_phase=None,
381  ),
382 )
383 
384 
385 CT_NET_CONSUMPTION_PHASE_SENSORS = {
386  (on_phase := PHASENAMES[phase]): [
387  replace(
388  sensor,
389  key=f"{sensor.key}_l{phase + 1}",
390  translation_key=f"{sensor.translation_key}_phase",
391  entity_registry_enabled_default=False,
392  on_phase=on_phase,
393  translation_placeholders={"phase_name": f"l{phase + 1}"},
394  )
395  for sensor in list(CT_NET_CONSUMPTION_SENSORS)
396  ]
397  for phase in range(3)
398 }
399 
400 CT_PRODUCTION_SENSORS = (
402  key="production_ct_frequency",
403  translation_key="production_ct_frequency",
404  native_unit_of_measurement=UnitOfFrequency.HERTZ,
405  state_class=SensorStateClass.MEASUREMENT,
406  device_class=SensorDeviceClass.FREQUENCY,
407  suggested_display_precision=1,
408  entity_registry_enabled_default=False,
409  value_fn=attrgetter("frequency"),
410  on_phase=None,
411  ),
413  key="production_ct_voltage",
414  translation_key="production_ct_voltage",
415  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
416  state_class=SensorStateClass.MEASUREMENT,
417  device_class=SensorDeviceClass.VOLTAGE,
418  suggested_unit_of_measurement=UnitOfElectricPotential.VOLT,
419  suggested_display_precision=1,
420  entity_registry_enabled_default=False,
421  value_fn=attrgetter("voltage"),
422  on_phase=None,
423  ),
425  key="production_ct_current",
426  translation_key="production_ct_current",
427  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
428  state_class=SensorStateClass.MEASUREMENT,
429  device_class=SensorDeviceClass.CURRENT,
430  suggested_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
431  suggested_display_precision=3,
432  entity_registry_enabled_default=False,
433  value_fn=attrgetter("current"),
434  on_phase=None,
435  ),
437  key="production_ct_powerfactor",
438  translation_key="production_ct_powerfactor",
439  device_class=SensorDeviceClass.POWER_FACTOR,
440  state_class=SensorStateClass.MEASUREMENT,
441  suggested_display_precision=2,
442  entity_registry_enabled_default=False,
443  value_fn=attrgetter("power_factor"),
444  on_phase=None,
445  ),
447  key="production_ct_metering_status",
448  translation_key="production_ct_metering_status",
449  device_class=SensorDeviceClass.ENUM,
450  options=list(CtMeterStatus),
451  entity_registry_enabled_default=False,
452  value_fn=attrgetter("metering_status"),
453  on_phase=None,
454  ),
456  key="production_ct_status_flags",
457  translation_key="production_ct_status_flags",
458  state_class=None,
459  entity_registry_enabled_default=False,
460  value_fn=lambda ct: 0 if ct.status_flags is None else len(ct.status_flags),
461  on_phase=None,
462  ),
463 )
464 
465 CT_PRODUCTION_PHASE_SENSORS = {
466  (on_phase := PHASENAMES[phase]): [
467  replace(
468  sensor,
469  key=f"{sensor.key}_l{phase + 1}",
470  translation_key=f"{sensor.translation_key}_phase",
471  entity_registry_enabled_default=False,
472  on_phase=on_phase,
473  translation_placeholders={"phase_name": f"l{phase + 1}"},
474  )
475  for sensor in list(CT_PRODUCTION_SENSORS)
476  ]
477  for phase in range(3)
478 }
479 
480 CT_STORAGE_SENSORS = (
482  key="lifetime_battery_discharged",
483  translation_key="lifetime_battery_discharged",
484  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
485  state_class=SensorStateClass.TOTAL_INCREASING,
486  device_class=SensorDeviceClass.ENERGY,
487  suggested_unit_of_measurement=UnitOfEnergy.MEGA_WATT_HOUR,
488  suggested_display_precision=3,
489  value_fn=attrgetter("energy_delivered"),
490  on_phase=None,
491  ),
493  key="lifetime_battery_charged",
494  translation_key="lifetime_battery_charged",
495  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
496  state_class=SensorStateClass.TOTAL_INCREASING,
497  device_class=SensorDeviceClass.ENERGY,
498  suggested_unit_of_measurement=UnitOfEnergy.MEGA_WATT_HOUR,
499  suggested_display_precision=3,
500  value_fn=attrgetter("energy_received"),
501  on_phase=None,
502  ),
504  key="battery_discharge",
505  translation_key="battery_discharge",
506  native_unit_of_measurement=UnitOfPower.WATT,
507  state_class=SensorStateClass.MEASUREMENT,
508  device_class=SensorDeviceClass.POWER,
509  suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
510  suggested_display_precision=3,
511  value_fn=attrgetter("active_power"),
512  on_phase=None,
513  ),
515  key="storage_ct_frequency",
516  translation_key="storage_ct_frequency",
517  native_unit_of_measurement=UnitOfFrequency.HERTZ,
518  state_class=SensorStateClass.MEASUREMENT,
519  device_class=SensorDeviceClass.FREQUENCY,
520  suggested_display_precision=1,
521  entity_registry_enabled_default=False,
522  value_fn=attrgetter("frequency"),
523  on_phase=None,
524  ),
526  key="storage_voltage",
527  translation_key="storage_ct_voltage",
528  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
529  state_class=SensorStateClass.MEASUREMENT,
530  device_class=SensorDeviceClass.VOLTAGE,
531  suggested_unit_of_measurement=UnitOfElectricPotential.VOLT,
532  suggested_display_precision=1,
533  entity_registry_enabled_default=False,
534  value_fn=attrgetter("voltage"),
535  on_phase=None,
536  ),
538  key="storage_ct_current",
539  translation_key="storage_ct_current",
540  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
541  state_class=SensorStateClass.MEASUREMENT,
542  device_class=SensorDeviceClass.CURRENT,
543  suggested_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
544  suggested_display_precision=3,
545  entity_registry_enabled_default=False,
546  value_fn=attrgetter("current"),
547  on_phase=None,
548  ),
550  key="storage_ct_powerfactor",
551  translation_key="storage_ct_powerfactor",
552  device_class=SensorDeviceClass.POWER_FACTOR,
553  state_class=SensorStateClass.MEASUREMENT,
554  suggested_display_precision=2,
555  entity_registry_enabled_default=False,
556  value_fn=attrgetter("power_factor"),
557  on_phase=None,
558  ),
560  key="storage_ct_metering_status",
561  translation_key="storage_ct_metering_status",
562  device_class=SensorDeviceClass.ENUM,
563  options=list(CtMeterStatus),
564  entity_registry_enabled_default=False,
565  value_fn=attrgetter("metering_status"),
566  on_phase=None,
567  ),
569  key="storage_ct_status_flags",
570  translation_key="storage_ct_status_flags",
571  state_class=None,
572  entity_registry_enabled_default=False,
573  value_fn=lambda ct: 0 if ct.status_flags is None else len(ct.status_flags),
574  on_phase=None,
575  ),
576 )
577 
578 
579 CT_STORAGE_PHASE_SENSORS = {
580  (on_phase := PHASENAMES[phase]): [
581  replace(
582  sensor,
583  key=f"{sensor.key}_l{phase + 1}",
584  translation_key=f"{sensor.translation_key}_phase",
585  entity_registry_enabled_default=False,
586  on_phase=on_phase,
587  translation_placeholders={"phase_name": f"l{phase + 1}"},
588  )
589  for sensor in list(CT_STORAGE_SENSORS)
590  ]
591  for phase in range(3)
592 }
593 
594 
595 @dataclass(frozen=True, kw_only=True)
597  """Describes an Envoy Encharge sensor entity."""
598 
599  value_fn: Callable[[EnvoyEncharge], datetime.datetime | int | float]
600 
601 
602 @dataclass(frozen=True)
604  """Mixin for required keys."""
605 
606 
607 @dataclass(frozen=True, kw_only=True)
609  """Describes an Envoy Encharge sensor entity."""
610 
611  value_fn: Callable[[EnvoyEnchargePower], int | float]
612 
613 
614 ENCHARGE_INVENTORY_SENSORS = (
616  key="temperature",
617  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
618  device_class=SensorDeviceClass.TEMPERATURE,
619  value_fn=attrgetter("temperature"),
620  ),
622  key=LAST_REPORTED_KEY,
623  translation_key=LAST_REPORTED_KEY,
624  native_unit_of_measurement=None,
625  device_class=SensorDeviceClass.TIMESTAMP,
626  value_fn=lambda encharge: dt_util.utc_from_timestamp(encharge.last_report_date),
627  ),
628 )
629 ENCHARGE_POWER_SENSORS = (
631  key="soc",
632  native_unit_of_measurement=PERCENTAGE,
633  device_class=SensorDeviceClass.BATTERY,
634  value_fn=attrgetter("soc"),
635  ),
637  key="apparent_power_mva",
638  native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
639  device_class=SensorDeviceClass.APPARENT_POWER,
640  value_fn=lambda encharge: encharge.apparent_power_mva * 0.001,
641  ),
643  key="real_power_mw",
644  native_unit_of_measurement=UnitOfPower.WATT,
645  device_class=SensorDeviceClass.POWER,
646  value_fn=lambda encharge: encharge.real_power_mw * 0.001,
647  ),
648 )
649 
650 
651 @dataclass(frozen=True, kw_only=True)
653  """Describes an Envoy Encharge sensor entity."""
654 
655  value_fn: Callable[[EnvoyEnpower], datetime.datetime | int | float]
656 
657 
658 ENPOWER_SENSORS = (
660  key="temperature",
661  native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
662  device_class=SensorDeviceClass.TEMPERATURE,
663  value_fn=attrgetter("temperature"),
664  ),
666  key=LAST_REPORTED_KEY,
667  translation_key=LAST_REPORTED_KEY,
668  device_class=SensorDeviceClass.TIMESTAMP,
669  value_fn=lambda enpower: dt_util.utc_from_timestamp(enpower.last_report_date),
670  ),
671 )
672 
673 
674 @dataclass(frozen=True)
676  """Mixin for required keys."""
677 
678 
679 @dataclass(frozen=True, kw_only=True)
681  """Describes an Envoy Encharge sensor entity."""
682 
683  value_fn: Callable[[EnvoyEnchargeAggregate], int]
684 
685 
686 ENCHARGE_AGGREGATE_SENSORS = (
688  key="battery_level",
689  native_unit_of_measurement=PERCENTAGE,
690  device_class=SensorDeviceClass.BATTERY,
691  value_fn=attrgetter("state_of_charge"),
692  ),
694  key="reserve_soc",
695  translation_key="reserve_soc",
696  native_unit_of_measurement=PERCENTAGE,
697  device_class=SensorDeviceClass.BATTERY,
698  value_fn=attrgetter("reserve_state_of_charge"),
699  ),
701  key="available_energy",
702  translation_key="available_energy",
703  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
704  device_class=SensorDeviceClass.ENERGY,
705  value_fn=attrgetter("available_energy"),
706  ),
708  key="reserve_energy",
709  translation_key="reserve_energy",
710  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
711  device_class=SensorDeviceClass.ENERGY,
712  value_fn=attrgetter("backup_reserve"),
713  ),
715  key="max_capacity",
716  translation_key="max_capacity",
717  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
718  device_class=SensorDeviceClass.ENERGY,
719  value_fn=attrgetter("max_available_capacity"),
720  ),
721 )
722 
723 
725  hass: HomeAssistant,
726  config_entry: EnphaseConfigEntry,
727  async_add_entities: AddEntitiesCallback,
728 ) -> None:
729  """Set up envoy sensor platform."""
730  coordinator = config_entry.runtime_data
731  envoy_data = coordinator.envoy.data
732  assert envoy_data is not None
733  _LOGGER.debug("Envoy data: %s", envoy_data)
734 
735  entities: list[Entity] = [
736  EnvoyProductionEntity(coordinator, description)
737  for description in PRODUCTION_SENSORS
738  ]
739  if envoy_data.system_consumption:
740  entities.extend(
741  EnvoyConsumptionEntity(coordinator, description)
742  for description in CONSUMPTION_SENSORS
743  )
744  if envoy_data.system_net_consumption:
745  entities.extend(
746  EnvoyNetConsumptionEntity(coordinator, description)
747  for description in NET_CONSUMPTION_SENSORS
748  )
749  # For each production phase reported add production entities
750  if envoy_data.system_production_phases:
751  entities.extend(
752  EnvoyProductionPhaseEntity(coordinator, description)
753  for use_phase, phase in envoy_data.system_production_phases.items()
754  for description in PRODUCTION_PHASE_SENSORS[use_phase]
755  if phase is not None
756  )
757  # For each consumption phase reported add consumption entities
758  if envoy_data.system_consumption_phases:
759  entities.extend(
760  EnvoyConsumptionPhaseEntity(coordinator, description)
761  for use_phase, phase in envoy_data.system_consumption_phases.items()
762  for description in CONSUMPTION_PHASE_SENSORS[use_phase]
763  if phase is not None
764  )
765  # For each net_consumption phase reported add consumption entities
766  if envoy_data.system_net_consumption_phases:
767  entities.extend(
768  EnvoyNetConsumptionPhaseEntity(coordinator, description)
769  for use_phase, phase in envoy_data.system_net_consumption_phases.items()
770  for description in NET_CONSUMPTION_PHASE_SENSORS[use_phase]
771  if phase is not None
772  )
773  # Add net consumption CT entities
774  if ctmeter := envoy_data.ctmeter_consumption:
775  entities.extend(
776  EnvoyConsumptionCTEntity(coordinator, description)
777  for description in CT_NET_CONSUMPTION_SENSORS
778  if ctmeter.measurement_type == CtType.NET_CONSUMPTION
779  )
780  # For each net consumption ct phase reported add net consumption entities
781  if phase_data := envoy_data.ctmeter_consumption_phases:
782  entities.extend(
783  EnvoyConsumptionCTPhaseEntity(coordinator, description)
784  for use_phase, phase in phase_data.items()
785  for description in CT_NET_CONSUMPTION_PHASE_SENSORS[use_phase]
786  if phase.measurement_type == CtType.NET_CONSUMPTION
787  )
788  # Add production CT entities
789  if ctmeter := envoy_data.ctmeter_production:
790  entities.extend(
791  EnvoyProductionCTEntity(coordinator, description)
792  for description in CT_PRODUCTION_SENSORS
793  if ctmeter.measurement_type == CtType.PRODUCTION
794  )
795  # For each production ct phase reported add production ct entities
796  if phase_data := envoy_data.ctmeter_production_phases:
797  entities.extend(
798  EnvoyProductionCTPhaseEntity(coordinator, description)
799  for use_phase, phase in phase_data.items()
800  for description in CT_PRODUCTION_PHASE_SENSORS[use_phase]
801  if phase.measurement_type == CtType.PRODUCTION
802  )
803  # Add storage CT entities
804  if ctmeter := envoy_data.ctmeter_storage:
805  entities.extend(
806  EnvoyStorageCTEntity(coordinator, description)
807  for description in CT_STORAGE_SENSORS
808  if ctmeter.measurement_type == CtType.STORAGE
809  )
810  # For each storage ct phase reported add storage ct entities
811  if phase_data := envoy_data.ctmeter_storage_phases:
812  entities.extend(
813  EnvoyStorageCTPhaseEntity(coordinator, description)
814  for use_phase, phase in phase_data.items()
815  for description in CT_STORAGE_PHASE_SENSORS[use_phase]
816  if phase.measurement_type == CtType.STORAGE
817  )
818 
819  if envoy_data.inverters:
820  entities.extend(
821  EnvoyInverterEntity(coordinator, description, inverter)
822  for description in INVERTER_SENSORS
823  for inverter in envoy_data.inverters
824  )
825 
826  if envoy_data.encharge_inventory:
827  entities.extend(
828  EnvoyEnchargeInventoryEntity(coordinator, description, encharge)
829  for description in ENCHARGE_INVENTORY_SENSORS
830  for encharge in envoy_data.encharge_inventory
831  )
832  if envoy_data.encharge_power:
833  entities.extend(
834  EnvoyEnchargePowerEntity(coordinator, description, encharge)
835  for description in ENCHARGE_POWER_SENSORS
836  for encharge in envoy_data.encharge_power
837  )
838  if envoy_data.encharge_aggregate:
839  entities.extend(
840  EnvoyEnchargeAggregateEntity(coordinator, description)
841  for description in ENCHARGE_AGGREGATE_SENSORS
842  )
843  if envoy_data.enpower:
844  entities.extend(
845  EnvoyEnpowerEntity(coordinator, description)
846  for description in ENPOWER_SENSORS
847  )
848 
849  async_add_entities(entities)
850 
851 
853  """Defines a base envoy entity."""
854 
855 
856 class EnvoySystemSensorEntity(EnvoySensorBaseEntity):
857  """Envoy system base entity."""
858 
859  _attr_icon = ICON
860 
861  def __init__(
862  self,
863  coordinator: EnphaseUpdateCoordinator,
864  description: SensorEntityDescription,
865  ) -> None:
866  """Initialize Envoy entity."""
867  super().__init__(coordinator, description)
868  self._attr_unique_id_attr_unique_id = f"{self.envoy_serial_num}_{description.key}"
869  self._attr_device_info_attr_device_info = DeviceInfo(
870  identifiers={(DOMAIN, self.envoy_serial_numenvoy_serial_num)},
871  manufacturer="Enphase",
872  model=coordinator.envoy.envoy_model,
873  name=coordinator.name,
874  sw_version=str(coordinator.envoy.firmware),
875  hw_version=coordinator.envoy.part_number,
876  serial_number=self.envoy_serial_numenvoy_serial_num,
877  )
878 
879 
881  """Envoy production entity."""
882 
883  entity_description: EnvoyProductionSensorEntityDescription
884 
885  @property
886  def native_value(self) -> int | None:
887  """Return the state of the sensor."""
888  system_production = self.datadatadata.system_production
889  assert system_production is not None
890  return self.entity_descriptionentity_description.value_fn(system_production)
891 
892 
894  """Envoy consumption entity."""
895 
896  entity_description: EnvoyConsumptionSensorEntityDescription
897 
898  @property
899  def native_value(self) -> int | None:
900  """Return the state of the sensor."""
901  system_consumption = self.datadatadata.system_consumption
902  assert system_consumption is not None
903  return self.entity_descriptionentity_description.value_fn(system_consumption)
904 
905 
907  """Envoy consumption entity."""
908 
909  entity_description: EnvoyConsumptionSensorEntityDescription
910 
911  @property
912  def native_value(self) -> int | None:
913  """Return the state of the sensor."""
914  system_net_consumption = self.datadatadata.system_net_consumption
915  assert system_net_consumption is not None
916  return self.entity_descriptionentity_description.value_fn(system_net_consumption)
917 
918 
920  """Envoy phase production entity."""
921 
922  entity_description: EnvoyProductionSensorEntityDescription
923 
924  @property
925  def native_value(self) -> int | None:
926  """Return the state of the sensor."""
927  if TYPE_CHECKING:
928  assert self.entity_descriptionentity_description.on_phase
929  assert self.datadatadata.system_production_phases
930 
931  if (
932  system_production := self.datadatadata.system_production_phases[
933  self.entity_descriptionentity_description.on_phase
934  ]
935  ) is None:
936  return None
937  return self.entity_descriptionentity_description.value_fn(system_production)
938 
939 
941  """Envoy phase consumption entity."""
942 
943  entity_description: EnvoyConsumptionSensorEntityDescription
944 
945  @property
946  def native_value(self) -> int | None:
947  """Return the state of the sensor."""
948  if TYPE_CHECKING:
949  assert self.entity_descriptionentity_description.on_phase
950  assert self.datadatadata.system_consumption_phases
951 
952  if (
953  system_consumption := self.datadatadata.system_consumption_phases[
954  self.entity_descriptionentity_description.on_phase
955  ]
956  ) is None:
957  return None
958  return self.entity_descriptionentity_description.value_fn(system_consumption)
959 
960 
962  """Envoy phase consumption entity."""
963 
964  entity_description: EnvoyConsumptionSensorEntityDescription
965 
966  @property
967  def native_value(self) -> int | None:
968  """Return the state of the sensor."""
969  if TYPE_CHECKING:
970  assert self.entity_descriptionentity_description.on_phase
971  assert self.datadatadata.system_net_consumption_phases
972 
973  if (
974  system_net_consumption := self.datadatadata.system_net_consumption_phases[
975  self.entity_descriptionentity_description.on_phase
976  ]
977  ) is None:
978  return None
979  return self.entity_descriptionentity_description.value_fn(system_net_consumption)
980 
981 
983  """Envoy net consumption CT entity."""
984 
985  entity_description: EnvoyCTSensorEntityDescription
986 
987  @property
989  self,
990  ) -> int | float | str | CtType | CtMeterStatus | CtStatusFlags | None:
991  """Return the state of the CT sensor."""
992  if (ctmeter := self.datadatadata.ctmeter_consumption) is None:
993  return None
994  return self.entity_descriptionentity_description.value_fn(ctmeter)
995 
996 
998  """Envoy net consumption CT phase entity."""
999 
1000  entity_description: EnvoyCTSensorEntityDescription
1001 
1002  @property
1004  self,
1005  ) -> int | float | str | CtType | CtMeterStatus | CtStatusFlags | None:
1006  """Return the state of the CT phase sensor."""
1007  if TYPE_CHECKING:
1008  assert self.entity_descriptionentity_description.on_phase
1009  if (ctmeter := self.datadatadata.ctmeter_consumption_phases) is None:
1010  return None
1011  return self.entity_descriptionentity_description.value_fn(
1012  ctmeter[self.entity_descriptionentity_description.on_phase]
1013  )
1014 
1015 
1017  """Envoy net consumption CT entity."""
1018 
1019  entity_description: EnvoyCTSensorEntityDescription
1020 
1021  @property
1023  self,
1024  ) -> int | float | str | CtType | CtMeterStatus | CtStatusFlags | None:
1025  """Return the state of the CT sensor."""
1026  if (ctmeter := self.datadatadata.ctmeter_production) is None:
1027  return None
1028  return self.entity_descriptionentity_description.value_fn(ctmeter)
1029 
1030 
1032  """Envoy net consumption CT phase entity."""
1033 
1034  entity_description: EnvoyCTSensorEntityDescription
1035 
1036  @property
1038  self,
1039  ) -> int | float | str | CtType | CtMeterStatus | CtStatusFlags | None:
1040  """Return the state of the CT phase sensor."""
1041  if TYPE_CHECKING:
1042  assert self.entity_descriptionentity_description.on_phase
1043  if (ctmeter := self.datadatadata.ctmeter_production_phases) is None:
1044  return None
1045  return self.entity_descriptionentity_description.value_fn(
1046  ctmeter[self.entity_descriptionentity_description.on_phase]
1047  )
1048 
1049 
1051  """Envoy net storage CT entity."""
1052 
1053  entity_description: EnvoyCTSensorEntityDescription
1054 
1055  @property
1057  self,
1058  ) -> int | float | str | CtType | CtMeterStatus | CtStatusFlags | None:
1059  """Return the state of the CT sensor."""
1060  if (ctmeter := self.datadatadata.ctmeter_storage) is None:
1061  return None
1062  return self.entity_descriptionentity_description.value_fn(ctmeter)
1063 
1064 
1066  """Envoy net storage CT phase entity."""
1067 
1068  entity_description: EnvoyCTSensorEntityDescription
1069 
1070  @property
1072  self,
1073  ) -> int | float | str | CtType | CtMeterStatus | CtStatusFlags | None:
1074  """Return the state of the CT phase sensor."""
1075  if TYPE_CHECKING:
1076  assert self.entity_descriptionentity_description.on_phase
1077  if (ctmeter := self.datadatadata.ctmeter_storage_phases) is None:
1078  return None
1079  return self.entity_descriptionentity_description.value_fn(
1080  ctmeter[self.entity_descriptionentity_description.on_phase]
1081  )
1082 
1083 
1085  """Envoy inverter entity."""
1086 
1087  _attr_icon = ICON
1088  entity_description: EnvoyInverterSensorEntityDescription
1089 
1091  self,
1092  coordinator: EnphaseUpdateCoordinator,
1093  description: EnvoyInverterSensorEntityDescription,
1094  serial_number: str,
1095  ) -> None:
1096  """Initialize Envoy inverter entity."""
1097  super().__init__(coordinator, description)
1098  self._serial_number_serial_number = serial_number
1099  key = description.key
1100  if key == INVERTERS_KEY:
1101  # Originally there was only one inverter sensor, so we don't want to
1102  # break existing installations by changing the unique_id.
1103  self._attr_unique_id_attr_unique_id = serial_number
1104  else:
1105  # Additional sensors have a unique_id that includes the
1106  # sensor key.
1107  self._attr_unique_id_attr_unique_id = f"{serial_number}_{key}"
1108  self._attr_device_info_attr_device_info = DeviceInfo(
1109  identifiers={(DOMAIN, serial_number)},
1110  name=f"Inverter {serial_number}",
1111  manufacturer="Enphase",
1112  model="Inverter",
1113  via_device=(DOMAIN, self.envoy_serial_numenvoy_serial_num),
1114  )
1115 
1116  @property
1117  def native_value(self) -> datetime.datetime | float | None:
1118  """Return the state of the sensor."""
1119  inverters = self.datadatadata.inverters
1120  assert inverters is not None
1121  # Some envoy fw versions return an empty inverter array every 4 hours when
1122  # no production is taking place. Prevent collection failure due to this
1123  # as other data seems fine. Inverters will show unknown during this cycle.
1124  if self._serial_number_serial_number not in inverters:
1125  _LOGGER.debug(
1126  "Inverter %s not in returned inverters array (size: %s)",
1127  self._serial_number_serial_number,
1128  len(inverters),
1129  )
1130  return None
1131  return self.entity_descriptionentity_description.value_fn(inverters[self._serial_number_serial_number])
1132 
1133 
1135  """Envoy Encharge sensor entity."""
1136 
1138  self,
1139  coordinator: EnphaseUpdateCoordinator,
1140  description: EnvoyEnchargeSensorEntityDescription
1141  | EnvoyEnchargePowerSensorEntityDescription,
1142  serial_number: str,
1143  ) -> None:
1144  """Initialize Encharge entity."""
1145  super().__init__(coordinator, description)
1146  self._serial_number_serial_number = serial_number
1147  self._attr_unique_id_attr_unique_id = f"{serial_number}_{description.key}"
1148  encharge_inventory = self.datadatadata.encharge_inventory
1149  assert encharge_inventory is not None
1150  self._attr_device_info_attr_device_info = DeviceInfo(
1151  identifiers={(DOMAIN, serial_number)},
1152  manufacturer="Enphase",
1153  model="Encharge",
1154  name=f"Encharge {serial_number}",
1155  sw_version=str(encharge_inventory[self._serial_number_serial_number].firmware_version),
1156  via_device=(DOMAIN, self.envoy_serial_numenvoy_serial_num),
1157  )
1158 
1159 
1161  """Envoy Encharge inventory entity."""
1162 
1163  entity_description: EnvoyEnchargeSensorEntityDescription
1164 
1165  @property
1166  def native_value(self) -> int | float | datetime.datetime | None:
1167  """Return the state of the inventory sensors."""
1168  encharge_inventory = self.datadatadata.encharge_inventory
1169  assert encharge_inventory is not None
1170  return self.entity_descriptionentity_description.value_fn(encharge_inventory[self._serial_number_serial_number])
1171 
1172 
1174  """Envoy Encharge power entity."""
1175 
1176  entity_description: EnvoyEnchargePowerSensorEntityDescription
1177 
1178  @property
1179  def native_value(self) -> int | float | None:
1180  """Return the state of the power sensors."""
1181  encharge_power = self.datadatadata.encharge_power
1182  assert encharge_power is not None
1183  return self.entity_descriptionentity_description.value_fn(encharge_power[self._serial_number_serial_number])
1184 
1185 
1187  """Envoy Encharge Aggregate sensor entity."""
1188 
1189  entity_description: EnvoyEnchargeAggregateSensorEntityDescription
1190 
1191  @property
1192  def native_value(self) -> int:
1193  """Return the state of the aggregate sensors."""
1194  encharge_aggregate = self.datadatadata.encharge_aggregate
1195  assert encharge_aggregate is not None
1196  return self.entity_descriptionentity_description.value_fn(encharge_aggregate)
1197 
1198 
1200  """Envoy Enpower sensor entity."""
1201 
1202  entity_description: EnvoyEnpowerSensorEntityDescription
1203 
1205  self,
1206  coordinator: EnphaseUpdateCoordinator,
1207  description: EnvoyEnpowerSensorEntityDescription,
1208  ) -> None:
1209  """Initialize Enpower entity."""
1210  super().__init__(coordinator, description)
1211  enpower_data = self.datadatadata.enpower
1212  assert enpower_data is not None
1213  self._attr_unique_id_attr_unique_id = f"{enpower_data.serial_number}_{description.key}"
1214  self._attr_device_info_attr_device_info = DeviceInfo(
1215  identifiers={(DOMAIN, enpower_data.serial_number)},
1216  manufacturer="Enphase",
1217  model="Enpower",
1218  name=f"Enpower {enpower_data.serial_number}",
1219  sw_version=str(enpower_data.firmware_version),
1220  via_device=(DOMAIN, self.envoy_serial_numenvoy_serial_num),
1221  )
1222 
1223  @property
1224  def native_value(self) -> datetime.datetime | int | float | None:
1225  """Return the state of the power sensors."""
1226  enpower = self.datadatadata.enpower
1227  assert enpower is not None
1228  return self.entity_descriptionentity_description.value_fn(enpower)
int|float|str|CtType|CtMeterStatus|CtStatusFlags|None native_value(self)
Definition: sensor.py:990
int|float|str|CtType|CtMeterStatus|CtStatusFlags|None native_value(self)
Definition: sensor.py:1005
None __init__(self, EnphaseUpdateCoordinator coordinator, EnvoyEnchargeSensorEntityDescription|EnvoyEnchargePowerSensorEntityDescription description, str serial_number)
Definition: sensor.py:1143
datetime.datetime|int|float|None native_value(self)
Definition: sensor.py:1224
None __init__(self, EnphaseUpdateCoordinator coordinator, EnvoyEnpowerSensorEntityDescription description)
Definition: sensor.py:1208
None __init__(self, EnphaseUpdateCoordinator coordinator, EnvoyInverterSensorEntityDescription description, str serial_number)
Definition: sensor.py:1095
int|float|str|CtType|CtMeterStatus|CtStatusFlags|None native_value(self)
Definition: sensor.py:1024
int|float|str|CtType|CtMeterStatus|CtStatusFlags|None native_value(self)
Definition: sensor.py:1039
int|float|str|CtType|CtMeterStatus|CtStatusFlags|None native_value(self)
Definition: sensor.py:1058
int|float|str|CtType|CtMeterStatus|CtStatusFlags|None native_value(self)
Definition: sensor.py:1073
None __init__(self, EnphaseUpdateCoordinator coordinator, SensorEntityDescription description)
Definition: sensor.py:865
None async_setup_entry(HomeAssistant hass, EnphaseConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:728