Skip to content

Commit 4b37fc2

Browse files
Merge pull request #70 from vincentwolsink/add_optional_entities
Add optional extra entities for metered envoy.
2 parents a72eeb1 + 66ed259 commit 4b37fc2

File tree

8 files changed

+135
-6
lines changed

8 files changed

+135
-6
lines changed

custom_components/enphase_envoy/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ def update_production_meters(streamdata: StreamData):
145145
{
146146
"production_" + phase: production_watts,
147147
"voltage_" + phase: streamdata.production[phase].volt,
148+
"ampere_" + phase: streamdata.production[phase].amps,
149+
"apparent_power_" + phase: streamdata.production[phase].volt_ampere,
150+
"power_factor" + phase: streamdata.production[phase].pf,
151+
"reactive_power_" + phase: streamdata.production[phase].var,
152+
"frequency_" + phase: streamdata.production[phase].hz,
148153
"consumption_" + phase: streamdata.consumption[phase].watts,
149154
}
150155
)

custom_components/enphase_envoy/config_flow.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
DEFAULT_SCAN_INTERVAL,
2929
DEFAULT_REALTIME_UPDATE_THROTTLE,
3030
DISABLE_INSTALLER_ACCOUNT_USE,
31+
ENABLE_ADDITIONAL_METRICS,
3132
)
3233

3334
_LOGGER = logging.getLogger(__name__)
@@ -272,6 +273,10 @@ async def async_step_user(self, user_input=None):
272273
)
273274
),
274275
): bool,
276+
vol.Optional(
277+
ENABLE_ADDITIONAL_METRICS,
278+
default=self.config_entry.options.get(ENABLE_ADDITIONAL_METRICS, False),
279+
): bool,
275280
}
276281
return self.async_show_form(step_id="user", data_schema=vol.Schema(schema))
277282

custom_components/enphase_envoy/const.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@
1313
from homeassistant.const import (
1414
Platform,
1515
PERCENTAGE,
16+
UnitOfApparentPower,
1617
UnitOfEnergy,
18+
UnitOfFrequency,
1719
UnitOfPower,
1820
UnitOfElectricPotential,
1921
UnitOfElectricCurrent,
2022
UnitOfTemperature,
23+
POWER_VOLT_AMPERE_REACTIVE,
2124
)
2225

2326
DOMAIN = "enphase_envoy"
@@ -35,6 +38,8 @@
3538

3639
LIVE_UPDATEABLE_ENTITIES = "live-update-entities"
3740
DISABLE_INSTALLER_ACCOUNT_USE = "disable_installer_account_use"
41+
ENABLE_ADDITIONAL_METRICS = "enable_additional_metrics"
42+
ADDITIONAL_METRICS = []
3843

3944
SENSORS = (
4045
SensorEntityDescription(
@@ -149,6 +154,26 @@
149154
state_class=SensorStateClass.MEASUREMENT,
150155
device_class=SensorDeviceClass.VOLTAGE,
151156
),
157+
SensorEntityDescription(
158+
key=f"ampere",
159+
name=f"Current Amps",
160+
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
161+
state_class=SensorStateClass.MEASUREMENT,
162+
device_class=SensorDeviceClass.CURRENT,
163+
),
164+
SensorEntityDescription(
165+
key=f"apparent_power",
166+
name=f"Apparent Power",
167+
native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
168+
state_class=SensorStateClass.MEASUREMENT,
169+
device_class=SensorDeviceClass.APPARENT_POWER,
170+
),
171+
)
172+
ADDITIONAL_METRICS.extend(
173+
[
174+
"ampere",
175+
"apparent_power",
176+
]
152177
)
153178

