From 88521a0e4a90dbe4aa2c3748d53cb8772e6f40bb Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Mon, 3 Feb 2025 16:05:51 +0100 Subject: [PATCH 1/3] consider medium charging currents --- packages/control/algorithm/common.py | 6 ++++-- packages/control/algorithm/surplus_controlled.py | 11 ++++++----- packages/control/algorithm/utils.py | 10 ++++++++++ packages/control/chargepoint/chargepoint.py | 3 ++- packages/control/counter.py | 5 +++-- packages/control/ev/ev.py | 10 ++++++---- 6 files changed, 31 insertions(+), 14 deletions(-) create mode 100644 packages/control/algorithm/utils.py diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index d72cd84778..e723b8b2e2 100644 --- a/packages/control/algorithm/common.py +++ b/packages/control/algorithm/common.py @@ -3,6 +3,7 @@ from control import data from control.algorithm.filter_chargepoints import get_chargepoints_by_mode +from control.algorithm.utils import get_medium_charging_current from control.chargepoint.chargepoint import Chargepoint from control.counter import Counter from helpermodules.timecheck import check_timestamp @@ -154,12 +155,13 @@ def update_raw_data(preferenced_chargepoints: List[Chargepoint], def consider_less_charging_chargepoint_in_loadmanagement(cp: Chargepoint, set_current: float) -> bool: if (data.data.counter_all_data.data.config.consider_less_charging is False and ((set_current - - cp.data.set.charging_ev_data.ev_template.data.nominal_difference) > max(cp.data.get.currents) and + cp.data.set.charging_ev_data.ev_template.data.nominal_difference) > get_medium_charging_current( + cp.data.get.currents) and cp.data.control_parameter.timestamp_charge_start is not None and check_timestamp(cp.data.control_parameter.timestamp_charge_start, LESS_CHARGING_TIMEOUT) is False)): log.debug( f"LP {cp.num} lädt deutlich unter dem Sollstrom und wird nur mit {cp.data.get.currents}A berücksichtigt.") - return max(cp.data.get.currents) + return get_medium_charging_current(cp.data.get.currents) else: return set_current # tested diff --git a/packages/control/algorithm/surplus_controlled.py b/packages/control/algorithm/surplus_controlled.py index 2f7cdf39ab..9c0270a985 100644 --- a/packages/control/algorithm/surplus_controlled.py +++ b/packages/control/algorithm/surplus_controlled.py @@ -7,6 +7,7 @@ from control.algorithm.filter_chargepoints import (get_chargepoints_by_chargemodes, get_chargepoints_by_mode_and_counter, get_preferenced_chargepoint_charging) +from control.algorithm.utils import get_medium_charging_current from control.chargepoint.charging_type import ChargingType from control.chargepoint.chargepoint import Chargepoint from control.chargepoint.chargepoint_state import ChargepointState, CHARGING_STATES @@ -115,16 +116,16 @@ def _limit_adjust_current(self, chargepoint: Chargepoint, new_current: float) -> else: # Um max. +/- 5A pro Zyklus regeln if (-MAX_CURRENT-nominal_difference - < new_current - max(chargepoint.data.get.currents) + < new_current - get_medium_charging_current(chargepoint.data.get.currents) < MAX_CURRENT+nominal_difference): current = new_current else: - if new_current < max(chargepoint.data.get.currents): - current = max(chargepoint.data.get.currents) - MAX_CURRENT + if new_current < get_medium_charging_current(chargepoint.data.get.currents): + current = get_medium_charging_current(chargepoint.data.get.currents) - MAX_CURRENT msg = f"Es darf um max {MAX_CURRENT}A unter den aktuell genutzten Strom geregelt werden." else: - current = max(chargepoint.data.get.currents) + MAX_CURRENT + current = get_medium_charging_current(chargepoint.data.get.currents) + MAX_CURRENT msg = f"Es darf um max {MAX_CURRENT}A über den aktuell genutzten Strom geregelt werden." chargepoint.set_state_and_log(msg) return max(current, @@ -139,7 +140,7 @@ def _fix_deviating_evse_current(self, chargepoint: Chargepoint) -> float: Wenn die Soll-Stromstärke nicht angepasst worden ist, nicht den ungenutzten EVSE-Strom aufschlagen.""" evse_current = chargepoint.data.get.evse_current if evse_current and chargepoint.data.set.current != chargepoint.data.set.current_prev: - offset = evse_current - max(chargepoint.data.get.currents) + offset = evse_current - get_medium_charging_current(chargepoint.data.get.currents) current_with_offset = chargepoint.data.set.current + offset current = min(current_with_offset, chargepoint.data.control_parameter.required_current) if current != chargepoint.data.set.current: diff --git a/packages/control/algorithm/utils.py b/packages/control/algorithm/utils.py new file mode 100644 index 0000000000..24f422bb60 --- /dev/null +++ b/packages/control/algorithm/utils.py @@ -0,0 +1,10 @@ +from typing import List + + +def get_medium_charging_current(currents: List[float]) -> float: + """Ermittelt den mittleren Ladestrom der Phasen, auf denen geladen wird. + """ + if any(x >= 0.5 for x in currents): + return sum(x for x in currents if x >= 0.5) / len([x for x in currents if x >= 0.5]) + else: + return 0.0 diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py index 5d810bf0c8..d510ea6331 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -21,6 +21,7 @@ import traceback from typing import Dict, Optional, Tuple +from control.algorithm.utils import get_medium_charging_current from control.chargelog import chargelog from control import data from control.chargemode import Chargemode @@ -696,7 +697,7 @@ def update(self, ev_list: Dict[str, Ev]) -> None: f"{self.data.control_parameter.submode}, Phasen: " f"{self.data.control_parameter.phases}" f", Priorität: {charging_ev.charge_template.data.prio}" - f", max. Ist-Strom: {max(self.data.get.currents)}") + f", mittlerer Ist-Strom: {get_medium_charging_current(self.data.get.currents)}") except Exception: log.exception("Fehler im Prepare-Modul für Ladepunkt "+str(self.num)) self.data.control_parameter.submode = "stop" diff --git a/packages/control/counter.py b/packages/control/counter.py index 80531f12a9..9a562b4951 100644 --- a/packages/control/counter.py +++ b/packages/control/counter.py @@ -7,6 +7,7 @@ from typing import List, Optional, Tuple from control import data +from control.algorithm.utils import get_medium_charging_current from control.chargemode import Chargemode from control.ev.ev import Ev from control.chargepoint.chargepoint import Chargepoint @@ -150,7 +151,7 @@ def _set_current_left(self, loadmanagement_available: bool) -> None: chargepoint.data.config.phase_1, chargepoint.data.get.currents) except KeyError: - element_current = [max(chargepoint.data.get.currents)]*3 + element_current = [get_medium_charging_current(chargepoint.data.get.currents)]*3 currents_raw = list(map(operator.sub, currents_raw, element_current)) currents_raw = list(map(operator.sub, self.data.config.max_currents, currents_raw)) if min(currents_raw) < 0: @@ -441,7 +442,7 @@ def switch_off_check_threshold(self, chargepoint: Chargepoint) -> bool: # Einen nach dem anderen abschalten, bis Ladeleistung des Speichers erreicht ist # und wieder eingespeist wird. self.data.set.reserved_surplus == 0)) - if switch_off_condition and max(chargepoint.data.get.currents) <= min_current: + if switch_off_condition and get_medium_charging_current(chargepoint.data.get.currents) <= min_current: if not charging_ev_data.ev_template.data.prevent_charge_stop: # EV, die ohnehin nicht laden, wird direkt die Ladefreigabe entzogen. # Würde man required_power vom released_evu_surplus subtrahieren, würden keine anderen EVs diff --git a/packages/control/ev/ev.py b/packages/control/ev/ev.py index 476157a903..077d7d8127 100644 --- a/packages/control/ev/ev.py +++ b/packages/control/ev/ev.py @@ -11,6 +11,7 @@ from typing import List, Optional, Tuple from control import data +from control.algorithm.utils import get_medium_charging_current from control.chargepoint.chargepoint_state import ChargepointState, PHASE_SWITCH_STATES from control.chargepoint.charging_type import ChargingType from control.chargepoint.control_parameter import ControlParameter @@ -309,10 +310,11 @@ def _check_phase_switch_conditions(self, all_surplus = data.data.counter_all_data.get_evu_counter().get_usable_surplus(feed_in_yield) required_surplus = control_parameter.min_current * max_phases_ev * 230 - get_power unblanced_load_limit_reached = limit.limiting_value == LimitingValue.UNBALANCED_LOAD - condition_1_to_3 = (((max(get_currents) > max_current and + condition_1_to_3 = (((get_medium_charging_current(get_currents) > max_current and all_surplus > required_surplus) or unblanced_load_limit_reached) and phases_in_use == 1) - condition_3_to_1 = max(get_currents) < min_current and all_surplus <= 0 and phases_in_use > 1 + condition_3_to_1 = get_medium_charging_current( + get_currents) < min_current and all_surplus <= 0 and phases_in_use > 1 if condition_1_to_3 or condition_3_to_1: return True, None else: @@ -361,8 +363,8 @@ def auto_phase_switch(self, new_current = self.ev_template.data.max_current_single_phase log.debug( - f'Genutzter Strom: {max(get_currents)}A, Überschuss: {all_surplus}W, benötigte neue Leistung: ' - f'{required_reserved_power}W') + f'Genutzter Strom: {get_medium_charging_current(get_currents)}A, Überschuss: {all_surplus}W, benötigte ' + f'neue Leistung: {required_reserved_power}W') # Wenn gerade umgeschaltet wird, darf kein Timer gestartet werden. if not self.ev_template.data.prevent_phase_switch: condition, condition_msg = self._check_phase_switch_conditions(control_parameter, From 2ea37c68a61503e0844af56dfbc058d42df8cfe4 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Mon, 3 Feb 2025 16:32:46 +0100 Subject: [PATCH 2/3] use measured voltages --- packages/control/algorithm/additional_current.py | 3 ++- packages/control/algorithm/common.py | 8 ++++---- packages/control/algorithm/min_current.py | 2 +- packages/control/algorithm/surplus_controlled.py | 4 +++- packages/control/chargepoint/chargepoint.py | 3 ++- packages/control/counter.py | 8 ++++---- packages/control/loadmanagement.py | 11 +++++++---- packages/control/loadmanagement_test.py | 2 +- 8 files changed, 24 insertions(+), 17 deletions(-) diff --git a/packages/control/algorithm/additional_current.py b/packages/control/algorithm/additional_current.py index 1cc76de58e..7d282bc5db 100644 --- a/packages/control/algorithm/additional_current.py +++ b/packages/control/algorithm/additional_current.py @@ -27,7 +27,8 @@ def set_additional_current(self) -> None: while len(preferenced_chargepoints): cp = preferenced_chargepoints[0] missing_currents, counts = common.get_missing_currents_left(preferenced_chargepoints) - available_currents, limit = Loadmanagement().get_available_currents(missing_currents, counter, cp) + available_currents, limit = Loadmanagement().get_available_currents( + missing_currents, cp.data.get.voltages, counter, cp) log.debug(f"cp {cp.num} available currents {available_currents} missing currents " f"{missing_currents} limit {limit.message}") cp.data.control_parameter.limit = limit diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index e723b8b2e2..98756e76a4 100644 --- a/packages/control/algorithm/common.py +++ b/packages/control/algorithm/common.py @@ -74,9 +74,9 @@ def set_current_counterdiff(diff_curent: float, counters = data.data.counter_all_data.get_counters_to_check(chargepoint.num) for counter in counters: if surplus: - data.data.counter_data[counter].update_surplus_values_left(diffs) + data.data.counter_data[counter].update_surplus_values_left(diffs, chargepoint.data.get.voltages) else: - data.data.counter_data[counter].update_values_left(diffs) + data.data.counter_data[counter].update_values_left(diffs, chargepoint.data.get.voltages) data.data.io_actions.dimming_set_import_power_left({"type": "cp", "id": chargepoint.num}, sum(diffs)*230) chargepoint.data.set.current = current @@ -146,9 +146,9 @@ def update_raw_data(preferenced_chargepoints: List[Chargepoint], counters = data.data.counter_all_data.get_counters_to_check(chargepoint.num) for counter in counters: if surplus: - data.data.counter_data[counter].update_surplus_values_left(diffs) + data.data.counter_data[counter].update_surplus_values_left(diffs, chargepoint.data.get.voltages) else: - data.data.counter_data[counter].update_values_left(diffs) + data.data.counter_data[counter].update_values_left(diffs, chargepoint.data.get.voltages) data.data.io_actions.dimming_set_import_power_left({"type": "cp", "id": chargepoint.num}, sum(diffs)*230) diff --git a/packages/control/algorithm/min_current.py b/packages/control/algorithm/min_current.py index ff24452cba..6a0ccd8c72 100644 --- a/packages/control/algorithm/min_current.py +++ b/packages/control/algorithm/min_current.py @@ -24,7 +24,7 @@ def set_min_current(self) -> None: missing_currents, counts = common.get_min_current(cp) if max(missing_currents) > 0: available_currents, limit = Loadmanagement().get_available_currents( - missing_currents, counter, cp) + missing_currents, cp.data.get.voltages, counter, cp) cp.data.control_parameter.limit = limit available_for_cp = common.available_current_for_cp( cp, counts, available_currents, missing_currents) diff --git a/packages/control/algorithm/surplus_controlled.py b/packages/control/algorithm/surplus_controlled.py index 9c0270a985..3c339fe02a 100644 --- a/packages/control/algorithm/surplus_controlled.py +++ b/packages/control/algorithm/surplus_controlled.py @@ -53,6 +53,7 @@ def _set(self, cp = chargepoints[0] missing_currents, counts = common.get_missing_currents_left(chargepoints) available_currents, limit = Loadmanagement().get_available_currents_surplus(missing_currents, + cp.data.get.voltages, counter, cp, feed_in=feed_in_yield) @@ -64,7 +65,8 @@ def _set(self, # Wenn die Differenz zwischen altem und neuem Soll-Strom größer als der Regelbereich ist, trotzdem # nachregeln, auch wenn der Regelbereich eingehalten wird. Sonst würde zB nicht berücksichtigt werden, # wenn noch ein Fahrzeug dazu kommmt. - if (pv_charging.control_range[1] - pv_charging.control_range[0]) / 230 < abs(dif_to_old_current): + if ((pv_charging.control_range[1] - pv_charging.control_range[0]) / + (sum(counter.data.get.voltages) / len(counter.data.get.voltages)) < abs(dif_to_old_current)): current = available_for_cp else: # Nicht mehr freigeben, wie das Lastmanagement vorgibt diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py index d510ea6331..5aadc0a798 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -599,7 +599,8 @@ def set_required_currents(self, required_current: float) -> None: self.set_state_and_log("Bitte in den Ladepunkt-Einstellungen die Einstellung 'Phase 1 des Ladekabels'" + " angeben. Andernfalls wird der benötigte Strom auf allen 3 Phasen vorgehalten, " + "was ggf eine unnötige Reduktion der Ladeleistung zur Folge hat.") - self.data.set.required_power = sum(control_parameter.required_currents) * 230 + self.data.set.required_power = sum( + [c * v for c, v in zip(control_parameter.required_currents, self.data.get.voltages)]) def set_timestamp_charge_start(self): # Beim Ladestart Timer laufen lassen, manche Fahrzeuge brauchen sehr lange. diff --git a/packages/control/counter.py b/packages/control/counter.py index 9a562b4951..8bd8dc845e 100644 --- a/packages/control/counter.py +++ b/packages/control/counter.py @@ -192,17 +192,17 @@ def _set_power_left(self, loadmanagement_available: bool) -> None: else: self.data.set.raw_power_left = None - def update_values_left(self, diffs) -> None: + def update_values_left(self, diffs, cp_voltages: List[float]) -> None: self.data.set.raw_currents_left = list(map(operator.sub, self.data.set.raw_currents_left, diffs)) if self.data.set.raw_power_left: - self.data.set.raw_power_left -= sum(diffs) * 230 + self.data.set.raw_power_left -= sum([c * v for c, v in zip(diffs, cp_voltages)]) log.debug(f'Zähler {self.num}: {self.data.set.raw_currents_left}A verbleibende Ströme, ' f'{self.data.set.raw_power_left}W verbleibende Leistung') - def update_surplus_values_left(self, diffs) -> None: + def update_surplus_values_left(self, diffs, cp_voltages: List[float]) -> None: self.data.set.raw_currents_left = list(map(operator.sub, self.data.set.raw_currents_left, diffs)) if self.data.set.surplus_power_left: - self.data.set.surplus_power_left -= sum(diffs) * 230 + self.data.set.surplus_power_left -= sum([c * v for c, v in zip(diffs, cp_voltages)]) log.debug(f'Zähler {self.num}: {self.data.set.raw_currents_left}A verbleibende Ströme, ' f'{self.data.set.surplus_power_left}W verbleibender Überschuss') diff --git a/packages/control/loadmanagement.py b/packages/control/loadmanagement.py index 0ad4b27350..7c9e7f9391 100644 --- a/packages/control/loadmanagement.py +++ b/packages/control/loadmanagement.py @@ -15,6 +15,7 @@ class Loadmanagement: def get_available_currents(self, missing_currents: List[float], + cp_voltages: List[float], counter: Counter, cp: Chargepoint, feed_in: int = 0) -> Tuple[List[float], LoadmanagementLimit]: @@ -35,7 +36,7 @@ def get_available_currents(self, limit = new_limit if new_limit.limiting_value is not None else limit available_currents, new_limit = self._limit_by_power( - counter, available_currents, counter.data.set.raw_power_left, feed_in) + counter, available_currents, cp_voltages, counter.data.set.raw_power_left, feed_in) limit = new_limit if new_limit.limiting_value is not None else limit if f"counter{counter.num}" == data.data.counter_all_data.get_evu_counter_str(): @@ -47,6 +48,7 @@ def get_available_currents(self, def get_available_currents_surplus(self, missing_currents: List[float], + cp_voltages: List[float], counter: Counter, cp: Chargepoint, feed_in: int = 0) -> Tuple[List[float], LoadmanagementLimit]: @@ -60,7 +62,7 @@ def get_available_currents_surplus(self, limit = new_limit if new_limit.limiting_value is not None else limit available_currents, new_limit = self._limit_by_power( - counter, available_currents, counter.data.set.surplus_power_left, feed_in) + counter, available_currents, cp_voltages, counter.data.set.surplus_power_left, feed_in) limit = new_limit if new_limit.limiting_value is not None else limit if f"counter{counter.num}" == data.data.counter_all_data.get_evu_counter_str(): @@ -93,6 +95,7 @@ def _limit_by_unbalanced_load(self, def _limit_by_power(self, counter: Counter, available_currents: List[float], + cp_voltages: List[float], raw_power_left: Optional[float], feed_in: Optional[float]) -> Tuple[List[float], LoadmanagementLimit]: currents = available_currents.copy() @@ -101,10 +104,10 @@ def _limit_by_power(self, if feed_in: raw_power_left = raw_power_left - feed_in log.debug(f"Verbleibende Leistung unter Berücksichtigung der Einspeisegrenze: {raw_power_left}W") - if sum(available_currents)*230 > raw_power_left: + if sum([c * v for c, v in zip(available_currents, cp_voltages)]) > raw_power_left: for i in range(0, 3): # Am meisten belastete Phase trägt am meisten zur Leistungsreduktion bei. - currents[i] = available_currents[i] / sum(available_currents) * raw_power_left / 230 + currents[i] = available_currents[i] / sum(available_currents) * raw_power_left / cp_voltages[i] log.debug(f"Leistungsüberschreitung auf {raw_power_left}W korrigieren: {available_currents}") limit = LoadmanagementLimit(LimitingValue.POWER.value.format(get_component_name_by_id(counter.num)), LimitingValue.POWER) diff --git a/packages/control/loadmanagement_test.py b/packages/control/loadmanagement_test.py index 8cc5f526be..33f1acd59e 100644 --- a/packages/control/loadmanagement_test.py +++ b/packages/control/loadmanagement_test.py @@ -30,7 +30,7 @@ def test_limit_by_power(available_currents: List[float], counter_name_mock = Mock(return_value=COUNTER_NAME) monkeypatch.setattr(loadmanagement, "get_component_name_by_id", counter_name_mock) # evaluation - currents = Loadmanagement()._limit_by_power(Counter(0), available_currents, raw_power_left, None) + currents = Loadmanagement()._limit_by_power(Counter(0), available_currents, [230]*3, raw_power_left, None) # assertion assert currents == expected_currents From b02231b50f32365769e5dbee28589adac4ece35f Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 11 Apr 2025 09:51:57 +0200 Subject: [PATCH 3/3] fix merge --- packages/control/algorithm/additional_current.py | 3 +-- packages/control/algorithm/min_current.py | 2 +- packages/control/loadmanagement.py | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/control/algorithm/additional_current.py b/packages/control/algorithm/additional_current.py index 7d282bc5db..1cc76de58e 100644 --- a/packages/control/algorithm/additional_current.py +++ b/packages/control/algorithm/additional_current.py @@ -27,8 +27,7 @@ def set_additional_current(self) -> None: while len(preferenced_chargepoints): cp = preferenced_chargepoints[0] missing_currents, counts = common.get_missing_currents_left(preferenced_chargepoints) - available_currents, limit = Loadmanagement().get_available_currents( - missing_currents, cp.data.get.voltages, counter, cp) + available_currents, limit = Loadmanagement().get_available_currents(missing_currents, counter, cp) log.debug(f"cp {cp.num} available currents {available_currents} missing currents " f"{missing_currents} limit {limit.message}") cp.data.control_parameter.limit = limit diff --git a/packages/control/algorithm/min_current.py b/packages/control/algorithm/min_current.py index 6a0ccd8c72..ff24452cba 100644 --- a/packages/control/algorithm/min_current.py +++ b/packages/control/algorithm/min_current.py @@ -24,7 +24,7 @@ def set_min_current(self) -> None: missing_currents, counts = common.get_min_current(cp) if max(missing_currents) > 0: available_currents, limit = Loadmanagement().get_available_currents( - missing_currents, cp.data.get.voltages, counter, cp) + missing_currents, counter, cp) cp.data.control_parameter.limit = limit available_for_cp = common.available_current_for_cp( cp, counts, available_currents, missing_currents) diff --git a/packages/control/loadmanagement.py b/packages/control/loadmanagement.py index 7c9e7f9391..24f431a055 100644 --- a/packages/control/loadmanagement.py +++ b/packages/control/loadmanagement.py @@ -15,7 +15,6 @@ class Loadmanagement: def get_available_currents(self, missing_currents: List[float], - cp_voltages: List[float], counter: Counter, cp: Chargepoint, feed_in: int = 0) -> Tuple[List[float], LoadmanagementLimit]: @@ -36,7 +35,7 @@ def get_available_currents(self, limit = new_limit if new_limit.limiting_value is not None else limit available_currents, new_limit = self._limit_by_power( - counter, available_currents, cp_voltages, counter.data.set.raw_power_left, feed_in) + counter, available_currents, cp.data.get.voltages, counter.data.set.raw_power_left, feed_in) limit = new_limit if new_limit.limiting_value is not None else limit if f"counter{counter.num}" == data.data.counter_all_data.get_evu_counter_str():