diff --git a/custom_components/evcc_intg/__init__.py b/custom_components/evcc_intg/__init__.py index 58d38af..c231f06 100644 --- a/custom_components/evcc_intg/__init__.py +++ b/custom_components/evcc_intg/__init__.py @@ -9,7 +9,12 @@ JSONKEY_VEHICLES, JSONKEY_PLANS, JSONKEY_PLANS_SOC, - JSONKEY_PLANS_TIME + JSONKEY_PLANS_TIME, + JSONKEY_STATISTICS, + JSONKEY_STATISTICS_TOTAL, + JSONKEY_STATISTICS_THISYEAR, + JSONKEY_STATISTICS_365D, + JSONKEY_STATISTICS_30D, ) from custom_components.evcc_intg.pyevcc_ha.keys import Tag, EP_TYPE, _camel_to_snake from homeassistant.config_entries import ConfigEntry, ConfigEntryState @@ -205,7 +210,8 @@ async def read_evcc_config_on_startup(self): else: self._currency = "€" - _LOGGER.debug(f"read_evcc_config: LPs: {len(self._loadpoint)} VEHs: {len(self._vehicle)} CT: '{self._cost_type}' CUR: {self._currency}") + _LOGGER.debug( + f"read_evcc_config: LPs: {len(self._loadpoint)} VEHs: {len(self._vehicle)} CT: '{self._cost_type}' CUR: {self._currency}") async def _async_update_data(self) -> dict: """Update data via library.""" @@ -246,10 +252,17 @@ def read_tag(self, tag: Tag, idx: int = None): elif tag.type == EP_TYPE.SITE: if tag.key in self.data: ret = self.data[tag.key] - + elif tag.type == EP_TYPE.STATISTICS: + ret = self.read_tag_statistics(tag=tag) # _LOGGER.debug(f"read from {tag.key} [@idx {idx}] -> {ret}") return ret + def read_tag_statistics(self, tag: Tag): + if JSONKEY_STATISTICS in self.data: + if tag.subtype in self.data[JSONKEY_STATISTICS]: + if tag.key in self.data[JSONKEY_STATISTICS][tag.subtype]: + return self.data[JSONKEY_STATISTICS][tag.subtype][tag.key] + def read_tag_loadpoint(self, tag: Tag, loadpoint_idx: int = None): if loadpoint_idx is not None and len(self.data[JSONKEY_LOADPOINTS]) > loadpoint_idx - 1: # if tag == Tag.CHARGECURRENTS: @@ -277,9 +290,11 @@ def read_tag_vehicle_int(self, tag: Tag, loadpoint_idx: int = None): else: _LOGGER.debug(f"read_tag_vehicle_int: vehicle_id is None for: {loadpoint_idx}") else: - _LOGGER.debug(f"read_tag_vehicle_int: {Tag.VEHICLENAME.key} not in {self.data[JSONKEY_LOADPOINTS][loadpoint_idx - 1]} for: {loadpoint_idx}") + _LOGGER.debug( + f"read_tag_vehicle_int: {Tag.VEHICLENAME.key} not in {self.data[JSONKEY_LOADPOINTS][loadpoint_idx - 1]} for: {loadpoint_idx}") else: - _LOGGER.debug(f"read_tag_vehicle_int: len of 'loadpoints' {len(self.data[JSONKEY_LOADPOINTS])} - requesting: {loadpoint_idx}") + _LOGGER.debug( + f"read_tag_vehicle_int: len of 'loadpoints' {len(self.data[JSONKEY_LOADPOINTS])} - requesting: {loadpoint_idx}") except Exception as err: _LOGGER.info(f"read_tag_vehicle_int: could not find a connected vehicle at loadpoint: {loadpoint_idx}") diff --git a/custom_components/evcc_intg/const.py b/custom_components/evcc_intg/const.py index afdabbb..30e96e6 100644 --- a/custom_components/evcc_intg/const.py +++ b/custom_components/evcc_intg/const.py @@ -491,6 +491,163 @@ class ExtSwitchEntityDescription(SwitchEntityDescription): suggested_display_precision=3 ), + ExtSensorEntityDescription( + tag=Tag.STATTOTALSOLARPERCENTAGE, + key=Tag.STATTOTALSOLARPERCENTAGE.entity_key, + icon="mdi:solar-power", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + device_class=None, + suggested_display_precision=4 + ), + ExtSensorEntityDescription( + tag=Tag.STATTOTALCHARGEDKWH, + key=Tag.STATTOTALCHARGEDKWH.entity_key, + icon="mdi:lightning-bolt-outline", + state_class=SensorStateClass.TOTAL_INCREASING, + native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + suggested_display_precision=4 + ), + ExtSensorEntityDescription( + tag=Tag.STATTOTALAVGPRICE, + key=Tag.STATTOTALAVGPRICE.entity_key, + icon="mdi:cash-multiple", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement="@@@", + device_class=None, + suggested_display_precision=4 + ), + ExtSensorEntityDescription( + tag=Tag.STATTOTALAVGCO2, + key=Tag.STATTOTALAVGCO2.entity_key, + icon="mdi:molecule-co2", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement="CO₂", + device_class=None, + suggested_display_precision=4, + entity_registry_enabled_default=False + ), + ExtSensorEntityDescription( + tag=Tag.STATTHISYEARSOLARPERCENTAGE, + key=Tag.STATTHISYEARSOLARPERCENTAGE.entity_key, + icon="mdi:solar-power", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + device_class=None, + suggested_display_precision=4, + entity_registry_enabled_default=False + ), + ExtSensorEntityDescription( + tag=Tag.STATTHISYEARCHARGEDKWH, + key=Tag.STATTHISYEARCHARGEDKWH.entity_key, + icon="mdi:lightning-bolt-outline", + state_class=SensorStateClass.TOTAL_INCREASING, + native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + suggested_display_precision=4, + entity_registry_enabled_default=False + ), + ExtSensorEntityDescription( + tag=Tag.STATTHISYEARAVGPRICE, + key=Tag.STATTHISYEARAVGPRICE.entity_key, + icon="mdi:cash-multiple", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement="@@@", + device_class=None, + suggested_display_precision=4, + entity_registry_enabled_default=False + ), + ExtSensorEntityDescription( + tag=Tag.STATTHISYEARAVGCO2, + key=Tag.STATTHISYEARAVGCO2.entity_key, + icon="mdi:molecule-co2", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement="CO₂", + device_class=None, + suggested_display_precision=4, + entity_registry_enabled_default=False + ), + ExtSensorEntityDescription( + tag=Tag.STAT365SOLARPERCENTAGE, + key=Tag.STAT365SOLARPERCENTAGE.entity_key, + icon="mdi:solar-power", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + device_class=None, + suggested_display_precision=4, + entity_registry_enabled_default=False + ), + ExtSensorEntityDescription( + tag=Tag.STAT365CHARGEDKWH, + key=Tag.STAT365CHARGEDKWH.entity_key, + icon="mdi:lightning-bolt-outline", + state_class=SensorStateClass.TOTAL_INCREASING, + native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + suggested_display_precision=4, + entity_registry_enabled_default=False + ), + ExtSensorEntityDescription( + tag=Tag.STAT365AVGPRICE, + key=Tag.STAT365AVGPRICE.entity_key, + icon="mdi:cash-multiple", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement="@@@", + device_class=None, + suggested_display_precision=4, + entity_registry_enabled_default=False + ), + ExtSensorEntityDescription( + tag=Tag.STAT365AVGCO2, + key=Tag.STAT365AVGCO2.entity_key, + icon="mdi:molecule-co2", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement="CO₂", + device_class=None, + suggested_display_precision=4, + entity_registry_enabled_default=False + ), + ExtSensorEntityDescription( + tag=Tag.STAT30SOLARPERCENTAGE, + key=Tag.STAT30SOLARPERCENTAGE.entity_key, + icon="mdi:solar-power", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + device_class=None, + suggested_display_precision=4, + entity_registry_enabled_default=False + ), + ExtSensorEntityDescription( + tag=Tag.STAT30CHARGEDKWH, + key=Tag.STAT30CHARGEDKWH.entity_key, + icon="mdi:lightning-bolt-outline", + state_class=SensorStateClass.TOTAL_INCREASING, + native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + suggested_display_precision=4, + entity_registry_enabled_default=False + ), + ExtSensorEntityDescription( + tag=Tag.STAT30AVGPRICE, + key=Tag.STAT30AVGPRICE.entity_key, + icon="mdi:cash-multiple", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement="@@@", + device_class=None, + suggested_display_precision=4, + entity_registry_enabled_default=False + ), + ExtSensorEntityDescription( + tag=Tag.STAT30AVGCO2, + key=Tag.STAT30AVGCO2.entity_key, + icon="mdi:molecule-co2", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement="CO₂", + device_class=None, + suggested_display_precision=4, + entity_registry_enabled_default=False + ) ] SENSOR_SENSORS_PER_LOADPOINT = [ diff --git a/custom_components/evcc_intg/manifest.json b/custom_components/evcc_intg/manifest.json index e77dba6..7e0a168 100644 --- a/custom_components/evcc_intg/manifest.json +++ b/custom_components/evcc_intg/manifest.json @@ -10,5 +10,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/marq24/ha-evcc/issues", "requirements": [], - "version": "2024.9.2" + "version": "2024.10.0" } diff --git a/custom_components/evcc_intg/pyevcc_ha/__init__.py b/custom_components/evcc_intg/pyevcc_ha/__init__.py index 272f1d9..4cc0210 100644 --- a/custom_components/evcc_intg/pyevcc_ha/__init__.py +++ b/custom_components/evcc_intg/pyevcc_ha/__init__.py @@ -7,7 +7,10 @@ from custom_components.evcc_intg.pyevcc_ha.const import ( TRANSLATIONS, - JSONKEY_LOADPOINTS, STATE_QUERY, JSONKEY_VEHICLES, STATES, + JSONKEY_LOADPOINTS, + STATE_QUERY, + JSONKEY_VEHICLES, + STATES, ) from custom_components.evcc_intg.pyevcc_ha.keys import EP_TYPE, Tag, IS_TRIGGER diff --git a/custom_components/evcc_intg/pyevcc_ha/const.py b/custom_components/evcc_intg/pyevcc_ha/const.py index 74531c7..3ee59d8 100644 --- a/custom_components/evcc_intg/pyevcc_ha/const.py +++ b/custom_components/evcc_intg/pyevcc_ha/const.py @@ -14,14 +14,19 @@ JSONKEY_GRIDPOWER: Final = "gridPower" JSONKEY_HOMEPOWER: Final = "homePower" JSONKEY_PVPOWER: Final = "pvPower" +JSONKEY_STATISTICS: Final = "statistics" +JSONKEY_STATISTICS_TOTAL: Final = "total" +JSONKEY_STATISTICS_THISYEAR: Final = "thisYear" +JSONKEY_STATISTICS_365D: Final = "365d" +JSONKEY_STATISTICS_30D: Final = "30d" STATES: Final = [JSONKEY_LOADPOINTS, JSONKEY_AUXPOWER, JSONKEY_BATTERYMODE, JSONKEY_BATTERYPOWER, JSONKEY_BATTERYSOC, - JSONKEY_GRIDCURRENTS, JSONKEY_GRIDPOWER, JSONKEY_HOMEPOWER, JSONKEY_PVPOWER, JSONKEY_VEHICLES] + JSONKEY_GRIDCURRENTS, JSONKEY_GRIDPOWER, JSONKEY_HOMEPOWER, JSONKEY_PVPOWER, JSONKEY_VEHICLES, JSONKEY_STATISTICS] # FILTER_LOADPOINTS: Final = f"?jq=.{JSONKEY_LOADPOINTS}" STATE_QUERY = ( - f"?jq={{{JSONKEY_LOADPOINTS}:.{JSONKEY_LOADPOINTS},{JSONKEY_AUXPOWER}:.{JSONKEY_AUXPOWER},{JSONKEY_BATTERYMODE}:.{JSONKEY_BATTERYMODE},{JSONKEY_BATTERYPOWER}:.{JSONKEY_BATTERYPOWER},{JSONKEY_BATTERYSOC}:.{JSONKEY_BATTERYSOC},{JSONKEY_GRIDCURRENTS}:.{JSONKEY_GRIDCURRENTS},{JSONKEY_GRIDPOWER}:.{JSONKEY_GRIDPOWER},{JSONKEY_HOMEPOWER}:.{JSONKEY_HOMEPOWER},{JSONKEY_HOMEPOWER}:.{JSONKEY_HOMEPOWER},{JSONKEY_PVPOWER}:.{JSONKEY_PVPOWER},{JSONKEY_VEHICLES}:.{JSONKEY_VEHICLES}}}" + f"?jq={{{JSONKEY_LOADPOINTS}:.{JSONKEY_LOADPOINTS},{JSONKEY_AUXPOWER}:.{JSONKEY_AUXPOWER},{JSONKEY_BATTERYMODE}:.{JSONKEY_BATTERYMODE},{JSONKEY_BATTERYPOWER}:.{JSONKEY_BATTERYPOWER},{JSONKEY_BATTERYSOC}:.{JSONKEY_BATTERYSOC},{JSONKEY_GRIDCURRENTS}:.{JSONKEY_GRIDCURRENTS},{JSONKEY_GRIDPOWER}:.{JSONKEY_GRIDPOWER},{JSONKEY_HOMEPOWER}:.{JSONKEY_HOMEPOWER},{JSONKEY_HOMEPOWER}:.{JSONKEY_HOMEPOWER},{JSONKEY_PVPOWER}:.{JSONKEY_PVPOWER},{JSONKEY_VEHICLES}:.{JSONKEY_VEHICLES},{JSONKEY_STATISTICS}:.{JSONKEY_STATISTICS}}}" ) MIN_CURRENT_LIST: Final = ["0.125", "0.25", "0.5", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", diff --git a/custom_components/evcc_intg/pyevcc_ha/keys.py b/custom_components/evcc_intg/pyevcc_ha/keys.py index 9241d57..702c52d 100644 --- a/custom_components/evcc_intg/pyevcc_ha/keys.py +++ b/custom_components/evcc_intg/pyevcc_ha/keys.py @@ -9,7 +9,13 @@ MIN_CURRENT_LIST, MAX_CURRENT_LIST, JSONKEY_LOADPOINTS, - JSONKEY_VEHICLES, BATTERY_LIST + JSONKEY_VEHICLES, + JSONKEY_STATISTICS, + JSONKEY_STATISTICS_TOTAL, + JSONKEY_STATISTICS_THISYEAR, + JSONKEY_STATISTICS_365D, + JSONKEY_STATISTICS_30D, + BATTERY_LIST ) # from aenum import Enum, extend_enum @@ -31,11 +37,14 @@ def _camel_to_snake(a_key: str): class EP_TYPE(Enum): LOADPOINTS = JSONKEY_LOADPOINTS VEHICLES = JSONKEY_VEHICLES + STATISTICS = JSONKEY_STATISTICS SITE = "site" class ApiKey(NamedTuple): key: str type: str + subtype: str = None + entity_key: str = None write_key: str = None write_type: str = None options: list[str] = None @@ -307,4 +316,28 @@ def __str__(self): VEHICLEPLANSSOC = ApiKey(key="vehiclePlansSoc", type=EP_TYPE.VEHICLES) VEHICLEPLANSTIME = ApiKey(key="vehiclePlansTime", type=EP_TYPE.VEHICLES) # delete plan button - VEHICLEPLANSDELETE= ApiKey(key="vehiclePlansDelete", type=EP_TYPE.VEHICLES, writeable=True, write_key="plan/soc") \ No newline at end of file + VEHICLEPLANSDELETE= ApiKey(key="vehiclePlansDelete", type=EP_TYPE.VEHICLES, writeable=True, write_key="plan/soc") + + ################################### + # STATISTICS + ################################### + + STATTOTALAVGCO2 = ApiKey(entity_key="statTotalAvgCo2", key="avgCo2", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_TOTAL) + STATTOTALAVGPRICE = ApiKey(entity_key="statTotalAvgPrice", key="avgPrice", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_TOTAL) + STATTOTALCHARGEDKWH = ApiKey(entity_key="statTotalChargedKWh", key="chargedKWh", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_TOTAL) + STATTOTALSOLARPERCENTAGE = ApiKey(entity_key="statTotalSolarPercentage", key="solarPercentage", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_TOTAL) + + STATTHISYEARAVGCO2 = ApiKey(entity_key="statThisYearAvgCo2", key="avgCo2", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_THISYEAR) + STATTHISYEARAVGPRICE = ApiKey(entity_key="statThisYearAvgPrice", key="avgPrice", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_THISYEAR) + STATTHISYEARCHARGEDKWH = ApiKey(entity_key="statThisYearChargedKWh", key="chargedKWh", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_THISYEAR) + STATTHISYEARSOLARPERCENTAGE = ApiKey(entity_key="statThisYearSolarPercentage", key="solarPercentage", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_THISYEAR) + + STAT365AVGCO2 = ApiKey(entity_key="stat365AvgCo2", key="avgCo2", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_365D) + STAT365AVGPRICE = ApiKey(entity_key="stat365AvgPrice", key="avgPrice", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_365D) + STAT365CHARGEDKWH = ApiKey(entity_key="stat365ChargedKWh", key="chargedKWh", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_365D) + STAT365SOLARPERCENTAGE = ApiKey(entity_key="stat365SolarPercentage", key="solarPercentage", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_365D) + + STAT30AVGCO2 = ApiKey(entity_key="stat30AvgCo2", key="avgCo2", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_30D) + STAT30AVGPRICE = ApiKey(entity_key="stat30AvgPrice", key="avgPrice", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_30D) + STAT30CHARGEDKWH = ApiKey(entity_key="stat30ChargedKWh", key="chargedKWh", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_30D) + STAT30SOLARPERCENTAGE = ApiKey(entity_key="stat30SolarPercentage", key="solarPercentage", type=EP_TYPE.STATISTICS, subtype=JSONKEY_STATISTICS_30D) \ No newline at end of file diff --git a/custom_components/evcc_intg/translations/de.json b/custom_components/evcc_intg/translations/de.json index 020bedb..8a1b991 100644 --- a/custom_components/evcc_intg/translations/de.json +++ b/custom_components/evcc_intg/translations/de.json @@ -242,7 +242,24 @@ "pvpower": {"name": "Leistung PV"}, "tariffgrid": {"name": "Kosten Netz"}, - "tariffpricehome": {"name": "Kosten Verbrauch"} + "tariffpricehome": {"name": "Kosten Verbrauch"}, + + "stattotalsolarpercentage": {"name": "Statistik: gesamt Sonnenenergie"}, + "stattotalchargedkwh": {"name": "Statistik: gesamt Ladeenergie"}, + "stattotalavgprice": {"name": "Statistik: gesamt Ø Preis"}, + "stattotalavgco2": {"name": "Statistik: gesamt Ø CO₂"}, + "statthisyearsolarpercentage": {"name": "Statistik: dieses Jahr Sonnenenergie"}, + "statthisyearchargedkwh": {"name": "Statistik: dieses Jahr Ladeenergie"}, + "statthisyearavgprice": {"name": "Statistik: dieses Jahr Ø Preis"}, + "statthisyearavgco2": {"name": "Statistik: dieses Jahr Ø CO₂"}, + "stat365solarpercentage": {"name": "Statistik: letzten 356 Tage Sonnenenergie"}, + "stat365chargedkwh": {"name": "Statistik: letzten 356 Tage Ladeenergie"}, + "stat365avgprice": {"name": "Statistik: letzten 356 Tage Ø Preis"}, + "stat365avgco2": {"name": "Statistik: letzten 356 Tage Ø CO₂"}, + "stat30solarpercentage": {"name": "Statistik: letzten 30 Tage Sonnenenergie"}, + "stat30chargedkwh": {"name": "Statistik: letzten 30 Tage Ladeenergie"}, + "stat30avgprice": {"name": "Statistik: letzten 30 Tage Ø Preis"}, + "stat30avgco2": {"name": "Statistik: letzten 30 Tage Ø CO₂"} }, "switch": { "batterydischargecontrol": {"name": "Hausbatterie: Entladesperre"} diff --git a/custom_components/evcc_intg/translations/en.json b/custom_components/evcc_intg/translations/en.json index ddd08c2..96c50bf 100644 --- a/custom_components/evcc_intg/translations/en.json +++ b/custom_components/evcc_intg/translations/en.json @@ -312,7 +312,24 @@ "pvpower": {"name": "Power Solar"}, "tariffgrid": {"name": "Costs Grid"}, - "tariffpricehome": {"name": "Costs Usage"} + "tariffpricehome": {"name": "Costs Usage"}, + + "stattotalsolarpercentage": {"name": "Statistics: Total Solar"}, + "stattotalchargedkwh": {"name": "Statistics: Total Charge energy"}, + "stattotalavgprice": {"name": "Statistics: Total Ø Price"}, + "stattotalavgco2": {"name": "Statistics: Total Ø CO₂"}, + "statthisyearsolarpercentage": {"name": "Statistics: This year Solar"}, + "statthisyearchargedkwh": {"name": "Statistics: This year Charge energy"}, + "statthisyearavgprice": {"name": "Statistics: This year Ø Price"}, + "statthisyearavgco2": {"name": "Statistics: This year Ø CO₂"}, + "stat365solarpercentage": {"name": "Statistics: Last 356 days Solar"}, + "stat365chargedkwh": {"name": "Statistics: Last 356 days Charge energy"}, + "stat365avgprice": {"name": "Statistics: Last 356 days Ø Price"}, + "stat365avgco2": {"name": "Statistics: Last 356 days Ø CO₂"}, + "stat30solarpercentage": {"name": "Statistics: Last 30 days Solar"}, + "stat30chargedkwh": {"name": "Statistics: Last 30 days Charge energy"}, + "stat30avgprice": {"name": "Statistics: Last 30 days Ø Price"}, + "stat30avgco2": {"name": "Statistics: Last 30 days Ø CO₂"} }, "switch": { "batterydischargecontrol": {"name": "Home-Battery: discharge lock"}