From fa7ba523d6610112d189a1402f12f4b766f4171d Mon Sep 17 00:00:00 2001 From: marq24 Date: Sat, 23 Sep 2023 23:33:38 +0200 Subject: [PATCH] - support for II Wallbox - make lala.cgi requests also dependant from active sensors (currently only wallbox) --- custom_components/senec/__init__.py | 19 +++- custom_components/senec/const.py | 26 +++++ .../senec/pysenec_ha/__init__.py | 107 ++++++++++++------ 3 files changed, 117 insertions(+), 35 deletions(-) diff --git a/custom_components/senec/__init__.py b/custom_components/senec/__init__.py index 76b23ee..2518394 100644 --- a/custom_components/senec/__init__.py +++ b/custom_components/senec/__init__.py @@ -34,6 +34,8 @@ CONF_SYSTYPE_INVERTER, CONF_SYSTYPE_WEB, CONF_DEV_MASTER_NUM, + MAIN_SENSOR_TYPES, + QUERY_WALLBOX_KEY, QUERY_SPARE_CAPACITY_KEY, ) @@ -141,7 +143,22 @@ def __init__(self, hass: HomeAssistant, session, config_entry): else: self._use_https = False - self.senec = Senec(host=self._host, use_https=self._use_https, websession=session) + # check if any of the wallbox-sensors is enabled... and only THEN + # we will include the 'WALLBOX' in our POST to the lala.cgi + opt = {QUERY_WALLBOX_KEY: False} + if hass is not None and config_entry.title is not None: + registry = entity_registry.async_get(hass) + if registry is not None: + sluged_title = slugify(config_entry.title) + for description in MAIN_SENSOR_TYPES: + if not opt[QUERY_WALLBOX_KEY] and 'wallbox_' in description.key: + a_sensor_id = f"sensor.{sluged_title}_{description.key}" + a_entity = registry.async_get(a_sensor_id) + if a_entity is not None and a_entity.disabled_by is None: + _LOGGER.info("***** QUERY_WALLBOX-DATA ********") + opt[QUERY_WALLBOX_KEY] = True + + self.senec = Senec(host=self._host, use_https=self._use_https, websession=session, options=opt) self.name = config_entry.title self._config_entry = config_entry diff --git a/custom_components/senec/const.py b/custom_components/senec/const.py index 4c72ace..413f9e8 100644 --- a/custom_components/senec/const.py +++ b/custom_components/senec/const.py @@ -69,6 +69,7 @@ DEFAULT_SCAN_INTERVAL_WEB = 300 DEFAULT_SCAN_INTERVAL_WEB_SENECV4 = 60 +QUERY_WALLBOX_KEY = "query_wallbox_data" QUERY_SPARE_CAPACITY_KEY = "query_spare_capacity" @dataclass @@ -1590,6 +1591,31 @@ class ExtBinarySensorEntityDescription(BinarySensorEntityDescription): device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), + ExtSensorEntityDescription( + entity_registry_enabled_default=False, + key="wallbox_2_power", + name="Wallbox II Power", + native_unit_of_measurement=POWER_WATT, + icon="mdi:car-arrow-left", + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + ), + ExtSensorEntityDescription( + entity_registry_enabled_default=False, + key="wallbox_2_ev_connected", + name="Wallbox II EV Connected", + icon="mdi:car-electric", + ), + ExtSensorEntityDescription( + entity_registry_enabled_default=False, + controls=("require_stats_fields"), + key="wallbox_2_energy", + name="Wallbox II charged", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + icon="mdi:ev-station", + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), ] INVERTER_SENSOR_TYPES = [ diff --git a/custom_components/senec/pysenec_ha/__init__.py b/custom_components/senec/pysenec_ha/__init__.py index c33b0fd..1c1cc14 100644 --- a/custom_components/senec/pysenec_ha/__init__.py +++ b/custom_components/senec/pysenec_ha/__init__.py @@ -16,8 +16,8 @@ from yarl import URL from typing import Union, cast - from custom_components.senec.const import ( + QUERY_WALLBOX_KEY, QUERY_SPARE_CAPACITY_KEY, ) @@ -46,7 +46,16 @@ class Senec: """Senec Home Battery Sensor""" - def __init__(self, host, use_https, websession): + def __init__(self, host, use_https, websession, options: dict = None): + _LOGGER.info("restarting Senec... with options: " + str(options)) + + self._QUERY_BMS = True + self._QUERY_STATS = True + if options is not None and QUERY_WALLBOX_KEY in options: + self._QUERY_WALLBOX = options[QUERY_WALLBOX_KEY] + else: + self._QUERY_WALLBOX = False + self.host = host self.websession: aiohttp.websession = websession if use_https: @@ -865,6 +874,31 @@ def wallbox_energy(self) -> float: if hasattr(self, '_raw') and "STATISTIC" in self._raw and "LIVE_WB_ENERGY" in self._raw["STATISTIC"]: return self._raw["STATISTIC"]["LIVE_WB_ENERGY"][0] + @property + def wallbox_2_power(self) -> float: + """ + Wallbox Total Charging Power (W) + Derived from the 3 phase voltages multiplied with the phase currents from the wallbox + """ + return self._raw["WALLBOX"]["L1_CHARGING_CURRENT"][1] * self._raw["PM1OBJ1"]["U_AC"][0] + \ + self._raw["WALLBOX"]["L2_CHARGING_CURRENT"][1] * self._raw["PM1OBJ1"]["U_AC"][1] + \ + self._raw["WALLBOX"]["L3_CHARGING_CURRENT"][1] * self._raw["PM1OBJ1"]["U_AC"][2] + + @property + def wallbox_2_ev_connected(self) -> bool: + """ + Wallbox EV Connected + """ + return self._raw["WALLBOX"]["EV_CONNECTED"][1] + + @property + def wallbox_2_energy(self) -> float: + """ + Wallbox Total Energy + """ + if hasattr(self, '_raw') and "STATISTIC" in self._raw and "LIVE_WB_ENERGY" in self._raw["STATISTIC"]: + return self._raw["STATISTIC"]["LIVE_WB_ENERGY"][1] + @property def fan_inv_lv(self) -> bool: if hasattr(self, '_raw') and "FAN_SPEED" in self._raw and "INV_LV" in self._raw["FAN_SPEED"]: @@ -901,15 +935,15 @@ async def read_senec_v31(self): "SAFE_CHARGE_RUNNING": "", "LI_STORAGE_MODE_RUNNING": "", }, - "STATISTIC": { - "LIVE_BAT_CHARGE": "", - "LIVE_BAT_DISCHARGE": "", - "LIVE_GRID_EXPORT": "", - "LIVE_GRID_IMPORT": "", - "LIVE_HOUSE_CONS": "", - "LIVE_PV_GEN": "", - "LIVE_WB_ENERGY": "", - }, + # "STATISTIC": { + # "LIVE_BAT_CHARGE": "", + # "LIVE_BAT_DISCHARGE": "", + # "LIVE_GRID_EXPORT": "", + # "LIVE_GRID_IMPORT": "", + # "LIVE_HOUSE_CONS": "", + # "LIVE_PV_GEN": "", + # "LIVE_WB_ENERGY": "", + # }, "TEMPMEASURE": { "BATTERY_TEMP": "", "CASE_TEMP": "", @@ -925,29 +959,35 @@ async def read_senec_v31(self): "PWR_UNIT": {"POWER_L1": "", "POWER_L2": "", "POWER_L3": ""}, "PM1OBJ1": {"FREQ": "", "U_AC": "", "I_AC": "", "P_AC": "", "P_TOTAL": ""}, "PM1OBJ2": {"FREQ": "", "U_AC": "", "I_AC": "", "P_AC": "", "P_TOTAL": ""}, - "BMS": { - "CELL_TEMPERATURES_MODULE_A": "", - "CELL_TEMPERATURES_MODULE_B": "", - "CELL_TEMPERATURES_MODULE_C": "", - "CELL_TEMPERATURES_MODULE_D": "", - "CELL_VOLTAGES_MODULE_A": "", - "CELL_VOLTAGES_MODULE_B": "", - "CELL_VOLTAGES_MODULE_C": "", - "CELL_VOLTAGES_MODULE_D": "", - "CURRENT": "", - "VOLTAGE": "", - "SOC": "", - "SOH": "", - "CYCLES": "", - }, - "WALLBOX": { - "L1_CHARGING_CURRENT": "", - "L2_CHARGING_CURRENT": "", - "L3_CHARGING_CURRENT": "", - "EV_CONNECTED": "" - }, + # "BMS": { + # "CELL_TEMPERATURES_MODULE_A": "", + # "CELL_TEMPERATURES_MODULE_B": "", + # "CELL_TEMPERATURES_MODULE_C": "", + # "CELL_TEMPERATURES_MODULE_D": "", + # "CELL_VOLTAGES_MODULE_A": "", + # "CELL_VOLTAGES_MODULE_B": "", + # "CELL_VOLTAGES_MODULE_C": "", + # "CELL_VOLTAGES_MODULE_D": "", + # "CURRENT": "", + # "VOLTAGE": "", + # "SOC": "", + # "SOH": "", + # "CYCLES": "", + # }, + # "WALLBOX": { + # "L1_CHARGING_CURRENT": "", + # "L2_CHARGING_CURRENT": "", + # "L3_CHARGING_CURRENT": "", + # "EV_CONNECTED": "" + # }, "FAN_SPEED": {}, } + if self._QUERY_BMS: + form.update({"BMS": {}}) + if self._QUERY_STATS: + form.update({"STATISTIC": {}}) + if self._QUERY_WALLBOX: + form.update({"WALLBOX": {}}) async with self.websession.post(self.url, json=form, ssl=False) as res: res.raise_for_status() @@ -1013,7 +1053,6 @@ async def read_senec_v21_all(self): "BAT1": {}, "BAT1OBJ1": {}, "BAT1OBJ2": {}, - "BAT1OBJ2": {}, "BAT1OBJ3": {}, "BAT1OBJ4": {}, "PWR_UNIT": {}, @@ -1351,7 +1390,7 @@ def derating(self) -> float: class MySenecWebPortal: def __init__(self, user, pwd, websession, master_plant_number: int = 0, options: dict = None): - _LOGGER.info("restarting... with options: "+str(options)) + _LOGGER.info("restarting MySenecWebPortal... with options: " + str(options)) if options is not None and QUERY_SPARE_CAPACITY_KEY in options: self._QUERY_SPARE_CAPACITY = options[QUERY_SPARE_CAPACITY_KEY]