diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index d72cd84778..98756e76a4 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 @@ -73,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 @@ -145,21 +146,22 @@ 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) 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..3c339fe02a 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 @@ -52,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) @@ -63,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 @@ -115,16 +118,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 +142,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..5aadc0a798 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 @@ -598,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. @@ -696,7 +698,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..8bd8dc845e 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: @@ -191,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') @@ -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, diff --git a/packages/control/loadmanagement.py b/packages/control/loadmanagement.py index 0ad4b27350..24f431a055 100644 --- a/packages/control/loadmanagement.py +++ b/packages/control/loadmanagement.py @@ -35,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, 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(): @@ -47,6 +47,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 +61,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 +94,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 +103,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