From 8f526ae3f30eb2e318681762d3ff143da655e6b6 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Tue, 10 Mar 2026 11:58:34 +0100 Subject: [PATCH 1/2] fix phase switch --- packages/control/algorithm/bidi_charging.py | 4 +- packages/control/algorithm/common.py | 9 ++-- .../control/algorithm/filter_chargepoints.py | 44 ++++++++++--------- .../algorithm/filter_chargepoints_test.py | 6 +-- packages/control/algorithm/no_current.py | 6 ++- .../control/algorithm/surplus_controlled.py | 10 ++--- .../algorithm/surplus_controlled_test.py | 6 +-- packages/control/bat_all.py | 5 ++- packages/control/bat_all_test.py | 10 +++-- packages/control/chargepoint/chargepoint.py | 5 ++- packages/control/counter.py | 5 ++- packages/control/process.py | 3 +- 12 files changed, 63 insertions(+), 50 deletions(-) diff --git a/packages/control/algorithm/bidi_charging.py b/packages/control/algorithm/bidi_charging.py index 52161874c9..4dec5a7b4c 100644 --- a/packages/control/algorithm/bidi_charging.py +++ b/packages/control/algorithm/bidi_charging.py @@ -1,7 +1,7 @@ import logging from control import data from control.algorithm.chargemodes import CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE -from control.algorithm.filter_chargepoints import get_chargepoints_by_mode +from control.algorithm.filter_chargepoints import get_chargepoints_with_required_current_by_chargemode from helpermodules.phase_handling import voltages_mean log = logging.getLogger(__name__) @@ -16,7 +16,7 @@ def set_bidi(self): log.debug(f"Nullpunktanpassung {grid_counter.data.set.surplus_power_left}W") zero_point_adjustment = grid_counter for mode_tuple in CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE: - preferenced_cps = get_chargepoints_by_mode(mode_tuple) + preferenced_cps = get_chargepoints_with_required_current_by_chargemode(mode_tuple) if preferenced_cps: log.info( f"Mode-Tuple {mode_tuple[0]} - {mode_tuple[1]} - {mode_tuple[2]}, Zähler {grid_counter.num}") diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index 11a345c128..60f81c7fe5 100644 --- a/packages/control/algorithm/common.py +++ b/packages/control/algorithm/common.py @@ -2,7 +2,7 @@ from typing import Iterable, List, Optional, Tuple from control import data -from control.algorithm.filter_chargepoints import get_chargepoints_by_mode +from control.algorithm.filter_chargepoints import get_chargepoints_with_required_current_by_chargemode from control.algorithm.utils import get_medium_charging_current from control.chargepoint.chargepoint import Chargepoint from control.counter import Counter @@ -27,10 +27,9 @@ def reset_current(): log.exception(f"Fehler im Algorithmus-Modul für Ladepunkt{cp.num}") -def reset_current_by_chargemode(mode_tuple: Tuple[Optional[str], str, bool]) -> None: - for mode in mode_tuple: - for cp in get_chargepoints_by_mode(mode): - cp.data.set.current = None +def reset_current_by_chargemode(modes: List[Tuple[Optional[str], str, bool]]) -> None: + for cp in get_chargepoints_with_required_current_by_chargemode(modes): + cp.data.set.current = None def mode_and_counter_generator(chargemodes: List) -> Iterable[Tuple[Tuple[Optional[str], str, bool], Counter]]: diff --git a/packages/control/algorithm/filter_chargepoints.py b/packages/control/algorithm/filter_chargepoints.py index 7af2f319f3..f523b1d399 100644 --- a/packages/control/algorithm/filter_chargepoints.py +++ b/packages/control/algorithm/filter_chargepoints.py @@ -1,6 +1,6 @@ # tested import logging -from typing import List, Optional, Tuple +from typing import List, Optional, Tuple, Union from control import data from control.chargepoint.chargepoint import Chargepoint @@ -12,28 +12,39 @@ def get_chargepoints_by_mode_and_counter(mode_tuple: Tuple[Optional[str], str, b counter: str) -> List[Chargepoint]: cps_to_counter = data.data.counter_all_data.get_chargepoints_of_counter(counter) cps_to_counter_ids = [int(cp[2:]) for cp in cps_to_counter] - cps_by_mode = get_chargepoints_by_mode(mode_tuple) + cps_by_mode = get_chargepoints_with_required_current_by_chargemode(mode_tuple) return list(filter(lambda cp: cp.num in cps_to_counter_ids, cps_by_mode)) # tested -def get_chargepoints_by_mode(mode_tuple: Tuple[Optional[str], str, bool]) -> List[Chargepoint]: - mode = mode_tuple[0] - submode = mode_tuple[1] - prio = mode_tuple[2] - # enthält alle LP, auf die das Tupel zutrifft - valid_chargepoints = [] - for cp in data.data.cp_data.values(): - if cp.data.control_parameter.required_current != 0: +def get_chargepoints_by_chargemode( + modes: Union[Tuple[Optional[str], str, bool], + Tuple[Tuple[Optional[str], str, bool]]]) -> List[Chargepoint]: + modes = modes if isinstance(modes[0], Tuple) else (modes,) + valid_chargepoints: List[Chargepoint] = [] + + for mode_tuple in modes: + mode = mode_tuple[0] + submode = mode_tuple[1] + prio = mode_tuple[2] + + for cp in data.data.cp_data.values(): if ((cp.data.control_parameter.prio == prio) and - (cp.data.control_parameter.chargemode == mode or - mode is None) and - (cp.data.control_parameter.submode == submode)): + (cp.data.control_parameter.chargemode == mode or mode is None) and + (cp.data.control_parameter.submode == submode) and + cp not in valid_chargepoints): valid_chargepoints.append(cp) return valid_chargepoints +def get_chargepoints_with_required_current_by_chargemode( + modes: Union[Tuple[Optional[str], str, bool], + Tuple[Tuple[Optional[str], str, bool]]]) -> List[Chargepoint]: + cps_by_mode = get_chargepoints_by_chargemode(modes) + return list(filter(lambda cp: cp.data.control_parameter.required_current != 0, cps_by_mode)) + + def get_preferenced_chargepoint_charging( chargepoints: List[Chargepoint]) -> Tuple[List[Chargepoint], List[Chargepoint]]: preferenced_chargepoints = _get_preferenced_chargepoint(chargepoints) @@ -104,10 +115,3 @@ def _get_preferenced_chargepoint(valid_chargepoints: List[Chargepoint]) -> List: except Exception: log.exception("Fehler im Algorithmus-Modul") return preferenced_chargepoints - - -def get_chargepoints_by_chargemodes(modes) -> List[Chargepoint]: - chargepoints: List[Chargepoint] = [] - for mode in modes: - chargepoints.extend(get_chargepoints_by_mode(mode)) - return chargepoints diff --git a/packages/control/algorithm/filter_chargepoints_test.py b/packages/control/algorithm/filter_chargepoints_test.py index a8b74dc8b0..def2524811 100644 --- a/packages/control/algorithm/filter_chargepoints_test.py +++ b/packages/control/algorithm/filter_chargepoints_test.py @@ -133,7 +133,7 @@ def setup_cp(cp: Chargepoint, required_current: float, mode_tuple: Tuple[str, st "cp2": setup_cp(mock_cp2, 6, mode_tuple_2)} # evaluation - valid_chargepoints = filter_chargepoints.get_chargepoints_by_mode(set_mode_tuple) + valid_chargepoints = filter_chargepoints.get_chargepoints_with_required_current_by_chargemode(set_mode_tuple) # assertion assert valid_chargepoints == expected_valid_chargepoints @@ -154,8 +154,8 @@ def test_get_chargepoints_by_mode_and_counter(chargepoints_of_counter: List[str] # setup get_chargepoints_of_counter_mock = Mock(return_value=chargepoints_of_counter) monkeypatch.setattr(CounterAll, "get_chargepoints_of_counter", get_chargepoints_of_counter_mock) - get_chargepoints_by_mode_mock = Mock(return_value=chargepoints_by_mode) - monkeypatch.setattr(filter_chargepoints, "get_chargepoints_by_mode", get_chargepoints_by_mode_mock) + get_chargepoints_by_chargemode_mock = Mock(return_value=chargepoints_by_mode) + monkeypatch.setattr(filter_chargepoints, "get_chargepoints_by_chargemode", get_chargepoints_by_chargemode_mock) data.data.counter_all_data = CounterAll() # evaluation diff --git a/packages/control/algorithm/no_current.py b/packages/control/algorithm/no_current.py index 0b55fed852..7306837565 100644 --- a/packages/control/algorithm/no_current.py +++ b/packages/control/algorithm/no_current.py @@ -2,7 +2,8 @@ from control import data from control.algorithm.chargemodes import CONSIDERED_CHARGE_MODES_NO_CURRENT -from control.algorithm.filter_chargepoints import get_chargepoints_by_chargemodes +from control.algorithm.filter_chargepoints import get_chargepoints_by_chargemode +from control.chargepoint.chargepoint_state import ChargepointState log = logging.getLogger(__name__) @@ -12,9 +13,10 @@ def __init__(self) -> None: pass def set_no_current(self) -> None: - chargepoints = get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_NO_CURRENT) + chargepoints = get_chargepoints_by_chargemode(CONSIDERED_CHARGE_MODES_NO_CURRENT) for cp in chargepoints: cp.data.set.current = 0 + cp.data.control_parameter.state = ChargepointState.NO_CHARGING_ALLOWED def set_none_current(self) -> None: for cp in data.data.cp_data.values(): diff --git a/packages/control/algorithm/surplus_controlled.py b/packages/control/algorithm/surplus_controlled.py index e2e894df51..a357b57574 100644 --- a/packages/control/algorithm/surplus_controlled.py +++ b/packages/control/algorithm/surplus_controlled.py @@ -5,7 +5,7 @@ from control.algorithm import common from control.algorithm.chargemodes import (CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE, CONSIDERED_CHARGE_MODES_PV_ONLY, CONSIDERED_CHARGE_MODES_SURPLUS) -from control.algorithm.filter_chargepoints import (get_chargepoints_by_chargemodes, +from control.algorithm.filter_chargepoints import (get_chargepoints_with_required_current_by_chargemode, get_chargepoints_by_mode_and_counter, get_preferenced_chargepoint_charging) from control.algorithm.utils import get_medium_charging_current @@ -40,7 +40,7 @@ def set_surplus_current(self) -> None: if preferenced_cps_without_set_current: for cp in preferenced_cps_without_set_current: cp.data.set.current = cp.data.set.target_current - for cp in get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_SURPLUS): + for cp in get_chargepoints_with_required_current_by_chargemode(CONSIDERED_CHARGE_MODES_SURPLUS): if cp.data.control_parameter.state in CHARGING_STATES: self._fix_deviating_evse_current(cp) @@ -127,7 +127,7 @@ def _fix_deviating_evse_current(self, chargepoint: Chargepoint) -> float: def check_submode_pv_charging(self) -> None: evu_counter = data.data.counter_all_data.get_evu_counter() - for cp in get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_PV_ONLY): + for cp in get_chargepoints_with_required_current_by_chargemode(CONSIDERED_CHARGE_MODES_PV_ONLY): try: def phase_switch_necessary() -> bool: return (cp.cp_state_hw_support_phase_switch() and @@ -159,8 +159,8 @@ def phase_switch_necessary() -> bool: log.exception(f"Fehler in der PV-gesteuerten Ladung bei {cp.num}") def set_required_current_to_max(self) -> None: - for cp in get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_SURPLUS + - CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE): + for cp in get_chargepoints_with_required_current_by_chargemode(CONSIDERED_CHARGE_MODES_SURPLUS + + CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE): try: charging_ev_data = cp.data.set.charging_ev_data required_currents = cp.data.control_parameter.required_currents diff --git a/packages/control/algorithm/surplus_controlled_test.py b/packages/control/algorithm/surplus_controlled_test.py index 962eca835b..6251148004 100644 --- a/packages/control/algorithm/surplus_controlled_test.py +++ b/packages/control/algorithm/surplus_controlled_test.py @@ -4,7 +4,7 @@ from control import data from control.algorithm import surplus_controlled -from control.algorithm.filter_chargepoints import get_chargepoints_by_chargemodes +from control.algorithm.filter_chargepoints import get_chargepoints_with_required_current_by_chargemode from control.algorithm.surplus_controlled import (CONSIDERED_CHARGE_MODES_PV_ONLY, SurplusControlled, limit_adjust_current) from control.chargemode import Chargemode @@ -89,7 +89,7 @@ def test_set_required_current_to_max(phases: int, required_currents=required_currents)) mock_cp1.template = CpTemplate() mock_get_chargepoints_surplus_controlled = Mock(return_value=[mock_cp1]) - monkeypatch.setattr(surplus_controlled, "get_chargepoints_by_chargemodes", + monkeypatch.setattr(surplus_controlled, "get_chargepoints_with_required_current_by_chargemode", mock_get_chargepoints_surplus_controlled) # execution @@ -149,7 +149,7 @@ def setup_cp(cp: Chargepoint, submode: str) -> Chargepoint: "cp2": setup_cp(mock_cp2, submode_2)} # evaluation - chargepoints = get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_PV_ONLY) + chargepoints = get_chargepoints_with_required_current_by_chargemode(CONSIDERED_CHARGE_MODES_PV_ONLY) # assertion assert chargepoints == expected_chargepoints diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index 30c47591d4..c5d43b96cc 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -24,7 +24,7 @@ from control import data from control.algorithm.chargemodes import CONSIDERED_CHARGE_MODES_CHARGING -from control.algorithm.filter_chargepoints import get_chargepoints_by_chargemodes +from control.algorithm.filter_chargepoints import get_chargepoints_with_required_current_by_chargemode from control.pv import Pv from helpermodules.constants import NO_ERROR from modules.common.abstract_device import AbstractDevice @@ -429,7 +429,8 @@ def set_power_limit_controllable(self): self.data.get.power_limit_controllable = False def get_charge_mode_vehicle_charge(self): - chargepoint_by_chargemodes = get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_CHARGING) + chargepoint_by_chargemodes = get_chargepoints_with_required_current_by_chargemode( + CONSIDERED_CHARGE_MODES_CHARGING) # Fahrzeuge laden vehicle_charging = (len(chargepoint_by_chargemodes) > 0 and data.data.cp_all_data.data.get.power > 100) diff --git a/packages/control/bat_all_test.py b/packages/control/bat_all_test.py index a047fad1da..ca3d98f972 100644 --- a/packages/control/bat_all_test.py +++ b/packages/control/bat_all_test.py @@ -259,8 +259,9 @@ def test_active_bat_control(params: BatControlParams, data_, monkeypatch): data.data.counter_data["counter0"].data.get.power = params.evu_power data.data.bat_all_data = b_all - get_chargepoints_by_chargemodes_mock = Mock(return_value=params.cps) - monkeypatch.setattr(bat_all, "get_chargepoints_by_chargemodes", get_chargepoints_by_chargemodes_mock) + get_chargepoints_with_required_current_by_chargemode_mock = Mock(return_value=params.cps) + monkeypatch.setattr(bat_all, "get_chargepoints_with_required_current_by_chargemode", + get_chargepoints_with_required_current_by_chargemode_mock) get_evu_counter_mock = Mock(return_value=data.data.counter_data["counter0"]) monkeypatch.setattr(data.data.counter_all_data, "get_evu_counter", get_evu_counter_mock) get_controllable_bat_components_mock = Mock(return_value=[MqttBat(MqttBatSetup(id=2), device_id=0)]) @@ -340,8 +341,9 @@ def test_control_price_limit(params: BatControlParams, data_, monkeypatch): data.data.counter_data["counter0"].data.get.power = params.evu_power data.data.bat_all_data = b_all - get_chargepoints_by_chargemodes_mock = Mock(return_value=params.cps) - monkeypatch.setattr(bat_all, "get_chargepoints_by_chargemodes", get_chargepoints_by_chargemodes_mock) + get_chargepoints_with_required_current_by_chargemode_mock = Mock(return_value=params.cps) + monkeypatch.setattr(bat_all, "get_chargepoints_with_required_current_by_chargemode", + get_chargepoints_with_required_current_by_chargemode_mock) get_evu_counter_mock = Mock(return_value=data.data.counter_data["counter0"]) monkeypatch.setattr(data.data.counter_all_data, "get_evu_counter", get_evu_counter_mock) get_controllable_bat_components_mock = Mock(return_value=[MqttBat(MqttBatSetup(id=2), device_id=0)]) diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py index 28af269c02..21b1fce141 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -370,7 +370,7 @@ def _is_phase_switch_required(self) -> bool: self.set_state_and_log( "Keine Phasenumschaltung, da wiederholtes Anstoßen der Umschaltung in den übergreifenden " "Ladeeinstellungen deaktiviert wurde. Die aktuelle " - "Phasenzahl wird bis zum Abstecken beibehalten.") + "Phasenzahl wird bis zum nächsten Lademoduswechsel beibehalten.") self.data.control_parameter.failed_phase_switches += 1 return phase_switch_required @@ -400,8 +400,10 @@ def check_phase_switch_completed(self): self.set_state_and_log(message) return if self.data.control_parameter.state == ChargepointState.WAIT_FOR_USING_PHASES: + log.debug("pos 1") if check_timestamp(self.data.control_parameter.timestamp_charge_start, charging_ev.ev_template.data.keep_charge_active_duration) is False: + log.debug("pos 2") if self.hw_supports_phase_switch() and self.failed_phase_switches_reached(): if phase_switch.phase_switch_thread_alive(self.num) is False: self.data.control_parameter.state = ChargepointState.PHASE_SWITCH_AWAITED @@ -463,6 +465,7 @@ def initiate_phase_switch(self): self.data.control_parameter.phases) self.data.set.phases_to_use = self.data.control_parameter.phases self.data.control_parameter.state = ChargepointState.PERFORMING_PHASE_SWITCH + self.data.set.current = 0 else: log.error("Phasenumschaltung an Ladepunkt" + str(self.num) + " nicht möglich, da gerade eine Umschaltung im Gange ist.") diff --git a/packages/control/counter.py b/packages/control/counter.py index b3e6fcddf0..f64eaa3600 100644 --- a/packages/control/counter.py +++ b/packages/control/counter.py @@ -8,7 +8,7 @@ from control import data from control.algorithm.chargemodes import CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE -from control.algorithm.filter_chargepoints import get_chargepoints_by_chargemodes +from control.algorithm.filter_chargepoints import get_chargepoints_with_required_current_by_chargemode from control.algorithm.utils import get_medium_charging_current from control.chargemode import Chargemode from control.chargepoint.chargepoint import Chargepoint @@ -519,7 +519,8 @@ def set_raw_surplus_power_left() -> None: """ grid_counter = data.data.counter_all_data.get_evu_counter() bidi_power = 0 - chargepoint_by_chargemodes = get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE) + chargepoint_by_chargemodes = get_chargepoints_with_required_current_by_chargemode( + CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE) for cp in chargepoint_by_chargemodes: bidi_power += cp.data.get.power grid_counter.data.set.surplus_power_left = grid_counter.data.get.power * -1 + bidi_power diff --git a/packages/control/process.py b/packages/control/process.py index 4d911fb41e..8deb71591a 100644 --- a/packages/control/process.py +++ b/packages/control/process.py @@ -40,9 +40,10 @@ def process_algorithm_results(self) -> None: if control_parameter.state == ChargepointState.NO_CHARGING_ALLOWED and cp.data.set.current != 0: control_parameter.state = ChargepointState.WAIT_FOR_USING_PHASES cp.set_timestamp_charge_start() + self._update_state(cp) else: control_parameter.state = ChargepointState.NO_CHARGING_ALLOWED - self._update_state(cp) + cp.data.set.current = 0 if cp.data.get.state_str is not None: Pub().pub("openWB/set/chargepoint/"+str(cp.num)+"/get/state_str", From 780f760980f77e067e70380b6fd9e3a64a23671d Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Tue, 10 Mar 2026 13:42:52 +0100 Subject: [PATCH 2/2] clean up --- packages/control/chargepoint/chargepoint.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py index 21b1fce141..6e010f90cc 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -400,10 +400,8 @@ def check_phase_switch_completed(self): self.set_state_and_log(message) return if self.data.control_parameter.state == ChargepointState.WAIT_FOR_USING_PHASES: - log.debug("pos 1") if check_timestamp(self.data.control_parameter.timestamp_charge_start, charging_ev.ev_template.data.keep_charge_active_duration) is False: - log.debug("pos 2") if self.hw_supports_phase_switch() and self.failed_phase_switches_reached(): if phase_switch.phase_switch_thread_alive(self.num) is False: self.data.control_parameter.state = ChargepointState.PHASE_SWITCH_AWAITED