diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py
index 3d1f81e6ef..e1ec881fb3 100644
--- a/packages/control/chargepoint/chargepoint.py
+++ b/packages/control/chargepoint/chargepoint.py
@@ -188,7 +188,7 @@ def _process_charge_stop(self) -> None:
self.data.set.ocpp_transaction_id = None
# muss vor dem Zurücksetzen der control parameter aufgerufen werden
self.data.set.charging_ev_data.reset_phase_switch(self.data.control_parameter)
- self.data.set.charging_ev_data.reset_phase_switch_delay(self.data.control_parameter, self.get_max_phase_hw())
+ self.data.set.charging_ev_data.reset_phase_switch_delay(self.data.control_parameter)
self.reset_control_parameter_at_charge_stop()
data.data.counter_all_data.get_evu_counter().reset_switch_on_off(self)
if self.data.get.plug_state is False and self.data.set.plug_state_prev is True:
@@ -664,7 +664,7 @@ def update(self, ev_list: Dict[str, Ev]) -> None:
if self.chargemode_changed or self.submode_changed:
data.data.counter_all_data.get_evu_counter().reset_switch_on_off(self)
- charging_ev.reset_phase_switch_delay(self.data.control_parameter, self.get_max_phase_hw())
+ charging_ev.reset_phase_switch_delay(self.data.control_parameter)
if self.chargemode_changed:
self.data.control_parameter.failed_phase_switches = 0
message = message_ev if message_ev else message
diff --git a/packages/control/chargepoint/control_parameter.py b/packages/control/chargepoint/control_parameter.py
index 17f5f11665..521bfdbc29 100644
--- a/packages/control/chargepoint/control_parameter.py
+++ b/packages/control/chargepoint/control_parameter.py
@@ -19,6 +19,9 @@ class ControlParameter:
phases: int = field(default=0, metadata={"topic": "control_parameter/phases"})
required_current: float = field(default=0, metadata={"topic": "control_parameter/required_current"})
required_currents: List[float] = field(default_factory=currents_list_factory)
+ # für auto_phase_switch(): die tatsächlich reservierte Leistung, damit das Zurücknehmen der Reservierung
+ # exakt zum Reservieren passt statt es (ggf. mit geänderten Werten) neu zu berechnen
+ reserved_power: float = field(default=0, metadata={"topic": "control_parameter/reserved_power"})
state: ChargepointState = field(default=ChargepointState.NO_CHARGING_ALLOWED,
metadata={"topic": "control_parameter/state"})
submode: Chargemode_enum = field(default=Chargemode_enum.STOP, metadata={"topic": "control_parameter/submode"})
diff --git a/packages/control/ev/ev.py b/packages/control/ev/ev.py
index 7e24057424..cc33adea78 100644
--- a/packages/control/ev/ev.py
+++ b/packages/control/ev/ev.py
@@ -321,8 +321,8 @@ def auto_phase_switch(self,
delay = surplus_config.vehicle.phase_switch_delay * 60
if phases_in_use == 1:
direction_str = f"Umschaltung von 1 auf {max_phases}"
- required_reserved_power = (control_parameter.min_current * max_phases * 230 -
- self.ev_template.data.max_current_single_phase * 230)
+ required_reserved_power = max(0, control_parameter.min_current * max_phases * 230 -
+ self.ev_template.data.max_current_single_phase * 230)
new_phase = max_phases
new_current = control_parameter.min_current
@@ -336,9 +336,9 @@ def auto_phase_switch(self,
waiting_time = surplus_config.vehicle.switch_off_delay
all_surplus = data.data.counter_all_data.get_evu_counter().get_usable_surplus()
if control_parameter.state == ChargepointState.PHASE_SWITCH_DELAY:
- # eigene reservierte Leistung addieren, wenn die Verzögerung für die Umschaltung läuft,
- # da diese Leistung bereits für die Umschaltung reserviert ist.
- surplus = all_surplus + max(0, required_reserved_power)
+ # mit der tatsächlich reservierten Leistung rechnen, nicht mit einer frisch berechneten - sonst
+ # können Reservieren und Zurücknehmen auseinanderlaufen, wenn sich zB max_phases währenddessen ändert
+ surplus = all_surplus + control_parameter.reserved_power
else:
surplus = all_surplus
@@ -358,8 +358,9 @@ def auto_phase_switch(self,
if condition:
# Wenn nach der Umschaltung weniger Leistung benötigt wird, soll während der Verzögerung keine
# neuen eingeschaltet werden.
+ control_parameter.reserved_power = required_reserved_power
data.data.counter_all_data.get_evu_counter(
- ).data.set.reserved_surplus += max(0, required_reserved_power)
+ ).data.set.reserved_surplus += control_parameter.reserved_power
message = self.PHASE_SWITCH_DELAY_TEXT.format(
direction_str,
self._remaining_phase_switch_time(control_parameter,
@@ -384,14 +385,16 @@ def auto_phase_switch(self,
message = self.PHASE_SWITCH_DELAY_TEXT.format(direction_str, remaining_time)
else:
data.data.counter_all_data.get_evu_counter(
- ).data.set.reserved_surplus -= max(0, required_reserved_power)
+ ).data.set.reserved_surplus -= control_parameter.reserved_power
+ control_parameter.reserved_power = 0
phases_to_use = new_phase
current = new_current
log.debug("Phasenumschaltung kann nun durchgeführt werden.")
control_parameter.state = ChargepointState.PHASE_SWITCH_AWAITED
else:
data.data.counter_all_data.get_evu_counter(
- ).data.set.reserved_surplus -= max(0, required_reserved_power)
+ ).data.set.reserved_surplus -= control_parameter.reserved_power
+ control_parameter.reserved_power = 0
message = f"Verzögerung für die {direction_str} Phasen abgebrochen{condition_msg}"
control_parameter.state = ChargepointState.CHARGING_ALLOWED
control_parameter.timestamp_phase_switch_buffer_start = None
@@ -429,7 +432,7 @@ def _remaining_phase_switch_time(self, control_parameter: ControlParameter,
control_parameter.timestamp_phase_switch_buffer_start = None
return True, None
- def reset_phase_switch_delay(self, control_parameter: ControlParameter, max_phases: int):
+ def reset_phase_switch_delay(self, control_parameter: ControlParameter):
""" Zurücksetzen der Zeitstempel und reservierten Leistung.
Die Phasenumschaltung kann nicht abgebrochen werden!
@@ -441,8 +444,8 @@ def reset_phase_switch_delay(self, control_parameter: ControlParameter, max_phas
# Wenn der Timer läuft, ist den Control-Parametern die alte Phasenzahl hinterlegt.
# bei der Umschaltung 3p1p wird keine Leistung reserviert
evu_counter = data.data.counter_all_data.get_evu_counter()
- reserved = max(0, control_parameter.min_current * max_phases * 230 -
- self.ev_template.data.max_current_single_phase * 230)
+ reserved = control_parameter.reserved_power
+ control_parameter.reserved_power = 0
evu_counter.data.set.reserved_surplus -= reserved
log.debug(f"Zurücksetzen von {reserved}W reservierter Leistung für die Phasenumschaltung. "
f"reservierte Leistung: {evu_counter.data.set.reserved_surplus} W")
diff --git a/packages/control/ev/ev_test.py b/packages/control/ev/ev_test.py
index e4e91b2bc7..0e65147c26 100644
--- a/packages/control/ev/ev_test.py
+++ b/packages/control/ev/ev_test.py
@@ -5,8 +5,10 @@
from control import data
+from control.chargepoint.chargepoint_state import ChargepointState
from control.chargepoint.control_parameter import ControlParameter
from control.ev.ev import Ev, get_ev_to_rfid
+from control.limiting_value import LimitingValue, LoadmanagementLimit
from helpermodules import timecheck
from modules.common.abstract_vehicle import VehicleUpdateData
from modules.vehicles.mqtt.config import MqttSocSetup
@@ -77,6 +79,64 @@ def test_remaining_phase_switch_time(timestamp_last_phase_switch,
assert result == expected_result
+@pytest.mark.parametrize(
+ "surplus, expected_condition",
+ [
+ pytest.param(6901, True, id="Überschuss reicht für min_current auf allen Phasen: umschalten"),
+ pytest.param(5000, False, id="Überschuss reicht nicht für min_current auf allen Phasen: nicht umschalten"),
+ ],
+)
+def test_check_phase_switch_conditions_required_surplus_based_on_min_current(surplus, expected_condition):
+ """ required_surplus muss auf control_parameter.min_current basieren. """
+ # setup
+ ev = Ev(0)
+ ev.ev_template.data.max_phases = 3
+ ev.ev_template.data.max_current_single_phase = 16
+ ev.ev_template.data.nominal_difference = 1
+ control_parameter = ControlParameter()
+ control_parameter.min_current = 10
+ control_parameter.phases = 1
+ control_parameter.required_current = 6
+ limit = LoadmanagementLimit(message=None, limiting_value=LimitingValue.CURRENT)
+
+ # execution
+ condition, _ = ev._check_phase_switch_conditions(
+ control_parameter=control_parameter,
+ evse_current=0,
+ get_currents=[0, 0, 0],
+ get_power=0,
+ max_current_cp=32,
+ limit=limit,
+ surplus=surplus,
+ )
+
+ # evaluation: required_surplus = min_current(10) * max_phases(3) * 230 = 6900
+ assert condition == expected_condition
+
+
+def test_reset_phase_switch_delay_uses_reserved_power_not_a_fresh_calculation(monkeypatch):
+ """ reset_phase_switch_delay() muss die beim Reservieren gespeicherte Leistung zurücknehmen, nicht neu
+ berechnen - sonst würde reserved_surplus driften, falls sich zB max_phases inzwischen geändert hat. """
+ # setup
+ ev = Ev(0)
+ ev.ev_template.data.max_current_single_phase = 16
+ control_parameter = ControlParameter()
+ control_parameter.state = ChargepointState.PHASE_SWITCH_DELAY
+ control_parameter.phases = 1
+ control_parameter.reserved_power = 500
+ evu_counter = Mock()
+ evu_counter.data.set.reserved_surplus = 1000
+ monkeypatch.setattr(data.data.counter_all_data, "get_evu_counter", Mock(return_value=evu_counter))
+
+ # execution
+ ev.reset_phase_switch_delay(control_parameter)
+
+ # evaluation
+ assert evu_counter.data.set.reserved_surplus == 500
+ assert control_parameter.reserved_power == 0
+ assert control_parameter.state == ChargepointState.CHARGING_ALLOWED
+
+
@pytest.fixture(autouse=True)
def data_module() -> None:
data.data_init(Mock())
diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py
index d67736b248..d9e751aca3 100644
--- a/packages/helpermodules/setdata.py
+++ b/packages/helpermodules/setdata.py
@@ -549,6 +549,7 @@ def process_control_parameter_topics(self, msg):
elif "/control_parameter/current_plan" in msg.topic:
self._validate_value(msg, int)
elif ("/control_parameter/min_current" in msg.topic or
+ "/control_parameter/reserved_power" in msg.topic or
"/control_parameter/timestamp_switch_on_off" in msg.topic or
"/control_parameter/timestamp_charge_start" in msg.topic or
"/control_parameter/timestamp_chargemode_changed" in msg.topic or
diff --git a/packages/modules/display_themes/cards/source/src/views/ChargePointsView.vue b/packages/modules/display_themes/cards/source/src/views/ChargePointsView.vue
index 2ba286e972..7f5958327e 100644
--- a/packages/modules/display_themes/cards/source/src/views/ChargePointsView.vue
+++ b/packages/modules/display_themes/cards/source/src/views/ChargePointsView.vue
@@ -1016,6 +1016,28 @@ export default {
>
Maximum
+