Skip to content

Commit b53a241

Browse files
committed
just speach less
1 parent fb4a3d4 commit b53a241

2 files changed

Lines changed: 49 additions & 29 deletions

File tree

custom_components/senec/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@
1313
"iot_class": "local_polling",
1414
"issue_tracker": "https://github.com/marq24/ha-senec-v3/issues",
1515
"requirements": ["xmltodict>=0.13.0", "packaging>=21.0", "python-dateutil>=2.8.0"],
16-
"version": "2025.6.0"
16+
"version": "2025.6.1"
1717
}

custom_components/senec/pysenec_ha/__init__.py

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from datetime import datetime
77
from http.cookies import BaseCookie, SimpleCookie, Morsel
88
from time import time
9-
from typing import Union, cast
9+
from typing import Union, cast, Final
1010

1111
import aiohttp
1212
import xmltodict
@@ -2690,6 +2690,8 @@ def yield_produced_total(self) -> float:
26902690
return self._yield_produced_total
26912691
return None
26922692

2693+
#USER_AGENT: Final = "SENEC.App/4.8.1 (LMFD) okhttp/4.12.0"
2694+
USER_AGENT: Final = "SENEC.App okhttp/4.12.0"
26932695

26942696
class MySenecWebPortal:
26952697
def __init__(self, user, pwd, web_session, master_plant_number: int = 0, lang: str = "en", options: dict = None):
@@ -2788,6 +2790,13 @@ def __init__(self, user, pwd, web_session, master_plant_number: int = 0, lang: s
27882790
self._SENEC_APP_TOTAL_V2 = APP_BASE_URL2 + "v2/senec/systems/%s/measurements?resolution=YEAR&from=1514764800&to=%s"
27892791
self._SENEC_APP_TECHDATA = APP_BASE_URL2 + "v1/senec/systems/%s/technical-data"
27902792

2793+
# 2025/06/11 [NEW APP URLS -but with LESS DATA!]
2794+
# APP_BASE_MEASSURE_URL3 = "https://senec-app-measurements-proxy.prod.senec.dev/"
2795+
# APP_BASE_SYSTEM_URL3 = "https://senec-app-systems-proxy.prod.senec.dev/"
2796+
# self._SENEC_APP_NOW = f"{APP_BASE_MEASSURE_URL3}v1/systems/%s/dashboard"
2797+
# self._SENEC_APP_TOTAL_V3= f"{APP_BASE_MEASSURE_URL3}v1/systems/%s/measurements?resolution=YEAR&from=2018-01-01T00:00:00Z&to=%s"
2798+
# self._SENEC_APP_TECHDATA= f"{APP_BASE_SYSTEM_URL3}systems/%s/details"
2799+
27912800
# https://app-gateway.prod.senec.dev/v1/senec/systems/%s/abilities
27922801
# https://app-gateway.prod.senec.dev/v1/senec/systems/%s/operational-mode -> "COM70"
27932802

@@ -2911,7 +2920,7 @@ async def app_authenticate(self, retry: bool = True, do_update: bool = False):
29112920
"username": self._SENEC_USERNAME,
29122921
"password": self._SENEC_PASSWORD
29132922
}
2914-
async with self.web_session.post(self._SENEC_APP_AUTH, json=auth_payload, ssl=False) as res:
2923+
async with self.web_session.post(self._SENEC_APP_AUTH, headers={"User-Agent": USER_AGENT}, json=auth_payload, ssl=False) as res:
29152924
try:
29162925
res.raise_for_status()
29172926
if res.status == 200:
@@ -2938,7 +2947,7 @@ async def app_authenticate(self, retry: bool = True, do_update: bool = False):
29382947
_LOGGER.warning(f"APP-API: Login failed with Code {res.status}")
29392948

29402949
except ClientResponseError as ioexc:
2941-
_LOGGER.warning(f"APP-API: Could not login to APP-API: {ioexc}")
2950+
_LOGGER.warning(f"APP-API: Could not login to APP-API: {type(ioexc)} - {ioexc}")
29422951

