Skip to content

Commit

Permalink
Merge pull request #70 from vincentwolsink/add_optional_entities
Browse files Browse the repository at this point in the history
Add optional extra entities for metered envoy.
  • Loading branch information
vincentwolsink authored Aug 1, 2023
2 parents a72eeb1 + 66ed259 commit 4b37fc2
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 6 deletions.
5 changes: 5 additions & 0 deletions custom_components/enphase_envoy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ def update_production_meters(streamdata: StreamData):
{
"production_" + phase: production_watts,
"voltage_" + phase: streamdata.production[phase].volt,
"ampere_" + phase: streamdata.production[phase].amps,
"apparent_power_" + phase: streamdata.production[phase].volt_ampere,
"power_factor" + phase: streamdata.production[phase].pf,
"reactive_power_" + phase: streamdata.production[phase].var,
"frequency_" + phase: streamdata.production[phase].hz,
"consumption_" + phase: streamdata.consumption[phase].watts,
}
)
Expand Down
5 changes: 5 additions & 0 deletions custom_components/enphase_envoy/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
DEFAULT_SCAN_INTERVAL,
DEFAULT_REALTIME_UPDATE_THROTTLE,
DISABLE_INSTALLER_ACCOUNT_USE,
ENABLE_ADDITIONAL_METRICS,
)

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -272,6 +273,10 @@ async def async_step_user(self, user_input=None):
)
),
): bool,
vol.Optional(
ENABLE_ADDITIONAL_METRICS,
default=self.config_entry.options.get(ENABLE_ADDITIONAL_METRICS, False),
): bool,
}
return self.async_show_form(step_id="user", data_schema=vol.Schema(schema))

Expand Down
71 changes: 71 additions & 0 deletions custom_components/enphase_envoy/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
from homeassistant.const import (
Platform,
PERCENTAGE,
UnitOfApparentPower,
UnitOfEnergy,
UnitOfFrequency,
UnitOfPower,
UnitOfElectricPotential,
UnitOfElectricCurrent,
UnitOfTemperature,
POWER_VOLT_AMPERE_REACTIVE,
)

DOMAIN = "enphase_envoy"
Expand All @@ -35,6 +38,8 @@

LIVE_UPDATEABLE_ENTITIES = "live-update-entities"
DISABLE_INSTALLER_ACCOUNT_USE = "disable_installer_account_use"
ENABLE_ADDITIONAL_METRICS = "enable_additional_metrics"
ADDITIONAL_METRICS = []

SENSORS = (
SensorEntityDescription(
Expand Down Expand Up @@ -149,6 +154,26 @@
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.VOLTAGE,
),
SensorEntityDescription(
key=f"ampere",
name=f"Current Amps",
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.CURRENT,
),
SensorEntityDescription(
key=f"apparent_power",
name=f"Apparent Power",
native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.APPARENT_POWER,
),
)
ADDITIONAL_METRICS.extend(
[
"ampere",
"apparent_power",
]
)

PHASE_SENSORS = []
Expand Down Expand Up @@ -186,6 +211,43 @@
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.VOLTAGE,
),
SensorEntityDescription(
key=f"ampere_{phase}",
name=f"Current Amps {phase.upper()}",
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.CURRENT,
),
SensorEntityDescription(
key=f"apparent_power_{phase}",
name=f"Apparent Power {phase.upper()}",
native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.APPARENT_POWER,
),
SensorEntityDescription(
key=f"reactive_power_{phase}",
name=f"Reactive Power {phase.upper()}",
native_unit_of_measurement=POWER_VOLT_AMPERE_REACTIVE,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.REACTIVE_POWER,
),
SensorEntityDescription(
key=f"frequency_{phase}",
name=f"Frequency {phase.upper()}",
native_unit_of_measurement=UnitOfFrequency.HERTZ,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.FREQUENCY,
suggested_display_precision=1,
),
SensorEntityDescription(
key=f"power_factor_{phase}",
name=f"Power Factor {phase.upper()}",
native_unit_of_measurement=None,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.POWER_FACTOR,
suggested_display_precision=2,
),
#
# Consumption entities
#
Expand Down Expand Up @@ -215,6 +277,15 @@
),
]
)
ADDITIONAL_METRICS.extend(
[
f"ampere_{phase}",
f"apparent_power_{phase}",
f"reactive_power_{phase}",
f"frequency_{phase}",
f"power_factor_{phase}",
]
)

