Skip to content

Commit

Permalink
merged PR#39 (manually)
Browse files Browse the repository at this point in the history
  • Loading branch information
marq24 committed Oct 20, 2023
1 parent 00f8b06 commit 778d8ae
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 54 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
15 changes: 6 additions & 9 deletions custom_components/senec/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""The senec integration."""
import asyncio
import logging
import json
import voluptuous as vol

from datetime import timedelta
Expand Down Expand Up @@ -29,6 +28,7 @@
SENEC_SECTION_TEMPMEASURE,
SENEC_SECTION_WALLBOX
)
from . import service as SenecService

from .const import (
DOMAIN,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
29 changes: 16 additions & 13 deletions custom_components/senec/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
),
]
Expand Down Expand Up @@ -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",
Expand All @@ -396,15 +400,15 @@ 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",
icon="mdi:toggle-switch",
device_class=SensorDeviceClass.ENUM,
options=PEAK_SHAVING_OPTIONS
),
SensorEntityDescription(
SensorEntityDescription(
entity_registry_enabled_default=False,
key="peakshaving_capacitylimit",
name="Peak Shaving Capacity Limit",
Expand All @@ -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",
Expand All @@ -423,7 +427,6 @@ class ExtSwitchEntityDescription(SwitchEntityDescription):
),
]


"""Supported main unit sensor types."""
MAIN_SENSOR_TYPES = [
ExtSensorEntityDescription(
Expand Down
37 changes: 21 additions & 16 deletions custom_components/senec/pysenec_ha/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"):
Expand Down Expand Up @@ -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
Expand All @@ -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"):
Expand Down Expand Up @@ -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}"
Expand All @@ -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()
Expand All @@ -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']}"

Expand All @@ -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}"
Expand All @@ -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}"
Expand Down Expand Up @@ -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:
Expand Down
19 changes: 9 additions & 10 deletions custom_components/senec/service.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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"
return "unavailable"
4 changes: 2 additions & 2 deletions custom_components/senec/services.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# 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"
# Different fields that your service accepts
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
Expand Down
17 changes: 17 additions & 0 deletions custom_components/senec/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
17 changes: 17 additions & 0 deletions custom_components/senec/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down

0 comments on commit 778d8ae

Please sign in to comment.