Skip to content

Commit

Permalink
Merge pull request #165 from catsmanac/Dev
Browse files Browse the repository at this point in the history
Fix for #164, get lifetime net production and consumption from ivp/meters/readings
  • Loading branch information
catsmanac authored Sep 26, 2023
2 parents 429cd21 + 569e75b commit 08fcdf9
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 18 deletions.
8 changes: 4 additions & 4 deletions custom_components/enphase_envoy_custom/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
key="lifetime_net_production",
name="Lifetime Net Energy Production",
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
state_class=SensorStateClass.TOTAL,
state_class=SensorStateClass.TOTAL_INCREASING,
device_class=SensorDeviceClass.ENERGY,
),
SensorEntityDescription(
Expand Down Expand Up @@ -204,7 +204,7 @@
key="lifetime_net_production_l1",
name="Lifetime Net Energy Production L1",
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
state_class=SensorStateClass.TOTAL,
state_class=SensorStateClass.TOTAL_INCREASING,
device_class=SensorDeviceClass.ENERGY,
),
SensorEntityDescription(
Expand All @@ -231,7 +231,7 @@
key="lifetime_net_production_l2",
name="Lifetime Net Energy Production L2",
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
state_class=SensorStateClass.TOTAL,
state_class=SensorStateClass.TOTAL_INCREASING,
device_class=SensorDeviceClass.ENERGY,
),
SensorEntityDescription(
Expand All @@ -258,7 +258,7 @@
key="lifetime_net_production_l3",
name="Lifetime Net Energy Production L3",
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
state_class=SensorStateClass.TOTAL,
state_class=SensorStateClass.TOTAL_INCREASING,
device_class=SensorDeviceClass.ENERGY,
),
SensorEntityDescription(
Expand Down
78 changes: 65 additions & 13 deletions custom_components/enphase_envoy_custom/envoy_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
ENDPOINT_URL_INFO_XML = "http{}://{}/info"
ENDPOINT_URL_METERS = "http{}://{}/ivp/meters"
ENDPOINT_URL_METERS_REPORTS = "http{}://{}/ivp/meters/reports"
ENDPOINT_URL_METERS_READINGS = "http{}://{}/ivp/meters/readings"

# pylint: disable=pointless-string-statement

Expand Down Expand Up @@ -179,6 +180,7 @@ def __init__( # pylint: disable=too-many-arguments
self.has_grid_status = True
self.serial_number_last_six = None
self.endpoint_meters_reports_json_results = None
self.endpoint_meters_readings_json_results = None
self.endpoint_production_json_results = None
self.endpoint_production_v1_results = None
self.endpoint_production_inverters = None
Expand Down Expand Up @@ -268,6 +270,14 @@ async def _update_from_meters_reports_endpoint(self):
"endpoint_meters_reports_json_results", ENDPOINT_URL_METERS_REPORTS
)

async def _update_from_meters_readings_endpoint(self):
"""Update from ivp/meters/readings endpoint."""
if self.endpoint_type == ENVOY_MODEL_S:
#only touch meters reports if confirmed envoy model S, other type choke up on this request
await self._update_endpoint(
"endpoint_meters_readings_json_results", ENDPOINT_URL_METERS_READINGS
)

async def _update_from_pc_endpoint(self,detectmode=False):
"""Update from PC endpoint."""
if not self._do_not_use_production_json or detectmode:
Expand Down Expand Up @@ -352,6 +362,7 @@ async def _update_meters_endpoint(self):
self.info_refresh_buffer_seconds,
)
await self._update_from_meters_reports_endpoint()
await self._update_from_meters_readings_endpoint()

async def _update_endpoint(self, attr, url):
"""Update a property from an endpoint."""
Expand Down Expand Up @@ -726,9 +737,46 @@ def create_json_errormessage(self):
+ "support the requested metric."
)

async def _meters_readings_value(self,field,report="net-consumption",phase=None):
"""Extract value from meters readings json"""
report_map = {"production": 0, "net-consumption": 1, "total-consumption": 1}

phase_map = {"l1": 0, "l2": 1, "l3": 2}
#meters readings is only available for ENVOY Metered with CT configured
if (self.endpoint_type == ENVOY_MODEL_S) and (
#net-consumption requires consumption CT installed is Solar power included mode
(report == "net-consumption"
and self.isConsumptionMeteringEnabled
and self.net_consumption_meters_type)
# production data requires production CT installed
or (report == "production" and self.isProductionMeteringEnabled )
#if at least consumption CT is installed total-consumption will be available even in Load only mode install
or (report == "total-consumption"
and self.isConsumptionMeteringEnabled
and not self.net_consumption_meters_type )
):
if self.endpoint_meters_readings_json_results:
raw_json = self.endpoint_meters_readings_json_results.json()
if phase == None:
try:
jsondata = raw_json[report_map[report]][field]
return jsondata
except (KeyError, IndexError):
return None