BINARY_SENSORS = (
BinarySensorEntityDescription(
Expand Down
46 changes: 40 additions & 6 deletions custom_components/enphase_envoy/envoy_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
ENDPOINT_URL_PRODUCTION_POWER = "https://{}/ivp/mod/603980032/mode/power"
ENDPOINT_URL_INFO_XML = "https://{}/info.xml"
ENDPOINT_URL_STREAM = "https://{}/stream/meter"
ENDPOINT_URL_PRODUCTION_REPORT = "https://{}/ivp/meters/reports/production"
ENDPOINT_URL_PDM_ENERGY = "https://{}/ivp/pdm/energy"

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

def __new__(cls, *a, **kw):
def __new__(cls, reader, **kw):
# Add phase CT production value attributes, as this class is
# chosen when one production CT is enabled.
for attr, path in {
"production": ".wNow",
"daily_production": ".whToday",
"lifetime_production": ".whLifetime",
"production": ".currW",
"lifetime_production": ".whDlvdCum",
"voltage": ".rmsVoltage",
"ampere": ".rmsCurrent",
"apparent_power": ".apprntPwr",
"power_factor": ".pwrFactor",
"reactive_power": ".reactPwr",
"frequency": ".freqHz",
}.items():
ct_path = cls._production_ct
setattr(cls, f"{attr}_value", ct_path + path)
ct_path = "endpoint_production_report"
setattr(cls, f"{attr}_value", f"{ct_path}.cumulative{path}")

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

setattr(
cls,
"daily_production_value",
"endpoint_production_json_results.production[?(@.type=='eim')].whToday",
)
for i, phase in enumerate(["l1", "l2", "l3"]):
setattr(
cls,
f"daily_production_{phase}_value"
"endpoint_production_json_results.production[?(@.type=='eim')].lines[{i}].whToday",
)

# When we're using the endpoint_production_report primarily, then the following
# endpoint can be used way less frequently
reader.uri_registry["endpoint_production_json_results"]["cache_time"] = 50
reader.uri_registry["endpoint_production_inverters"]["cache_time"] = 290

return EnvoyMetered.__new__(cls)


Expand Down Expand Up @@ -640,6 +662,7 @@ def url(endpoint, *a, **kw):
iurl("production_power", ENDPOINT_URL_PRODUCTION_POWER, cache=3600)
url("info_results", ENDPOINT_URL_INFO_XML, cache=86400)
url("inventory_results", ENDPOINT_URL_INVENTORY, cache=300)
url("production_report", ENDPOINT_URL_PRODUCTION_REPORT, cache=0)
iurl("pdm_energy", ENDPOINT_URL_PDM_ENERGY)

# If IPv6 address then enclose host in brackets
Expand Down Expand Up @@ -1279,6 +1302,11 @@ async def _async_getattr(self, key):
lifetime_production_phase = _async_getattr
lifetime_consumption_phase = _async_getattr
voltage_phase = _async_getattr
frequency_phase = _async_getattr
ampere_phase = _async_getattr
apparent_power_phase = _async_getattr
power_factor_phase = _async_getattr
reactive_power_phase = _async_getattr

async def set_production_power(self, power_on):
if self.endpoint_production_power is not None:
Expand Down Expand Up @@ -1369,6 +1397,9 @@ def run_in_console(
self.production_phase("production_l1"),
self.production_phase("production_l2"),
self.production_phase("production_l3"),
self.frequency_phase("frequency_l1"),
self.frequency_phase("frequency_l2"),
self.frequency_phase("frequency_l3"),
return_exceptions=False,
)
)
Expand All @@ -1392,6 +1423,9 @@ def run_in_console(
"production_phase(l1)",
"production_phase(l2)",
"production_phase(l3)",
"frequency(l1)",
"frequency(l2)",
"frequency(l3)",
]
pprint.pprint(dict(zip(fields, results)))

Expand Down
11 changes: 11 additions & 0 deletions custom_components/enphase_envoy/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
SENSORS,
PHASE_SENSORS,
LIVE_UPDATEABLE_ENTITIES,
ENABLE_ADDITIONAL_METRICS,
ADDITIONAL_METRICS,
)


Expand All @@ -36,9 +38,14 @@ async def async_setup_entry(
coordinator = data[COORDINATOR]
name = data[NAME]
live_entities = data[LIVE_UPDATEABLE_ENTITIES]
options = config_entry.options

entities = []
for sensor_description in SENSORS:
if not options.get(ENABLE_ADDITIONAL_METRICS, False):
if sensor_description.key in ADDITIONAL_METRICS:
continue

if sensor_description.key == "inverters":
if coordinator.data.get("inverters_production") is not None:
for inverter in coordinator.data["inverters_production"]:
Expand Down Expand Up @@ -158,6 +165,10 @@ async def async_setup_entry(
)

for sensor_description in PHASE_SENSORS:
if not options.get(ENABLE_ADDITIONAL_METRICS, False):
if sensor_description.key in ADDITIONAL_METRICS:
continue

data = coordinator.data.get(sensor_description.key)
if data is None:
continue
Expand Down
1 change: 1 addition & 0 deletions custom_components/enphase_envoy/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"realtime_update_throttle": "Minimum time between realtime entity updates [s]",
"disable_negative_production": "Disable negative production values",
"time_between_update": "Minimum time between entity updates [s]",
"enable_additional_metrics": "[Metered only] Enable additional metrics like total amps, frequency, apparent and reactive power and power factor.",
"disable_installer_account_use": "Do not collect data that requires installer or DIY enphase account"
},
"data_description": {
Expand Down
1 change: 1 addition & 0 deletions custom_components/enphase_envoy/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"realtime_update_throttle": "Minimum time between realtime entity updates [s]",
"disable_negative_production": "Disable negative production values",
"time_between_update": "Minimum time between entity updates [s]",
"enable_additional_metrics": "[Metered only] Enable additional metrics like total amps, frequency, apparent and reactive power and power factor.",
"disable_installer_account_use": "Do not collect data that requires installer or DIY enphase account"
},
"data_description": {
Expand Down
1 change: 1 addition & 0 deletions custom_components/enphase_envoy/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"realtime_update_throttle": "Minimale tijd tussen real-time updates [s]",
"disable_negative_production": "Voorkom negatieve productie waardes",
"time_between_update": "Minimum tijd tussen entity updates [s]",
"enable_additional_metrics": "[Metered only] Extra metrics inschakelen, zoals total amps, frequency, apparent en reactive power en power factor.",
"disable_installer_account_use": "Haal geen data op die een installateur of DHZ enphase account vereisen"
},
"data_description": {
Expand Down

0 comments on commit 4b37fc2

Please sign in to comment.