From 131db6ad13946e79f1938912783fa7b9b3000563 Mon Sep 17 00:00:00 2001 From: Thomas Papendieck <14850347+tpd-opitz@users.noreply.github.com> Date: Wed, 12 Nov 2025 22:47:05 +0100 Subject: [PATCH 1/3] Update charge_template.py - optional expects remaining time untill plan target legacy code (before price based charging) calculated remaining time in selected_plan as time until charge start. This does not make sense when looking for cheap time slots before the plan target time. --- packages/control/ev/charge_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/control/ev/charge_template.py b/packages/control/ev/charge_template.py index 5cd3fb49f0..bd62394287 100644 --- a/packages/control/ev/charge_template.py +++ b/packages/control/ev/charge_template.py @@ -623,7 +623,7 @@ def end_of_today_timestamp() -> int: def tomorrow(timestamp: int) -> str: return 'morgen ' if end_of_today_timestamp() < timestamp else '' hour_list = data.data.optional_data.et_get_loading_hours( - selected_plan.duration, selected_plan.remaining_time) + selected_plan.duration, selected_plan.duration + selected_plan.remaining_time) log.debug(f"Günstige Ladezeiten: {hour_list}") if data.data.optional_data.et_is_charging_allowed_hours_list(hour_list): From 065b756125b65669a79ddb60fd9096dfe9a4fbf9 Mon Sep 17 00:00:00 2001 From: Thomas Papendieck <14850347+tpd-opitz@users.noreply.github.com> Date: Thu, 13 Nov 2025 12:39:17 +0100 Subject: [PATCH 2/3] improve charge message --- packages/control/ev/charge_template.py | 51 ++++++++++++++------- packages/control/ev/charge_template_test.py | 10 ++-- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/packages/control/ev/charge_template.py b/packages/control/ev/charge_template.py index bd62394287..b4279385bb 100644 --- a/packages/control/ev/charge_template.py +++ b/packages/control/ev/charge_template.py @@ -603,25 +603,42 @@ def scheduled_charging_calc_current(self, # 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.et_is_charging_allowed_hours_list(hour) - return ("Geladen wird "+("jetzt und " - if is_loading_hour(hour_list) - else '') + - "zu folgenden Uhrzeiten: " + - ", ".join([tomorrow(hour) + - datetime.datetime.fromtimestamp(hour).strftime('%-H:%M') - for hour in (sorted(hour_list) - if not is_loading_hour(hour_list) - else (sorted(hour_list)[1:] if len(hour_list) > 1 else []))]) - + ".") - - def end_of_today_timestamp() -> int: - return datetime.datetime.now().replace( - hour=23, minute=59, second=59, microsecond=999000).timestamp() - - def tomorrow(timestamp: int) -> str: - return 'morgen ' if end_of_today_timestamp() < timestamp else '' + + 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] + + loading_message = "Geladen wird "+("jetzt" + if is_loading_hour(hour_list) + else '') + loading_message += ((" und " if is_loading_hour(hour_list) else "") + + f"heute {convert_loading_hours_to_string(loading_times_today)}" + if 0 < len(loading_times_today) + else '') + loading_message += (" sowie " + if 0 < len(loading_times_tomorrow) + else '') + loading_message += (f"morgen {convert_loading_hours_to_string(loading_times_tomorrow)}" + if 0 < len(loading_times_tomorrow) + else '') + return loading_message + '.' + hour_list = data.data.optional_data.et_get_loading_hours( selected_plan.duration, selected_plan.duration + selected_plan.remaining_time) diff --git a/packages/control/ev/charge_template_test.py b/packages/control/ev/charge_template_test.py index 6cd0fb7eb2..03866a4cdc 100644 --- a/packages/control/ev/charge_template_test.py +++ b/packages/control/ev/charge_template_test.py @@ -311,7 +311,7 @@ def test_scheduled_charging_calc_current_no_plans(): 14, "instant_charging", ChargeTemplate.SCHEDULED_CHARGING_CHEAP_HOUR.format( - "Geladen wird jetzt und zu folgenden Uhrzeiten: morgen 8:00."), + "Geladen wird jetzt sowie morgen 8:00."), 3), id="cheap_hour_charge_with_instant_charging"), pytest.param(True, 79, 80, 70, LOADING_HOURS_TODAY, @@ -319,7 +319,7 @@ def test_scheduled_charging_calc_current_no_plans(): 14, "instant_charging", ChargeTemplate.SCHEDULED_CHARGING_CHEAP_HOUR.format( - "Geladen wird jetzt und zu folgenden Uhrzeiten: ."), + "Geladen wird jetzt."), 3), id="SOC limit reached but scheduled SOC not, no further loading hours"), pytest.param(False, 79, 80, 90, LOADING_HOURS_TODAY, @@ -327,7 +327,7 @@ def test_scheduled_charging_calc_current_no_plans(): 6, "pv_charging", ChargeTemplate.SCHEDULED_CHARGING_EXPENSIVE_HOUR.format( - "Geladen wird zu folgenden Uhrzeiten: 8:00."), + "Geladen wird heute 8:00."), 0), id="expensive_hour_charge_with_pv"), pytest.param(False, 79, 80, 70, LOADING_HOURS_TODAY, @@ -335,7 +335,7 @@ def test_scheduled_charging_calc_current_no_plans(): 0, "stop", ChargeTemplate.SCHEDULED_CHARGING_EXPENSIVE_HOUR_REACHED_MAX_SOC.format( - "Geladen wird zu folgenden Uhrzeiten: 8:00."), + "Geladen wird heute 8:00."), 3), id="expensive_hour_no_charge_with_pv "), pytest.param(False, 79, 80, 70, LOADING_HOURS_TODAY + LOADING_HOURS_TOMORROW, @@ -343,7 +343,7 @@ def test_scheduled_charging_calc_current_no_plans(): 0, "stop", ChargeTemplate.SCHEDULED_CHARGING_EXPENSIVE_HOUR_REACHED_MAX_SOC.format( - "Geladen wird zu folgenden Uhrzeiten: 8:00, morgen 8:00."), + "Geladen wird heute 8:00 sowie morgen 8:00."), 3), id="expensive_hour_no_charge_with_pv scheduled for tomorrow"), pytest.param(False, 79, 60, 80, LOADING_HOURS_TODAY, From 9ed276ec36b43543f118890d05e1e95716546946 Mon Sep 17 00:00:00 2001 From: Thomas Papendieck <14850347+tpd-opitz@users.noreply.github.com> Date: Thu, 13 Nov 2025 12:43:20 +0100 Subject: [PATCH 3/3] fix flake --- packages/control/ev/charge_template.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/control/ev/charge_template.py b/packages/control/ev/charge_template.py index b4279385bb..0b216d4423 100644 --- a/packages/control/ev/charge_template.py +++ b/packages/control/ev/charge_template.py @@ -627,8 +627,8 @@ def convert_loading_hours_to_string(hour_list: List[int]) -> str: loading_message = "Geladen wird "+("jetzt" if is_loading_hour(hour_list) else '') - loading_message += ((" und " if is_loading_hour(hour_list) else "") + - f"heute {convert_loading_hours_to_string(loading_times_today)}" + loading_message += ((" und " if is_loading_hour(hour_list) else "") + + f"heute {convert_loading_hours_to_string(loading_times_today)}" if 0 < len(loading_times_today) else '') loading_message += (" sowie "