diff --git a/src/eos_connect.py b/src/eos_connect.py index a8620ba..201f898 100644 --- a/src/eos_connect.py +++ b/src/eos_connect.py @@ -237,6 +237,7 @@ def mqtt_control_callback(command): load_interface = LoadInterface( config_manager.config.get("load", {}), time_zone, + evcc_interface, ) battery_interface = BatteryInterface( diff --git a/src/interfaces/evcc_interface.py b/src/interfaces/evcc_interface.py index f40a9e8..b34d72c 100644 --- a/src/interfaces/evcc_interface.py +++ b/src/interfaces/evcc_interface.py @@ -87,23 +87,6 @@ def __init__( self.last_known_charging_state = False # off, pv, pvmin, now self.last_known_charging_mode = None - self.current_detail_data_list = [ - { - "connected": False, - "charging": False, - "mode": "off", - "chargeDuration": 0, - "chargeRemainingDuration": 0, - "chargedEnergy": 0, - "chargeRemainingEnergy": 0, - "sessionEnergy": 0, - "vehicleSoc": 0, - "vehicleRange": 0, - "vehicleOdometer": 0, - "vehicleName": "", - "smartCostActive": False, - } - ] self.external_battery_mode_en = ext_bat_mode self.external_battery_mode = "off" # Default mode @@ -212,6 +195,8 @@ def __get_default_detail_data(self): "vehicleOdometer": 0, "vehicleName": "", "smartCostActive": False, + "planProjectedStart": "", + "planProjectedEnd": "", } ] @@ -418,6 +403,8 @@ def __get_states_of_loadpoints(self, loadpoints, vehicles): "vehicleOdometer": loadpoint.get("vehicleOdometer", 0), "vehicleName": vehicle_name, "smartCostActive": loadpoint.get("smartCostActive", False), + "planProjectedStart": loadpoint.get("planProjectedStart", ""), + "planProjectedEnd": loadpoint.get("planProjectedEnd", ""), } self.current_detail_data_list.append(detail_data) return True diff --git a/src/interfaces/load_interface.py b/src/interfaces/load_interface.py index 32ab3a7..5bf0eee 100644 --- a/src/interfaces/load_interface.py +++ b/src/interfaces/load_interface.py @@ -11,6 +11,8 @@ import requests import pytz +from interfaces.evcc_interface import EvccInterface + logger = logging.getLogger("__main__") logger.info("[LOAD-IF] loading module ") @@ -26,7 +28,9 @@ def __init__( self, config, timezone=None, # Changed default to None + evcc_interface: EvccInterface = None, ): + self.evcc_interface = evcc_interface self.src = config.get("source", "") self.url = config.get("url", "") self.load_sensor = config.get("load_sensor", "") @@ -56,6 +60,26 @@ def __init__( self.__check_config() + def __parse_iso_time_timezone(self, time_str): + time = datetime.fromisoformat(time_str) + + # If no timezone is configured, return the time as-is + if self.time_zone is None: + return time + + if time.tzinfo is None: + # If datetime is naive, localize it + if hasattr(self.time_zone, 'localize'): + # pytz timezone object + time = self.time_zone.localize(time) + else: + # zoneinfo.ZoneInfo object + time = time.replace(tzinfo=self.time_zone) + else: + # Convert to configured timezone + time = time.astimezone(self.time_zone) + return time + def __check_config(self): """ Checks if the configuration is valid. @@ -615,6 +639,32 @@ def __create_load_profile_weekdays(self): + " will improve with collected data" ) + if self.evcc_interface is not None: + # use projected charge start and end time to calculate car load + evcc_detail = self.evcc_interface.get_current_detail_data() + try: + charge_start = self.__parse_iso_time_timezone(evcc_detail[0]["planProjectedStart"]) + charge_end = self.__parse_iso_time_timezone(evcc_detail[0]["planProjectedEnd"]) + charge_amount = evcc_detail[0].get("chargeRemainingEnergy", 0) + charge_duration = ((charge_end - charge_start).total_seconds() + 3599) / 3600 + charge_per_hour = charge_amount / charge_duration if charge_duration > 0 else 0 + day_start = now.replace(hour=0, minute=0, second=0, microsecond=0) + start_index = int((charge_start - day_start).total_seconds() / 3600) + end_index = start_index + int(charge_duration) + for i in range(start_index, end_index): + if 0 <= i < len(load_profile): + load_profile[i] += charge_per_hour + logger.debug( + "[LOAD-IF] Adding projected EV charge load of %5.1f Wh at hour index %d", + charge_per_hour, i + ) + logger.info( + "[LOAD-IF] Adjusted load profile for projected EV charging from %s to %s", + charge_start, charge_end + ) + except: + pass + logger.debug("[LOAD-IF] Load profile values : %s", load_profile) return load_profile def get_load_profile(self, tgt_duration, start_time=None):