diff --git a/custom_components/enphase_envoy/const.py b/custom_components/enphase_envoy/const.py index 64f87e8..37a0a63 100644 --- a/custom_components/enphase_envoy/const.py +++ b/custom_components/enphase_envoy/const.py @@ -173,9 +173,16 @@ def get_model_name(model, hardware_id): state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.TEMPERATURE, ), + SensorEntityDescription( + key="batteries_power", + name="Power", + native_unit_of_measurement=UnitOfPower.WATT, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.POWER, + ), SensorEntityDescription( key="batteries_percentFull", - name="Percentage Full", + name="Charged", native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.BATTERY, @@ -193,35 +200,44 @@ def get_model_name(model, hardware_id): native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.ENERGY, + entity_category=EntityCategory.DIAGNOSTIC, ), SensorEntityDescription( - key="batteries_encharge_capacity_current", - name="Current Capacity", + key="batteries_encharge_available_energy", + name="Available Energy", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.ENERGY, ), SensorEntityDescription( - key="avg_batteries_percentFull", - name="Batteries Percentage Full", + key="agg_batteries_soc", + name="Batteries Charged", native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.BATTERY, ), SensorEntityDescription( - key="total_batteries_encharge_capacity", + key="agg_batteries_capacity", name="Batteries Capacity", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.ENERGY, + entity_category=EntityCategory.DIAGNOSTIC, ), SensorEntityDescription( - key="total_batteries_encharge_capacity_current", - name="Batteries Current Capacity", + key="agg_batteries_available_energy", + name="Batteries Available Energy", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.ENERGY, ), + SensorEntityDescription( + key="agg_batteries_power", + name="Batteries Power", + native_unit_of_measurement=UnitOfPower.WATT, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.POWER, + ), SensorEntityDescription( key="voltage", name="Current Voltage", @@ -420,6 +436,12 @@ def get_model_name(model, hardware_id): device_class=BinarySensorDeviceClass.CONNECTIVITY, entity_category=EntityCategory.DIAGNOSTIC, ), + BinarySensorEntityDescription( + key="batteries_dc_switch_off", + name="DC Switch", + device_class=BinarySensorDeviceClass.PROBLEM, + entity_category=EntityCategory.DIAGNOSTIC, + ), ) PRODUCION_POWER_SWITCH = SwitchEntityDescription( diff --git a/custom_components/enphase_envoy/envoy_reader.py b/custom_components/enphase_envoy/envoy_reader.py index 8cb6f6a..9ff564c 100644 --- a/custom_components/enphase_envoy/envoy_reader.py +++ b/custom_components/enphase_envoy/envoy_reader.py @@ -39,6 +39,8 @@ # Battery endpoints ENDPOINT_URL_ENSEMBLE_INVENTORY = "https://{}/ivp/ensemble/inventory" +ENDPOINT_URL_ENSEMBLE_SECCTRL = "https://{}/ivp/ensemble/secctrl" +ENDPOINT_URL_ENSEMBLE_POWER = "https://{}/ivp/ensemble/power" # Inverter endpoints ENDPOINT_URL_INVENTORY = "https://{}/inventory.json" @@ -509,14 +511,27 @@ def batteries(self): "%Y-%m-%d %H:%M:%S", time.localtime(item["last_rpt_date"]) ) if "encharge_capacity" in item and "percentFull" in item: - item["encharge_capacity_current"] = item["encharge_capacity"] * ( + item["encharge_available_energy"] = item["encharge_capacity"] * ( item["percentFull"] / 100 ) - battery_dict[item["serial_num"]] = item return battery_dict + @envoy_property(required_endpoint="endpoint_ensemble_power") + def batteries_power(self): + return self._path_to_dict("endpoint_ensemble_power.devices:", "serial_num") + + @envoy_property(required_endpoint="endpoint_ensemble_power") + def agg_batteries_power(self): + batteries_data = self._resolve_path("endpoint_ensemble_power.devices:") + if batteries_data: + return int(sum(batt['real_power_mw'] for batt in batteries_data) / 1000) + + agg_batteries_capacity_value = "endpoint_ensemble_secctrl.Enc_max_available_capacity" + agg_batteries_soc_value = "endpoint_ensemble_secctrl.ENC_agg_soc" + agg_batteries_available_energy_value = "endpoint_ensemble_secctrl.ENC_agg_avail_energy" + class EnvoyMetered(EnvoyStandard): """ @@ -667,7 +682,9 @@ def url(endpoint, *a, **kw): url("production_json", ENDPOINT_URL_PRODUCTION_JSON, cache=0) url("production_v1", ENDPOINT_URL_PRODUCTION_V1, cache=20) url("production_inverters", ENDPOINT_URL_PRODUCTION_INVERTERS, cache=20) - url("ensemble_inventory", ENDPOINT_URL_ENSEMBLE_INVENTORY) + url("ensemble_inventory", ENDPOINT_URL_ENSEMBLE_INVENTORY, cache=20) + url("ensemble_secctrl", ENDPOINT_URL_ENSEMBLE_SECCTRL, cache=20) + url("ensemble_power", ENDPOINT_URL_ENSEMBLE_POWER, cache=20) # cache for home_json will be set based on grid_status availability url("home_json", ENDPOINT_URL_HOME_JSON) iurl("devstatus", ENDPOINT_URL_DEVSTATUS, cache=20) diff --git a/custom_components/enphase_envoy/sensor.py b/custom_components/enphase_envoy/sensor.py index f34b93b..2544c56 100644 --- a/custom_components/enphase_envoy/sensor.py +++ b/custom_components/enphase_envoy/sensor.py @@ -96,26 +96,11 @@ async def async_setup_entry( ) ) - elif sensor_description.key.startswith("total_batteries_"): + elif sensor_description.key.startswith("agg_batteries_"): if coordinator.data.get("batteries") is not None: entity_name = f"{name} {sensor_description.name}" entities.append( - TotalBatteriesEntity( - sensor_description, - entity_name, - name, - config_entry.unique_id, - None, - coordinator, - config_entry.data[CONF_HOST], - ) - ) - - elif sensor_description.key.startswith("avg_batteries_"): - if coordinator.data.get("batteries") is not None: - entity_name = f"{name} {sensor_description.name}" - entities.append( - AvgBatteriesEntity( + CoordinatedEnvoyEntity( sensor_description, entity_name, name, @@ -395,11 +380,19 @@ def unique_id(self): def native_value(self): """Return the state of the sensor.""" if self.coordinator.data.get("batteries") is not None: - return ( - self.coordinator.data.get("batteries") - .get(self._device_serial_number) - .get(self.entity_description.key[10:]) - ) + if self.entity_description.key == "batteries_power": + return int( + self.coordinator.data.get("batteries_power") + .get(self._device_serial_number) + .get("real_power_mw") + / 1000 + ) + else: + return ( + self.coordinator.data.get("batteries") + .get(self._device_serial_number) + .get(self.entity_description.key[10:]) + ) return None @@ -445,39 +438,3 @@ def device_info(self) -> DeviceInfo | None: sw_version=sw_version, hw_version=resolve_hardware_id(hw_version), ) - - -class AvgBatteriesEntity(CoordinatedEnvoyEntity): - @property - def native_value(self): - """Return the state of the sensor.""" - if self.coordinator.data.get("batteries") is not None: - avg_value = 0 - for battery in self.coordinator.data.get("batteries").keys(): - avg_value += ( - self.coordinator.data.get("batteries") - .get(battery) - .get(self.entity_description.key[14:]) - ) - - return avg_value / len(self.coordinator.data.get("batteries")) - - return None - - -class TotalBatteriesEntity(CoordinatedEnvoyEntity): - @property - def native_value(self): - """Return the state of the sensor.""" - if self.coordinator.data.get("batteries") is not None: - total_value = 0 - for battery in self.coordinator.data.get("batteries").keys(): - total_value += ( - self.coordinator.data.get("batteries") - .get(battery) - .get(self.entity_description.key[16:]) - ) - - return total_value - - return None diff --git a/test_data/envoy_metered/endpoint_ensemble_power.json b/test_data/envoy_metered/endpoint_ensemble_power.json new file mode 100644 index 0000000..73cc8dd --- /dev/null +++ b/test_data/envoy_metered/endpoint_ensemble_power.json @@ -0,0 +1,22 @@ +{ + "devices:": [ + { + "serial_num": "122323085065", + "real_power_mw": 179000, + "apparent_power_mva": 179000, + "soc": 89 + }, + { + "serial_num": "122323085067", + "real_power_mw": 190000, + "apparent_power_mva": 190000, + "soc": 89 + }, + { + "serial_num": "122323085069", + "real_power_mw": 183000, + "apparent_power_mva": 183000, + "soc": 89 + } + ] +} \ No newline at end of file diff --git a/test_data/envoy_metered/endpoint_ensemble_secctrl.json b/test_data/envoy_metered/endpoint_ensemble_secctrl.json new file mode 100644 index 0000000..90bb79d --- /dev/null +++ b/test_data/envoy_metered/endpoint_ensemble_secctrl.json @@ -0,0 +1,27 @@ +{ + "shutdown": false, + "freq_bias_hz": 0.19460000097751618, + "voltage_bias_v": 2.365999937057495, + "freq_bias_hz_q8": 313, + "voltage_bias_v_q5": 75, + "freq_bias_hz_phaseb": 0.0, + "voltage_bias_v_phaseb": 0.0, + "freq_bias_hz_q8_phaseb": 0, + "voltage_bias_v_q5_phaseb": 0, + "freq_bias_hz_phasec": 0.0, + "voltage_bias_v_phasec": 0.0, + "freq_bias_hz_q8_phasec": 0, + "voltage_bias_v_q5_phasec": 0, + "configured_backup_soc": 0, + "adjusted_backup_soc": 0, + "agg_soc": 89, + "Max_energy": 10500, + "ENC_agg_soc": 89, + "ENC_agg_soh": 100, + "ENC_agg_backup_energy": 0, + "ENC_agg_avail_energy": 9345, + "Enc_commissioned_capacity": 10500, + "Enc_max_available_capacity": 10500, + "ACB_agg_soc": 0, + "ACB_agg_energy": 0 +} \ No newline at end of file