154179
PHASE_SENSORS = []
@@ -186,6 +211,43 @@
186211
state_class=SensorStateClass.MEASUREMENT,
187212
device_class=SensorDeviceClass.VOLTAGE,
188213
),
214+
SensorEntityDescription(
215+
key=f"ampere_{phase}",
216+
name=f"Current Amps {phase.upper()}",
217+
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
218+
state_class=SensorStateClass.MEASUREMENT,
219+
device_class=SensorDeviceClass.CURRENT,
220+
),
221+
SensorEntityDescription(
222+
key=f"apparent_power_{phase}",
223+
name=f"Apparent Power {phase.upper()}",
224+
native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
225+
state_class=SensorStateClass.MEASUREMENT,
226+
device_class=SensorDeviceClass.APPARENT_POWER,
227+
),
228+
SensorEntityDescription(
229+
key=f"reactive_power_{phase}",
230+
name=f"Reactive Power {phase.upper()}",
231+
native_unit_of_measurement=POWER_VOLT_AMPERE_REACTIVE,
232+
state_class=SensorStateClass.MEASUREMENT,
233+
device_class=SensorDeviceClass.REACTIVE_POWER,
234+
),
235+
SensorEntityDescription(
236+
key=f"frequency_{phase}",
237+
name=f"Frequency {phase.upper()}",
238+
native_unit_of_measurement=UnitOfFrequency.HERTZ,
239+
state_class=SensorStateClass.MEASUREMENT,
240+
device_class=SensorDeviceClass.FREQUENCY,
241+
suggested_display_precision=1,
242+
),
243+
SensorEntityDescription(
244+
key=f"power_factor_{phase}",
245+
name=f"Power Factor {phase.upper()}",
246+
native_unit_of_measurement=None,
247+
state_class=SensorStateClass.MEASUREMENT,
248+
device_class=SensorDeviceClass.POWER_FACTOR,
249+
suggested_display_precision=2,
250+
),
189251
#
190252
# Consumption entities
191253
#
@@ -215,6 +277,15 @@
215277
),
216278
]
217279
)
280+
ADDITIONAL_METRICS.extend(
281+
[
282+
f"ampere_{phase}",
283+
f"apparent_power_{phase}",
284+
f"reactive_power_{phase}",
285+
f"frequency_{phase}",
286+
f"power_factor_{phase}",
287+
]
288+
)
218289

219290
BINARY_SENSORS = (
220291
BinarySensorEntityDescription(

custom_components/enphase_envoy/envoy_reader.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
ENDPOINT_URL_PRODUCTION_POWER = "https://{}/ivp/mod/603980032/mode/power"
3434
ENDPOINT_URL_INFO_XML = "https://{}/info.xml"
3535
ENDPOINT_URL_STREAM = "https://{}/stream/meter"
36+
ENDPOINT_URL_PRODUCTION_REPORT = "https://{}/ivp/meters/reports/production"
3637
ENDPOINT_URL_PDM_ENERGY = "https://{}/ivp/pdm/energy"
3738

3839
ENVOY_MODEL_M = "Metered"
@@ -548,23 +549,44 @@ def __new__(cls, *a, **kw):
548549
class EnvoyMeteredWithCT(EnvoyMetered):
549550
"""Adds CT based sensors, like current usage per phase"""
550551

551-
def __new__(cls, *a, **kw):
552+
def __new__(cls, reader, **kw):
552553
# Add phase CT production value attributes, as this class is
553554
# chosen when one production CT is enabled.
554555
for attr, path in {
555-
"production": ".wNow",
556-
"daily_production": ".whToday",
557-
"lifetime_production": ".whLifetime",
556+
"production": ".currW",
557+
"lifetime_production": ".whDlvdCum",
558558
"voltage": ".rmsVoltage",
559+
"ampere": ".rmsCurrent",
560+
"apparent_power": ".apprntPwr",
561+
"power_factor": ".pwrFactor",
562+
"reactive_power": ".reactPwr",
563+
"frequency": ".freqHz",
559564
}.items():
560-
ct_path = cls._production_ct
561-
setattr(cls, f"{attr}_value", ct_path + path)
565+
ct_path = "endpoint_production_report"
566+
setattr(cls, f"{attr}_value", f"{ct_path}.cumulative{path}")
562567

563568
# Also create paths for all phases.
564569
for i, phase in enumerate(["l1", "l2", "l3"]):
565570
full_path = f"{ct_path}.lines[{i}]{path}"
566571
setattr(cls, f"{attr}_{phase}_value", full_path)
567572

573+
setattr(
574+
cls,
575+
"daily_production_value",
576+
"endpoint_production_json_results.production[?(@.type=='eim')].whToday",
577+
)
578+
for i, phase in enumerate(["l1", "l2", "l3"]):
579+
setattr(
580+
cls,
581+
f"daily_production_{phase}_value"
582+
"endpoint_production_json_results.production[?(@.type=='eim')].lines[{i}].whToday",
583+
)
584+
585+
# When we're using the endpoint_production_report primarily, then the following
586+
# endpoint can be used way less frequently
587+
reader.uri_registry["endpoint_production_json_results"]["cache_time"] = 50
588+
reader.uri_registry["endpoint_production_inverters"]["cache_time"] = 290
589+
568590
return EnvoyMetered.__new__(cls)
569591

570592

@@ -640,6 +662,7 @@ def url(endpoint, *a, **kw):
640662
iurl("production_power", ENDPOINT_URL_PRODUCTION_POWER, cache=3600)
641663
url("info_results", ENDPOINT_URL_INFO_XML, cache=86400)
642664
url("inventory_results", ENDPOINT_URL_INVENTORY, cache=300)
665+
url("production_report", ENDPOINT_URL_PRODUCTION_REPORT, cache=0)
643666
iurl("pdm_energy", ENDPOINT_URL_PDM_ENERGY)
644667

645668
# If IPv6 address then enclose host in brackets
@@ -1279,6 +1302,11 @@ async def _async_getattr(self, key):
12791302
lifetime_production_phase = _async_getattr
12801303
lifetime_consumption_phase = _async_getattr
12811304
voltage_phase = _async_getattr
1305+
frequency_phase = _async_getattr
1306+
ampere_phase = _async_getattr
1307+
apparent_power_phase = _async_getattr
1308+
power_factor_phase = _async_getattr
1309+
reactive_power_phase = _async_getattr
12821310

12831311
async def set_production_power(self, power_on):
12841312
if self.endpoint_production_power is not None:
@@ -1369,6 +1397,9 @@ def run_in_console(
13691397
self.production_phase("production_l1"),
13701398
self.production_phase("production_l2"),
13711399
self.production_phase("production_l3"),
1400+
self.frequency_phase("frequency_l1"),
1401+
self.frequency_phase("frequency_l2"),
1402+
self.frequency_phase("frequency_l3"),
13721403
return_exceptions=False,
13731404
)
13741405
)
@@ -1392,6 +1423,9 @@ def run_in_console(
13921423
"production_phase(l1)",
13931424
"production_phase(l2)",
13941425
"production_phase(l3)",
1426+
"frequency(l1)",
1427+
"frequency(l2)",
1428+
"frequency(l3)",
13951429
]
13961430
pprint.pprint(dict(zip(fields, results)))
13971431

custom_components/enphase_envoy/sensor.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
SENSORS,
2424
PHASE_SENSORS,
2525
LIVE_UPDATEABLE_ENTITIES,
26+
ENABLE_ADDITIONAL_METRICS,
27+
ADDITIONAL_METRICS,
2628
)
2729