#if production data requested and multiple phases are configured and requested phase is in count of configured phases return data or
#if consumption data requested and multiple phases are configured and requested phase is in count of configured phases return date
if ((self.production_meters_phase_count > 1 and phase_map[phase] < self.production_meters_phase_count and report=="production")
or (self.consumption_meters_phase_count > 1 and phase_map[phase] < self.consumption_meters_phase_count and report!="production")):
try:
jsondata = raw_json[report_map[report]]["channels"][phase_map[phase]][field]
return jsondata
except (KeyError, IndexError):
return None
return None

async def _meters_report_value(self,field,report="net-consumption",phase=None):
"""Extract value from meters reports json if net-consumption meter is available"""
async def _meters_report_value(self,field,report="production",phase=None):
"""Extract value from meters reports json if consumption meter is available"""
report_map = {"production": 0, "net-consumption": 1, "total-consumption": 2}
phase_map = {"l1": 0, "l2": 1, "l3": 2}
#meters reports is only available for ENVOY Metered with CT configured
Expand Down Expand Up @@ -804,7 +852,7 @@ async def consumption(self,phase=None):

async def net_consumption(self,phase=None):
"""Report cumulative or phase Power consumption (to/from grid) from consumption CT meters report"""
jsondata = await self._meters_report_value("currW",report="net-consumption",phase=phase)
jsondata = await self._meters_readings_value("instantaneousDemand",report="net-consumption",phase=phase)
if jsondata is None:
return self.message_consumption_not_available if phase is None else None
return int(jsondata)
Expand Down Expand Up @@ -979,7 +1027,7 @@ async def lifetime_production_phase(self, phase):

async def lifetime_net_production(self,phase=None):
"""Report cumulative or phase lifetime net production (exported to grid) from consumption CT meters report"""
jsondata = await self._meters_report_value("whRcvdCum",report="net-consumption",phase=phase)
jsondata = await self._meters_readings_value("actEnergyRcvd",report="net-consumption",phase=phase)
if jsondata is None:
return self.message_consumption_not_available if phase is None else None
return int(jsondata)
Expand All @@ -993,7 +1041,7 @@ async def lifetime_consumption(self,phase=None):

async def lifetime_net_consumption(self,phase=None):
"""Report cumulative or phase lifetime net-consumption from consumption CT meters report"""
jsondata = await self._meters_report_value("whDlvdCum",report="net-consumption",phase=phase)
jsondata = await self._meters_readings_value("actEnergyDlvd",report="net-consumption",phase=phase)
if jsondata is None:
return self.message_consumption_not_available if phase is None else None
return int(jsondata)
Expand Down Expand Up @@ -1118,6 +1166,10 @@ async def envoy_info(self):
device_data["Endpoint-meters"] = self.endpoint_meters_json_results.text
else:
device_data["Endpoint-meters"] = self.endpoint_meters_json_results
if self.endpoint_meters_readings_json_results:
device_data["Endpoint-meters-readings"] = self.endpoint_meters_readings_json_results.text
else:
device_data["Endpoint-meters-readings"] = self.endpoint_meters_readings_json_results
if self.endpoint_meters_reports_json_results:
device_data["Endpoint-meters-reports"] = self.endpoint_meters_reports_json_results.text
else:
Expand Down Expand Up @@ -1197,8 +1249,8 @@ def run_in_console(self, dumpraw=False,loopcount=1,waittime=1):
self.pf(),
self.voltage(),
self.frequency(),
self.current_consumption(),
self.current_production(),
self.consumption_Current(),
self.production_Current(),
#get values for phase L2
self.production_phase("l2"),
self.consumption("l2"),
Expand All @@ -1212,8 +1264,8 @@ def run_in_console(self, dumpraw=False,loopcount=1,waittime=1):
self.pf("l2"),
self.voltage("l2"),
self.frequency("l2"),
self.current_consumption("l2"),
self.current_production("l2"),
self.consumption_Current("l2"),
self.production_Current("l2"),
return_exceptions=False,
)
)
Expand All @@ -1234,8 +1286,8 @@ def run_in_console(self, dumpraw=False,loopcount=1,waittime=1):
print(f"pf: {results[14]}")
print(f"voltage: {results[15]}")
print(f"frequency: {results[16]}")
print(f"current_consumption: {results[17]}")
print(f"current_production: {results[18]}")
print(f"consumption_Current: {results[17]}")
print(f"production_Current: {results[18]}")
print("--Phase L2 values--")
print(f"production: {results[19]}")
print(f"consumption: {results[20]}")
Expand All @@ -1249,8 +1301,8 @@ def run_in_console(self, dumpraw=False,loopcount=1,waittime=1):
print(f"pf: {results[28]}")
print(f"voltage: {results[29]}")
print(f"frequency: {results[30]}")
print(f"current_consumption: {results[31]}")
print(f"current_production: {results[32]}")
print(f"consumption_Current: {results[31]}")
print(f"production_Current: {results[32]}")
if "401" in str(data_results):
print(
"inverters_production: Unable to retrieve inverter data - Authentication failure"
Expand Down
2 changes: 1 addition & 1 deletion custom_components/enphase_envoy_custom/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
"codeowners": ["@briancmpbll"],
"config_flow": true,
"iot_class": "local_polling",
"version": "0.0.18"
"version": "0.0.19"
}

0 comments on commit 08fcdf9

Please sign in to comment.