Skip to content

Commit

Permalink
First implementation of spare capacity management (#19)
Browse files Browse the repository at this point in the history
* First implementation of spare capacity management

* -Enhancement of spare capacity management
-Added information to readme.md
-Added @io-debug to codeowners

* updated readme.md
  • Loading branch information
io-debug authored Sep 17, 2023
1 parent d4bb6b5 commit 4876218
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 5 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ support this repo in the future with possible enhancements for the WEB-API__.

- Added German Setup/GUI "translation" (not for the sensor's yet)

- Added support to read and update the spare capacity ("Notstromreserve")
- 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 and update the spare capacity with this integration.
- Precondition: You are using the Web Api
- Since the functionallity is disabled by default, navigate to Settings -> Devices & Services, select the Senec Integration and click on the entities of the Web API.
Here you can activate the "Spare Capacity".
Once activated you can add the entity to your dashboard. When you click on the shown spare capacity on your dashboard a slider will be shown. With this slider you can change the spare capacity. The chance will automatically be synchronized with mein-senec.de


## Switching to this fork...

Please find in all information you need to know
Expand Down
2 changes: 1 addition & 1 deletion custom_components/senec/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
SCAN_INTERVAL = timedelta(seconds=60)
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)

PLATFORMS = ["sensor", "binary_sensor", "switch"]
PLATFORMS = ["sensor", "binary_sensor", "switch", "number"]


async def async_setup(hass: HomeAssistant, config: dict):
Expand Down
21 changes: 19 additions & 2 deletions custom_components/senec/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
SensorStateClass,
)
from homeassistant.components.switch import SwitchEntityDescription
from homeassistant.components.number import (
NumberEntityDescription,
NumberDeviceClass
)
from homeassistant.const import (
ENERGY_KILO_WATT_HOUR,
PERCENTAGE,
Expand Down Expand Up @@ -75,6 +79,21 @@ class ExtSensorEntityDescription(SensorEntityDescription):
class ExtBinarySensorEntityDescription(BinarySensorEntityDescription):
icon_off: str | None = None

"""Supported number implementations"""
WEB_NUMBER_SENYOR_TYPES = [
NumberEntityDescription(
entity_registry_enabled_default=False,
key="spare_capacity",
name="Spare Capacity",
device_class = NumberDeviceClass.BATTERY,
mode = "slider",
native_max_value = 100,
native_min_value = 0,
native_step = 1,
native_unit_of_measurement = PERCENTAGE,
icon="mdi:battery-lock",
),
]

"""Supported main unit switch types."""
MAIN_SWITCH_TYPES = [
Expand Down Expand Up @@ -386,7 +405,6 @@ class ExtBinarySensorEntityDescription(BinarySensorEntityDescription):
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),

ExtSensorEntityDescription(
key="solar_mpp1_potential",
name="MPP1 Potential",
Expand Down Expand Up @@ -459,7 +477,6 @@ class ExtBinarySensorEntityDescription(BinarySensorEntityDescription):
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.MEASUREMENT,
),

ExtSensorEntityDescription(
key="enfluri_net_freq",
name="Enfluri Net Frequency",
Expand Down
5 changes: 3 additions & 2 deletions custom_components/senec/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
"codeowners": [
"@marq24",
"@mchwalisz",
"@mstuettgen"
"@mstuettgen",
"@io-debug"
],
"config_flow": true,
"dependencies": [],
"documentation": "https://github.com/marq24/ha-senec-v3/blob/master/README.md",
"iot_class": "local_polling",
"issue_tracker": "https://github.com/marq24/ha-senec-v3/issues",
"requirements": [],
"version": "3.0.6"
"version": "3.0.7"
}
63 changes: 63 additions & 0 deletions custom_components/senec/number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Platform for Senec numbers."""
import logging

from homeassistant.components.number import NumberEntity, NumberEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.util import slugify
from homeassistant.const import CONF_TYPE

from . import SenecDataUpdateCoordinator, SenecEntity
from .const import (
DOMAIN,
WEB_NUMBER_SENYOR_TYPES,
CONF_SYSTYPE_WEB
)

_LOGGER = logging.getLogger(__name__)

async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities):
"""Initialize sensor platform from config entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
entities = []

#Take care that CONF_TYPE = CONF_SYSTEYPE_WEB, since the implementation works with the web API
if CONF_TYPE in config_entry.data and config_entry.data[CONF_TYPE] == CONF_SYSTYPE_WEB:
for description in WEB_NUMBER_SENYOR_TYPES:
entity = SenecNumber(coordinator, description)
entities.append(entity)
async_add_entities(entities)

"""Implementation for the spare capacity of the senec device"""
class SenecNumber(SenecEntity, NumberEntity):

def __init__(
self,
coordinator: SenecDataUpdateCoordinator,
description: NumberEntityDescription,
):
"""Initialize"""
super().__init__(coordinator=coordinator, description=description)
if (hasattr(self.entity_description, 'entity_registry_enabled_default')):
self._attr_entity_registry_enabled_default = self.entity_description.entity_registry_enabled_default
else:
self._attr_entity_registry_enabled_default = True
title = self.coordinator._config_entry.title
key = self.entity_description.key
name = self.entity_description.name
self.entity_id = f"number.{slugify(title)}_{key}"
self._attr_name = f"{title} {name}"

@property
def state(self) -> int:
number = self.entity_description.key
value = getattr(self.coordinator.senec, number)
return int(value)

async def async_set_native_value(self, value: int) -> None:
"""Update the current value."""
updated_spare_capacity = int(value)
api = self.coordinator.senec
await api.set_spare_capacity(updated_spare_capacity)
self.async_schedule_update_ha_state(force_refresh=True)

61 changes: 61 additions & 0 deletions custom_components/senec/pysenec_ha/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1371,6 +1371,13 @@ def __init__(self, user, pwd, websession, master_plant_number: int = 0):
self._SENEC_API_URL_START = "https://mein-senec.de/endkunde/api/status/getstatus.php?type="
self._SENEC_API_URL_END = "&period=all&anlageNummer=%s"

# Calls for spare capacity - Base URL has to be followed by master plant number
self._SENEC_API_SPARE_CAPACITY_BASE_URL = "https://mein-senec.de/endkunde/api/senec/"
# Call the following URL (GET-Request) in order to get the spare capacity as int in the response body
self._SENEC_API_GET_SPARE_CAPACITY = "/emergencypower/reserve-in-percent"
# Call the following URL (Post Request) in order to set the spare capacity
self._SENEC_API_SET_SPARE_CAPACITY = "/emergencypower?reserve-in-percent="

# can be used in all api calls, names come from senec website
self._API_KEYS = [
"accuimport", # what comes OUT OF the accu
Expand All @@ -1390,6 +1397,7 @@ def __init__(self, user, pwd, websession, master_plant_number: int = 0):
self._energy_entities = {}
self._power_entities = {}
self._battery_entities = {}
self._spare_capacity = 0 #initialize the spare_capacity with 0
self._isAuthenticated = False

def checkCookieJarType(self):
Expand Down Expand Up @@ -1480,9 +1488,57 @@ async def update(self):
self.checkCookieJarType()
await self.update_now_kW_stats()
await self.update_full_kWh_stats()
await self.update_spare_capacity()
else:
await self.authenticate(doUpdate=True, throw401=False)

"""This function will update the spare capacity over the web api"""
async def update_spare_capacity(self):
_LOGGER.debug("***** update_spare_capacity(self) ********")
a_url = f"{self._SENEC_API_SPARE_CAPACITY_BASE_URL}{self._master_plant_number}{self._SENEC_API_GET_SPARE_CAPACITY}"
async with self.websession.get(a_url) as res:
try:
res.raise_for_status()
if res.status == 200:
self._spare_capacity = await res.text()
else:
self._isAuthenticated = False
await self.update()

except ClientResponseError as exc:
if exc.status == 401:
self.purgeSenecCookies()

self._isAuthenticated = False
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}"
#payload = f"reserve-in-percent={new_spare_capacity}"
async with self.websession.post(a_url, ssl=False) as res:
try:
res.raise_for_status()
if res.status == 200:
_LOGGER.debug("***** Set Spare Capacity successfully ********")
#res_body = await res.text()
#if res_body == "true":
#await self.update()
else:
self._isAuthenticated = False
await self.authenticate(doUpdate=False, throw401=False)
await self.set_spare_capacity(new_spare_capacity)

except ClientResponseError as exc:
if exc.status == 401:
self.purgeSenecCookies()

self._isAuthenticated = False
await self.authenticate(doUpdate=False, throw401=True)
await self.set_spare_capacity(new_spare_capacity)



async def update_now_kW_stats(self):
_LOGGER.debug("***** update_now_kW_stats(self) ********")
# grab NOW and TODAY stats
Expand Down Expand Up @@ -1602,6 +1658,11 @@ async def update_get_systems(self, a_plant_number: int):
self._isAuthenticated = False
await self.authenticate(doUpdate=False, throw401=False)

@property
def spare_capacity(self) -> int:
if hasattr(self, '_spare_capacity'):
return int(self._spare_capacity)

@property
def senec_num(self) -> str:
if hasattr(self, '_dev_number'):
Expand Down

0 comments on commit 4876218

Please sign in to comment.