2830

@@ -36,9 +38,14 @@ async def async_setup_entry(
3638
coordinator = data[COORDINATOR]
3739
name = data[NAME]
3840
live_entities = data[LIVE_UPDATEABLE_ENTITIES]
41+
options = config_entry.options
3942

4043
entities = []
4144
for sensor_description in SENSORS:
45+
if not options.get(ENABLE_ADDITIONAL_METRICS, False):
46+
if sensor_description.key in ADDITIONAL_METRICS:
47+
continue
48+
4249
if sensor_description.key == "inverters":
4350
if coordinator.data.get("inverters_production") is not None:
4451
for inverter in coordinator.data["inverters_production"]:
@@ -158,6 +165,10 @@ async def async_setup_entry(
158165
)
159166

160167
for sensor_description in PHASE_SENSORS:
168+
if not options.get(ENABLE_ADDITIONAL_METRICS, False):
169+
if sensor_description.key in ADDITIONAL_METRICS:
170+
continue
171+
161172
data = coordinator.data.get(sensor_description.key)
162173
if data is None:
163174
continue

custom_components/enphase_envoy/strings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"realtime_update_throttle": "Minimum time between realtime entity updates [s]",
3333
"disable_negative_production": "Disable negative production values",
3434
"time_between_update": "Minimum time between entity updates [s]",
35+
"enable_additional_metrics": "[Metered only] Enable additional metrics like total amps, frequency, apparent and reactive power and power factor.",
3536
"disable_installer_account_use": "Do not collect data that requires installer or DIY enphase account"
3637
},
3738
"data_description": {

custom_components/enphase_envoy/translations/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"realtime_update_throttle": "Minimum time between realtime entity updates [s]",
3333
"disable_negative_production": "Disable negative production values",
3434
"time_between_update": "Minimum time between entity updates [s]",
35+
"enable_additional_metrics": "[Metered only] Enable additional metrics like total amps, frequency, apparent and reactive power and power factor.",
3536
"disable_installer_account_use": "Do not collect data that requires installer or DIY enphase account"
3637
},
3738
"data_description": {

custom_components/enphase_envoy/translations/nl.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"realtime_update_throttle": "Minimale tijd tussen real-time updates [s]",
3333
"disable_negative_production": "Voorkom negatieve productie waardes",
3434
"time_between_update": "Minimum tijd tussen entity updates [s]",
35+
"enable_additional_metrics": "[Metered only] Extra metrics inschakelen, zoals total amps, frequency, apparent en reactive power en power factor.",
3536
"disable_installer_account_use": "Haal geen data op die een installateur of DHZ enphase account vereisen"
3637
},
3738
"data_description": {

0 commit comments

Comments
 (0)