diff --git a/packages/control/consumer/consumer.py b/packages/control/consumer/consumer.py index 03499cc25f..a7819344f6 100644 --- a/packages/control/consumer/consumer.py +++ b/packages/control/consumer/consumer.py @@ -1,5 +1,6 @@ +import datetime import logging -from typing import Callable, Optional, Tuple +from typing import Callable, Dict, List, Optional, Tuple, Union from control import data from control.algorithm.utils import get_medium_charging_current from control.chargemode import Chargemode @@ -7,6 +8,7 @@ from control.consumer.consumer_data import ConsumerData, ConsumerUsage, MeterOnlyConfig, ResetModes, WaitForStartStates from control.load_protocol import Load from helpermodules import timecheck +from helpermodules.abstract_plans import ContinuousScheduledPlanConsumer, SuspendableScheduledPlanConsumer from helpermodules.phase_handling import convert_single_evu_phase_to_cp_phase, voltages_mean from modules.common.configurable_consumer import ConfigurableConsumer @@ -54,10 +56,23 @@ def update(self): self.set_mode_changed(submode, mode) self.set_control_parameter(min_current, required_current, self.data.config.connected_phases, submode, mode) self.set_state_and_log(message) + self.process_on_time() log.debug( f"Verbraucher {self.num}: Sollstrom {required_current}, min. Ist-Strom {max(self.data.get.currents)}," f" Modus {mode}, Submodus {submode}, {message}") + def process_on_time(self): + if self.data.control_parameter.timestamp_charge_start is None: + if max(self.data.get.currents) > self.data.config.min_current: + self.data.control_parameter.timestamp_charge_start = timecheck.create_timestamp() + elif max(self.data.get.currents) < self.data.config.min_current: + self.reset_timestamp_start() + else: + self.data.set.on_time = timecheck.create_timestamp() - self.data.control_parameter.timestamp_charge_start + + def reset_timestamp_start(self): + self.data.control_parameter.timestamp_charge_start = None + PRICE_LIMIT_EXCEEDED = "Preislimit für Verbraucher aktiv, aktueller Preis zu hoch." PRICE_LIMIT_EXCEEDED_CONTINOUS_STILL_RUNNING = ("Preislimit für Verbraucher aktiv, aktueller Preis zu hoch. " "Verbraucher läuft weiter, da der Verbraucher nicht abgeschaltet " @@ -67,7 +82,7 @@ def update(self): "Verbraucher nicht abgeschaltet werden darf.") def get_parameter(self) -> Tuple[int, int, Optional[str], Chargemode, Chargemode]: - if timecheck.create_timestamp() < self.data.set.timestamp_last_current_set + self.data.config.min_intervall: + if timecheck.create_timestamp() < self.data.set.timestamp_last_current_set + self.data.config.min_interval: log.debug("Intervall für neuen Schaltbefehl nicht abgelaufen.") return (self.data.control_parameter.min_current, self.data.control_parameter.required_current, @@ -111,170 +126,169 @@ def _convert_power_to_current(self, power: float) -> float: BUFFER_START_EARLIER = 600 # 10 Min vor dem geplanten Start kann begonnen werden def scheduled_charging(self) -> Tuple[Chargemode, str]: - return self.data.config.min_current, "Zielladen noch nicht implementiert", Chargemode.STOP, Chargemode.STOP plan_data, remaining_time, duration = self._find_recent_plan(self.data.usage.scheduled_charging.plans) if plan_data: - self.data.config.control_parameter.current_plan = plan_data.id + self.data.control_parameter.current_plan = plan_data.id else: - self.data.config.control_parameter.current_plan = None + self.data.control_parameter.current_plan = None return self.scheduled_charging_calc_current( plan_data, remaining_time, duration) - # def _find_recent_plan(self, - # plans: List[Union[ContinuousScheduledPlanConsumer, SuspendableScheduledPlanConsumer]]): - # plans_diff_end_date = [] - # for p in plans: - # try: - # plans_diff_end_date.append( - # {p.id: timecheck.check_end_time(p, self.BUFFER_AFTER_END_TIME)}) - # log.debug(f"Verbleibende Zeit bis zum Zieltermin [s]: {plans_diff_end_date}") - # except Exception: - # log.exception("Fehler im ev-Modul "+str(self.data.id)) - # if plans_diff_end_date: - # # ermittle den Key vom kleinsten value in plans_diff_end_date - # filtered_plans = [d for d in plans_diff_end_date if list(d.values())[0] is not None] - # if filtered_plans: - # sorted_plans = sorted(filtered_plans, key=lambda x: list(x.values())[0]) - # for plan in sorted_plans: - # if self.BUFFER_AFTER_END_TIME < list(plan.values())[0]: - # plan_dict = plan - # break - # else: - # return None - # plan_id = list(plan_dict.keys())[0] - # diff_end_date = list(plan_dict.values())[0] - - # for p in plans: - # if p.id == plan_id: - # plan = p - - # remaining_time, duration = self._calc_remaining_time(plan, diff_end_date) - - # return plan, remaining_time, duration - # else: - # return None - - # def _calc_remaining_time(self, - # plan: Union[ContinuousScheduledPlanConsumer, SuspendableScheduledPlanConsumer], - # diff_end_date: float) -> Tuple[float, float]: - # duration = plan.duration - self.data.set.log.on_time - # remaining_time = diff_end_date - duration - - # log.debug(f"Verbleibende Zeit bis zum Ladestart [s]:{remaining_time}, Dauer [h]: {duration/3600}") - # return remaining_time, duration - - # SCHEDULED_REACHED_MAX_ON_TIME = ("Zielladen abgeschlossen, da die geplante Ladedauer bereits überschritten " - # "wurde.") - # SCHEDULED_CHARGING_REACHED_MAX_AND_LIMIT_SOC = ( - # "Zielladen abgeschlossen, da das Limit für Fahrzeug Laden mit Überschuss (SoC-Limit)" - # " sowie der Fahrzeug-SoC (Ziel-SoC) bereits erreicht wurde. ") - # SCHEDULED_CHARGING_REACHED_AMOUNT = "Zielladen abgeschlossen, da die Energiemenge bereits erreicht wurde. " - # SCHEDULED_CHARGING_REACHED_SCHEDULED_SOC = ("Falls vorhanden wird mit EVU-Überschuss geladen, da der Ziel-Soc " - # "für Zielladen bereits erreicht wurde. ") - # SCHEDULED_CHARGING_BIDI = ("Der Ziel-Soc für Zielladen wurde bereits erreicht. Das Auto wird " - # "bidirektional ge-/entladen, sodass möglichst weder Bezug noch " - # "Einspeisung erfolgt. ") - # SCHEDULED_CHARGING_NO_PLANS_CONFIGURED = "Kein Zielladen, da keine Ziel-Termine konfiguriert sind." - # SCHEDULED_CHARGING_NO_DATE_PENDING = "Kein Zielladen, da kein Ziel-Termin ansteht. " - # SCHEDULED_CHARGING_USE_PV = "Zielladen startet {}. Falls vorhanden, wird mit Überschuss geladen. " - # SCHEDULED_CHARGING_MAX_CURRENT = "Zielladen mit {}A. Der Ladestrom wurde erhöht, um das Ziel zu erreichen. " - # SCHEDULED_CHARGING_LIMITED_BY_SOC = 'einen SoC von {}%' - # SCHEDULED_CHARGING_LIMITED_BY_AMOUNT = '{}kWh geladene Energie' - # SCHEDULED_CHARGING_IN_TIME = ('Zielladen mit mindestens {}A, um {} um {} zu erreichen. Falls vorhanden wird ' - # 'zusätzlich EVU-Überschuss geladen. ') - # SCHEDULED_CHARGING_CHEAP_HOUR = "Zielladen, da ein günstiger Zeitpunkt zum preisbasierten Laden ist. {}" - # SCHEDULED_CHARGING_EXPENSIVE_HOUR = ( - # "Zielladen ausstehend, da jetzt kein günstiger Zeitpunkt zum preisbasierten " - # "Laden ist. {} Falls vorhanden, wird mit Überschuss geladen. ") - # SCHEDULED_CHARGING_EXPENSIVE_HOUR_REACHED_MAX_SOC = ( - # "Zielladen ausstehend, da jetzt kein günstiger Zeitpunkt zum preisbasierten " - # "Laden ist. {} " + - # "Kein Zielladen mit Überschuss, da das SoC-Limit für Überschuss-Laden erreicht wurde.") - - # def scheduled_charging_calc_current(self, - # plan: Optional[Union[ContinuousScheduledPlanConsumer, - # SuspendableScheduledPlanConsumer]], - # remaining_time: float, - # duration: float) -> Tuple[str, str]: - # submode = "stop" - # if plan is None: - # if len(self.data.usage.scheduled_charging.plans) == 0: - # return submode, self.SCHEDULED_CHARGING_NO_PLANS_CONFIGURED - # else: - # return submode, self.SCHEDULED_CHARGING_NO_DATE_PENDING - # if self.data.set.log.on_time >= duration: - # message = self.SCHEDULED_REACHED_MAX_ON_TIME - # elif (0 < remaining_time < self.BUFFER_START_EARLIER): - # submode = "instant_charging" - # # weniger als die berechnete Zeit verfügbar - # elif remaining_time <= 0: - # submode = "instant_charging" - # else: - # # Wenn dynamische Tarife aktiv sind, prüfen, ob jetzt ein günstiger Zeitpunkt zum Laden - # # ist. - # if plan.et_active: - # def get_hours_message() -> str: - # def end_of_today_timestamp() -> int: - # return datetime.datetime.now().replace( - # hour=23, minute=59, second=59, microsecond=999000).timestamp() - - # def is_loading_hour(hour: int) -> bool: - # return data.data.optional_data.ep_is_charging_allowed_hours_list(hour) - - # def convert_loading_hours_to_string(hour_list: List[int]) -> str: - # if 1 < len(hour_list): - # times_string = ", ".join(hour.strftime('%-H:%M') for hour in hour_list[:-1]) - # return times_string + " und " + hour_list[-1].strftime('%-H:%M') - # else: - # return ", ".join(hour.strftime('%-H:%M') for hour in hour_list) - # midnight = end_of_today_timestamp() - # loading_times_today = [datetime.datetime.fromtimestamp(hour) - # for hour in sorted(hour_list) if hour <= midnight] - # loading_times_today = (loading_times_today[1:] - # if is_loading_hour(hour_list) else loading_times_today) - # loading_times_tomorrow = [datetime.datetime.fromtimestamp(hour) - # for hour in sorted(hour_list) if hour > midnight] - - # parts = [] - - # if is_loading_hour(hour_list): - # parts.append("jetzt") - - # if 0 < len(loading_times_today): - # if parts: - # parts.append(" und ") - # parts.append(f"heute {convert_loading_hours_to_string(loading_times_today)}") - - # if 0 < len(loading_times_tomorrow): - # if parts: - # parts.append(" sowie ") - # parts.append(f"morgen {convert_loading_hours_to_string(loading_times_tomorrow)}") - - # loading_message = "Geladen wird " + "".join(parts) - # return loading_message + '.' - - # hour_list = data.data.optional_data.ep_get_loading_hours(duration, duration + remaining_time) - - # log.debug(f"Günstige Ladezeiten: {hour_list}") - # if data.data.optional_data.ep_is_charging_allowed_hours_list(hour_list): - # message = self.SCHEDULED_CHARGING_CHEAP_HOUR.format(get_hours_message()) - # submode = "instant_charging" - # else: - # message = self.SCHEDULED_CHARGING_EXPENSIVE_HOUR.format(get_hours_message()) - # submode = "pv_charging" - # else: - # now = datetime.datetime.today() - # start_time = now + datetime.timedelta(seconds=remaining_time) - # if start_time.year == now.year and start_time.month == now.month and start_time.day == now.day: - # message = self.SCHEDULED_CHARGING_USE_PV.format( - # f"um {start_time.strftime('%-H:%M')} Uhr") - # else: - # message = self.SCHEDULED_CHARGING_USE_PV.format( - # f"am {start_time.strftime('%d.%m')} um {start_time.strftime('%-H:%M')} Uhr") - # submode = "pv_charging" - # return submode, message + def _find_recent_plan(self, + plans: List[Union[ContinuousScheduledPlanConsumer, SuspendableScheduledPlanConsumer]]): + plans_diff_end_date: List[Dict[int, float]] = [] + for p in plans: + try: + plans_diff_end_date.append( + {p.id: timecheck.check_end_time(p, self.BUFFER_AFTER_END_TIME)}) + log.debug(f"Verbleibende Zeit bis zum Zieltermin [s]: {plans_diff_end_date}") + except Exception: + log.exception("Fehler im ev-Modul "+str(self.num)) + if plans_diff_end_date: + # ermittle den Key vom kleinsten value in plans_diff_end_date + filtered_plans = [d for d in plans_diff_end_date if list(d.values())[0] is not None] + if filtered_plans: + sorted_plans = sorted(filtered_plans, key=lambda x: list(x.values())[0]) + for plan in sorted_plans: + if self.BUFFER_AFTER_END_TIME < list(plan.values())[0]: + plan_dict = plan + break + else: + return None + plan_id = list(plan_dict.keys())[0] + diff_end_date = list(plan_dict.values())[0] + + for p in plans: + if p.id == plan_id: + plan = p + + remaining_time, duration = self._calc_remaining_time(plan, diff_end_date) + + return plan, remaining_time, duration + else: + return None + + def _calc_remaining_time(self, + plan: Union[ContinuousScheduledPlanConsumer, SuspendableScheduledPlanConsumer], + diff_end_date: float) -> Tuple[float, float]: + duration = plan.duration - self.data.set.on_time + remaining_time = diff_end_date - duration + + log.debug(f"Verbleibende Zeit bis zum Ladestart [s]:{remaining_time}, Dauer [h]: {duration/3600}") + return remaining_time, duration + + SCHEDULED_REACHED_MAX_ON_TIME = ("Zielladen abgeschlossen, da die geplante Ladedauer bereits überschritten " + "wurde.") + SCHEDULED_CHARGING_REACHED_MAX_AND_LIMIT_SOC = ( + "Zielladen abgeschlossen, da das Limit für Fahrzeug Laden mit Überschuss (SoC-Limit)" + " sowie der Fahrzeug-SoC (Ziel-SoC) bereits erreicht wurde. ") + SCHEDULED_CHARGING_REACHED_AMOUNT = "Zielladen abgeschlossen, da die Energiemenge bereits erreicht wurde. " + SCHEDULED_CHARGING_REACHED_SCHEDULED_SOC = ("Falls vorhanden wird mit EVU-Überschuss geladen, da der Ziel-Soc " + "für Zielladen bereits erreicht wurde. ") + SCHEDULED_CHARGING_BIDI = ("Der Ziel-Soc für Zielladen wurde bereits erreicht. Das Auto wird " + "bidirektional ge-/entladen, sodass möglichst weder Bezug noch " + "Einspeisung erfolgt. ") + SCHEDULED_CHARGING_NO_PLANS_CONFIGURED = "Kein Zielladen, da keine Ziel-Termine konfiguriert sind." + SCHEDULED_CHARGING_NO_DATE_PENDING = "Kein Zielladen, da kein Ziel-Termin ansteht. " + SCHEDULED_CHARGING_USE_PV = "Zielladen startet {}. Falls vorhanden, wird mit Überschuss geladen. " + SCHEDULED_CHARGING_MAX_CURRENT = "Zielladen mit {}A. Der Ladestrom wurde erhöht, um das Ziel zu erreichen. " + SCHEDULED_CHARGING_LIMITED_BY_SOC = 'einen SoC von {}%' + SCHEDULED_CHARGING_LIMITED_BY_AMOUNT = '{}kWh geladene Energie' + SCHEDULED_CHARGING_IN_TIME = ('Zielladen mit mindestens {}A, um {} um {} zu erreichen. Falls vorhanden wird ' + 'zusätzlich EVU-Überschuss geladen. ') + SCHEDULED_CHARGING_CHEAP_HOUR = "Zielladen, da ein günstiger Zeitpunkt zum preisbasierten Laden ist. {}" + SCHEDULED_CHARGING_EXPENSIVE_HOUR = ( + "Zielladen ausstehend, da jetzt kein günstiger Zeitpunkt zum preisbasierten " + "Laden ist. {} Falls vorhanden, wird mit Überschuss geladen. ") + SCHEDULED_CHARGING_EXPENSIVE_HOUR_REACHED_MAX_SOC = ( + "Zielladen ausstehend, da jetzt kein günstiger Zeitpunkt zum preisbasierten " + "Laden ist. {} " + + "Kein Zielladen mit Überschuss, da das SoC-Limit für Überschuss-Laden erreicht wurde.") + + def scheduled_charging_calc_current(self, + plan: Optional[Union[ContinuousScheduledPlanConsumer, + SuspendableScheduledPlanConsumer]], + remaining_time: float, + duration: float) -> Tuple[str, str]: + submode = "stop" + if plan is None: + if len(self.data.usage.scheduled_charging.plans) == 0: + return submode, self.SCHEDULED_CHARGING_NO_PLANS_CONFIGURED + else: + return submode, self.SCHEDULED_CHARGING_NO_DATE_PENDING + if self.data.set.on_time >= duration: + message = self.SCHEDULED_REACHED_MAX_ON_TIME + elif (0 < remaining_time < self.BUFFER_START_EARLIER): + submode = "instant_charging" + # weniger als die berechnete Zeit verfügbar + elif remaining_time <= 0: + submode = "instant_charging" + else: + # Wenn dynamische Tarife aktiv sind, prüfen, ob jetzt ein günstiger Zeitpunkt zum Laden + # ist. + if data.data.optional_data.data.electricity_pricing.configured: + def get_hours_message() -> str: + def end_of_today_timestamp() -> int: + return datetime.datetime.now().replace( + hour=23, minute=59, second=59, microsecond=999000).timestamp() + + def is_loading_hour(hour: int) -> bool: + return data.data.optional_data.ep_is_charging_allowed_hours_list(hour) + + def convert_loading_hours_to_string(hour_list: List[int]) -> str: + if 1 < len(hour_list): + times_string = ", ".join(hour.strftime('%-H:%M') for hour in hour_list[:-1]) + return times_string + " und " + hour_list[-1].strftime('%-H:%M') + else: + return ", ".join(hour.strftime('%-H:%M') for hour in hour_list) + midnight = end_of_today_timestamp() + loading_times_today = [datetime.datetime.fromtimestamp(hour) + for hour in sorted(hour_list) if hour <= midnight] + loading_times_today = (loading_times_today[1:] + if is_loading_hour(hour_list) else loading_times_today) + loading_times_tomorrow = [datetime.datetime.fromtimestamp(hour) + for hour in sorted(hour_list) if hour > midnight] + + parts = [] + + if is_loading_hour(hour_list): + parts.append("jetzt") + + if 0 < len(loading_times_today): + if parts: + parts.append(" und ") + parts.append(f"heute {convert_loading_hours_to_string(loading_times_today)}") + + if 0 < len(loading_times_tomorrow): + if parts: + parts.append(" sowie ") + parts.append(f"morgen {convert_loading_hours_to_string(loading_times_tomorrow)}") + + loading_message = "Geladen wird " + "".join(parts) + return loading_message + '.' + + hour_list = data.data.optional_data.ep_get_loading_hours(duration, duration + remaining_time) + + log.debug(f"Günstige Ladezeiten: {hour_list}") + if data.data.optional_data.ep_is_charging_allowed_hours_list(hour_list): + message = self.SCHEDULED_CHARGING_CHEAP_HOUR.format(get_hours_message()) + submode = "instant_charging" + else: + message = self.SCHEDULED_CHARGING_EXPENSIVE_HOUR.format(get_hours_message()) + submode = "pv_charging" + else: + now = datetime.datetime.today() + start_time = now + datetime.timedelta(seconds=remaining_time) + if start_time.year == now.year and start_time.month == now.month and start_time.day == now.day: + message = self.SCHEDULED_CHARGING_USE_PV.format( + f"um {start_time.strftime('%-H:%M')} Uhr") + else: + message = self.SCHEDULED_CHARGING_USE_PV.format( + f"am {start_time.strftime('%d.%m')} um {start_time.strftime('%-H:%M')} Uhr") + submode = "pv_charging" + return submode, message TIME_CHARGING_MIN_BAT_SOC_REACHED = ("Betrieb mit Zeitladen nach Speicher-SoC nicht möglich, da der SoC des" " Speichers unter dem minimalen SoC liegt.") @@ -400,6 +414,11 @@ def wait_for_start_handler( def reset_wait_for_start(self): self.data.set.wait_for_start_state = WaitForStartStates.WAIT_FOR_DEVICE_START + def midnight_handler(self): + self.reset_chargemode_at_midnight() + self.reset_wait_for_start() + self.reset_timestamp_start() + def reset_chargemode_at_midnight(self): if self.data.usage.reset_chargemode.mode == ResetModes.MIDNIGHT: if self.data.usage.chargemode != self.data.usage.reset_chargemode.chargemode: diff --git a/packages/control/consumer/consumer_data.py b/packages/control/consumer/consumer_data.py index fb765d43a8..3ec679609f 100644 --- a/packages/control/consumer/consumer_data.py +++ b/packages/control/consumer/consumer_data.py @@ -13,7 +13,7 @@ @dataclass class EcoCharging: - price_limit: float = 0.07 + price_limit: float = 0.0002 @dataclass @@ -125,7 +125,7 @@ class ConsumerConfig: phase_1: int = 1 max_power: float = 5000 min_current: float = 0.5 - min_intervall: int = 60 + min_interval: int = 60 @dataclass @@ -158,6 +158,7 @@ class Set: target_current: float = 0 charge_state_prev: bool = False log: Log = field(default_factory=lambda: Log()) + on_time: float = field(default=0, metadata={"topic": "set/on_time"}) power: Optional[float] = None timestamp_last_current_set: float = field(default=0, metadata={"topic": "set/timestamp_last_current_set"}) wait_for_start_state: WaitForStartStates = field( diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index a80c0157b9..5962b767d8 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -1250,6 +1250,7 @@ def process_consumer_topic(self, msg): elif (re.search("consumer/[0-9]+/get/power$", msg.topic) is not None or re.search("consumer/[0-9]+/set/power$", msg.topic) is not None or re.search("consumer/[0-9]+/set/current$", msg.topic) is not None or + re.search("consumer/[0-9]+/set/on_time$", msg.topic) is not None or re.search("consumer/[0-9]+/set/plug_time$", msg.topic) is not None or re.search("consumer/[0-9]+/set/timestamp_last_current_set$", msg.topic) is not None or re.search("consumer/[0-9]+/get/set_power$", msg.topic) is not None): diff --git a/packages/helpermodules/timecheck.py b/packages/helpermodules/timecheck.py index ccc37b5606..1e8a3d7088 100644 --- a/packages/helpermodules/timecheck.py +++ b/packages/helpermodules/timecheck.py @@ -117,7 +117,7 @@ def is_timeframe_valid(now: datetime.datetime, begin: datetime.datetime, end: da def check_end_time(plan: Union[ContinuousScheduledPlanConsumer, ScheduledChargingPlan, SuspendableScheduledPlanConsumer], - buffer: Optional[float]) -> Optional[float]: + buffer: Optional[float]) -> float: """ gibt die verbleibende Zeit in Sekunden zurück. Return @@ -127,7 +127,6 @@ def check_end_time(plan: Union[ContinuousScheduledPlanConsumer, """ now = datetime.datetime.today() end = datetime.datetime.strptime(plan.time, '%H:%M') - remaining_time = None if plan.frequency.selected == "once": endDate = datetime.datetime.strptime(plan.frequency.once, "%Y-%m-%d") end = end.replace(endDate.year, endDate.month, endDate.day) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index d0bcd5f36e..35280eaded 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -204,6 +204,7 @@ class UpdateConfig: "^openWB/consumer/[0-9]+/get/state_str$", "^openWB/consumer/[0-9]+/get/voltages$", "^openWB/consumer/[0-9]+/set/current$", + "^openWB/consumer/[0-9]+/set/on_time$", "^openWB/consumer/[0-9]+/set/phases_to_use$", "^openWB/consumer/[0-9]+/set/plug_time$", "^openWB/consumer/[0-9]+/set/timestamp_last_current_set$", diff --git a/packages/main.py b/packages/main.py index 1e9570706d..9243b0bbcc 100755 --- a/packages/main.py +++ b/packages/main.py @@ -236,8 +236,7 @@ def handler_midnight(self): f.write("") with ChangedValuesContext(loadvars_.event_module_update_completed): for consumer in data.data.consumer_data.values(): - consumer.reset_wait_for_start() - consumer.reset_chargemode_at_midnight() + consumer.midnight_handler() except Exception: log.exception("Fehler im Main-Modul")