29432952
async def app_update_context(self, retry: bool = True):
29442953
_LOGGER.debug("***** app_update_context(self) ********")
@@ -2952,7 +2961,7 @@ async def app_update_context(self, retry: bool = True):
29522961
async def app_get_master_plant_id(self, retry: bool = True):
29532962
_LOGGER.debug("***** APP-API: get_master_plant_id(self) ********")
29542963
if self._app_is_authenticated:
2955-
headers = {"Authorization": self._app_token}
2964+
headers = {"Authorization": self._app_token, "User-Agent": USER_AGENT}
29562965
try:
29572966
async with self.web_session.get(self._SENEC_APP_GET_SYSTEMS, headers=headers, ssl=False) as res:
29582967
try:
@@ -3002,14 +3011,11 @@ async def app_get_master_plant_id(self, retry: bool = True):
30023011
await self.app_authenticate(retry=False)
30033012
except Exception as exc:
30043013
if res is not None:
3005-
_LOGGER.error(
3006-
f"APP-API: Error while access {self._SENEC_APP_GET_SYSTEMS}: '{exc}' - Response is: '{res}' [retry={retry}]")
3014+
_LOGGER.error(f"APP-API: Error while access {self._SENEC_APP_GET_SYSTEMS}: '{exc}' - Response is: '{res}' [retry={retry}]")
30073015
else:
3008-
_LOGGER.error(
3009-
f"APP-API: Error while access {self._SENEC_APP_GET_SYSTEMS}: '{exc}' [retry={retry}]")
3016+
_LOGGER.error(f"APP-API: Error while access {self._SENEC_APP_GET_SYSTEMS}: '{exc}' [retry={retry}]")
30103017
except Exception as exc:
3011-
_LOGGER.error(
3012-
f"APP-API: Error when try to call 'self.web_session.get()' for {self._SENEC_APP_GET_SYSTEMS}: '{exc}' [retry={retry}]")
3018+
_LOGGER.error(f"APP-API: Error when try to call 'self.web_session.get()' for {self._SENEC_APP_GET_SYSTEMS}: '{exc}' [retry={retry}]")
30133019
else:
30143020
if retry:
30153021
await self.app_authenticate(retry=False)
@@ -3019,7 +3025,7 @@ async def app_get_data(self, a_url: str) -> dict:
30193025
if self._app_token is not None:
30203026
_LOGGER.debug(f"APP-API get {a_url}")
30213027
try:
3022-
headers = {"Authorization": self._app_token}
3028+
headers = {"Authorization": self._app_token, "User-Agent": USER_AGENT}
30233029
async with self.web_session.get(url=a_url, headers=headers, ssl=False) as res:
30243030
res.raise_for_status()
30253031
if res.status == 200:
@@ -3075,6 +3081,12 @@ async def app_update_total(self, retry: bool = True):
30753081
# status_url = f"{self._SENEC_APP_TOTAL}" % (
30763082
# str(self._app_master_plant_id), today.strftime('%Y'), today.strftime('%m'))
30773083
status_url = f"{self._SENEC_APP_TOTAL_V2}" % (str(self._app_master_plant_id), str(int(today.timestamp())))
3084+
3085+
# 2025/06/11 [might be required later, when SENEC will shut down the old endpoints]
3086+
# today = datetime.now(timezone.utc) + relativedelta(months=+1)
3087+
# to_date = today.strftime('%Y-%m-%dT%H:%M:%SZ')
3088+
# status_url = f"{self._SENEC_APP_TOTAL_V3}" % (str(self._app_master_plant_id), to_date)
3089+
30783090
data = await self.app_get_data(a_url=status_url)
30793091
if data is not None and "measurements" in data and "timeseries" in data:
30803092
self._app_raw_total_v2 = data
@@ -3178,7 +3190,7 @@ async def app_post_data(self, a_url: str, post_data: dict, read_response: bool =
31783190
if self._app_token is not None:
31793191
_LOGGER.debug(f"APP-API post {post_data} to {a_url}")
31803192
try:
3181-
headers = {"Authorization": self._app_token}
3193+
headers = {"Authorization": self._app_token, "User-Agent": USER_AGENT}
31823194
async with self.web_session.post(url=a_url, headers=headers, json=post_data, ssl=False) as res:
31833195
res.raise_for_status()
31843196
if res.status == 200:
@@ -3402,7 +3414,7 @@ async def app_set_allow_intercharge_all(self, value_to_set: bool, sync: bool = T
34023414
async def app_get_system_abilities(self):
34033415
# 'app_get_system_abilities' not used (yet)
34043416
if self._app_master_plant_id is not None and self._app_token is not None:
3405-
headers = {"Authorization": self._app_token}
3417+
headers = {"Authorization": self._app_token, "User-Agent": USER_AGENT}
34063418
a_url = f"{self._SENEC_APP_GET_ABILITIES}" % str(self._app_master_plant_id)
34073419
async with self.web_session.get(url=a_url, headers=headers, ssl=False) as res:
34083420
res.raise_for_status()
@@ -3550,7 +3562,7 @@ async def update(self):
35503562
async def update_peak_shaving(self):
35513563
_LOGGER.info("***** update_peak_shaving(self) ********")
35523564
a_url = f"{self._SENEC_API_GET_PEAK_SHAVING}{self._master_plant_number}"
3553-
async with self.web_session.get(a_url, ssl=False) as res:
3565+
async with self.web_session.get(a_url, headers={"User-Agent": USER_AGENT}, ssl=False) as res:
35543566
try:
35553567
res.raise_for_status()
35563568
if res.status == 200:
@@ -3586,7 +3598,7 @@ async def set_peak_shaving(self, new_peak_shaving: dict):
35863598
# Senec self allways sends all get-parameter, even if not needed. So we will do it the same way
35873599
a_url = f"{self._SENEC_API_SET_PEAK_SHAVING_BASE_URL}{self._master_plant_number}&mode={new_peak_shaving['mode'].upper()}&capacityLimit={new_peak_shaving['capacity']}&endzeit={new_peak_shaving['end_time']}"
35883600

3589-
async with self.web_session.post(a_url, ssl=False) as res:
3601+
async with self.web_session.post(a_url, headers={"User-Agent": USER_AGENT}, ssl=False) as res:
35903602
try:
35913603
res.raise_for_status()
35923604
if res.status == 200:
@@ -3612,7 +3624,7 @@ async def set_peak_shaving(self, new_peak_shaving: dict):
36123624
async def update_spare_capacity(self):
36133625
_LOGGER.info("***** update_spare_capacity(self) ********")
36143626
a_url = f"{self._SENEC_API_SPARE_CAPACITY_BASE_URL}{self._master_plant_number}{self._SENEC_API_GET_SPARE_CAPACITY}"
3615-
async with self.web_session.get(a_url, ssl=False) as res:
3627+
async with self.web_session.get(a_url, headers={"User-Agent": USER_AGENT}, ssl=False) as res:
36163628
try:
36173629
res.raise_for_status()
36183630
if res.status == 200:
@@ -3639,7 +3651,7 @@ async def set_spare_capacity(self, new_spare_capacity: int):
36393651
_LOGGER.debug("***** set_spare_capacity(self) ********")
36403652
a_url = f"{self._SENEC_API_SPARE_CAPACITY_BASE_URL}{self._master_plant_number}{self._SENEC_API_SET_SPARE_CAPACITY}{new_spare_capacity}"
36413653

3642-
async with self.web_session.post(a_url, ssl=False) as res:
3654+
async with self.web_session.post(a_url, headers={"User-Agent": USER_AGENT}, ssl=False) as res:
36433655
try:
36443656
res.raise_for_status()
36453657
if res.status == 200:
@@ -3748,7 +3760,7 @@ async def update_sgready_state(self):
37483760
if self.SGREADY_SUPPORTED:
37493761
_LOGGER.info("***** update_update_sgready_state(self) ********")
37503762
a_url = f"{self._SENEC_API_GET_SGREADY_STATE}" % (str(self._master_plant_number))
3751-
async with self.web_session.get(a_url, ssl=False) as res:
3763+
async with self.web_session.get(a_url, headers={"User-Agent": USER_AGENT}, ssl=False) as res:
37523764
try:
37533765
res.raise_for_status()
37543766
if res.status == 200:
@@ -3781,7 +3793,7 @@ async def update_sgready_conf(self):
37813793
if self.SGREADY_SUPPORTED:
37823794
_LOGGER.info("***** update_update_sgready_conf(self) ********")
37833795
a_url = f"{self._SENEC_API_GET_SGREADY_CONF}" % (str(self._master_plant_number))
3784-
async with self.web_session.get(a_url, ssl=False) as res:
3796+
async with self.web_session.get(a_url, headers={"User-Agent": USER_AGENT}, ssl=False) as res:
37853797
try:
37863798
res.raise_for_status()
37873799
if res.status == 200:
@@ -3821,7 +3833,7 @@ async def set_sgready_conf(self, new_sgready_data: dict):
38213833
post_data[a_key] = self._sgready_conf_data[a_key]
38223834

38233835
if len(post_data) > 0 and post_data_to_backend:
3824-
async with self.web_session.post(a_url, ssl=False, json=post_data) as res:
3836+
async with self.web_session.post(a_url, headers={"User-Agent": USER_AGENT}, ssl=False, json=post_data) as res:
38253837
try:
38263838
res.raise_for_status()
38273839
if res.status == 200:
@@ -3867,7 +3879,7 @@ async def update_get_customer(self):
38673879
_LOGGER.debug("***** update_get_customer(self) ********")
38683880

38693881
# grab NOW and TODAY stats
3870-
async with self.web_session.get(self._SENEC_WEB_GET_CUSTOMER, ssl=False) as res:
3882+
async with self.web_session.get(self._SENEC_WEB_GET_CUSTOMER, headers={"User-Agent": USER_AGENT}, ssl=False) as res:
38713883
res.raise_for_status()
38723884
if res.status == 200:
38733885
try:
@@ -3890,7 +3902,7 @@ async def update_get_systems(self, a_plant_number: int, autodetect_mode: bool):
38903902
_LOGGER.debug("***** update_get_systems(self) ********")
38913903

38923904
a_url = f"{self._SENEC_WEB_GET_SYSTEM_INFO}" % str(a_plant_number)
3893-
async with self.web_session.get(a_url, ssl=False) as res:
3905+
async with self.web_session.get(a_url, headers={"User-Agent": USER_AGENT}, ssl=False) as res:
38943906
res.raise_for_status()
38953907
if res.status == 200:
38963908
try:
@@ -4198,7 +4210,10 @@ def system_state(self) -> str:
41984210
# 'mainControllerState': {'name': 'EIGENVERBRAUCH', 'severity': 'INFO'}, 'firmwareVersion': '123',
41994211
# 'guiVersion': 123}, 'warranty': {'endDate': 1700000000, 'warrantyTermInMonths': 123},
42004212
if self._app_raw_tech_data is not None and "mcu" in self._app_raw_tech_data:
4201-
return self._app_raw_tech_data["mcu"]["mainControllerState"]["name"].replace('_', ' ')
4213+
if "mainControllerState" in self._app_raw_tech_data["mcu"]:
4214+
return self._app_raw_tech_data["mcu"]["mainControllerState"]["name"].replace('_', ' ')
4215+
elif "mainControllerUnitState" in self._app_raw_tech_data["mcu"]:
4216+
return self._app_raw_tech_data["mcu"]["mainControllerUnitState"]["name"].replace('_', ' ')
42024217

42034218
#######################################################################################################
42044219
# 'batteryInverter': {'state': {'name': 'RUN_GRID', 'severity': 'INFO'}, 'vendor': 'XXX',
@@ -4269,28 +4284,33 @@ def battery_temp_max(self) -> float:
42694284
def _battery_module_count(self) -> int:
42704285
# internal use only...
42714286
if self._app_raw_tech_data is not None and "batteryPack" in self._app_raw_tech_data:
4272-
return self._app_raw_tech_data["batteryPack"]["numberOfBatteryModules"]
4287+
if "numberOfBatteryModules" in self._app_raw_tech_data["batteryPack"]:
4288+
return self._app_raw_tech_data["batteryPack"]["numberOfBatteryModules"]
42734289
return 0
42744290

42754291
@property
42764292
def battery_state_voltage(self) -> float:
42774293
if self._app_raw_tech_data is not None and "batteryPack" in self._app_raw_tech_data:
4278-
return self._app_raw_tech_data["batteryPack"]["currentVoltageInV"]
4294+
if "currentVoltageInV" in self._app_raw_tech_data["batteryPack"]:
4295+
return self._app_raw_tech_data["batteryPack"]["currentVoltageInV"]
42794296

42804297
@property
42814298
def battery_state_current(self) -> float:
42824299
if self._app_raw_tech_data is not None and "batteryPack" in self._app_raw_tech_data:
4283-
return self._app_raw_tech_data["batteryPack"]["currentCurrentInA"]
4300+
if "currentCurrentInA" in self._app_raw_tech_data["batteryPack"]:
4301+
return self._app_raw_tech_data["batteryPack"]["currentCurrentInA"]
42844302

42854303
@property
42864304
def _not_used_currentChargingLevelInPercent(self) -> float:
42874305
if self._app_raw_tech_data is not None and "batteryPack" in self._app_raw_tech_data:
4288-
return self._app_raw_tech_data["batteryPack"]["currentChargingLevelInPercent"]
4306+
if "currentChargingLevelInPercent" in self._app_raw_tech_data["batteryPack"]:
4307+
return self._app_raw_tech_data["batteryPack"]["currentChargingLevelInPercent"]
42894308

42904309
@property
42914310
def battery_soh_remaining_capacity(self) -> float:
42924311
if self._app_raw_tech_data is not None and "batteryPack" in self._app_raw_tech_data:
4293-
return self._app_raw_tech_data["batteryPack"]["remainingCapacityInPercent"]
4312+
if "remainingCapacityInPercent" in self._app_raw_tech_data["batteryPack"]:
4313+
return self._app_raw_tech_data["batteryPack"]["remainingCapacityInPercent"]
42944314

42954315
#######################################################################################################
42964316
# 'batteryModules': [{'ordinal': 1, 'state': {'state': 'OK', 'severity': 'INFO'}, 'vendor': 'XXX',

0 commit comments

Comments
 (0)