From 778d8aea4669372bb8f4c913ff8f2ea925c8e53e Mon Sep 17 00:00:00 2001 From: marq24 Date: Fri, 20 Oct 2023 07:53:39 +0200 Subject: [PATCH] merged PR#39 (manually) --- README.md | 8 ++-- custom_components/senec/__init__.py | 15 +++----- custom_components/senec/const.py | 29 ++++++++------- .../senec/pysenec_ha/__init__.py | 37 +++++++++++-------- custom_components/senec/service.py | 19 +++++----- custom_components/senec/services.yaml | 4 +- custom_components/senec/translations/de.json | 17 +++++++++ custom_components/senec/translations/en.json | 17 +++++++++ 8 files changed, 92 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index df1f548..a0395be 100644 --- a/README.md +++ b/README.md @@ -161,10 +161,10 @@ To enable a disabled function or sensor navigate to Settings -> Devices and Serv The following features are provided by the Web API: -| Feature | Description | enabled by default | -|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------| -| WEBAPI Spare Capacity | Current spare capacity in percent with the option to update. Precondition: When you are using "SENEC Backup Power pro" and you are able to see and update the spare capacity at mein-senec.de, than you can read andupdate the spare capacity with this integration. | no | -|Service: Set Peak Shaving|When using the Web API, you can use the Peak Shaving Service. This service gives you the abilty to switch the Mode (Deactivated, Automatic, Manual). In the manual mode you can define a battery capacity limit, so that the capacity can be used for charging later, as well as a end time - to realease the battery capacity limit. |yes| +| Feature | Description | enabled by default | +|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------| +| WEBAPI Spare Capacity | Current spare capacity in percent with the option to update. Precondition: When you are using "SENEC Backup Power pro" and you are able to see and update the spare capacity at mein-senec.de, than you can read andupdate the spare capacity with this integration. | no | +| Service: Set Peak Shaving | When using the Web API, you can use the Peak Shaving Service. This service gives you the abilty to switch the Mode (Deactivated, Automatic, Manual). In the manual mode you can define a battery capacity limit, so that the capacity can be used for charging later, as well as a end time - to realease the battery capacity limit. | yes | #### Sensors diff --git a/custom_components/senec/__init__.py b/custom_components/senec/__init__.py index b73c951..ce6a10d 100644 --- a/custom_components/senec/__init__.py +++ b/custom_components/senec/__init__.py @@ -1,7 +1,6 @@ """The senec integration.""" import asyncio import logging -import json import voluptuous as vol from datetime import timedelta @@ -29,6 +28,7 @@ SENEC_SECTION_TEMPMEASURE, SENEC_SECTION_WALLBOX ) +from . import service as SenecService from .const import ( DOMAIN, @@ -60,8 +60,6 @@ QUERY_SPARE_CAPACITY_KEY, QUERY_PEAK_SHAVING_KEY, ) -from . import service as SenecService - _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=60) @@ -118,10 +116,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): coordinator._device_serial = coordinator.senec.serial_number coordinator._device_version = None # senec_web_client.firmwareVersion - #Register Services + # Register Services service = SenecService.SenecService(hass, config_entry, coordinator) hass.services.async_register(DOMAIN, "set_peakshaving", service.set_peakshaving) - hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][config_entry.entry_id] = coordinator @@ -174,15 +171,15 @@ def __init__(self, hass: HomeAssistant, session, config_entry): # this is enough to check the current enabled/disabled status of the 'spare_capacity' control registry = entity_registry.async_get(hass) - + if registry is not None: - #Spare Capacity + # Spare Capacity spare_capacity_entity = registry.async_get(sce_id) if spare_capacity_entity is not None: if spare_capacity_entity.disabled_by is None: _LOGGER.info("***** QUERY_SPARE_CAPACITY! ********") opt[QUERY_SPARE_CAPACITY_KEY] = True - #Peak Shaving + # Peak Shaving ps_gridlimit = registry.async_get(ps_gridlimit_id) ps_mode = registry.async_get(ps_mode_id) ps_capacity = registry.async_get(ps_capacity_id) @@ -192,7 +189,7 @@ def __init__(self, hass: HomeAssistant, session, config_entry): if ps_gridlimit.disabled_by is None or ps_mode.disabled_by is None or ps_capacity.disabled_by is None or ps_end is None: _LOGGER.info("***** QUERY_PEAK_SHAVING! ********") opt[QUERY_PEAK_SHAVING_KEY] = True - + self.senec = MySenecWebPortal(user=user, pwd=pwd, websession=session, master_plant_number=a_master_plant_number, options=opt) diff --git a/custom_components/senec/const.py b/custom_components/senec/const.py index 3a9e319..f52f0a0 100644 --- a/custom_components/senec/const.py +++ b/custom_components/senec/const.py @@ -96,35 +96,39 @@ QUERY_SPARE_CAPACITY_KEY = "query_spare_capacity" QUERY_PEAK_SHAVING_KEY = "query_peak_shaving" -#Peak Shaving Options -PEAK_SHAVING_OPTIONS = ["DEACTIVATED","MANUAL","AUTO"] +# Peak Shaving Options +PEAK_SHAVING_OPTIONS = ["DEACTIVATED", "MANUAL", "AUTO"] + @dataclass class ExtSensorEntityDescription(SensorEntityDescription): controls: list[str] | None = None senec_lala_section: str | None = None + @dataclass class ExtBinarySensorEntityDescription(BinarySensorEntityDescription): icon_off: str | None = None senec_lala_section: str | None = None + @dataclass class ExtSwitchEntityDescription(SwitchEntityDescription): update_after_switch_delay_in_sec: int = 0 + """Supported number implementations""" WEB_NUMBER_SENYOR_TYPES = [ NumberEntityDescription( entity_registry_enabled_default=False, key="spare_capacity", name="Spare Capacity", - device_class = NumberDeviceClass.BATTERY, - mode = NumberMode.SLIDER, - native_max_value = 50, - native_min_value = 0, - native_step = 1, - native_unit_of_measurement = PERCENTAGE, + device_class=NumberDeviceClass.BATTERY, + mode=NumberMode.SLIDER, + native_max_value=50, + native_min_value=0, + native_step=1, + native_unit_of_measurement=PERCENTAGE, icon="mdi:battery-lock", ), ] @@ -385,7 +389,7 @@ class ExtSwitchEntityDescription(SwitchEntityDescription): suggested_display_precision=3, state_class=SensorStateClass.MEASUREMENT, ), - #Peak Shaving + # Peak Shaving SensorEntityDescription( entity_registry_enabled_default=False, key="gridexport_limit", @@ -396,7 +400,7 @@ class ExtSwitchEntityDescription(SwitchEntityDescription): suggested_display_precision=0, state_class=SensorStateClass.MEASUREMENT, ), - SensorEntityDescription( + SensorEntityDescription( entity_registry_enabled_default=False, key="peakshaving_mode", name="Peak Shaving Mode", @@ -404,7 +408,7 @@ class ExtSwitchEntityDescription(SwitchEntityDescription): device_class=SensorDeviceClass.ENUM, options=PEAK_SHAVING_OPTIONS ), - SensorEntityDescription( + SensorEntityDescription( entity_registry_enabled_default=False, key="peakshaving_capacitylimit", name="Peak Shaving Capacity Limit", @@ -414,7 +418,7 @@ class ExtSwitchEntityDescription(SwitchEntityDescription): suggested_display_precision=0, state_class=SensorStateClass.MEASUREMENT, ), - SensorEntityDescription( + SensorEntityDescription( entity_registry_enabled_default=False, key="peakshaving_enddate", name="Peak Shaving End Time", @@ -423,7 +427,6 @@ class ExtSwitchEntityDescription(SwitchEntityDescription): ), ] - """Supported main unit sensor types.""" MAIN_SENSOR_TYPES = [ ExtSensorEntityDescription( diff --git a/custom_components/senec/pysenec_ha/__init__.py b/custom_components/senec/pysenec_ha/__init__.py index a1b65c8..5fd8431 100644 --- a/custom_components/senec/pysenec_ha/__init__.py +++ b/custom_components/senec/pysenec_ha/__init__.py @@ -1844,21 +1844,20 @@ class MySenecWebPortal: def __init__(self, user, pwd, websession, master_plant_number: int = 0, options: dict = None): _LOGGER.info(f"restarting MySenecWebPortal... for user: '{user}' with options: {options}") - #Check if spare capacity is in options + # Check if spare capacity is in options if options is not None and QUERY_SPARE_CAPACITY_KEY in options: self._QUERY_SPARE_CAPACITY = options[QUERY_SPARE_CAPACITY_KEY] - #check if peak shaving is in options + # check if peak shaving is in options if options is not None and QUERY_PEAK_SHAVING_KEY in options: self._QUERY_PEAK_SHAVING = options[QUERY_PEAK_SHAVING_KEY] - #Variable to save latest update time for spare capacity + # Variable to save latest update time for spare capacity self._QUERY_SPARE_CAPACITY_TS = 0 - #Variable to save latest update time for peak shaving + # Variable to save latest update time for peak shaving self._QUERY_PEAK_SHAVING_TS = 0 - loop = aiohttp.helpers.get_running_loop(websession.loop) senec_jar = MySenecCookieJar(loop=loop); if hasattr(websession, "_cookie_jar"): @@ -1895,7 +1894,7 @@ def __init__(self, user, pwd, websession, master_plant_number: int = 0, options: # Call for export limit and current peak shaving information - to be followed by master plant number self._SENEC_API_GET_PEAK_SHAVING = "https://mein-senec.de/endkunde/api/peakshaving/getSettings?anlageNummer=" - #Call to set spare capacity information - Base URL + # Call to set spare capacity information - Base URL self._SENEC_API_SET_PEAK_SHAVING_BASE_URL = "https://mein-senec.de/endkunde/api/peakshaving/saveSettings?anlageNummer=" # can be used in all api calls, names come from senec website @@ -1919,7 +1918,7 @@ def __init__(self, user, pwd, websession, master_plant_number: int = 0, options: self._battery_entities = {} self._spare_capacity = 0 # initialize the spare_capacity with 0 self._isAuthenticated = False - self._peakShaving_entities = {} + self._peakShaving_entities = {} def checkCookieJarType(self): if hasattr(self.websession, "_cookie_jar"): @@ -2021,8 +2020,8 @@ async def update(self): else: await self.authenticate(doUpdate=True, throw401=False) - """This function will update peak shaving information""" + async def update_peak_shaving(self): _LOGGER.info("***** update_peak_shaving(self) ********") a_url = f"{self._SENEC_API_GET_PEAK_SHAVING}{self._master_plant_number}" @@ -2032,13 +2031,16 @@ async def update_peak_shaving(self): if res.status == 200: r_json = await res.json() - #GET Data from JSON - self._peakShaving_entities["einspeisebegrenzungKwpInPercent"] = r_json["einspeisebegrenzungKwpInPercent"] + # GET Data from JSON + self._peakShaving_entities["einspeisebegrenzungKwpInPercent"] = r_json[ + "einspeisebegrenzungKwpInPercent"] self._peakShaving_entities["peakShavingMode"] = r_json["peakShavingMode"] - self._peakShaving_entities["peakShavingCapacityLimitInPercent"] = r_json["peakShavingCapacityLimitInPercent"] - self._peakShaving_entities["peakShavingEndDate"] = datetime.fromtimestamp(r_json["peakShavingEndDate"]/1000) #from miliseconds to seconds + self._peakShaving_entities["peakShavingCapacityLimitInPercent"] = r_json[ + "peakShavingCapacityLimitInPercent"] + self._peakShaving_entities["peakShavingEndDate"] = datetime.fromtimestamp( + r_json["peakShavingEndDate"] / 1000) # from miliseconds to seconds - self._QUERY_PEAK_SHAVING_TS= time() #Update timer, that the next update takes place in 24 hours + self._QUERY_PEAK_SHAVING_TS = time() # Update timer, that the next update takes place in 24 hours else: self._isAuthenticated = False await self.update() @@ -2051,9 +2053,10 @@ async def update_peak_shaving(self): await self.update() """This function will set the peak shaving data over the web api""" + async def set_peak_shaving(self, new_peak_shaving: dict): _LOGGER.debug("***** set_peak_shaving(self, new_peak_shaving) ********") - + # Senec self allways sends all get-parameter, even if not needed. So we will do it the same way a_url = f"{self._SENEC_API_SET_PEAK_SHAVING_BASE_URL}{self._master_plant_number}&mode={new_peak_shaving['mode']}&capacityLimit={new_peak_shaving['capacity']}&endzeit={new_peak_shaving['end_time']}" @@ -2079,6 +2082,7 @@ async def set_peak_shaving(self, new_peak_shaving: dict): await self.set_peak_shaving(new_peak_shaving) """This function will update the spare capacity over the web api""" + async def update_spare_capacity(self): _LOGGER.info("***** update_spare_capacity(self) ********") a_url = f"{self._SENEC_API_SPARE_CAPACITY_BASE_URL}{self._master_plant_number}{self._SENEC_API_GET_SPARE_CAPACITY}" @@ -2100,6 +2104,7 @@ async def update_spare_capacity(self): await self.update() """This function will set the spare capacity over the web api""" + async def set_spare_capacity(self, new_spare_capacity: int): _LOGGER.debug("***** set_spare_capacity(self) ********") a_url = f"{self._SENEC_API_SPARE_CAPACITY_BASE_URL}{self._master_plant_number}{self._SENEC_API_SET_SPARE_CAPACITY}{new_spare_capacity}" @@ -2411,12 +2416,12 @@ def acculevel_now(self) -> int: def gridexport_limit(self) -> int: if hasattr(self, "_peakShaving_entities") and "einspeisebegrenzungKwpInPercent" in self._peakShaving_entities: return self._peakShaving_entities["einspeisebegrenzungKwpInPercent"] - + @property def peakshaving_mode(self) -> int: if hasattr(self, "_peakShaving_entities") and "peakShavingMode" in self._peakShaving_entities: return self._peakShaving_entities["peakShavingMode"] - + @property def peakshaving_capacitylimit(self) -> int: if hasattr(self, "_peakShaving_entities") and "peakShavingCapacityLimitInPercent" in self._peakShaving_entities: diff --git a/custom_components/senec/service.py b/custom_components/senec/service.py index 72c499a..bac313c 100644 --- a/custom_components/senec/service.py +++ b/custom_components/senec/service.py @@ -1,13 +1,11 @@ """ Services for SENEC Device""" from datetime import datetime -from homeassistant.helpers import entity_registry -from homeassistant.config_entries import ConfigEntry -from homeassistant.util import slugify + class SenecService(): - - def __init__(self, hass, config, coordinator): + + def __init__(self, hass, config, coordinator): """ Initialize """ self._hass = hass self._config = config @@ -21,18 +19,19 @@ async def set_peakshaving(self, call): if end_time is not None: selected_time = datetime.strptime(end_time, "%H:%M:%S").time() - end_time = datetime.combine(datetime.today(), selected_time) # User sets just a time, create a valid timestamp - end_time= int(end_time.timestamp()) * 1000 # We have to send the timestamp in miliseconds to senec + end_time = datetime.combine(datetime.today(), + selected_time) # User sets just a time, create a valid timestamp + end_time = int(end_time.timestamp()) * 1000 # We have to send the timestamp in miliseconds to senec try: - new_peak_shaving = {"mode": mode, "capacity": capacity,"end_time": end_time} + new_peak_shaving = {"mode": mode, "capacity": capacity, "end_time": end_time} await self._coordinator.senec.set_peak_shaving(new_peak_shaving) # Force update # registry = entity_registry.async_get(self._hass) # peakshaving_mode_key = f"sensor.{slugify(ConfigEntry.title)}_peakshaving_mode".lower() # entity = registry.async_get(peakshaving_mode_key) - # entity.async_schedule_update_ha_state(force_refresh=True) + # entity.async_schedule_update_ha_state(force_refresh=True) return True except ValueError: - return "unavailable" \ No newline at end of file + return "unavailable" diff --git a/custom_components/senec/services.yaml b/custom_components/senec/services.yaml index d7699b6..e8c71ab 100644 --- a/custom_components/senec/services.yaml +++ b/custom_components/senec/services.yaml @@ -1,7 +1,7 @@ # Service ID set_peakshaving: # Service name as shown in UI - name: Set Peak Shaving + name: Set Peak Shaving # Description of the service description: Sets Peak Shaving Mode, battery capacity limit and end time. # Battery capacity limit and end time can just be set, if the selected mode is "Manual" @@ -9,7 +9,7 @@ set_peakshaving: fields: # Key of the field mode: - # Whether or not field is required (default = false) + # Whether field is required (default = false) required: true # Field name as shown in UI name: Peak Shaving Mode diff --git a/custom_components/senec/translations/de.json b/custom_components/senec/translations/de.json index 3f534da..bef26fb 100644 --- a/custom_components/senec/translations/de.json +++ b/custom_components/senec/translations/de.json @@ -119,6 +119,23 @@ }, "entity": { "sensor": { + "gridexport_limit": { + "name": "Einspeisebegrenzung" + }, + "peakshaving_mode": { + "name": "Peak Shaving Modus", + "state": { + "DEACTIVATED": "Deaktiviert", + "MANUAL": "Manuell", + "AUTO": "Automatisch" + } + }, + "peakshaving_capacitylimit": { + "name": "Peak Shaving Akku Begrenzung" + }, + "peakshaving_enddate": { + "name": "Peak Shaving Endzeit" + }, "spare_capacity": { "name": "Reservekapazität" }, diff --git a/custom_components/senec/translations/en.json b/custom_components/senec/translations/en.json index da628e9..766c0df 100644 --- a/custom_components/senec/translations/en.json +++ b/custom_components/senec/translations/en.json @@ -119,6 +119,23 @@ }, "entity": { "sensor": { + "gridexport_limit": { + "name": "Grid Export limit" + }, + "peakshaving_mode": { + "name": "Peak Shaving Mode", + "state": { + "DEACTIVATED": "Deactivated", + "MANUAL": "Manual", + "AUTO": "Automatic" + } + }, + "peakshaving_capacitylimit": { + "name": "Peak Shaving battery capacity limit" + }, + "peakshaving_enddate": { + "name": "Peak Shaving end time" + }, "spare_capacity": { "name": "Spare Capacity" },