From 31fd6e173853e81098fd14ca62accd04b683bc77 Mon Sep 17 00:00:00 2001 From: Bastian Stahmer Date: Sun, 17 Sep 2023 14:37:01 +0000 Subject: [PATCH 1/6] added additional powermeters --- custom_components/e3dc_rscp/config_flow.py | 1 + custom_components/e3dc_rscp/coordinator.py | 64 +++++++++++++++++++++- custom_components/e3dc_rscp/sensor.py | 16 ++++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/custom_components/e3dc_rscp/config_flow.py b/custom_components/e3dc_rscp/config_flow.py index 1aa8f53..b56b752 100644 --- a/custom_components/e3dc_rscp/config_flow.py +++ b/custom_components/e3dc_rscp/config_flow.py @@ -76,6 +76,7 @@ def _async_check_login(self) -> None: password=self._password, host=self._host, rscpkey=self._rscpkey, + config={}, ) async def validate_input(self) -> str | None: diff --git a/custom_components/e3dc_rscp/coordinator.py b/custom_components/e3dc_rscp/coordinator.py index 759acae..cf858d5 100644 --- a/custom_components/e3dc_rscp/coordinator.py +++ b/custom_components/e3dc_rscp/coordinator.py @@ -42,6 +42,7 @@ def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None: self.username: str | None = config_entry.data.get(CONF_USERNAME) self.password: str | None = config_entry.data.get(CONF_PASSWORD) self.rscpkey: str | None = config_entry.data.get(CONF_RSCPKEY) + self.e3dcconfig = {} assert isinstance(config_entry.unique_id, str) self.uid: str = config_entry.unique_id @@ -58,6 +59,22 @@ async def async_connect(self): self.password, self.host, self.rscpkey, + self.e3dcconfig, + ) + except Exception as ex: + raise ConfigEntryAuthFailed from ex + + # get the additional powermeters and re-create the e3dc object with the proper configuration. + self.e3dcconfig["powermeters"] = self.e3dc.get_powermeters() + delete_e3dcinstance(self.e3dc) + try: + self.e3dc: E3DC = await self.hass.async_add_executor_job( + create_e3dcinstance, + self.username, + self.password, + self.host, + self.rscpkey, + self.e3dcconfig, ) except Exception as ex: raise ConfigEntryAuthFailed from ex @@ -125,6 +142,12 @@ async def _async_update_data(self) -> dict[str, Any]: ) self._process_manual_charge(request_data) + _LOGGER.debug("Polling additional powermeters") + powermeters_data: dict[ + str, Any | None + ] = await self.hass.async_add_executor_job(self.e3dc.get_powermeters_data, True) + self._process_powermeters_data(powermeters_data) + # Only poll power statstics once per minute. E3DC updates it only once per 15 # minutes anyway, this should be a good compromise to get the metrics shortly # before the end of the day. @@ -205,6 +228,31 @@ def _process_manual_charge(self, request_data) -> None: # request_data, "EMS_MANUAL_CHARGE_LASTSTART" # )[2] + def _process_powermeters_data(self, powermeters_data) -> None: + for powermeter_data in powermeters_data: + if powermeter_data["index"] != 0: + self._mydata["powermeters"][powermeter_data["index"]]["name"] = ( + powermeter_data["typeName"].replace("TYPE", "").lower() + + "_" + + powermeter_data["index"] + ) + self._mydata["powermeters"][powermeter_data["index"]][ + "type" + ] = powermeter_data["type"] + self._mydata["powermeters"][powermeter_data["index"]][ + "typeName" + ] = powermeter_data["typeName"] + self._mydata["powermeters"][powermeter_data["index"]]["power"] = ( + powermeter_data["power"]["L1"] + + powermeter_data["power"]["L2"] + + powermeter_data["power"]["L3"] + ) + self._mydata["powermeters"][powermeter_data["index"]]["energy"] = ( + powermeter_data["energy"]["L1"] + + powermeter_data["energy"]["L2"] + + powermeter_data["energy"]["L3"] + ) + async def _load_timezone_settings(self): """Load the current timezone offset from the E3DC, using its local timezone data. @@ -448,7 +496,9 @@ async def async_manual_charge(self, charge_amount: int) -> None: _LOGGER.debug("Successfully started manual charging") -def create_e3dcinstance(username: str, password: str, host: str, rscpkey: str) -> E3DC: +def create_e3dcinstance( + username: str, password: str, host: str, rscpkey: str, config: dict +) -> E3DC: """Create the actual E3DC instance, this will try to connect and authenticate.""" e3dc = E3DC( E3DC.CONNECT_LOCAL, @@ -456,5 +506,17 @@ def create_e3dcinstance(username: str, password: str, host: str, rscpkey: str) - password=password, ipAddress=host, key=rscpkey, + configuration=config, ) return e3dc + + +def delete_e3dcinstance(e3dc: E3DC) -> None: + """Delete the actual E3DC instance.""" + del e3dc + return + + +def get_powermeters_data(self) -> dict[str, Any]: + """Return the powermeter data of the E3DC instance.""" + return self._mydata["powermeters"] diff --git a/custom_components/e3dc_rscp/sensor.py b/custom_components/e3dc_rscp/sensor.py index e2bde50..e7eb5ed 100644 --- a/custom_components/e3dc_rscp/sensor.py +++ b/custom_components/e3dc_rscp/sensor.py @@ -385,6 +385,22 @@ async def async_setup_entry( E3DCSensor(coordinator, description, entry.unique_id) for description in SENSOR_DESCRIPTIONS ] + + # Add the additional identified powermeters + for powermeter in coordinator.get_powermeters_data(): + description = SensorEntityDescription( + key=powermeter["name"], + translation_key=powermeter["name"], + icon="mdi:meter-electric", + native_unit_of_measurement=UnitOfPower.WATT, + suggested_unit_of_measurement=UnitOfPower.KILO_WATT, + suggested_display_precision=2, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + entity_registry_enabled_default=True, + ) + entities.append(E3DCSensor(coordinator, description, entry.unique_id)) + async_add_entities(entities) From 7c881c7d7cb0dca533bc558773d601f3e195181a Mon Sep 17 00:00:00 2001 From: Bastian Stahmer Date: Sun, 17 Sep 2023 18:20:20 +0000 Subject: [PATCH 2/6] added dynamic sensor additions --- custom_components/e3dc_rscp/coordinator.py | 69 ++++++++++--------- custom_components/e3dc_rscp/sensor.py | 28 ++++++-- custom_components/e3dc_rscp/strings.json | 48 +++++++++++++ .../e3dc_rscp/translations/en.json | 52 +++++++++++++- 4 files changed, 156 insertions(+), 41 deletions(-) diff --git a/custom_components/e3dc_rscp/coordinator.py b/custom_components/e3dc_rscp/coordinator.py index cf858d5..05c5ca3 100644 --- a/custom_components/e3dc_rscp/coordinator.py +++ b/custom_components/e3dc_rscp/coordinator.py @@ -6,6 +6,7 @@ from typing import Any import pytz + from e3dc import E3DC # Missing Exports:; SendError, from e3dc._rscpLib import rscpFindTag @@ -30,6 +31,7 @@ class E3DCCoordinator(DataUpdateCoordinator[dict[str, Any]]): """E3DC Coordinator, fetches all relevant data and provides proxies for all service calls.""" e3dc: E3DC = None + _e3dcconfig: dict[str, Any] = {} _mydata: dict[str, Any] = {} _sw_version: str = "" _update_guard_powersettings: bool = False @@ -42,7 +44,6 @@ def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None: self.username: str | None = config_entry.data.get(CONF_USERNAME) self.password: str | None = config_entry.data.get(CONF_PASSWORD) self.rscpkey: str | None = config_entry.data.get(CONF_RSCPKEY) - self.e3dcconfig = {} assert isinstance(config_entry.unique_id, str) self.uid: str = config_entry.unique_id @@ -50,6 +51,10 @@ def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None: hass, _LOGGER, name=DOMAIN, update_interval=timedelta(seconds=10) ) + def get_e3dcconfig(self) -> dict: + """Return the E3DC config dict.""" + return self._e3dcconfig + async def async_connect(self): """Establish connection to E3DC.""" try: @@ -59,14 +64,26 @@ async def async_connect(self): self.password, self.host, self.rscpkey, - self.e3dcconfig, + self._e3dcconfig, ) except Exception as ex: raise ConfigEntryAuthFailed from ex - # get the additional powermeters and re-create the e3dc object with the proper configuration. - self.e3dcconfig["powermeters"] = self.e3dc.get_powermeters() + _LOGGER.debug("async_connect setting up additional powermeters") + # get the additional powermeters and re-create + # the e3dc object with the proper configuration. + self._e3dcconfig["powermeters"] = self.e3dc.get_powermeters() + for powermeter in self._e3dcconfig["powermeters"]: + powermeter["name"] = ( + powermeter["typeName"].replace("_TYPE").lower() + + "_" + + str(powermeter["index"]) + ) + powermeter["key"] = "additional_powermeter_" + str(powermeter["index"]) + _LOGGER.debug(self._e3dcconfig) + delete_e3dcinstance(self.e3dc) + try: self.e3dc: E3DC = await self.hass.async_add_executor_job( create_e3dcinstance, @@ -74,7 +91,7 @@ async def async_connect(self): self.password, self.host, self.rscpkey, - self.e3dcconfig, + self._e3dcconfig, ) except Exception as ex: raise ConfigEntryAuthFailed from ex @@ -145,7 +162,7 @@ async def _async_update_data(self) -> dict[str, Any]: _LOGGER.debug("Polling additional powermeters") powermeters_data: dict[ str, Any | None - ] = await self.hass.async_add_executor_job(self.e3dc.get_powermeters_data, True) + ] = await self.hass.async_add_executor_job(self.e3dc.get_powermeters_data) self._process_powermeters_data(powermeters_data) # Only poll power statstics once per minute. E3DC updates it only once per 15 @@ -166,6 +183,8 @@ async def _async_update_data(self) -> dict[str, Any]: else: _LOGGER.debug("Skipping power metrics poll.") + for item in self._mydata.items(): + _LOGGER.debug(item) return self._mydata def _process_power_settings(self, power_settings: dict[str, Any | None]): @@ -231,27 +250,18 @@ def _process_manual_charge(self, request_data) -> None: def _process_powermeters_data(self, powermeters_data) -> None: for powermeter_data in powermeters_data: if powermeter_data["index"] != 0: - self._mydata["powermeters"][powermeter_data["index"]]["name"] = ( - powermeter_data["typeName"].replace("TYPE", "").lower() - + "_" - + powermeter_data["index"] - ) - self._mydata["powermeters"][powermeter_data["index"]][ - "type" - ] = powermeter_data["type"] - self._mydata["powermeters"][powermeter_data["index"]][ - "typeName" - ] = powermeter_data["typeName"] - self._mydata["powermeters"][powermeter_data["index"]]["power"] = ( - powermeter_data["power"]["L1"] - + powermeter_data["power"]["L2"] - + powermeter_data["power"]["L3"] - ) - self._mydata["powermeters"][powermeter_data["index"]]["energy"] = ( - powermeter_data["energy"]["L1"] - + powermeter_data["energy"]["L2"] - + powermeter_data["energy"]["L3"] - ) + for powermeter_config in self._e3dcconfig["powermeters"]: + if powermeter_data["index"] == powermeter_config["index"]: + self._mydata[powermeter_config["key"] + "_power"] = ( + powermeter_data["power"]["L1"] + + powermeter_data["power"]["L2"] + + powermeter_data["power"]["L3"] + ) + self._mydata[powermeter_config["key"] + "_energy"] = ( + powermeter_data["energy"]["L1"] + + powermeter_data["energy"]["L2"] + + powermeter_data["energy"]["L3"] + ) async def _load_timezone_settings(self): """Load the current timezone offset from the E3DC, using its local timezone data. @@ -515,8 +525,3 @@ def delete_e3dcinstance(e3dc: E3DC) -> None: """Delete the actual E3DC instance.""" del e3dc return - - -def get_powermeters_data(self) -> dict[str, Any]: - """Return the powermeter data of the E3DC instance.""" - return self._mydata["powermeters"] diff --git a/custom_components/e3dc_rscp/sensor.py b/custom_components/e3dc_rscp/sensor.py index e7eb5ed..d55a59c 100644 --- a/custom_components/e3dc_rscp/sensor.py +++ b/custom_components/e3dc_rscp/sensor.py @@ -386,21 +386,35 @@ async def async_setup_entry( for description in SENSOR_DESCRIPTIONS ] - # Add the additional identified powermeters - for powermeter in coordinator.get_powermeters_data(): - description = SensorEntityDescription( - key=powermeter["name"], - translation_key=powermeter["name"], + # Add Sensor descriptions for additional powermeters + for powermeter_config in coordinator.get_e3dcconfig()["powermeters"]: + energy_description = SensorEntityDescription( + key=powermeter_config["key"] + "_energy", + translation_key=powermeter_config["key"] + "_energy", + icon="mdi:meter-electric", + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + suggested_display_precision=2, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + entity_registry_enabled_default=True, + ) + entities.append(coordinator, energy_description, entry.unique_id) + + power_description = SensorEntityDescription( + key=powermeter_config["key"] + "_power", + translation_key=powermeter_config["key"] + "_power", icon="mdi:meter-electric", native_unit_of_measurement=UnitOfPower.WATT, suggested_unit_of_measurement=UnitOfPower.KILO_WATT, - suggested_display_precision=2, + suggested_display_precision=1, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=True, ) - entities.append(E3DCSensor(coordinator, description, entry.unique_id)) + entities.append(coordinator, power_description, entry.unique_id) + _LOGGER.debug(entities) async_add_entities(entities) diff --git a/custom_components/e3dc_rscp/strings.json b/custom_components/e3dc_rscp/strings.json index 5e2121d..9b8fab5 100644 --- a/custom_components/e3dc_rscp/strings.json +++ b/custom_components/e3dc_rscp/strings.json @@ -142,6 +142,54 @@ }, "manual-charge-energy": { "name": "Energy charged from grid" + }, + "additional_powermeter_0_energy": { + "name": "Additional Powermeter 0 Energy" + }, + "additional_powermeter_1_energy": { + "name": "Additional Powermeter 1 Energy" + }, + "additional_powermeter_2_energy": { + "name": "Additional Powermeter 2 Energy" + }, + "additional_powermeter_3_energy": { + "name": "Additional Powermeter 3 Energy" + }, + "additional_powermeter_4_energy": { + "name": "Additional Powermeter 4 Energy" + }, + "additional_powermeter_5_energy": { + "name": "Additional Powermeter 5 Energy" + }, + "additional_powermeter_6_energy": { + "name": "Additional Powermeter 6 Energy" + }, + "additional_powermeter_7_energy": { + "name": "Additional Powermeter 7 Energy" + }, + "additional_powermeter_0_power": { + "name": "Additional Powermeter 0 Power" + }, + "additional_powermeter_1_power": { + "name": "Additional Powermeter 1 Power" + }, + "additional_powermeter_2_power": { + "name": "Additional Powermeter 2 Power" + }, + "additional_powermeter_3_power": { + "name": "Additional Powermeter 3 Power" + }, + "additional_powermeter_4_power": { + "name": "Additional Powermeter 4 Power" + }, + "additional_powermeter_5_power": { + "name": "Additional Powermeter 5 Power" + }, + "additional_powermeter_6_power": { + "name": "Additional Powermeter 6 Power" + }, + "additional_powermeter_7_power": { + "name": "Additional Powermeter 7 Power" } }, "switch": { diff --git a/custom_components/e3dc_rscp/translations/en.json b/custom_components/e3dc_rscp/translations/en.json index 165f14d..95e4307 100644 --- a/custom_components/e3dc_rscp/translations/en.json +++ b/custom_components/e3dc_rscp/translations/en.json @@ -37,7 +37,7 @@ "name": "Power limits" }, "manual-charge-active": { - "name": "Manual charge" + "name": "Manual charge" } }, "sensor": { @@ -141,7 +141,55 @@ "name": "Self consumption - today" }, "manual-charge-energy": { - "name": "Energy charged from grid" + "name": "Energy charged from grid" + }, + "additional_powermeter_0_energy": { + "name": "Additional Powermeter 0 Energy" + }, + "additional_powermeter_1_energy": { + "name": "Additional Powermeter 1 Energy" + }, + "additional_powermeter_2_energy": { + "name": "Additional Powermeter 2 Energy" + }, + "additional_powermeter_3_energy": { + "name": "Additional Powermeter 3 Energy" + }, + "additional_powermeter_4_energy": { + "name": "Additional Powermeter 4 Energy" + }, + "additional_powermeter_5_energy": { + "name": "Additional Powermeter 5 Energy" + }, + "additional_powermeter_6_energy": { + "name": "Additional Powermeter 6 Energy" + }, + "additional_powermeter_7_energy": { + "name": "Additional Powermeter 7 Energy" + }, + "additional_powermeter_0_power": { + "name": "Additional Powermeter 0 Power" + }, + "additional_powermeter_1_power": { + "name": "Additional Powermeter 1 Power" + }, + "additional_powermeter_2_power": { + "name": "Additional Powermeter 2 Power" + }, + "additional_powermeter_3_power": { + "name": "Additional Powermeter 3 Power" + }, + "additional_powermeter_4_power": { + "name": "Additional Powermeter 4 Power" + }, + "additional_powermeter_5_power": { + "name": "Additional Powermeter 5 Power" + }, + "additional_powermeter_6_power": { + "name": "Additional Powermeter 6 Power" + }, + "additional_powermeter_7_power": { + "name": "Additional Powermeter 7 Power" } }, "switch": { From b55a5c0c4cf289c17cfe942668c6006e66a22e2b Mon Sep 17 00:00:00 2001 From: Bastian Stahmer Date: Sun, 17 Sep 2023 19:45:15 +0000 Subject: [PATCH 3/6] improved dynamic creation of entities --- custom_components/e3dc_rscp/config_flow.py | 1 - custom_components/e3dc_rscp/coordinator.py | 53 +++++++++++++--------- custom_components/e3dc_rscp/sensor.py | 50 ++++++++++---------- 3 files changed, 57 insertions(+), 47 deletions(-) diff --git a/custom_components/e3dc_rscp/config_flow.py b/custom_components/e3dc_rscp/config_flow.py index b56b752..1aa8f53 100644 --- a/custom_components/e3dc_rscp/config_flow.py +++ b/custom_components/e3dc_rscp/config_flow.py @@ -76,7 +76,6 @@ def _async_check_login(self) -> None: password=self._password, host=self._host, rscpkey=self._rscpkey, - config={}, ) async def validate_input(self) -> str | None: diff --git a/custom_components/e3dc_rscp/coordinator.py b/custom_components/e3dc_rscp/coordinator.py index 05c5ca3..20d3dd7 100644 --- a/custom_components/e3dc_rscp/coordinator.py +++ b/custom_components/e3dc_rscp/coordinator.py @@ -57,6 +57,7 @@ def get_e3dcconfig(self) -> dict: async def async_connect(self): """Establish connection to E3DC.""" + try: self.e3dc: E3DC = await self.hass.async_add_executor_job( create_e3dcinstance, @@ -64,23 +65,24 @@ async def async_connect(self): self.password, self.host, self.rscpkey, - self._e3dcconfig, ) except Exception as ex: raise ConfigEntryAuthFailed from ex - _LOGGER.debug("async_connect setting up additional powermeters") # get the additional powermeters and re-create # the e3dc object with the proper configuration. self._e3dcconfig["powermeters"] = self.e3dc.get_powermeters() for powermeter in self._e3dcconfig["powermeters"]: - powermeter["name"] = ( - powermeter["typeName"].replace("_TYPE").lower() - + "_" - + str(powermeter["index"]) - ) - powermeter["key"] = "additional_powermeter_" + str(powermeter["index"]) - _LOGGER.debug(self._e3dcconfig) + if powermeter["index"] == 0: + powermeter["name"] = "pm_root" + powermeter["key"] = "root_powermeter" + else: + powermeter["name"] = ( + powermeter["typeName"].replace("_TYPE", "").lower() + + "_" + + str(powermeter["index"]) + ) + powermeter["key"] = "additional_powermeter_" + str(powermeter["index"]) delete_e3dcinstance(self.e3dc) @@ -162,7 +164,9 @@ async def _async_update_data(self) -> dict[str, Any]: _LOGGER.debug("Polling additional powermeters") powermeters_data: dict[ str, Any | None - ] = await self.hass.async_add_executor_job(self.e3dc.get_powermeters_data) + ] = await self.hass.async_add_executor_job( + self.e3dc.get_powermeters_data, None, True + ) self._process_powermeters_data(powermeters_data) # Only poll power statstics once per minute. E3DC updates it only once per 15 @@ -183,8 +187,6 @@ async def _async_update_data(self) -> dict[str, Any]: else: _LOGGER.debug("Skipping power metrics poll.") - for item in self._mydata.items(): - _LOGGER.debug(item) return self._mydata def _process_power_settings(self, power_settings: dict[str, Any | None]): @@ -507,17 +509,26 @@ async def async_manual_charge(self, charge_amount: int) -> None: def create_e3dcinstance( - username: str, password: str, host: str, rscpkey: str, config: dict + username: str, password: str, host: str, rscpkey: str, config: dict = None ) -> E3DC: """Create the actual E3DC instance, this will try to connect and authenticate.""" - e3dc = E3DC( - E3DC.CONNECT_LOCAL, - username=username, - password=password, - ipAddress=host, - key=rscpkey, - configuration=config, - ) + if config is None: + e3dc = E3DC( + E3DC.CONNECT_LOCAL, + username=username, + password=password, + ipAddress=host, + key=rscpkey, + ) + else: + e3dc = E3DC( + E3DC.CONNECT_LOCAL, + username=username, + password=password, + ipAddress=host, + key=rscpkey, + configuration=config, + ) return e3dc diff --git a/custom_components/e3dc_rscp/sensor.py b/custom_components/e3dc_rscp/sensor.py index d55a59c..670eb7c 100644 --- a/custom_components/e3dc_rscp/sensor.py +++ b/custom_components/e3dc_rscp/sensor.py @@ -388,33 +388,33 @@ async def async_setup_entry( # Add Sensor descriptions for additional powermeters for powermeter_config in coordinator.get_e3dcconfig()["powermeters"]: - energy_description = SensorEntityDescription( - key=powermeter_config["key"] + "_energy", - translation_key=powermeter_config["key"] + "_energy", - icon="mdi:meter-electric", - native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, - suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - suggested_display_precision=2, - device_class=SensorDeviceClass.ENERGY, - state_class=SensorStateClass.TOTAL_INCREASING, - entity_registry_enabled_default=True, - ) - entities.append(coordinator, energy_description, entry.unique_id) + if powermeter_config["index"] != 0: + energy_description = SensorEntityDescription( + key=powermeter_config["key"] + "_energy", + translation_key=powermeter_config["key"] + "_energy", + icon="mdi:meter-electric", + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + suggested_display_precision=2, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ) + entities.append( + E3DCSensor(coordinator, energy_description, entry.unique_id) + ) - power_description = SensorEntityDescription( - key=powermeter_config["key"] + "_power", - translation_key=powermeter_config["key"] + "_power", - icon="mdi:meter-electric", - native_unit_of_measurement=UnitOfPower.WATT, - suggested_unit_of_measurement=UnitOfPower.KILO_WATT, - suggested_display_precision=1, - device_class=SensorDeviceClass.POWER, - state_class=SensorStateClass.MEASUREMENT, - entity_registry_enabled_default=True, - ) - entities.append(coordinator, power_description, entry.unique_id) + power_description = SensorEntityDescription( + key=powermeter_config["key"] + "_power", + translation_key=powermeter_config["key"] + "_power", + icon="mdi:meter-electric", + native_unit_of_measurement=UnitOfPower.WATT, + suggested_unit_of_measurement=UnitOfPower.KILO_WATT, + suggested_display_precision=1, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + ) + entities.append(E3DCSensor(coordinator, power_description, entry.unique_id)) - _LOGGER.debug(entities) async_add_entities(entities) From 7f30800cb37e174adff9659bca9b85d76e6f3c69 Mon Sep 17 00:00:00 2001 From: Bastian Stahmer Date: Mon, 18 Sep 2023 19:57:41 +0000 Subject: [PATCH 4/6] added dynamic names for additional powermeters --- custom_components/e3dc_rscp/coordinator.py | 17 ++++--- custom_components/e3dc_rscp/sensor.py | 12 +++-- custom_components/e3dc_rscp/strings.json | 48 ------------------- .../e3dc_rscp/translations/en.json | 48 ------------------- 4 files changed, 19 insertions(+), 106 deletions(-) diff --git a/custom_components/e3dc_rscp/coordinator.py b/custom_components/e3dc_rscp/coordinator.py index 20d3dd7..9f3b71d 100644 --- a/custom_components/e3dc_rscp/coordinator.py +++ b/custom_components/e3dc_rscp/coordinator.py @@ -74,15 +74,20 @@ async def async_connect(self): self._e3dcconfig["powermeters"] = self.e3dc.get_powermeters() for powermeter in self._e3dcconfig["powermeters"]: if powermeter["index"] == 0: - powermeter["name"] = "pm_root" - powermeter["key"] = "root_powermeter" + powermeter["name"] = "Root PM" + powermeter["key"] = "root_pm" else: powermeter["name"] = ( - powermeter["typeName"].replace("_TYPE", "").lower() + powermeter["typeName"] + .replace("PM_TYPE_", "") + .replace("_", " ") + .capitalize() + ) + powermeter["key"] = ( + powermeter["typeName"].replace("PM_TYPE_", "").lower() + "_" + str(powermeter["index"]) ) - powermeter["key"] = "additional_powermeter_" + str(powermeter["index"]) delete_e3dcinstance(self.e3dc) @@ -254,12 +259,12 @@ def _process_powermeters_data(self, powermeters_data) -> None: if powermeter_data["index"] != 0: for powermeter_config in self._e3dcconfig["powermeters"]: if powermeter_data["index"] == powermeter_config["index"]: - self._mydata[powermeter_config["key"] + "_power"] = ( + self._mydata[powermeter_config["key"]] = ( powermeter_data["power"]["L1"] + powermeter_data["power"]["L2"] + powermeter_data["power"]["L3"] ) - self._mydata[powermeter_config["key"] + "_energy"] = ( + self._mydata[powermeter_config["key"] + "_total"] = ( powermeter_data["energy"]["L1"] + powermeter_data["energy"]["L2"] + powermeter_data["energy"]["L3"] diff --git a/custom_components/e3dc_rscp/sensor.py b/custom_components/e3dc_rscp/sensor.py index 670eb7c..3134058 100644 --- a/custom_components/e3dc_rscp/sensor.py +++ b/custom_components/e3dc_rscp/sensor.py @@ -390,8 +390,10 @@ async def async_setup_entry( for powermeter_config in coordinator.get_e3dcconfig()["powermeters"]: if powermeter_config["index"] != 0: energy_description = SensorEntityDescription( - key=powermeter_config["key"] + "_energy", - translation_key=powermeter_config["key"] + "_energy", + has_entity_name=True, + name=powermeter_config["name"] + " - total", + key=powermeter_config["key"] + "_total", + translation_key=powermeter_config["key"] + "_total", icon="mdi:meter-electric", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, @@ -404,8 +406,10 @@ async def async_setup_entry( ) power_description = SensorEntityDescription( - key=powermeter_config["key"] + "_power", - translation_key=powermeter_config["key"] + "_power", + has_entity_name=True, + name=powermeter_config["name"], + key=powermeter_config["key"], + translation_key=powermeter_config["key"], icon="mdi:meter-electric", native_unit_of_measurement=UnitOfPower.WATT, suggested_unit_of_measurement=UnitOfPower.KILO_WATT, diff --git a/custom_components/e3dc_rscp/strings.json b/custom_components/e3dc_rscp/strings.json index 9b8fab5..5e2121d 100644 --- a/custom_components/e3dc_rscp/strings.json +++ b/custom_components/e3dc_rscp/strings.json @@ -142,54 +142,6 @@ }, "manual-charge-energy": { "name": "Energy charged from grid" - }, - "additional_powermeter_0_energy": { - "name": "Additional Powermeter 0 Energy" - }, - "additional_powermeter_1_energy": { - "name": "Additional Powermeter 1 Energy" - }, - "additional_powermeter_2_energy": { - "name": "Additional Powermeter 2 Energy" - }, - "additional_powermeter_3_energy": { - "name": "Additional Powermeter 3 Energy" - }, - "additional_powermeter_4_energy": { - "name": "Additional Powermeter 4 Energy" - }, - "additional_powermeter_5_energy": { - "name": "Additional Powermeter 5 Energy" - }, - "additional_powermeter_6_energy": { - "name": "Additional Powermeter 6 Energy" - }, - "additional_powermeter_7_energy": { - "name": "Additional Powermeter 7 Energy" - }, - "additional_powermeter_0_power": { - "name": "Additional Powermeter 0 Power" - }, - "additional_powermeter_1_power": { - "name": "Additional Powermeter 1 Power" - }, - "additional_powermeter_2_power": { - "name": "Additional Powermeter 2 Power" - }, - "additional_powermeter_3_power": { - "name": "Additional Powermeter 3 Power" - }, - "additional_powermeter_4_power": { - "name": "Additional Powermeter 4 Power" - }, - "additional_powermeter_5_power": { - "name": "Additional Powermeter 5 Power" - }, - "additional_powermeter_6_power": { - "name": "Additional Powermeter 6 Power" - }, - "additional_powermeter_7_power": { - "name": "Additional Powermeter 7 Power" } }, "switch": { diff --git a/custom_components/e3dc_rscp/translations/en.json b/custom_components/e3dc_rscp/translations/en.json index 95e4307..503aa3d 100644 --- a/custom_components/e3dc_rscp/translations/en.json +++ b/custom_components/e3dc_rscp/translations/en.json @@ -142,54 +142,6 @@ }, "manual-charge-energy": { "name": "Energy charged from grid" - }, - "additional_powermeter_0_energy": { - "name": "Additional Powermeter 0 Energy" - }, - "additional_powermeter_1_energy": { - "name": "Additional Powermeter 1 Energy" - }, - "additional_powermeter_2_energy": { - "name": "Additional Powermeter 2 Energy" - }, - "additional_powermeter_3_energy": { - "name": "Additional Powermeter 3 Energy" - }, - "additional_powermeter_4_energy": { - "name": "Additional Powermeter 4 Energy" - }, - "additional_powermeter_5_energy": { - "name": "Additional Powermeter 5 Energy" - }, - "additional_powermeter_6_energy": { - "name": "Additional Powermeter 6 Energy" - }, - "additional_powermeter_7_energy": { - "name": "Additional Powermeter 7 Energy" - }, - "additional_powermeter_0_power": { - "name": "Additional Powermeter 0 Power" - }, - "additional_powermeter_1_power": { - "name": "Additional Powermeter 1 Power" - }, - "additional_powermeter_2_power": { - "name": "Additional Powermeter 2 Power" - }, - "additional_powermeter_3_power": { - "name": "Additional Powermeter 3 Power" - }, - "additional_powermeter_4_power": { - "name": "Additional Powermeter 4 Power" - }, - "additional_powermeter_5_power": { - "name": "Additional Powermeter 5 Power" - }, - "additional_powermeter_6_power": { - "name": "Additional Powermeter 6 Power" - }, - "additional_powermeter_7_power": { - "name": "Additional Powermeter 7 Power" } }, "switch": { From 76d6d69237b7143798f0dc97c04369c6b74c122a Mon Sep 17 00:00:00 2001 From: Bastian Stahmer Date: Thu, 28 Sep 2023 18:50:25 +0000 Subject: [PATCH 5/6] Incorporated torbens PR improvements --- custom_components/e3dc_rscp/coordinator.py | 68 +++++++++++----------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/custom_components/e3dc_rscp/coordinator.py b/custom_components/e3dc_rscp/coordinator.py index 9f3b71d..ceed596 100644 --- a/custom_components/e3dc_rscp/coordinator.py +++ b/custom_components/e3dc_rscp/coordinator.py @@ -6,7 +6,6 @@ from typing import Any import pytz - from e3dc import E3DC # Missing Exports:; SendError, from e3dc._rscpLib import rscpFindTag @@ -51,13 +50,8 @@ def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None: hass, _LOGGER, name=DOMAIN, update_interval=timedelta(seconds=10) ) - def get_e3dcconfig(self) -> dict: - """Return the E3DC config dict.""" - return self._e3dcconfig - async def async_connect(self): """Establish connection to E3DC.""" - try: self.e3dc: E3DC = await self.hass.async_add_executor_job( create_e3dcinstance, @@ -69,11 +63,38 @@ async def async_connect(self): except Exception as ex: raise ConfigEntryAuthFailed from ex - # get the additional powermeters and re-create - # the e3dc object with the proper configuration. + await self._async_connect_additional_powermeters() + self._mydata["system-derate-percent"] = self.e3dc.deratePercent + self._mydata["system-derate-power"] = self.e3dc.deratePower + self._mydata["system-additional-source-available"] = ( + self.e3dc.externalSourceAvailable != 0 + ) + self._mydata[ + "system-battery-installed-capacity" + ] = self.e3dc.installedBatteryCapacity + self._mydata["system-battery-installed-peak"] = self.e3dc.installedPeakPower + self._mydata["system-ac-maxpower"] = self.e3dc.maxAcPower + self._mydata["system-battery-charge-max"] = self.e3dc.maxBatChargePower + self._mydata["system-battery-discharge-max"] = self.e3dc.maxBatDischargePower + self._mydata["system-mac"] = self.e3dc.macAddress + self._mydata["model"] = self.e3dc.model + self._mydata[ + "system-battery-discharge-minimum-default" + ] = self.e3dc.startDischargeDefault + + # Idea: Maybe Port this to e3dc lib, it can query this in one go during startup. + self._sw_version = await self._async_e3dc_request_single_tag( + "INFO_REQ_SW_RELEASE" + ) + + await self._load_timezone_settings() + + async def _async_connect_additional_powermeters(self): + """Identifies the installed powermeters and re-establishes the connection to the E3DC with the corresponding powermeters config.""" + ROOT_PM_INDEX = 0 # Index 0 is always the root powermeter of the E3DC self._e3dcconfig["powermeters"] = self.e3dc.get_powermeters() for powermeter in self._e3dcconfig["powermeters"]: - if powermeter["index"] == 0: + if powermeter["index"] == ROOT_PM_INDEX: powermeter["name"] = "Root PM" powermeter["key"] = "root_pm" else: @@ -103,31 +124,6 @@ async def async_connect(self): except Exception as ex: raise ConfigEntryAuthFailed from ex - self._mydata["system-derate-percent"] = self.e3dc.deratePercent - self._mydata["system-derate-power"] = self.e3dc.deratePower - self._mydata["system-additional-source-available"] = ( - self.e3dc.externalSourceAvailable != 0 - ) - self._mydata[ - "system-battery-installed-capacity" - ] = self.e3dc.installedBatteryCapacity - self._mydata["system-battery-installed-peak"] = self.e3dc.installedPeakPower - self._mydata["system-ac-maxpower"] = self.e3dc.maxAcPower - self._mydata["system-battery-charge-max"] = self.e3dc.maxBatChargePower - self._mydata["system-battery-discharge-max"] = self.e3dc.maxBatDischargePower - self._mydata["system-mac"] = self.e3dc.macAddress - self._mydata["model"] = self.e3dc.model - self._mydata[ - "system-battery-discharge-minimum-default" - ] = self.e3dc.startDischargeDefault - - # Idea: Maybe Port this to e3dc lib, it can query this in one go during startup. - self._sw_version = await self._async_e3dc_request_single_tag( - "INFO_REQ_SW_RELEASE" - ) - - await self._load_timezone_settings() - async def _async_e3dc_request_single_tag(self, tag: str) -> Any: """Send a single tag request to E3DC, wraps lib call for async usage, supplies defaults.""" @@ -512,6 +508,10 @@ async def async_manual_charge(self, charge_amount: int) -> None: else: _LOGGER.debug("Successfully started manual charging") + def get_e3dcconfig(self) -> dict: + """Return the E3DC config dict.""" + return self._e3dcconfig + def create_e3dcinstance( username: str, password: str, host: str, rscpkey: str, config: dict = None From 714af301e0e3e14bb0e649c4a5ce2fb4d24eabef Mon Sep 17 00:00:00 2001 From: Bastian Stahmer Date: Sat, 7 Oct 2023 06:04:50 +0000 Subject: [PATCH 6/6] translation keys added --- custom_components/e3dc_rscp/coordinator.py | 11 +++-- custom_components/e3dc_rscp/sensor.py | 4 +- custom_components/e3dc_rscp/strings.json | 54 ++++++++++++++++++++++ 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/custom_components/e3dc_rscp/coordinator.py b/custom_components/e3dc_rscp/coordinator.py index ceed596..b74c05a 100644 --- a/custom_components/e3dc_rscp/coordinator.py +++ b/custom_components/e3dc_rscp/coordinator.py @@ -96,7 +96,7 @@ async def _async_connect_additional_powermeters(self): for powermeter in self._e3dcconfig["powermeters"]: if powermeter["index"] == ROOT_PM_INDEX: powermeter["name"] = "Root PM" - powermeter["key"] = "root_pm" + powermeter["key"] = "root-pm" else: powermeter["name"] = ( powermeter["typeName"] @@ -105,8 +105,11 @@ async def _async_connect_additional_powermeters(self): .capitalize() ) powermeter["key"] = ( - powermeter["typeName"].replace("PM_TYPE_", "").lower() - + "_" + powermeter["typeName"] + .replace("PM_TYPE_", "") + .replace("_", "-") + .lower() + + "-" + str(powermeter["index"]) ) @@ -260,7 +263,7 @@ def _process_powermeters_data(self, powermeters_data) -> None: + powermeter_data["power"]["L2"] + powermeter_data["power"]["L3"] ) - self._mydata[powermeter_config["key"] + "_total"] = ( + self._mydata[powermeter_config["key"] + "-total"] = ( powermeter_data["energy"]["L1"] + powermeter_data["energy"]["L2"] + powermeter_data["energy"]["L3"] diff --git a/custom_components/e3dc_rscp/sensor.py b/custom_components/e3dc_rscp/sensor.py index 3134058..5a54124 100644 --- a/custom_components/e3dc_rscp/sensor.py +++ b/custom_components/e3dc_rscp/sensor.py @@ -392,8 +392,8 @@ async def async_setup_entry( energy_description = SensorEntityDescription( has_entity_name=True, name=powermeter_config["name"] + " - total", - key=powermeter_config["key"] + "_total", - translation_key=powermeter_config["key"] + "_total", + key=powermeter_config["key"] + "-total", + translation_key=powermeter_config["key"] + "-total", icon="mdi:meter-electric", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, diff --git a/custom_components/e3dc_rscp/strings.json b/custom_components/e3dc_rscp/strings.json index b1b75ea..8b2bc9e 100644 --- a/custom_components/e3dc_rscp/strings.json +++ b/custom_components/e3dc_rscp/strings.json @@ -142,6 +142,60 @@ }, "manual-charge-energy": { "name": "Energy charged from grid" + }, + "undefined": { + "name": "Undefined Powermeter" + }, + "root-pm": { + "name": "Root Powermeter" + }, + "additional": { + "name": "Additional powermeter" + }, + "additional-production": { + "name": "Additional production" + }, + "additional-consumption": { + "name": "Additional consumption" + }, + "farm": { + "name": "Farm" + }, + "unused": { + "name": "Unused powermeter" + }, + "wallbox": { + "name": "Wallbox" + }, + "farm-additional": { + "name": "Farm additional powermeter" + }, + "undefined-total": { + "name": "Undefined Powermeter - total" + }, + "root-pm-total": { + "name": "Root Powermeter - total" + }, + "additional-total": { + "name": "Additional Powermeter - total" + }, + "additional-production-total": { + "name": "Additional production - total" + }, + "additional-consumption-total": { + "name": "Additional consumption - total" + }, + "farm-total": { + "name": "Farm - total" + }, + "unused-total": { + "name": "Unused powermeter - total" + }, + "wallbox-total": { + "name": "Wallbox - total" + }, + "farm-additional-total": { + "name": "Farm additional powermeter - total" } }, "switch": {