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 + + Automatik + diff --git a/packages/modules/web_themes/colors/source/src/components/chargePointList/cpConfig/ConfigPv.vue b/packages/modules/web_themes/colors/source/src/components/chargePointList/cpConfig/ConfigPv.vue index d820678972..d10e27e7d0 100755 --- a/packages/modules/web_themes/colors/source/src/components/chargePointList/cpConfig/ConfigPv.vue +++ b/packages/modules/web_themes/colors/source/src/components/chargePointList/cpConfig/ConfigPv.vue @@ -76,10 +76,10 @@ diff --git a/packages/modules/web_themes/koala/source/src/components/ChargePointPvSettings.vue b/packages/modules/web_themes/koala/source/src/components/ChargePointPvSettings.vue index 9087f1e042..449b69a66b 100644 --- a/packages/modules/web_themes/koala/source/src/components/ChargePointPvSettings.vue +++ b/packages/modules/web_themes/koala/source/src/components/ChargePointPvSettings.vue @@ -117,6 +117,7 @@ const phaseOptions = [ const phaseOptionsMinSoc = [ { value: 1, label: '1' }, { value: 3, label: 'Maximum' }, + { value: 0, label: 'Automatik' }, ]; const pvMinCurrent = computed(() => diff --git a/packages/modules/web_themes/standard_legacy/web/index.html b/packages/modules/web_themes/standard_legacy/web/index.html index 6208e79c60..61c7d694c8 100644 --- a/packages/modules/web_themes/standard_legacy/web/index.html +++ b/packages/modules/web_themes/standard_legacy/web/index.html @@ -955,6 +955,9 @@

Einstellungen für "PV"

+